686a32268384a0786b93b8abe9a5df30dfffca83
[reactos.git] / rostests / rosautotest / CWineTest.cpp
1 /*
2 * PROJECT: ReactOS Automatic Testing Utility
3 * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation
4 * PURPOSE: Class implementing functions for handling Wine tests
5 * COPYRIGHT: Copyright 2009 Colin Finck <colin@reactos.org>
6 */
7
8 #include "precomp.h"
9
10 static const DWORD ListTimeout = 10000;
11
12 /**
13 * Constructs a CWineTest object.
14 */
15 CWineTest::CWineTest()
16 {
17 WCHAR WindowsDirectory[MAX_PATH];
18
19 /* Zero-initialize variables */
20 m_hFind = NULL;
21 m_hReadPipe = NULL;
22 m_hWritePipe = NULL;
23 m_ListBuffer = NULL;
24 memset(&m_StartupInfo, 0, sizeof(m_StartupInfo));
25
26 /* Set up m_TestPath */
27 if(!GetWindowsDirectoryW(WindowsDirectory, MAX_PATH))
28 FATAL("GetWindowsDirectoryW failed");
29
30 m_TestPath = WindowsDirectory;
31 m_TestPath += L"\\bin\\";
32 }
33
34 /**
35 * Destructs a CWineTest object.
36 */
37 CWineTest::~CWineTest()
38 {
39 if(m_hFind)
40 FindClose(m_hFind);
41
42 if(m_hReadPipe)
43 CloseHandle(m_hReadPipe);
44
45 if(m_hWritePipe)
46 CloseHandle(m_hWritePipe);
47
48 if(m_ListBuffer)
49 delete m_ListBuffer;
50 }
51
52 /**
53 * Gets the next module test file using the FindFirstFileW/FindNextFileW API.
54 *
55 * @return
56 * true if we found a next file, otherwise false.
57 */
58 bool
59 CWineTest::GetNextFile()
60 {
61 bool FoundFile = false;
62 WIN32_FIND_DATAW fd;
63
64 /* Did we already begin searching for files? */
65 if(m_hFind)
66 {
67 /* Then get the next file (if any) */
68 if(FindNextFileW(m_hFind, &fd))
69 FoundFile = true;
70 }
71 else
72 {
73 /* Start searching for test files */
74 wstring FindPath = m_TestPath;
75
76 /* Did the user specify a module? */
77 if(Configuration.GetModule().empty())
78 {
79 /* No module, so search for all files in that directory */
80 FindPath += L"*.exe";
81 }
82 else
83 {
84 /* Search for files with the pattern "modulename_*" */
85 FindPath += Configuration.GetModule();
86 FindPath += L"_*.exe";
87 }
88
89 /* Search for the first file and check whether we got one */
90 m_hFind = FindFirstFileW(FindPath.c_str(), &fd);
91
92 if(m_hFind != INVALID_HANDLE_VALUE)
93 FoundFile = true;
94 }
95
96 if(FoundFile)
97 m_CurrentFile = fd.cFileName;
98
99 return FoundFile;
100 }
101
102 /**
103 * Executes the --list command of a module test file to get information about the available tests.
104 *
105 * @return
106 * The number of bytes we read into the m_ListBuffer member variable by capturing the output of the --list command.
107 */
108 DWORD
109 CWineTest::DoListCommand()
110 {
111 DWORD BytesAvailable;
112 DWORD Temp;
113 wstring CommandLine;
114
115 /* Build the command line */
116 CommandLine = m_TestPath;
117 CommandLine += m_CurrentFile;
118 CommandLine += L" --list";
119
120 {
121 /* Start the process for getting all available tests */
122 CProcess Process(CommandLine, &m_StartupInfo);
123
124 /* Wait till this process ended */
125 if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED)
126 FATAL("WaitForSingleObject failed for the test list\n");
127 }
128
129 /* Read the output data into a buffer */
130 if(!PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
131 FATAL("PeekNamedPipe failed for the test list\n");
132
133 /* Check if we got any */
134 if(!BytesAvailable)
135 {
136 stringstream ss;
137
138 ss << "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile) << endl;
139 SSEXCEPTION;
140 }
141
142 /* Read the data */
143 m_ListBuffer = new char[BytesAvailable];
144
145 if(!ReadFile(m_hReadPipe, m_ListBuffer, BytesAvailable, &Temp, NULL))
146 FATAL("ReadPipe failed\n");
147
148 return BytesAvailable;
149 }
150
151 /**
152 * Gets the next test from m_ListBuffer, which was filled with information from the --list command.
153 *
154 * @return
155 * true if a next test was found, otherwise false.
156 */
157 bool
158 CWineTest::GetNextTest()
159 {
160 PCHAR pEnd;
161 static DWORD BufferSize;
162 static PCHAR pStart;
163
164 if(!m_ListBuffer)
165 {
166 /* Perform the --list command */
167 BufferSize = DoListCommand();
168
169 /* Move the pointer to the first test */
170 pStart = strchr(m_ListBuffer, '\n');
171 pStart += 5;
172 }
173
174 /* If we reach the buffer size, we finished analyzing the output of this test */
175 if(pStart >= (m_ListBuffer + BufferSize))
176 {
177 /* Clear m_CurrentFile to indicate that */
178 m_CurrentFile.clear();
179
180 /* Also free the memory for the list buffer */
181 delete m_ListBuffer;
182 m_ListBuffer = NULL;
183
184 return false;
185 }
186
187 /* Get start and end of this test name */
188 pEnd = pStart;
189
190 while(*pEnd != '\r')
191 ++pEnd;
192
193 /* Store the test name */
194 m_CurrentTest = string(pStart, pEnd);
195
196 /* Move the pointer to the next test */
197 pStart = pEnd + 6;
198
199 return true;
200 }
201
202 /**
203 * Interface to CTestList-derived classes for getting all information about the next test to be run.
204 *
205 * @return
206 * Returns a pointer to a CTestInfo object containing all available information about the next test.
207 */
208 CTestInfo*
209 CWineTest::GetNextTestInfo()
210 {
211 while(!m_CurrentFile.empty() || GetNextFile())
212 {
213 while(GetNextTest())
214 {
215 /* If the user specified a test through the command line, check this here */
216 if(!Configuration.GetTest().empty() && Configuration.GetTest() != m_CurrentTest)
217 continue;
218
219 {
220 auto_ptr<CTestInfo> TestInfo(new CTestInfo());
221 size_t UnderscorePosition;
222
223 /* Build the command line */
224 TestInfo->CommandLine = m_TestPath;
225 TestInfo->CommandLine += m_CurrentFile;
226 TestInfo->CommandLine += ' ';
227 TestInfo->CommandLine += AsciiToUnicode(m_CurrentTest);
228
229 /* Store the Module name */
230 UnderscorePosition = m_CurrentFile.find_last_of('_');
231
232 if(UnderscorePosition == m_CurrentFile.npos)
233 {
234 stringstream ss;
235
236 ss << "Invalid test file name: " << UnicodeToAscii(m_CurrentFile) << endl;
237 SSEXCEPTION;
238 }
239
240 TestInfo->Module = UnicodeToAscii(m_CurrentFile.substr(0, UnderscorePosition));
241
242 /* Store the test */
243 TestInfo->Test = m_CurrentTest;
244
245 return TestInfo.release();
246 }
247 }
248 }
249
250 return NULL;
251 }
252
253 /**
254 * Runs a Wine test and captures the output
255 *
256 * @param TestInfo
257 * Pointer to a CTestInfo object containing information about the test.
258 * Will contain the test log afterwards if the user wants to submit data.
259 */
260 void
261 CWineTest::RunTest(CTestInfo* TestInfo)
262 {
263 bool BreakLoop = false;
264 DWORD BytesAvailable;
265 DWORD Temp;
266 stringstream ss, ssFinish;
267 DWORD StartTime = GetTickCount();
268 float TotalTime;
269 string tailString;
270
271 ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl;
272 StringOut(ss.str(), TRUE);
273
274 StartTime = GetTickCount();
275
276 {
277 /* Execute the test */
278 CProcess Process(TestInfo->CommandLine, &m_StartupInfo);
279
280 /* Receive all the data from the pipe */
281 do
282 {
283 /* When the application finished, make sure that we peek the pipe one more time, so that we get all data.
284 If the following condition would be the while() condition, we might hit a race condition:
285 - We check for data with PeekNamedPipe -> no data available
286 - The application outputs its data and finishes
287 - WaitForSingleObject reports that the application has finished and we break the loop without receiving any data
288 */
289 if(WaitForSingleObject(Process.GetProcessHandle(), 0) != WAIT_TIMEOUT)
290 BreakLoop = true;
291
292 if(!PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL))
293 FATAL("PeekNamedPipe failed for the test run\n");
294
295 if(BytesAvailable)
296 {
297 /* There is data, so get it and output it */
298 auto_array_ptr<char> Buffer(new char[BytesAvailable + 1]);
299
300 if(!ReadFile(m_hReadPipe, Buffer, BytesAvailable, &Temp, NULL))
301 FATAL("ReadFile failed for the test run\n");
302
303 /* Output text through StringOut, even while the test is still running */
304 Buffer[BytesAvailable] = 0;
305 tailString = StringOut(tailString.append(string(Buffer)), FALSE);
306
307 if(Configuration.DoSubmit())
308 TestInfo->Log += Buffer;
309 }
310 }
311 while(!BreakLoop);
312 }
313
314 /* Print what's left */
315 if(!tailString.empty())
316 StringOut(tailString, TRUE);
317
318 TotalTime = ((float)GetTickCount() - StartTime)/1000;
319 ssFinish << "Test " << TestInfo->Test << " completed in ";
320 ssFinish << setprecision(2) << fixed << TotalTime << " seconds." << endl;
321 StringOut(ssFinish.str(), TRUE);
322 }
323
324 /**
325 * Interface to other classes for running all desired Wine tests.
326 */
327 void
328 CWineTest::Run()
329 {
330 auto_ptr<CTestList> TestList;
331 auto_ptr<CWebService> WebService;
332 CTestInfo* TestInfo;
333 SECURITY_ATTRIBUTES SecurityAttributes;
334
335 /* Create a pipe for getting the output of the tests */
336 SecurityAttributes.nLength = sizeof(SecurityAttributes);
337 SecurityAttributes.bInheritHandle = TRUE;
338 SecurityAttributes.lpSecurityDescriptor = NULL;
339
340 if(!CreatePipe(&m_hReadPipe, &m_hWritePipe, &SecurityAttributes, 0))
341 FATAL("CreatePipe failed\n");
342
343 m_StartupInfo.cb = sizeof(m_StartupInfo);
344 m_StartupInfo.dwFlags = STARTF_USESTDHANDLES;
345 m_StartupInfo.hStdOutput = m_hWritePipe;
346
347 /* The virtual test list is of course faster, so it should be preferred over
348 the journaled one.
349 Enable the journaled one only in case ...
350 - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2)
351 - we shall keep information for Crash Recovery
352 - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */
353 if(Configuration.IsReactOS() && Configuration.DoCrashRecovery() && Configuration.GetModule().empty())
354 {
355 /* Use a test list with a permanent journal */
356 TestList.reset(new CJournaledTestList(this));
357 }
358 else
359 {
360 /* Use the fast virtual test list with no additional overhead */
361 TestList.reset(new CVirtualTestList(this));
362 }
363
364 /* Initialize the Web Service interface if required */
365 if(Configuration.DoSubmit())
366 WebService.reset(new CWebService());
367
368 /* Get information for each test to run */
369 while((TestInfo = TestList->GetNextTestInfo()) != 0)
370 {
371 auto_ptr<CTestInfo> TestInfoPtr(TestInfo);
372
373 RunTest(TestInfo);
374
375 if(Configuration.DoSubmit() && !TestInfo->Log.empty())
376 WebService->Submit("wine", TestInfo);
377
378 StringOut("\n\n", TRUE);
379 }
380 }