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