Introducing the "ReactOS Automatic Testing Utility", superseding our current syssetup...
[reactos.git] / rostests / rosautotest / winetests.c
1 /*
2 * PROJECT: ReactOS Automatic Testing Utility
3 * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
4 * PURPOSE: Running Wine Tests automatically
5 * COPYRIGHT: Copyright 2008-2009 Colin Finck <colin@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 /**
11 * Runs a specific test for a specific module.
12 * If we get results for a test, they are submitted to the Web Service.
13 *
14 * @param CommandLine
15 * Command line to run (should be the path to a module's test suite together with a parameter for the specified test)
16 *
17 * @param hReadPipe
18 * Handle to the Read Pipe set up in RunWineTests.
19 *
20 * @param StartupInfo
21 * Pointer to the StartupInfo structure set up in RunWineTests.
22 *
23 * @param GetSuiteIDData
24 * Pointer to the GetSuiteIDData structure set up in IntRunModuleTests.
25 *
26 * @param SubmitData
27 * Pointer to the SubmitData structure set up in RunWineTests.
28 *
29 * @return
30 * TRUE if everything went well, FALSE otherwise.
31 */
32 static BOOL
33 IntRunTest(PWSTR CommandLine, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_GETSUITEID_DATA GetSuiteIDData, PWINE_SUBMIT_DATA SubmitData)
34 {
35 BOOL BreakLoop = FALSE;
36 DWORD BytesAvailable;
37 DWORD LogAvailable = 0;
38 DWORD LogLength = 0;
39 DWORD LogPosition = 0;
40 DWORD Temp;
41 PCHAR Buffer;
42 PROCESS_INFORMATION ProcessInfo;
43
44 if(AppOptions.Submit)
45 {
46 /* Allocate one block for the log */
47 SubmitData->Log = HeapAlloc(hProcessHeap, 0, BUFFER_BLOCKSIZE);
48 LogAvailable = BUFFER_BLOCKSIZE;
49 LogLength = BUFFER_BLOCKSIZE;
50 }
51
52 /* Execute the test */
53 StringOut("Running Wine Test, Module: ");
54 StringOut(GetSuiteIDData->Module);
55 StringOut(", Test: ");
56 StringOut(GetSuiteIDData->Test);
57 StringOut("\n");
58
59 if(!CreateProcessW(NULL, CommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
60 {
61 StringOut("CreateProcessW for running the test failed\n");
62 return FALSE;
63 }
64
65 /* Receive all the data from the pipe */
66 do
67 {
68 /* When the application finished, make sure that we peek the pipe one more time, so that we get all data.
69 If the following condition would be the while() condition, we might hit a race condition:
70 - We check for data with PeekNamedPipe -> no data available
71 - The application outputs its data and finishes
72 - WaitForSingleObject reports that the application has finished and we break the loop without receiving any data
73 */
74 if(WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0)
75 BreakLoop = TRUE;
76
77 if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
78 {
79 StringOut("PeekNamedPipe failed for the test run\n");
80 return FALSE;
81 }
82
83 if(BytesAvailable)
84 {
85 /* There is data, so get it and output it */
86 Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable + 1);
87
88 if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
89 {
90 StringOut("ReadFile failed for the test run\n");
91 return FALSE;
92 }
93
94 /* Output all test output through StringOut, even while the test is still running */
95 Buffer[BytesAvailable] = 0;
96 StringOut(Buffer);
97
98 if(AppOptions.Submit)
99 {
100 /* Also store it in the buffer */
101 if(BytesAvailable > LogAvailable)
102 {
103 /* Allocate enough new blocks to hold all available data */
104 Temp = ((BytesAvailable - LogAvailable) / BUFFER_BLOCKSIZE + 1) * BUFFER_BLOCKSIZE;
105 LogAvailable += Temp;
106 LogLength += Temp;
107 SubmitData->Log = HeapReAlloc(hProcessHeap, 0, SubmitData->Log, LogLength);
108 }
109
110 memcpy(&SubmitData->Log[LogPosition], Buffer, BytesAvailable);
111 LogPosition += BytesAvailable;
112 LogAvailable -= BytesAvailable;
113 }
114
115 HeapFree(hProcessHeap, 0, Buffer);
116 }
117 }
118 while(!BreakLoop);
119
120 if(AppOptions.Submit)
121 {
122 SubmitData->Log[LogLength - LogAvailable] = 0;
123
124 /* If we got any output, submit it to the web service */
125 if(*SubmitData->Log)
126 {
127 /* We don't want to waste any ID's, so only request them if we can be sure that we have results to submit. */
128
129 /* Get a Test ID if we don't have one yet */
130 if(!SubmitData->General.TestID)
131 {
132 SubmitData->General.TestID = GetTestID(WineTest);
133
134 if(!SubmitData->General.TestID)
135 return FALSE;
136 }
137
138 /* Get a Suite ID for this combination */
139 SubmitData->General.SuiteID = GetSuiteID(WineTest, GetSuiteIDData);
140
141 if(!SubmitData->General.SuiteID)
142 return FALSE;
143
144 /* Submit the stuff */
145 Submit(WineTest, SubmitData);
146
147 /* Cleanup */
148 HeapFree(hProcessHeap, 0, SubmitData->General.SuiteID);
149 }
150
151 /* Cleanup */
152 HeapFree(hProcessHeap, 0, SubmitData->Log);
153 }
154
155 StringOut("\n\n");
156
157 return TRUE;
158 }
159
160 /**
161 * Runs the desired tests for a specified module.
162 *
163 * @param File
164 * The file name of the module's test suite.
165 *
166 * @param FilePath
167 * The full path to the file of the module's test suite.
168 *
169 * @param hReadPipe
170 * Handle to the Read Pipe set up in RunWineTests.
171 *
172 * @param StartupInfo
173 * Pointer to the StartupInfo structure set up in RunWineTests.
174 *
175 * @param SubmitData
176 * Pointer to the SubmitData structure set up in RunWineTests.
177 *
178 * @return
179 * TRUE if everything went well, FALSE otherwise.
180 */
181 static BOOL
182 IntRunModuleTests(PWSTR File, PWSTR FilePath, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_SUBMIT_DATA SubmitData)
183 {
184 DWORD BytesAvailable;
185 DWORD Length;
186 DWORD Temp;
187 PCHAR Buffer;
188 PCHAR pStart;
189 PCHAR pEnd;
190 PROCESS_INFORMATION ProcessInfo;
191 size_t FilePosition;
192 WINE_GETSUITEID_DATA GetSuiteIDData;
193
194 /* Build the full command line */
195 FilePosition = wcslen(FilePath);
196 FilePath[FilePosition++] = ' ';
197 FilePath[FilePosition] = 0;
198 wcscat(FilePath, L"--list");
199
200 /* Store the tested module name */
201 Length = wcschr(File, L'_') - File;
202 GetSuiteIDData.Module = HeapAlloc(hProcessHeap, 0, Length + 1);
203 WideCharToMultiByte(CP_ACP, 0, File, Length, GetSuiteIDData.Module, Length, NULL, NULL);
204 GetSuiteIDData.Module[Length] = 0;
205
206 /* Start the process for getting all available tests */
207 if(!CreateProcessW(NULL, FilePath, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
208 {
209 StringOut("CreateProcessW for getting the available tests failed\n");
210 return FALSE;
211 }
212
213 /* Wait till this process ended */
214 if(WaitForSingleObject(ProcessInfo.hProcess, INFINITE) == WAIT_FAILED)
215 {
216 StringOut("WaitForSingleObject failed for the test list\n");
217 return FALSE;
218 }
219
220 /* Read the output data into a buffer */
221 if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
222 {
223 StringOut("PeekNamedPipe failed for the test list\n");
224 return FALSE;
225 }
226
227 Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable);
228
229 if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
230 {
231 StringOut("ReadFile failed\n");
232 return FALSE;
233 }
234
235 /* Jump to the first available test */
236 pStart = strchr(Buffer, '\n');
237 pStart += 5;
238
239 while(pStart < (Buffer + BytesAvailable))
240 {
241 /* Get start and end of this test name */
242 pEnd = pStart;
243
244 while(*pEnd != '\r')
245 ++pEnd;
246
247 /* Store the test name */
248 GetSuiteIDData.Test = HeapAlloc(hProcessHeap, 0, pEnd - pStart + 1);
249 memcpy(GetSuiteIDData.Test, pStart, pEnd - pStart);
250 GetSuiteIDData.Test[pEnd - pStart] = 0;
251
252 /* If the user gave us a test to run, we check whether the module's test suite really provides this test. */
253 if(!AppOptions.Test || (AppOptions.Test && !strcmp(AppOptions.Test, GetSuiteIDData.Test)))
254 {
255 /* Build the command line for this test */
256 Length = MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, NULL, 0);
257 MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, &FilePath[FilePosition], Length * sizeof(WCHAR));
258 FilePath[FilePosition + Length] = 0;
259
260 if(!IntRunTest(FilePath, hReadPipe, StartupInfo, &GetSuiteIDData, SubmitData))
261 return FALSE;
262 }
263
264 /* Cleanup */
265 HeapFree(hProcessHeap, 0, GetSuiteIDData.Test);
266
267 /* Move to the next test */
268 pStart = pEnd + 6;
269 }
270
271 /* Cleanup */
272 HeapFree(hProcessHeap, 0, GetSuiteIDData.Module);
273 HeapFree(hProcessHeap, 0, Buffer);
274
275 return TRUE;
276 }
277
278 /**
279 * Runs the Wine tests according to the options specified by the parameters.
280 *
281 * @return
282 * TRUE if everything went well, FALSE otherwise.
283 */
284 BOOL
285 RunWineTests()
286 {
287 GENERAL_FINISH_DATA FinishData;
288 HANDLE hFind;
289 HANDLE hReadPipe;
290 HANDLE hWritePipe;
291 SECURITY_ATTRIBUTES SecurityAttributes;
292 STARTUPINFOW StartupInfo = {0};
293 size_t PathPosition;
294 WCHAR FilePath[MAX_PATH];
295 WIN32_FIND_DATAW fd;
296 WINE_SUBMIT_DATA SubmitData = { {0} };
297
298 /* Create a pipe for getting the output of the tests */
299 SecurityAttributes.nLength = sizeof(SecurityAttributes);
300 SecurityAttributes.bInheritHandle = TRUE;
301 SecurityAttributes.lpSecurityDescriptor = NULL;
302
303 if(!CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0))
304 {
305 StringOut("CreatePipe failed\n");
306 return FALSE;
307 }
308
309 StartupInfo.cb = sizeof(StartupInfo);
310 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
311 StartupInfo.hStdOutput = hWritePipe;
312
313 /* Build the path for finding the tests */
314 if(GetWindowsDirectoryW(FilePath, MAX_PATH) > MAX_PATH - 60)
315 {
316 StringOut("Windows directory path is too long\n");
317 return FALSE;
318 }
319
320 wcscat(FilePath, L"\\bin\\");
321 PathPosition = wcslen(FilePath);
322
323 if(AppOptions.Module)
324 {
325 /* Find a test belonging to this module */
326 wcscat(FilePath, AppOptions.Module);
327 wcscat(FilePath, L"_*.exe");
328 }
329 else
330 {
331 /* Find all tests */
332 wcscat(FilePath, L"*.exe");
333 }
334
335 hFind = FindFirstFileW(FilePath, &fd);
336
337 if(hFind == INVALID_HANDLE_VALUE)
338 {
339 StringOut("FindFirstFileW failed\n");
340 return FALSE;
341 }
342
343 /* Run the tests */
344 do
345 {
346 /* Build the full path to the test suite */
347 wcscpy(&FilePath[PathPosition], fd.cFileName);
348
349 /* Run it */
350 if(!IntRunModuleTests(fd.cFileName, FilePath, hReadPipe, &StartupInfo, &SubmitData))
351 return FALSE;
352 }
353 while(FindNextFileW(hFind, &fd));
354
355 /* Cleanup */
356 FindClose(hFind);
357
358 if(AppOptions.Submit && SubmitData.General.TestID)
359 {
360 /* We're done with the tests, so close this test run */
361 FinishData.TestID = SubmitData.General.TestID;
362
363 if(!Finish(WineTest, &FinishData))
364 return FALSE;
365
366 /* Cleanup */
367 HeapFree(hProcessHeap, 0, FinishData.TestID);
368 }
369
370 CloseHandle(hReadPipe);
371 CloseHandle(hWritePipe);
372
373 return TRUE;
374 }