[ROSAUTOTEST]
[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 FATAL("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 FATAL("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 SSEXCEPTION;
138 }
139
140 /* Read the data */
141 m_ListBuffer = new char[BytesAvailable];
142
143 if(!Pipe.Read(m_ListBuffer, BytesAvailable, &Temp))
144 FATAL("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 while(GetNextTest())
212 {
213 /* If the user specified a test through the command line, check this here */
214 if(!Configuration.GetTest().empty() && Configuration.GetTest() != m_CurrentTest)
215 continue;
216
217 {
218 auto_ptr<CTestInfo> TestInfo(new CTestInfo());
219 size_t UnderscorePosition;
220
221 /* Build the command line */
222 TestInfo->CommandLine = m_TestPath;
223 TestInfo->CommandLine += m_CurrentFile;
224 TestInfo->CommandLine += ' ';
225 TestInfo->CommandLine += AsciiToUnicode(m_CurrentTest);
226
227 /* Store the Module name */
228 UnderscorePosition = m_CurrentFile.find_last_of('_');
229
230 if(UnderscorePosition == m_CurrentFile.npos)
231 {
232 stringstream ss;
233
234 ss << "Invalid test file name: " << UnicodeToAscii(m_CurrentFile) << endl;
235 SSEXCEPTION;
236 }
237
238 TestInfo->Module = UnicodeToAscii(m_CurrentFile.substr(0, UnderscorePosition));
239
240 /* Store the test */
241 TestInfo->Test = m_CurrentTest;
242
243 return TestInfo.release();
244 }
245 }
246 }
247
248 return NULL;
249 }
250
251 /**
252 * Runs a Wine test and captures the output
253 *
254 * @param TestInfo
255 * Pointer to a CTestInfo object containing information about the test.
256 * Will contain the test log afterwards if the user wants to submit data.
257 */
258 void
259 CWineTest::RunTest(CTestInfo* TestInfo)
260 {
261 DWORD BytesAvailable;
262 stringstream ss, ssFinish;
263 DWORD StartTime;
264 float TotalTime;
265 string tailString;
266 CPipe Pipe;
267 char Buffer[1024];
268
269 ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl;
270 StringOut(ss.str());
271
272 StartTime = GetTickCount();
273
274 {
275 /* Execute the test */
276 CPipedProcess Process(TestInfo->CommandLine, Pipe);
277
278 /* Receive all the data from the pipe */
279 while(Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable) && BytesAvailable)
280 {
281 /* Output text through StringOut, even while the test is still running */
282 Buffer[BytesAvailable] = 0;
283 tailString = StringOut(tailString.append(string(Buffer)), false);
284
285 if(Configuration.DoSubmit())
286 TestInfo->Log += Buffer;
287 }
288 if(GetLastError() != ERROR_BROKEN_PIPE)
289 FATAL("CPipe::Read failed for the test run\n");
290 }
291
292 /* Print what's left */
293 if(!tailString.empty())
294 StringOut(tailString);
295
296 TotalTime = ((float)GetTickCount() - StartTime)/1000;
297 ssFinish << "Test " << TestInfo->Test << " completed in ";
298 ssFinish << setprecision(2) << fixed << TotalTime << " seconds." << endl;
299 StringOut(ssFinish.str());
300 }
301
302 /**
303 * Interface to other classes for running all desired Wine tests.
304 */
305 void
306 CWineTest::Run()
307 {
308 auto_ptr<CTestList> TestList;
309 auto_ptr<CWebService> WebService;
310 CTestInfo* TestInfo;
311
312 /* The virtual test list is of course faster, so it should be preferred over
313 the journaled one.
314 Enable the journaled one only in case ...
315 - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2)
316 - we shall keep information for Crash Recovery
317 - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */
318 if(Configuration.IsReactOS() && Configuration.DoCrashRecovery() && Configuration.GetModule().empty())
319 {
320 /* Use a test list with a permanent journal */
321 TestList.reset(new CJournaledTestList(this));
322 }
323 else
324 {
325 /* Use the fast virtual test list with no additional overhead */
326 TestList.reset(new CVirtualTestList(this));
327 }
328
329 /* Initialize the Web Service interface if required */
330 if(Configuration.DoSubmit())
331 WebService.reset(new CWebService());
332
333 /* Get information for each test to run */
334 while((TestInfo = TestList->GetNextTestInfo()) != 0)
335 {
336 auto_ptr<CTestInfo> TestInfoPtr(TestInfo);
337
338 RunTest(TestInfo);
339
340 if(Configuration.DoSubmit() && !TestInfo->Log.empty())
341 WebService->Submit("wine", TestInfo);
342
343 StringOut("\n\n");
344 }
345 }