- Add some checks to prevent crashes in unexpected situations and add useful error...
[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 BOOL ReturnValue = FALSE;
37 DWORD BytesAvailable;
38 DWORD LogAvailable = 0;
39 DWORD LogLength = 0;
40 DWORD LogPosition = 0;
41 DWORD Temp;
42 PCHAR Buffer = NULL;
43 PROCESS_INFORMATION ProcessInfo = {0};
44
45 if(AppOptions.Submit)
46 {
47 /* Allocate one block for the log */
48 SubmitData->Log = HeapAlloc(hProcessHeap, 0, BUFFER_BLOCKSIZE);
49 LogAvailable = BUFFER_BLOCKSIZE;
50 LogLength = BUFFER_BLOCKSIZE;
51 }
52
53 /* Allocate a buffer with the exact size of the output string.
54 We have to output this string in one call to prevent a race condition, when another application also outputs a string over the debug line. */
55 Buffer = HeapAlloc(hProcessHeap, 0, 27 + strlen(GetSuiteIDData->Module) + 8 + strlen(GetSuiteIDData->Test) + 2);
56 sprintf(Buffer, "Running Wine Test, Module: %s, Test: %s\n", GetSuiteIDData->Module, GetSuiteIDData->Test);
57 StringOut(Buffer);
58 HeapFree(hProcessHeap, 0, Buffer);
59 Buffer = NULL;
60
61 /* Execute the test */
62 if(!CreateProcessW(NULL, CommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
63 {
64 StringOut("CreateProcessW for running the test failed\n");
65 goto Cleanup;
66 }
67
68 /* Receive all the data from the pipe */
69 do
70 {
71 /* When the application finished, make sure that we peek the pipe one more time, so that we get all data.
72 If the following condition would be the while() condition, we might hit a race condition:
73 - We check for data with PeekNamedPipe -> no data available
74 - The application outputs its data and finishes
75 - WaitForSingleObject reports that the application has finished and we break the loop without receiving any data
76 */
77 if(WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0)
78 BreakLoop = TRUE;
79
80 if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
81 {
82 StringOut("PeekNamedPipe failed for the test run\n");
83 goto Cleanup;
84 }
85
86 if(BytesAvailable)
87 {
88 /* There is data, so get it and output it */
89 Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable + 1);
90
91 if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
92 {
93 StringOut("ReadFile failed for the test run\n");
94 goto Cleanup;
95 }
96
97 /* Output all test output through StringOut, even while the test is still running */
98 Buffer[BytesAvailable] = 0;
99 StringOut(Buffer);
100
101 if(AppOptions.Submit)
102 {
103 /* Also store it in the buffer */
104 if(BytesAvailable > LogAvailable)
105 {
106 /* Allocate enough new blocks to hold all available data */
107 Temp = ((BytesAvailable - LogAvailable) / BUFFER_BLOCKSIZE + 1) * BUFFER_BLOCKSIZE;
108 LogAvailable += Temp;
109 LogLength += Temp;
110 SubmitData->Log = HeapReAlloc(hProcessHeap, 0, SubmitData->Log, LogLength);
111 }
112
113 memcpy(&SubmitData->Log[LogPosition], Buffer, BytesAvailable);
114 LogPosition += BytesAvailable;
115 LogAvailable -= BytesAvailable;
116 }
117
118 HeapFree(hProcessHeap, 0, Buffer);
119 Buffer = NULL;
120 }
121 }
122 while(!BreakLoop);
123
124 if(AppOptions.Submit)
125 {
126 SubmitData->Log[LogLength - LogAvailable] = 0;
127
128 /* If we got any output, submit it to the web service */
129 if(*SubmitData->Log)
130 {
131 /* We don't want to waste any ID's, so only request them if we can be sure that we have results to submit. */
132
133 /* Get a Test ID if we don't have one yet */
134 if(!SubmitData->General.TestID)
135 {
136 SubmitData->General.TestID = GetTestID(WineTest);
137
138 if(!SubmitData->General.TestID)
139 goto Cleanup;
140 }
141
142 /* Get a Suite ID for this combination */
143 SubmitData->General.SuiteID = GetSuiteID(WineTest, GetSuiteIDData);
144
145 if(!SubmitData->General.SuiteID)
146 goto Cleanup;
147
148 /* Submit the stuff */
149 Submit(WineTest, SubmitData);
150 }
151 }
152
153 StringOut("\n\n");
154
155 ReturnValue = TRUE;
156
157 Cleanup:
158 if(Buffer)
159 HeapFree(hProcessHeap, 0, Buffer);
160
161 if(ProcessInfo.hProcess)
162 HeapFree(hProcessHeap, 0, ProcessInfo.hProcess);
163
164 if(ProcessInfo.hThread)
165 HeapFree(hProcessHeap, 0, ProcessInfo.hThread);
166
167 if(SubmitData->General.SuiteID)
168 {
169 HeapFree(hProcessHeap, 0, SubmitData->General.SuiteID);
170 SubmitData->General.SuiteID = NULL;
171 }
172
173 if(SubmitData->Log)
174 {
175 HeapFree(hProcessHeap, 0, SubmitData->Log);
176 SubmitData->Log = NULL;
177 }
178
179 return ReturnValue;
180 }
181
182 /**
183 * Runs the desired tests for a specified module.
184 *
185 * @param File
186 * The file name of the module's test suite.
187 *
188 * @param FilePath
189 * The full path to the file of the module's test suite.
190 *
191 * @param hReadPipe
192 * Handle to the Read Pipe set up in RunWineTests.
193 *
194 * @param StartupInfo
195 * Pointer to the StartupInfo structure set up in RunWineTests.
196 *
197 * @param SubmitData
198 * Pointer to the SubmitData structure set up in RunWineTests.
199 *
200 * @return
201 * TRUE if everything went well, FALSE otherwise.
202 */
203 static BOOL
204 IntRunModuleTests(PWSTR File, PWSTR FilePath, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_SUBMIT_DATA SubmitData)
205 {
206 BOOL ReturnValue = FALSE;
207 DWORD BytesAvailable;
208 DWORD Length;
209 DWORD Temp;
210 PCHAR Buffer = NULL;
211 PCHAR pStart;
212 PCHAR pEnd;
213 PROCESS_INFORMATION ProcessInfo = {0};
214 PWSTR pUnderscore;
215 size_t FilePosition;
216 WINE_GETSUITEID_DATA GetSuiteIDData = {0};
217
218 /* Build the full command line */
219 FilePosition = wcslen(FilePath);
220 FilePath[FilePosition++] = ' ';
221 FilePath[FilePosition] = 0;
222 wcscat(FilePath, L"--list");
223
224 /* Find the underscore in the file name */
225 pUnderscore = wcschr(File, L'_');
226
227 if(!pUnderscore)
228 {
229 StringOut("Invalid test file name: ");
230
231 Length = wcslen(File);
232 Buffer = HeapAlloc(hProcessHeap, 0, Length + 1);
233 WideCharToMultiByte(CP_ACP, 0, File, Length + 1, Buffer, Length + 1, NULL, NULL);
234
235 StringOut(Buffer);
236 StringOut("\n");
237
238 goto Cleanup;
239 }
240
241 /* Store the tested module name */
242 Length = pUnderscore - File;
243 GetSuiteIDData.Module = HeapAlloc(hProcessHeap, 0, Length + 1);
244 WideCharToMultiByte(CP_ACP, 0, File, Length, GetSuiteIDData.Module, Length, NULL, NULL);
245 GetSuiteIDData.Module[Length] = 0;
246
247 /* Start the process for getting all available tests */
248 if(!CreateProcessW(NULL, FilePath, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
249 {
250 StringOut("CreateProcessW for getting the available tests failed\n");
251 goto Cleanup;
252 }
253
254 /* Wait till this process ended */
255 if(WaitForSingleObject(ProcessInfo.hProcess, INFINITE) == WAIT_FAILED)
256 {
257 StringOut("WaitForSingleObject failed for the test list\n");
258 goto Cleanup;
259 }
260
261 /* Read the output data into a buffer */
262 if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
263 {
264 StringOut("PeekNamedPipe failed for the test list\n");
265 goto Cleanup;
266 }
267
268 /* Check if we got any */
269 if(!BytesAvailable)
270 {
271 StringOut("The --list command did not return any data for ");
272
273 Length = wcslen(File);
274 Buffer = HeapAlloc(hProcessHeap, 0, Length + 1);
275 WideCharToMultiByte(CP_ACP, 0, File, Length + 1, Buffer, Length + 1, NULL, NULL);
276
277 StringOut(Buffer);
278 StringOut("\n");
279
280 goto Cleanup;
281 }
282
283 /* Read the data */
284 Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable);
285
286 if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
287 {
288 StringOut("ReadFile failed\n");
289 goto Cleanup;
290 }
291
292 /* Jump to the first available test */
293 pStart = strchr(Buffer, '\n');
294 pStart += 5;
295
296 while(pStart < (Buffer + BytesAvailable))
297 {
298 /* Get start and end of this test name */
299 pEnd = pStart;
300
301 while(*pEnd != '\r')
302 ++pEnd;
303
304 /* Store the test name */
305 GetSuiteIDData.Test = HeapAlloc(hProcessHeap, 0, pEnd - pStart + 1);
306 memcpy(GetSuiteIDData.Test, pStart, pEnd - pStart);
307 GetSuiteIDData.Test[pEnd - pStart] = 0;
308
309 /* If the user gave us a test to run, we check whether the module's test suite really provides this test. */
310 if(!AppOptions.Test || (AppOptions.Test && !strcmp(AppOptions.Test, GetSuiteIDData.Test)))
311 {
312 /* Build the command line for this test */
313 Length = MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, NULL, 0);
314 MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, &FilePath[FilePosition], Length * sizeof(WCHAR));
315 FilePath[FilePosition + Length] = 0;
316
317 if(!IntRunTest(FilePath, hReadPipe, StartupInfo, &GetSuiteIDData, SubmitData))
318 goto Cleanup;
319 }
320
321 /* Cleanup */
322 HeapFree(hProcessHeap, 0, GetSuiteIDData.Test);
323 GetSuiteIDData.Test = NULL;
324
325 /* Move to the next test */
326 pStart = pEnd + 6;
327 }
328
329 ReturnValue = TRUE;
330
331 Cleanup:
332 if(GetSuiteIDData.Module)
333 HeapFree(hProcessHeap, 0, GetSuiteIDData.Module);
334
335 if(GetSuiteIDData.Test)
336 HeapFree(hProcessHeap, 0, GetSuiteIDData.Test);
337
338 if(Buffer)
339 HeapFree(hProcessHeap, 0, Buffer);
340
341 if(ProcessInfo.hProcess)
342 CloseHandle(ProcessInfo.hProcess);
343
344 if(ProcessInfo.hThread)
345 CloseHandle(ProcessInfo.hThread);
346
347 return ReturnValue;
348 }
349
350 /**
351 * Runs the Wine tests according to the options specified by the parameters.
352 *
353 * @return
354 * TRUE if everything went well, FALSE otherwise.
355 */
356 BOOL
357 RunWineTests()
358 {
359 BOOL ReturnValue = FALSE;
360 GENERAL_FINISH_DATA FinishData;
361 HANDLE hFind = NULL;
362 HANDLE hReadPipe = NULL;
363 HANDLE hWritePipe = NULL;
364 SECURITY_ATTRIBUTES SecurityAttributes;
365 STARTUPINFOW StartupInfo = {0};
366 size_t PathPosition;
367 WCHAR FilePath[MAX_PATH];
368 WIN32_FIND_DATAW fd;
369 WINE_SUBMIT_DATA SubmitData = { {0} };
370
371 /* Create a pipe for getting the output of the tests */
372 SecurityAttributes.nLength = sizeof(SecurityAttributes);
373 SecurityAttributes.bInheritHandle = TRUE;
374 SecurityAttributes.lpSecurityDescriptor = NULL;
375
376 if(!CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0))
377 {
378 StringOut("CreatePipe failed\n");
379 goto Cleanup;
380 }
381
382 StartupInfo.cb = sizeof(StartupInfo);
383 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
384 StartupInfo.hStdOutput = hWritePipe;
385
386 /* Build the path for finding the tests */
387 if(GetWindowsDirectoryW(FilePath, MAX_PATH) > MAX_PATH - 60)
388 {
389 StringOut("Windows directory path is too long\n");
390 goto Cleanup;
391 }
392
393 wcscat(FilePath, L"\\bin\\");
394 PathPosition = wcslen(FilePath);
395
396 if(AppOptions.Module)
397 {
398 /* Find a test belonging to this module */
399 wcscat(FilePath, AppOptions.Module);
400 wcscat(FilePath, L"_*.exe");
401 }
402 else
403 {
404 /* Find all tests */
405 wcscat(FilePath, L"*.exe");
406 }
407
408 hFind = FindFirstFileW(FilePath, &fd);
409
410 if(hFind == INVALID_HANDLE_VALUE)
411 {
412 StringOut("FindFirstFileW failed\n");
413 goto Cleanup;
414 }
415
416 /* Run the tests */
417 do
418 {
419 /* Build the full path to the test suite */
420 wcscpy(&FilePath[PathPosition], fd.cFileName);
421
422 /* Run it */
423 if(!IntRunModuleTests(fd.cFileName, FilePath, hReadPipe, &StartupInfo, &SubmitData))
424 goto Cleanup;
425 }
426 while(FindNextFileW(hFind, &fd));
427
428 /* Close this test run if necessary */
429 if(SubmitData.General.TestID)
430 {
431 FinishData.TestID = SubmitData.General.TestID;
432
433 if(!Finish(WineTest, &FinishData))
434 goto Cleanup;
435 }
436
437 ReturnValue = TRUE;
438
439 Cleanup:
440 if(SubmitData.General.TestID)
441 HeapFree(hProcessHeap, 0, SubmitData.General.TestID);
442
443 if(hFind && hFind != INVALID_HANDLE_VALUE)
444 FindClose(hFind);
445
446 if(hReadPipe)
447 CloseHandle(hReadPipe);
448
449 if(hWritePipe)
450 CloseHandle(hWritePipe);
451
452 return ReturnValue;
453 }