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>
10 static const DWORD ListTimeout
= 10000;
13 * Constructs a CWineTest object.
15 CWineTest::CWineTest()
17 WCHAR WindowsDirectory
[MAX_PATH
];
19 /* Zero-initialize variables */
24 memset(&m_StartupInfo
, 0, sizeof(m_StartupInfo
));
26 /* Set up m_TestPath */
27 if(!GetWindowsDirectoryW(WindowsDirectory
, MAX_PATH
))
28 FATAL("GetWindowsDirectoryW failed");
30 m_TestPath
= WindowsDirectory
;
31 m_TestPath
+= L
"\\bin\\";
35 * Destructs a CWineTest object.
37 CWineTest::~CWineTest()
43 CloseHandle(m_hReadPipe
);
46 CloseHandle(m_hWritePipe
);
53 * Gets the next module test file using the FindFirstFileW/FindNextFileW API.
56 * true if we found a next file, otherwise false.
59 CWineTest::GetNextFile()
61 bool FoundFile
= false;
64 /* Did we already begin searching for files? */
67 /* Then get the next file (if any) */
68 if(FindNextFileW(m_hFind
, &fd
))
73 /* Start searching for test files */
74 wstring FindPath
= m_TestPath
;
76 /* Did the user specify a module? */
77 if(Configuration
.GetModule().empty())
79 /* No module, so search for all files in that directory */
84 /* Search for files with the pattern "modulename_*" */
85 FindPath
+= Configuration
.GetModule();
86 FindPath
+= L
"_*.exe";
89 /* Search for the first file and check whether we got one */
90 m_hFind
= FindFirstFileW(FindPath
.c_str(), &fd
);
92 if(m_hFind
!= INVALID_HANDLE_VALUE
)
97 m_CurrentFile
= fd
.cFileName
;
103 * Executes the --list command of a module test file to get information about the available tests.
106 * The number of bytes we read into the m_ListBuffer member variable by capturing the output of the --list command.
109 CWineTest::DoListCommand()
111 DWORD BytesAvailable
;
115 /* Build the command line */
116 CommandLine
= m_TestPath
;
117 CommandLine
+= m_CurrentFile
;
118 CommandLine
+= L
" --list";
121 /* Start the process for getting all available tests */
122 CProcess
Process(CommandLine
, &m_StartupInfo
);
124 /* Wait till this process ended */
125 if(WaitForSingleObject(Process
.GetProcessHandle(), ListTimeout
) == WAIT_FAILED
)
126 FATAL("WaitForSingleObject failed for the test list\n");
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");
133 /* Check if we got any */
138 ss
<< "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile
) << endl
;
143 m_ListBuffer
= new char[BytesAvailable
];
145 if(!ReadFile(m_hReadPipe
, m_ListBuffer
, BytesAvailable
, &Temp
, NULL
))
146 FATAL("ReadPipe failed\n");
148 return BytesAvailable
;
152 * Gets the next test from m_ListBuffer, which was filled with information from the --list command.
155 * true if a next test was found, otherwise false.
158 CWineTest::GetNextTest()
161 static DWORD BufferSize
;
166 /* Perform the --list command */
167 BufferSize
= DoListCommand();
169 /* Move the pointer to the first test */
170 pStart
= strchr(m_ListBuffer
, '\n');
174 /* If we reach the buffer size, we finished analyzing the output of this test */
175 if(pStart
>= (m_ListBuffer
+ BufferSize
))
177 /* Clear m_CurrentFile to indicate that */
178 m_CurrentFile
.clear();
180 /* Also free the memory for the list buffer */
181 delete[] m_ListBuffer
;
187 /* Get start and end of this test name */
193 /* Store the test name */
194 m_CurrentTest
= string(pStart
, pEnd
);
196 /* Move the pointer to the next test */
203 * Interface to CTestList-derived classes for getting all information about the next test to be run.
206 * Returns a pointer to a CTestInfo object containing all available information about the next test.
209 CWineTest::GetNextTestInfo()
211 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();
254 * Runs a Wine test and captures the output
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.
261 CWineTest::RunTest(CTestInfo
* TestInfo
)
263 bool BreakLoop
= false;
264 DWORD BytesAvailable
;
266 stringstream ss
, ssFinish
;
267 DWORD StartTime
= GetTickCount();
271 ss
<< "Running Wine Test, Module: " << TestInfo
->Module
<< ", Test: " << TestInfo
->Test
<< endl
;
274 StartTime
= GetTickCount();
277 /* Execute the test */
278 CProcess
Process(TestInfo
->CommandLine
, &m_StartupInfo
);
280 /* Receive all the data from the pipe */
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
289 if(WaitForSingleObject(Process
.GetProcessHandle(), 0) != WAIT_TIMEOUT
)
292 if(!PeekNamedPipe(m_hReadPipe
, NULL
, 0, NULL
, &BytesAvailable
, NULL
))
293 FATAL("PeekNamedPipe failed for the test run\n");
297 /* There is data, so get it and output it */
298 auto_array_ptr
<char> Buffer(new char[BytesAvailable
+ 1]);
300 if(!ReadFile(m_hReadPipe
, Buffer
, BytesAvailable
, &Temp
, NULL
))
301 FATAL("ReadFile failed for the test run\n");
303 /* Output text through StringOut, even while the test is still running */
304 Buffer
[BytesAvailable
] = 0;
305 tailString
= StringOut(tailString
.append(string(Buffer
)), false);
307 if(Configuration
.DoSubmit())
308 TestInfo
->Log
+= Buffer
;
314 /* Print what's left */
315 if(!tailString
.empty())
316 StringOut(tailString
);
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());
325 * Interface to other classes for running all desired Wine tests.
330 auto_ptr
<CTestList
> TestList
;
331 auto_ptr
<CWebService
> WebService
;
333 SECURITY_ATTRIBUTES SecurityAttributes
;
335 /* Create a pipe for getting the output of the tests */
336 SecurityAttributes
.nLength
= sizeof(SecurityAttributes
);
337 SecurityAttributes
.bInheritHandle
= TRUE
;
338 SecurityAttributes
.lpSecurityDescriptor
= NULL
;
340 if(!CreatePipe(&m_hReadPipe
, &m_hWritePipe
, &SecurityAttributes
, 0))
341 FATAL("CreatePipe failed\n");
343 m_StartupInfo
.cb
= sizeof(m_StartupInfo
);
344 m_StartupInfo
.dwFlags
= STARTF_USESTDHANDLES
;
345 m_StartupInfo
.hStdInput
= GetStdHandle(STD_INPUT_HANDLE
);
346 m_StartupInfo
.hStdOutput
= m_hWritePipe
;
347 m_StartupInfo
.hStdError
= m_hWritePipe
;
349 /* The virtual test list is of course faster, so it should be preferred over
351 Enable the journaled one only in case ...
352 - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2)
353 - we shall keep information for Crash Recovery
354 - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */
355 if(Configuration
.IsReactOS() && Configuration
.DoCrashRecovery() && Configuration
.GetModule().empty())
357 /* Use a test list with a permanent journal */
358 TestList
.reset(new CJournaledTestList(this));
362 /* Use the fast virtual test list with no additional overhead */
363 TestList
.reset(new CVirtualTestList(this));
366 /* Initialize the Web Service interface if required */
367 if(Configuration
.DoSubmit())
368 WebService
.reset(new CWebService());
370 /* Get information for each test to run */
371 while((TestInfo
= TestList
->GetNextTestInfo()) != 0)
373 auto_ptr
<CTestInfo
> TestInfoPtr(TestInfo
);
377 if(Configuration
.DoSubmit() && !TestInfo
->Log
.empty())
378 WebService
->Submit("wine", TestInfo
);