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>
10 static const DWORD ListTimeout
= 10000;
13 * Constructs a CWineTest object.
15 CWineTest::CWineTest()
16 : m_hFind(NULL
), m_ListBuffer(NULL
)
18 WCHAR wszDirectory
[MAX_PATH
];
20 /* Set up m_TestPath */
21 if (GetEnvironmentVariableW(L
"ROSAUTOTEST_DIR", wszDirectory
, MAX_PATH
))
23 m_TestPath
= wszDirectory
;
24 if (*m_TestPath
.rbegin() != L
'\\')
29 if (!GetWindowsDirectoryW(wszDirectory
, MAX_PATH
))
30 FATAL("GetWindowsDirectoryW failed");
32 m_TestPath
= wszDirectory
;
33 m_TestPath
+= L
"\\bin\\";
38 * Destructs a CWineTest object.
40 CWineTest::~CWineTest()
50 * Gets the next module test file using the FindFirstFileW/FindNextFileW API.
53 * true if we found a next file, otherwise false.
56 CWineTest::GetNextFile()
58 bool FoundFile
= false;
61 /* Did we already begin searching for files? */
64 /* Then get the next file (if any) */
65 if(FindNextFileW(m_hFind
, &fd
))
70 /* Start searching for test files */
71 wstring FindPath
= m_TestPath
;
73 /* Did the user specify a module? */
74 if(Configuration
.GetModule().empty())
76 /* No module, so search for all files in that directory */
81 /* Search for files with the pattern "modulename_*" */
82 FindPath
+= Configuration
.GetModule();
83 FindPath
+= L
"_*.exe";
86 /* Search for the first file and check whether we got one */
87 m_hFind
= FindFirstFileW(FindPath
.c_str(), &fd
);
89 if(m_hFind
!= INVALID_HANDLE_VALUE
)
94 m_CurrentFile
= fd
.cFileName
;
100 * Executes the --list command of a module test file to get information about the available tests.
103 * The number of bytes we read into the m_ListBuffer member variable by capturing the output of the --list command.
106 CWineTest::DoListCommand()
108 DWORD BytesAvailable
;
113 /* Build the command line */
114 CommandLine
= m_TestPath
;
115 CommandLine
+= m_CurrentFile
;
116 CommandLine
+= L
" --list";
119 /* Start the process for getting all available tests */
120 CPipedProcess
Process(CommandLine
, Pipe
);
122 /* Wait till this process ended */
123 if(WaitForSingleObject(Process
.GetProcessHandle(), ListTimeout
) == WAIT_FAILED
)
124 TESTEXCEPTION("WaitForSingleObject failed for the test list\n");
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");
131 /* Check if we got any */
136 ss
<< "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile
) << endl
;
137 TESTEXCEPTION(ss
.str());
141 m_ListBuffer
= new char[BytesAvailable
];
143 if(!Pipe
.Read(m_ListBuffer
, BytesAvailable
, &Temp
))
144 TESTEXCEPTION("CPipe::Read failed\n");
146 return BytesAvailable
;
150 * Gets the next test from m_ListBuffer, which was filled with information from the --list command.
153 * true if a next test was found, otherwise false.
156 CWineTest::GetNextTest()
159 static DWORD BufferSize
;
164 /* Perform the --list command */
165 BufferSize
= DoListCommand();
167 /* Move the pointer to the first test */
168 pStart
= strchr(m_ListBuffer
, '\n');
172 /* If we reach the buffer size, we finished analyzing the output of this test */
173 if(pStart
>= (m_ListBuffer
+ BufferSize
))
175 /* Clear m_CurrentFile to indicate that */
176 m_CurrentFile
.clear();
178 /* Also free the memory for the list buffer */
179 delete[] m_ListBuffer
;
185 /* Get start and end of this test name */
191 /* Store the test name */
192 m_CurrentTest
= string(pStart
, pEnd
);
194 /* Move the pointer to the next test */
201 * Interface to CTestList-derived classes for getting all information about the next test to be run.
204 * Returns a pointer to a CTestInfo object containing all available information about the next test.
207 CWineTest::GetNextTestInfo()
209 while(!m_CurrentFile
.empty() || GetNextFile())
215 /* If the user specified a test through the command line, check this here */
216 if(!Configuration
.GetTest().empty() && Configuration
.GetTest() != m_CurrentTest
)
220 auto_ptr
<CTestInfo
> TestInfo(new CTestInfo());
221 size_t UnderscorePosition
;
223 /* Build the command line */
224 TestInfo
->CommandLine
= m_TestPath
;
225 TestInfo
->CommandLine
+= m_CurrentFile
;
226 TestInfo
->CommandLine
+= ' ';
227 TestInfo
->CommandLine
+= AsciiToUnicode(m_CurrentTest
);
229 /* Store the Module name */
230 UnderscorePosition
= m_CurrentFile
.find_last_of('_');
232 if(UnderscorePosition
== m_CurrentFile
.npos
)
236 ss
<< "Invalid test file name: " << UnicodeToAscii(m_CurrentFile
) << endl
;
240 TestInfo
->Module
= UnicodeToAscii(m_CurrentFile
.substr(0, UnderscorePosition
));
243 TestInfo
->Test
= m_CurrentTest
;
245 return TestInfo
.release();
249 catch(CTestException
& e
)
253 ss
<< "An exception occurred trying to list tests for: " << UnicodeToAscii(m_CurrentFile
) << endl
;
255 StringOut(e
.GetMessage());
257 m_CurrentFile
.clear();
258 delete[] m_ListBuffer
;
266 * Runs a Wine test and captures the output
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.
273 CWineTest::RunTest(CTestInfo
* TestInfo
)
275 DWORD BytesAvailable
;
276 stringstream ss
, ssFinish
;
283 ss
<< "Running Wine Test, Module: " << TestInfo
->Module
<< ", Test: " << TestInfo
->Test
<< endl
;
286 StartTime
= GetTickCount();
290 /* Execute the test */
291 CPipedProcess
Process(TestInfo
->CommandLine
, Pipe
);
293 /* Receive all the data from the pipe */
294 while(Pipe
.Read(Buffer
, sizeof(Buffer
) - 1, &BytesAvailable
) && BytesAvailable
)
296 /* Output text through StringOut, even while the test is still running */
297 Buffer
[BytesAvailable
] = 0;
298 tailString
= StringOut(tailString
.append(string(Buffer
)), false);
300 if(Configuration
.DoSubmit())
301 TestInfo
->Log
+= Buffer
;
303 if(GetLastError() != ERROR_BROKEN_PIPE
)
304 TESTEXCEPTION("CPipe::Read failed for the test run\n");
306 catch(CTestException
& e
)
308 if(!tailString
.empty())
309 StringOut(tailString
);
311 StringOut(e
.GetMessage());
312 TestInfo
->Log
+= e
.GetMessage();
315 /* Print what's left */
316 if(!tailString
.empty())
317 StringOut(tailString
);
319 TotalTime
= ((float)GetTickCount() - StartTime
)/1000;
320 ssFinish
<< "Test " << TestInfo
->Test
<< " completed in ";
321 ssFinish
<< setprecision(2) << fixed
<< TotalTime
<< " seconds." << endl
;
322 StringOut(ssFinish
.str());
323 TestInfo
->Log
+= ssFinish
.str();
327 * Interface to other classes for running all desired Wine tests.
332 auto_ptr
<CTestList
> TestList
;
333 auto_ptr
<CWebService
> WebService
;
337 /* The virtual test list is of course faster, so it should be preferred over
339 Enable the journaled one only in case ...
340 - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2)
341 - we shall keep information for Crash Recovery
342 - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */
343 if(Configuration
.IsReactOS() && Configuration
.DoCrashRecovery() && Configuration
.GetModule().empty())
345 /* Use a test list with a permanent journal */
346 TestList
.reset(new CJournaledTestList(this));
350 /* Use the fast virtual test list with no additional overhead */
351 TestList
.reset(new CVirtualTestList(this));
354 /* Initialize the Web Service interface if required */
355 if(Configuration
.DoSubmit())
356 WebService
.reset(new CWebService());
358 /* Disable error dialogs if we're running in non-interactive mode */
359 if(!Configuration
.IsInteractive())
360 ErrorMode
= SetErrorMode(SEM_FAILCRITICALERRORS
| SEM_NOGPFAULTERRORBOX
);
362 /* Get information for each test to run */
363 while((TestInfo
= TestList
->GetNextTestInfo()) != 0)
365 auto_ptr
<CTestInfo
> TestInfoPtr(TestInfo
);
369 if(Configuration
.DoSubmit() && !TestInfo
->Log
.empty())
370 WebService
->Submit("wine", TestInfo
);
375 /* We're done with all tests. Finish this run */
376 if(Configuration
.DoSubmit())
377 WebService
->Finish("wine");
379 /* Restore the original error mode */
380 if(!Configuration
.IsInteractive())
381 SetErrorMode(ErrorMode
);