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