5ab622a558271d7fa4045759b412236d409392fa
[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 /* Close the process handles */
121 CloseHandle(ProcessInfo.hProcess);
122 CloseHandle(ProcessInfo.hThread);
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 return FALSE;
140 }
141
142 /* Get a Suite ID for this combination */
143 SubmitData->General.SuiteID = GetSuiteID(WineTest, GetSuiteIDData);
144
145 if(!SubmitData->General.SuiteID)
146 return FALSE;
147
148 /* Submit the stuff */
149 Submit(WineTest, SubmitData);
150
151 /* Cleanup */
152 HeapFree(hProcessHeap, 0, SubmitData->General.SuiteID);
153 }
154
155 /* Cleanup */
156 HeapFree(hProcessHeap, 0, SubmitData->Log);
157 }
158
159 StringOut("\n\n");
160
161 return TRUE;
162 }
163
164 /**
165 * Runs the desired tests for a specified module.
166 *
167 * @param File
168 * The file name of the module's test suite.
169 *
170 * @param FilePath
171 * The full path to the file of the module's test suite.
172 *
173 * @param hReadPipe
174 * Handle to the Read Pipe set up in RunWineTests.
175 *
176 * @param StartupInfo
177 * Pointer to the StartupInfo structure set up in RunWineTests.
178 *
179 * @param SubmitData
180 * Pointer to the SubmitData structure set up in RunWineTests.
181 *
182 * @return
183 * TRUE if everything went well, FALSE otherwise.
184 */
185 static BOOL
186 IntRunModuleTests(PWSTR File, PWSTR FilePath, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_SUBMIT_DATA SubmitData)
187 {
188 DWORD BytesAvailable;
189 DWORD Length;
190 DWORD Temp;
191 PCHAR Buffer;
192 PCHAR pStart;
193 PCHAR pEnd;
194 PROCESS_INFORMATION ProcessInfo;
195 size_t FilePosition;
196 WINE_GETSUITEID_DATA GetSuiteIDData;
197
198 /* Build the full command line */
199 FilePosition = wcslen(FilePath);
200 FilePath[FilePosition++] = ' ';
201 FilePath[FilePosition] = 0;
202 wcscat(FilePath, L"--list");
203
204 /* Store the tested module name */
205 Length = wcschr(File, L'_') - File;
206 GetSuiteIDData.Module = HeapAlloc(hProcessHeap, 0, Length + 1);
207 WideCharToMultiByte(CP_ACP, 0, File, Length, GetSuiteIDData.Module, Length, NULL, NULL);
208 GetSuiteIDData.Module[Length] = 0;
209
210 /* Start the process for getting all available tests */
211 if(!CreateProcessW(NULL, FilePath, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo))
212 {
213 StringOut("CreateProcessW for getting the available tests failed\n");
214 return FALSE;
215 }
216
217 /* Wait till this process ended */
218 if(WaitForSingleObject(ProcessInfo.hProcess, INFINITE) == WAIT_FAILED)
219 {
220 StringOut("WaitForSingleObject failed for the test list\n");
221 return FALSE;
222 }
223
224 /* Close the process handles */
225 CloseHandle(ProcessInfo.hProcess);
226 CloseHandle(ProcessInfo.hThread);
227
228 /* Read the output data into a buffer */
229 if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
230 {
231 StringOut("PeekNamedPipe failed for the test list\n");
232 return FALSE;
233 }
234
235 Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable);
236
237 if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
238 {
239 StringOut("ReadFile failed\n");
240 return FALSE;
241 }
242
243 /* Jump to the first available test */
244 pStart = strchr(Buffer, '\n');
245 pStart += 5;
246
247 while(pStart < (Buffer + BytesAvailable))
248 {
249 /* Get start and end of this test name */
250 pEnd = pStart;
251
252 while(*pEnd != '\r')
253 ++pEnd;
254
255 /* Store the test name */
256 GetSuiteIDData.Test = HeapAlloc(hProcessHeap, 0, pEnd - pStart + 1);
257 memcpy(GetSuiteIDData.Test, pStart, pEnd - pStart);
258 GetSuiteIDData.Test[pEnd - pStart] = 0;
259
260 /* If the user gave us a test to run, we check whether the module's test suite really provides this test. */
261 if(!AppOptions.Test || (AppOptions.Test && !strcmp(AppOptions.Test, GetSuiteIDData.Test)))
262 {
263 /* Build the command line for this test */
264 Length = MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, NULL, 0);
265 MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, &FilePath[FilePosition], Length * sizeof(WCHAR));
266 FilePath[FilePosition + Length] = 0;
267
268 if(!IntRunTest(FilePath, hReadPipe, StartupInfo, &GetSuiteIDData, SubmitData))
269 return FALSE;
270 }
271
272 /* Cleanup */
273 HeapFree(hProcessHeap, 0, GetSuiteIDData.Test);
274
275 /* Move to the next test */
276 pStart = pEnd + 6;
277 }
278
279 /* Cleanup */
280 HeapFree(hProcessHeap, 0, GetSuiteIDData.Module);
281 HeapFree(hProcessHeap, 0, Buffer);
282
283 return TRUE;
284 }
285
286 /**
287 * Runs the Wine tests according to the options specified by the parameters.
288 *
289 * @return
290 * TRUE if everything went well, FALSE otherwise.
291 */
292 BOOL
293 RunWineTests()
294 {
295 GENERAL_FINISH_DATA FinishData;
296 HANDLE hFind;
297 HANDLE hReadPipe;
298 HANDLE hWritePipe;
299 SECURITY_ATTRIBUTES SecurityAttributes;
300 STARTUPINFOW StartupInfo = {0};
301 size_t PathPosition;
302 WCHAR FilePath[MAX_PATH];
303 WIN32_FIND_DATAW fd;
304 WINE_SUBMIT_DATA SubmitData = { {0} };
305
306 /* Create a pipe for getting the output of the tests */
307 SecurityAttributes.nLength = sizeof(SecurityAttributes);
308 SecurityAttributes.bInheritHandle = TRUE;
309 SecurityAttributes.lpSecurityDescriptor = NULL;
310
311 if(!CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0))
312 {
313 StringOut("CreatePipe failed\n");
314 return FALSE;
315 }
316
317 StartupInfo.cb = sizeof(StartupInfo);
318 StartupInfo.dwFlags = STARTF_USESTDHANDLES;
319 StartupInfo.hStdOutput = hWritePipe;
320
321 /* Build the path for finding the tests */
322 if(GetWindowsDirectoryW(FilePath, MAX_PATH) > MAX_PATH - 60)
323 {
324 StringOut("Windows directory path is too long\n");
325 return FALSE;
326 }
327
328 wcscat(FilePath, L"\\bin\\");
329 PathPosition = wcslen(FilePath);
330
331 if(AppOptions.Module)
332 {
333 /* Find a test belonging to this module */
334 wcscat(FilePath, AppOptions.Module);
335 wcscat(FilePath, L"_*.exe");
336 }
337 else
338 {
339 /* Find all tests */
340 wcscat(FilePath, L"*.exe");
341 }
342
343 hFind = FindFirstFileW(FilePath, &fd);
344
345 if(hFind == INVALID_HANDLE_VALUE)
346 {
347 StringOut("FindFirstFileW failed\n");
348 return FALSE;
349 }
350
351 /* Run the tests */
352 do
353 {
354 /* Build the full path to the test suite */
355 wcscpy(&FilePath[PathPosition], fd.cFileName);
356
357 /* Run it */
358 if(!IntRunModuleTests(fd.cFileName, FilePath, hReadPipe, &StartupInfo, &SubmitData))
359 return FALSE;
360 }
361 while(FindNextFileW(hFind, &fd));
362
363 /* Cleanup */
364 FindClose(hFind);
365
366 if(AppOptions.Submit && SubmitData.General.TestID)
367 {
368 /* We're done with the tests, so close this test run */
369 FinishData.TestID = SubmitData.General.TestID;
370
371 if(!Finish(WineTest, &FinishData))
372 return FALSE;
373
374 /* Cleanup */
375 HeapFree(hProcessHeap, 0, FinishData.TestID);
376 }
377
378 CloseHandle(hReadPipe);
379 CloseHandle(hWritePipe);
380
381 return TRUE;
382 }