[CDFS]
[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-2015 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 : m_hFind(NULL), m_ListBuffer(NULL)
17 {
18 WCHAR wszDirectory[MAX_PATH];
19
20 /* Set up m_TestPath */
21 if (GetEnvironmentVariableW(L"ROSAUTOTEST_DIR", wszDirectory, MAX_PATH))
22 {
23 m_TestPath = wszDirectory;
24 if (*m_TestPath.rbegin() != L'\\')
25 m_TestPath += L'\\';
26 }
27 else
28 {
29 if (!GetWindowsDirectoryW(wszDirectory, MAX_PATH))
30 FATAL("GetWindowsDirectoryW failed");
31
32 m_TestPath = wszDirectory;
33 m_TestPath += L"\\bin\\";
34 }
35 }
36
37 /**
38 * Destructs a CWineTest object.
39 */
40 CWineTest::~CWineTest()
41 {
42 if(m_hFind)
43 FindClose(m_hFind);
44
45 if(m_ListBuffer)
46 delete m_ListBuffer;
47 }
48
49 /**
50 * Gets the next module test file using the FindFirstFileW/FindNextFileW API.
51 *
52 * @return
53 * true if we found a next file, otherwise false.
54 */
55 bool
56 CWineTest::GetNextFile()
57 {
58 bool FoundFile = false;
59 WIN32_FIND_DATAW fd;
60
61 /* Did we already begin searching for files? */
62 if(m_hFind)
63 {
64 /* Then get the next file (if any) */
65 if(FindNextFileW(m_hFind, &fd))
66 FoundFile = true;
67 }
68 else
69 {
70 /* Start searching for test files */
71 wstring FindPath = m_TestPath;
72
73 /* Did the user specify a module? */
74 if(Configuration.GetModule().empty())
75 {
76 /* No module, so search for all files in that directory */
77 FindPath += L"*.exe";
78 }
79 else
80 {
81 /* Search for files with the pattern "modulename_*" */
82 FindPath += Configuration.GetModule();
83 FindPath += L"_*.exe";
84 }
85
86 /* Search for the first file and check whether we got one */
87 m_hFind = FindFirstFileW(FindPath.c_str(), &fd);
88
89 if(m_hFind != INVALID_HANDLE_VALUE)
90 FoundFile = true;
91 }
92
93 if(FoundFile)
94 m_CurrentFile = fd.cFileName;
95
96 return FoundFile;
97 }
98
99 /**
100 * Executes the --list command of a module test file to get information about the available tests.
101 *
102 * @return
103 * The number of bytes we read into the m_ListBuffer member variable by capturing the output of the --list command.
104 */
105 DWORD
106 CWineTest::DoListCommand()
107 {
108 DWORD BytesAvailable;
109 DWORD Temp;
110 wstring CommandLine;
111 CPipe Pipe;
112
113 /* Build the command line */
114 CommandLine = m_TestPath;
115 CommandLine += m_CurrentFile;
116 CommandLine += L" --list";
117
118 {
119 /* Start the process for getting all available tests */
120 CPipedProcess Process(CommandLine, Pipe);
121
122 /* Wait till this process ended */
123 if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED)
124 TESTEXCEPTION("WaitForSingleObject failed for the test list\n");
125 }
126
127 /* Read the output data into a buffer */
128 if(!Pipe.Peek(NULL, 0, NULL, &BytesAvailable))
129 TESTEXCEPTION("CPipe::Peek failed for the test list\n");
130
131 /* Check if we got any */
132 if(!BytesAvailable)
133 {
134 stringstream ss;
135
136 ss << "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile) << endl;
137 TESTEXCEPTION(ss.str());
138 }
139
140 /* Read the data */
141 m_ListBuffer = new char[BytesAvailable];
142
143 if(!Pipe.Read(m_ListBuffer, BytesAvailable, &Temp))
144 TESTEXCEPTION("CPipe::Read failed\n");
145
146 return BytesAvailable;
147 }
148
149 /**
150 * Gets the next test from m_ListBuffer, which was filled with information from the --list command.
151 *
152 * @return
153 * true if a next test was found, otherwise false.
154 */
155 bool
156 CWineTest::GetNextTest()
157 {
158 PCHAR pEnd;
159 static DWORD BufferSize;
160 static PCHAR pStart;
161
162 if(!m_ListBuffer)
163 {
164 /* Perform the --list command */
165 BufferSize = DoListCommand();
166
167 /* Move the pointer to the first test */
168 pStart = strchr(m_ListBuffer, '\n');
169 pStart += 5;
170 }
171
172 /* If we reach the buffer size, we finished analyzing the output of this test */
173 if(pStart >= (m_ListBuffer + BufferSize))
174 {
175 /* Clear m_CurrentFile to indicate that */
176 m_CurrentFile.clear();
177
178 /* Also free the memory for the list buffer */
179 delete[] m_ListBuffer;
180 m_ListBuffer = NULL;
181
182 return false;
183 }
184
185 /* Get start and end of this test name */
186 pEnd = pStart;
187
188 while(*pEnd != '\r')
189 ++pEnd;
190
191 /* Store the test name */
192 m_CurrentTest = string(pStart, pEnd);
193
194 /* Move the pointer to the next test */
195 pStart = pEnd + 6;
196
197 return true;
198 }
199
200 /**
201 * Interface to CTestList-derived classes for getting all information about the next test to be run.
202 *
203 * @return
204 * Returns a pointer to a CTestInfo object containing all available information about the next test.
205 */
206 CTestInfo*
207 CWineTest::GetNextTestInfo()
208 {
209 while(!m_CurrentFile.empty() || GetNextFile())
210 {
211 try
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 catch(CTestException& e)
250 {
251 stringstream ss;
252
253 ss << "An exception occurred trying to list tests for: " << UnicodeToAscii(m_CurrentFile) << endl;
254 StringOut(ss.str());
255 StringOut(e.GetMessage());
256 StringOut("\n");
257 m_CurrentFile.clear();
258 delete[] m_ListBuffer;
259 }
260 }
261
262 return NULL;
263 }
264
265 /**
266 * Runs a Wine test and captures the output
267 *
268 * @param TestInfo
269 * Pointer to a CTestInfo object containing information about the test.
270 * Will contain the test log afterwards if the user wants to submit data.
271 */
272 void
273 CWineTest::RunTest(CTestInfo* TestInfo)
274 {
275 DWORD BytesAvailable;
276 stringstream ss, ssFinish;
277 DWORD StartTime;
278 float TotalTime;
279 CPipe Pipe;
280 char Buffer[1024];
281
282 ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl;
283 StringOut(ss.str());
284
285 StartTime = GetTickCount();
286
287 try
288 {
289 /* Execute the test */
290 CPipedProcess Process(TestInfo->CommandLine, Pipe);
291
292 /* Receive all the data from the pipe */
293 while(Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable) && BytesAvailable)
294 {
295 /* Output text through StringOut, even while the test is still running */
296 Buffer[BytesAvailable] = 0;
297 StringOut(string(Buffer));
298
299 if(Configuration.DoSubmit())
300 TestInfo->Log += Buffer;
301 }
302 if(GetLastError() != ERROR_BROKEN_PIPE)
303 TESTEXCEPTION("CPipe::Read failed for the test run\n");
304 }
305 catch(CTestException& e)
306 {
307 StringOut(e.GetMessage());
308 TestInfo->Log += e.GetMessage();
309 }
310
311 TotalTime = ((float)GetTickCount() - StartTime)/1000;
312 ssFinish << "Test " << TestInfo->Test << " completed in ";
313 ssFinish << setprecision(2) << fixed << TotalTime << " seconds." << endl;
314 StringOut(ssFinish.str());
315 TestInfo->Log += ssFinish.str();
316 }
317
318 /**
319 * Interface to other classes for running all desired Wine tests.
320 */
321 void
322 CWineTest::Run()
323 {
324 auto_ptr<CTestList> TestList;
325 auto_ptr<CWebService> WebService;
326 CTestInfo* TestInfo;
327 DWORD ErrorMode;
328
329 /* The virtual test list is of course faster, so it should be preferred over
330 the journaled one.
331 Enable the journaled one only in case ...
332 - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2)
333 - we shall keep information for Crash Recovery
334 - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */
335 if(Configuration.IsReactOS() && Configuration.DoCrashRecovery() && Configuration.GetModule().empty())
336 {
337 /* Use a test list with a permanent journal */
338 TestList.reset(new CJournaledTestList(this));
339 }
340 else
341 {
342 /* Use the fast virtual test list with no additional overhead */
343 TestList.reset(new CVirtualTestList(this));
344 }
345
346 /* Initialize the Web Service interface if required */
347 if(Configuration.DoSubmit())
348 WebService.reset(new CWebService());
349
350 /* Disable error dialogs if we're running in non-interactive mode */
351 if(!Configuration.IsInteractive())
352 ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
353
354 /* Get information for each test to run */
355 while((TestInfo = TestList->GetNextTestInfo()) != 0)
356 {
357 auto_ptr<CTestInfo> TestInfoPtr(TestInfo);
358
359 RunTest(TestInfo);
360
361 if(Configuration.DoSubmit() && !TestInfo->Log.empty())
362 WebService->Submit("wine", TestInfo);
363
364 StringOut("\n\n");
365 }
366
367 /* We're done with all tests. Finish this run */
368 if(Configuration.DoSubmit())
369 WebService->Finish("wine");
370
371 /* Restore the original error mode */
372 if(!Configuration.IsInteractive())
373 SetErrorMode(ErrorMode);
374 }