1c2c66affSColin Finck /*
2c2c66affSColin Finck * PROJECT: ReactOS Automatic Testing Utility
3c2c66affSColin Finck * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
4c2c66affSColin Finck * PURPOSE: Class implementing functions for handling Wine tests
57dd4d225SColin Finck * COPYRIGHT: Copyright 2009-2019 Colin Finck (colin@reactos.org)
6c2c66affSColin Finck */
7c2c66affSColin Finck
8c2c66affSColin Finck #include "precomp.h"
9c2c66affSColin Finck
10c2c66affSColin Finck static const DWORD ListTimeout = 10000;
1186ae6b02SColin Finck
1286ae6b02SColin Finck // This value needs to be lower than the <timeout> configured in sysreg.xml! (usually 180000)
1386ae6b02SColin Finck // Otherwise, sysreg2 kills the VM before we can kill the process.
1486ae6b02SColin Finck static const DWORD ProcessActivityTimeout = 170000;
1586ae6b02SColin Finck
16c2c66affSColin Finck
17c2c66affSColin Finck /**
18c2c66affSColin Finck * Constructs a CWineTest object.
19c2c66affSColin Finck */
CWineTest()20c2c66affSColin Finck CWineTest::CWineTest()
21c2c66affSColin Finck : m_hFind(NULL), m_ListBuffer(NULL)
22c2c66affSColin Finck {
23c2c66affSColin Finck WCHAR wszDirectory[MAX_PATH];
24c2c66affSColin Finck
25c2c66affSColin Finck /* Set up m_TestPath */
26c2c66affSColin Finck if (GetEnvironmentVariableW(L"ROSAUTOTEST_DIR", wszDirectory, MAX_PATH))
27c2c66affSColin Finck {
28c2c66affSColin Finck m_TestPath = wszDirectory;
29c2c66affSColin Finck if (*m_TestPath.rbegin() != L'\\')
30c2c66affSColin Finck m_TestPath += L'\\';
31c2c66affSColin Finck }
32c2c66affSColin Finck else
33c2c66affSColin Finck {
34c2c66affSColin Finck if (!GetWindowsDirectoryW(wszDirectory, MAX_PATH))
35f79ca12eSSerge Gautherie FATAL("GetWindowsDirectoryW failed\n");
36c2c66affSColin Finck
37c2c66affSColin Finck m_TestPath = wszDirectory;
38c2c66affSColin Finck m_TestPath += L"\\bin\\";
39c2c66affSColin Finck }
40c2c66affSColin Finck }
41c2c66affSColin Finck
42c2c66affSColin Finck /**
43c2c66affSColin Finck * Destructs a CWineTest object.
44c2c66affSColin Finck */
~CWineTest()45c2c66affSColin Finck CWineTest::~CWineTest()
46c2c66affSColin Finck {
47c2c66affSColin Finck if(m_hFind)
48c2c66affSColin Finck FindClose(m_hFind);
49c2c66affSColin Finck
50c2c66affSColin Finck if(m_ListBuffer)
51c2c66affSColin Finck delete m_ListBuffer;
52c2c66affSColin Finck }
53c2c66affSColin Finck
54c2c66affSColin Finck /**
55c2c66affSColin Finck * Gets the next module test file using the FindFirstFileW/FindNextFileW API.
56c2c66affSColin Finck *
57c2c66affSColin Finck * @return
58c2c66affSColin Finck * true if we found a next file, otherwise false.
59c2c66affSColin Finck */
60c2c66affSColin Finck bool
GetNextFile()61c2c66affSColin Finck CWineTest::GetNextFile()
62c2c66affSColin Finck {
63c2c66affSColin Finck bool FoundFile = false;
64c2c66affSColin Finck WIN32_FIND_DATAW fd;
65c2c66affSColin Finck
66c2c66affSColin Finck /* Did we already begin searching for files? */
67c2c66affSColin Finck if(m_hFind)
68c2c66affSColin Finck {
69c2c66affSColin Finck /* Then get the next file (if any) */
70c2c66affSColin Finck if(FindNextFileW(m_hFind, &fd))
71c2c66affSColin Finck FoundFile = true;
72c2c66affSColin Finck }
73c2c66affSColin Finck else
74c2c66affSColin Finck {
75c2c66affSColin Finck /* Start searching for test files */
76c2c66affSColin Finck wstring FindPath = m_TestPath;
77c2c66affSColin Finck
78c2c66affSColin Finck /* Did the user specify a module? */
79c2c66affSColin Finck if(Configuration.GetModule().empty())
80c2c66affSColin Finck {
81c2c66affSColin Finck /* No module, so search for all files in that directory */
82c2c66affSColin Finck FindPath += L"*.exe";
83c2c66affSColin Finck }
84c2c66affSColin Finck else
85c2c66affSColin Finck {
86c2c66affSColin Finck /* Search for files with the pattern "modulename_*" */
87c2c66affSColin Finck FindPath += Configuration.GetModule();
88c2c66affSColin Finck FindPath += L"_*.exe";
89c2c66affSColin Finck }
90c2c66affSColin Finck
91c2c66affSColin Finck /* Search for the first file and check whether we got one */
92c2c66affSColin Finck m_hFind = FindFirstFileW(FindPath.c_str(), &fd);
93c2c66affSColin Finck
94c2c66affSColin Finck if(m_hFind != INVALID_HANDLE_VALUE)
95c2c66affSColin Finck FoundFile = true;
96c2c66affSColin Finck }
97c2c66affSColin Finck
98c2c66affSColin Finck if(FoundFile)
99c2c66affSColin Finck m_CurrentFile = fd.cFileName;
100c2c66affSColin Finck
101c2c66affSColin Finck return FoundFile;
102c2c66affSColin Finck }
103c2c66affSColin Finck
104c2c66affSColin Finck /**
105c2c66affSColin Finck * Executes the --list command of a module test file to get information about the available tests.
106c2c66affSColin Finck *
107c2c66affSColin Finck * @return
108c2c66affSColin Finck * The number of bytes we read into the m_ListBuffer member variable by capturing the output of the --list command.
109c2c66affSColin Finck */
110c2c66affSColin Finck DWORD
DoListCommand()111c2c66affSColin Finck CWineTest::DoListCommand()
112c2c66affSColin Finck {
113c2c66affSColin Finck DWORD BytesAvailable;
114c2c66affSColin Finck DWORD Temp;
115c2c66affSColin Finck wstring CommandLine;
116c2c66affSColin Finck CPipe Pipe;
117c2c66affSColin Finck
118c2c66affSColin Finck /* Build the command line */
119c2c66affSColin Finck CommandLine = m_TestPath;
120c2c66affSColin Finck CommandLine += m_CurrentFile;
121c2c66affSColin Finck CommandLine += L" --list";
122c2c66affSColin Finck
123c2c66affSColin Finck {
124c2c66affSColin Finck /* Start the process for getting all available tests */
125c2c66affSColin Finck CPipedProcess Process(CommandLine, Pipe);
126c2c66affSColin Finck
127c2c66affSColin Finck /* Wait till this process ended */
128c2c66affSColin Finck if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED)
129c2c66affSColin Finck TESTEXCEPTION("WaitForSingleObject failed for the test list\n");
130c2c66affSColin Finck }
131c2c66affSColin Finck
132c2c66affSColin Finck /* Read the output data into a buffer */
133c2c66affSColin Finck if(!Pipe.Peek(NULL, 0, NULL, &BytesAvailable))
134c2c66affSColin Finck TESTEXCEPTION("CPipe::Peek failed for the test list\n");
135c2c66affSColin Finck
136c2c66affSColin Finck /* Check if we got any */
137c2c66affSColin Finck if(!BytesAvailable)
138c2c66affSColin Finck {
139c2c66affSColin Finck stringstream ss;
140c2c66affSColin Finck
141c2c66affSColin Finck ss << "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile) << endl;
142c2c66affSColin Finck TESTEXCEPTION(ss.str());
143c2c66affSColin Finck }
144c2c66affSColin Finck
145c2c66affSColin Finck /* Read the data */
146c2c66affSColin Finck m_ListBuffer = new char[BytesAvailable];
147c2c66affSColin Finck
1487dd4d225SColin Finck if(Pipe.Read(m_ListBuffer, BytesAvailable, &Temp, INFINITE) != ERROR_SUCCESS)
149c2c66affSColin Finck TESTEXCEPTION("CPipe::Read failed\n");
150c2c66affSColin Finck
151c2c66affSColin Finck return BytesAvailable;
152c2c66affSColin Finck }
153c2c66affSColin Finck
154c2c66affSColin Finck /**
155c2c66affSColin Finck * Gets the next test from m_ListBuffer, which was filled with information from the --list command.
156c2c66affSColin Finck *
157c2c66affSColin Finck * @return
158c2c66affSColin Finck * true if a next test was found, otherwise false.
159c2c66affSColin Finck */
160c2c66affSColin Finck bool
GetNextTest()161c2c66affSColin Finck CWineTest::GetNextTest()
162c2c66affSColin Finck {
163c2c66affSColin Finck PCHAR pEnd;
164c2c66affSColin Finck static DWORD BufferSize;
165c2c66affSColin Finck static PCHAR pStart;
166c2c66affSColin Finck
167c2c66affSColin Finck if(!m_ListBuffer)
168c2c66affSColin Finck {
169c2c66affSColin Finck /* Perform the --list command */
170c2c66affSColin Finck BufferSize = DoListCommand();
171c2c66affSColin Finck
172c2c66affSColin Finck /* Move the pointer to the first test */
173c2c66affSColin Finck pStart = strchr(m_ListBuffer, '\n');
174c2c66affSColin Finck pStart += 5;
175c2c66affSColin Finck }
176c2c66affSColin Finck
177c2c66affSColin Finck /* If we reach the buffer size, we finished analyzing the output of this test */
178c2c66affSColin Finck if(pStart >= (m_ListBuffer + BufferSize))
179c2c66affSColin Finck {
180c2c66affSColin Finck /* Clear m_CurrentFile to indicate that */
181c2c66affSColin Finck m_CurrentFile.clear();
182c2c66affSColin Finck
183c2c66affSColin Finck /* Also free the memory for the list buffer */
184c2c66affSColin Finck delete[] m_ListBuffer;
185c2c66affSColin Finck m_ListBuffer = NULL;
186c2c66affSColin Finck
187c2c66affSColin Finck return false;
188c2c66affSColin Finck }
189c2c66affSColin Finck
190c2c66affSColin Finck /* Get start and end of this test name */
191c2c66affSColin Finck pEnd = pStart;
192c2c66affSColin Finck
193c2c66affSColin Finck while(*pEnd != '\r')
194c2c66affSColin Finck ++pEnd;
195c2c66affSColin Finck
196c2c66affSColin Finck /* Store the test name */
197c2c66affSColin Finck m_CurrentTest = string(pStart, pEnd);
198c2c66affSColin Finck
199c2c66affSColin Finck /* Move the pointer to the next test */
200c2c66affSColin Finck pStart = pEnd + 6;
201c2c66affSColin Finck
202c2c66affSColin Finck return true;
203c2c66affSColin Finck }
204c2c66affSColin Finck
205c2c66affSColin Finck /**
206c2c66affSColin Finck * Interface to CTestList-derived classes for getting all information about the next test to be run.
207c2c66affSColin Finck *
208c2c66affSColin Finck * @return
209c2c66affSColin Finck * Returns a pointer to a CTestInfo object containing all available information about the next test.
210c2c66affSColin Finck */
211c2c66affSColin Finck CTestInfo*
GetNextTestInfo()212c2c66affSColin Finck CWineTest::GetNextTestInfo()
213c2c66affSColin Finck {
214c2c66affSColin Finck while(!m_CurrentFile.empty() || GetNextFile())
215c2c66affSColin Finck {
216ff6b1381SMark Jansen /* The user asked for a list of all modules */
217ff6b1381SMark Jansen if (Configuration.ListModulesOnly())
218ff6b1381SMark Jansen {
219ff6b1381SMark Jansen std::stringstream ss;
220ff6b1381SMark Jansen ss << "Module: " << UnicodeToAscii(m_CurrentFile) << endl;
221ff6b1381SMark Jansen m_CurrentFile.clear();
222ff6b1381SMark Jansen StringOut(ss.str());
223ff6b1381SMark Jansen continue;
224ff6b1381SMark Jansen }
225ff6b1381SMark Jansen
226c2c66affSColin Finck try
227c2c66affSColin Finck {
228c2c66affSColin Finck while(GetNextTest())
229c2c66affSColin Finck {
230c2c66affSColin Finck /* If the user specified a test through the command line, check this here */
231c2c66affSColin Finck if(!Configuration.GetTest().empty() && Configuration.GetTest() != m_CurrentTest)
232c2c66affSColin Finck continue;
233c2c66affSColin Finck
234c2c66affSColin Finck {
235c2c66affSColin Finck auto_ptr<CTestInfo> TestInfo(new CTestInfo());
236c2c66affSColin Finck size_t UnderscorePosition;
237c2c66affSColin Finck
238c2c66affSColin Finck /* Build the command line */
239c2c66affSColin Finck TestInfo->CommandLine = m_TestPath;
240c2c66affSColin Finck TestInfo->CommandLine += m_CurrentFile;
241c2c66affSColin Finck TestInfo->CommandLine += ' ';
242c2c66affSColin Finck TestInfo->CommandLine += AsciiToUnicode(m_CurrentTest);
243c2c66affSColin Finck
244c2c66affSColin Finck /* Store the Module name */
245c2c66affSColin Finck UnderscorePosition = m_CurrentFile.find_last_of('_');
246c2c66affSColin Finck
247c2c66affSColin Finck if(UnderscorePosition == m_CurrentFile.npos)
248c2c66affSColin Finck {
249c2c66affSColin Finck stringstream ss;
250c2c66affSColin Finck
251c2c66affSColin Finck ss << "Invalid test file name: " << UnicodeToAscii(m_CurrentFile) << endl;
252c2c66affSColin Finck SSEXCEPTION;
253c2c66affSColin Finck }
254c2c66affSColin Finck
255c2c66affSColin Finck TestInfo->Module = UnicodeToAscii(m_CurrentFile.substr(0, UnderscorePosition));
256c2c66affSColin Finck
257c2c66affSColin Finck /* Store the test */
258c2c66affSColin Finck TestInfo->Test = m_CurrentTest;
259c2c66affSColin Finck
260c2c66affSColin Finck return TestInfo.release();
261c2c66affSColin Finck }
262c2c66affSColin Finck }
263c2c66affSColin Finck }
264c2c66affSColin Finck catch(CTestException& e)
265c2c66affSColin Finck {
266c2c66affSColin Finck stringstream ss;
267c2c66affSColin Finck
268c2c66affSColin Finck ss << "An exception occurred trying to list tests for: " << UnicodeToAscii(m_CurrentFile) << endl;
269c2c66affSColin Finck StringOut(ss.str());
270c2c66affSColin Finck StringOut(e.GetMessage());
271c2c66affSColin Finck StringOut("\n");
272c2c66affSColin Finck m_CurrentFile.clear();
273c2c66affSColin Finck delete[] m_ListBuffer;
274c2c66affSColin Finck }
275c2c66affSColin Finck }
276c2c66affSColin Finck
277c2c66affSColin Finck return NULL;
278c2c66affSColin Finck }
279c2c66affSColin Finck
280c2c66affSColin Finck /**
281c2c66affSColin Finck * Runs a Wine test and captures the output
282c2c66affSColin Finck *
283c2c66affSColin Finck * @param TestInfo
284c2c66affSColin Finck * Pointer to a CTestInfo object containing information about the test.
285c2c66affSColin Finck * Will contain the test log afterwards if the user wants to submit data.
286c2c66affSColin Finck */
287c2c66affSColin Finck void
RunTest(CTestInfo * TestInfo)288c2c66affSColin Finck CWineTest::RunTest(CTestInfo* TestInfo)
289c2c66affSColin Finck {
290c2c66affSColin Finck DWORD BytesAvailable;
291c2c66affSColin Finck stringstream ss, ssFinish;
292c2c66affSColin Finck DWORD StartTime;
293c2c66affSColin Finck float TotalTime;
294c2c66affSColin Finck string tailString;
295c2c66affSColin Finck CPipe Pipe;
296c2c66affSColin Finck char Buffer[1024];
297c2c66affSColin Finck
298c2c66affSColin Finck ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl;
299c2c66affSColin Finck StringOut(ss.str());
300c2c66affSColin Finck
301*6a9a7391STimo Kreuzer SetCurrentDirectoryW(m_TestPath.c_str());
302*6a9a7391STimo Kreuzer
303c2c66affSColin Finck StartTime = GetTickCount();
304c2c66affSColin Finck
305c2c66affSColin Finck try
306c2c66affSColin Finck {
307c2c66affSColin Finck /* Execute the test */
308c2c66affSColin Finck CPipedProcess Process(TestInfo->CommandLine, Pipe);
309c2c66affSColin Finck
310c2c66affSColin Finck /* Receive all the data from the pipe */
3117dd4d225SColin Finck for (;;)
3127dd4d225SColin Finck {
3137dd4d225SColin Finck DWORD dwReadResult = Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable, ProcessActivityTimeout);
3147dd4d225SColin Finck if (dwReadResult == ERROR_SUCCESS)
315c2c66affSColin Finck {
316c2c66affSColin Finck /* Output text through StringOut, even while the test is still running */
317c2c66affSColin Finck Buffer[BytesAvailable] = 0;
318c2c66affSColin Finck tailString = StringOut(tailString.append(string(Buffer)), false);
319c2c66affSColin Finck
320c2c66affSColin Finck if (Configuration.DoSubmit())
321c2c66affSColin Finck TestInfo->Log += Buffer;
322c2c66affSColin Finck }
3237dd4d225SColin Finck else if (dwReadResult == ERROR_BROKEN_PIPE)
3247dd4d225SColin Finck {
3257dd4d225SColin Finck // The process finished and has been terminated.
3267dd4d225SColin Finck break;
3277dd4d225SColin Finck }
3287dd4d225SColin Finck else if (dwReadResult == WAIT_TIMEOUT)
3297dd4d225SColin Finck {
3307dd4d225SColin Finck // The process activity timeout above has elapsed without any new data.
3317dd4d225SColin Finck TESTEXCEPTION("Timeout while waiting for the test process\n");
3327dd4d225SColin Finck }
3337dd4d225SColin Finck else
3347dd4d225SColin Finck {
3357dd4d225SColin Finck // An unexpected error.
336c2c66affSColin Finck TESTEXCEPTION("CPipe::Read failed for the test run\n");
337c2c66affSColin Finck }
3387dd4d225SColin Finck }
3397dd4d225SColin Finck }
340c2c66affSColin Finck catch(CTestException& e)
341c2c66affSColin Finck {
342c2c66affSColin Finck if(!tailString.empty())
343c2c66affSColin Finck StringOut(tailString);
344c2c66affSColin Finck tailString.clear();
345c2c66affSColin Finck StringOut(e.GetMessage());
346c2c66affSColin Finck TestInfo->Log += e.GetMessage();
347c2c66affSColin Finck }
348c2c66affSColin Finck
349c2c66affSColin Finck /* Print what's left */
350c2c66affSColin Finck if(!tailString.empty())
351c2c66affSColin Finck StringOut(tailString);
352c2c66affSColin Finck
353c2c66affSColin Finck TotalTime = ((float)GetTickCount() - StartTime)/1000;
354c2c66affSColin Finck ssFinish << "Test " << TestInfo->Test << " completed in ";
355c2c66affSColin Finck ssFinish << setprecision(2) << fixed << TotalTime << " seconds." << endl;
356c2c66affSColin Finck StringOut(ssFinish.str());
357c2c66affSColin Finck TestInfo->Log += ssFinish.str();
358c2c66affSColin Finck }
359c2c66affSColin Finck
360c2c66affSColin Finck /**
361c2c66affSColin Finck * Interface to other classes for running all desired Wine tests.
362c2c66affSColin Finck */
363c2c66affSColin Finck void
Run()364c2c66affSColin Finck CWineTest::Run()
365c2c66affSColin Finck {
366c2c66affSColin Finck auto_ptr<CTestList> TestList;
367c2c66affSColin Finck auto_ptr<CWebService> WebService;
368c2c66affSColin Finck CTestInfo* TestInfo;
369c2c66affSColin Finck DWORD ErrorMode;
370c2c66affSColin Finck
371c2c66affSColin Finck /* The virtual test list is of course faster, so it should be preferred over
372c2c66affSColin Finck the journaled one.
373c2c66affSColin Finck Enable the journaled one only in case ...
374c2c66affSColin Finck - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2)
375c2c66affSColin Finck - we shall keep information for Crash Recovery
376c2c66affSColin Finck - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */
377c2c66affSColin Finck if(Configuration.IsReactOS() && Configuration.DoCrashRecovery() && Configuration.GetModule().empty())
378c2c66affSColin Finck {
379c2c66affSColin Finck /* Use a test list with a permanent journal */
380c2c66affSColin Finck TestList.reset(new CJournaledTestList(this));
381c2c66affSColin Finck }
382c2c66affSColin Finck else
383c2c66affSColin Finck {
384c2c66affSColin Finck /* Use the fast virtual test list with no additional overhead */
385c2c66affSColin Finck TestList.reset(new CVirtualTestList(this));
386c2c66affSColin Finck }
387c2c66affSColin Finck
388c2c66affSColin Finck /* Initialize the Web Service interface if required */
389c2c66affSColin Finck if(Configuration.DoSubmit())
390c2c66affSColin Finck WebService.reset(new CWebService());
391c2c66affSColin Finck
392c2c66affSColin Finck /* Disable error dialogs if we're running in non-interactive mode */
393c2c66affSColin Finck if(!Configuration.IsInteractive())
394c2c66affSColin Finck ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
395c2c66affSColin Finck
396c2c66affSColin Finck /* Get information for each test to run */
397c2c66affSColin Finck while((TestInfo = TestList->GetNextTestInfo()) != 0)
398c2c66affSColin Finck {
399c2c66affSColin Finck auto_ptr<CTestInfo> TestInfoPtr(TestInfo);
400c2c66affSColin Finck
401c2c66affSColin Finck RunTest(TestInfo);
402c2c66affSColin Finck
403c2c66affSColin Finck if(Configuration.DoSubmit() && !TestInfo->Log.empty())
404c2c66affSColin Finck WebService->Submit("wine", TestInfo);
405c2c66affSColin Finck
406c2c66affSColin Finck StringOut("\n\n");
407c2c66affSColin Finck }
408c2c66affSColin Finck
409c2c66affSColin Finck /* We're done with all tests. Finish this run */
410c2c66affSColin Finck if(Configuration.DoSubmit())
411c2c66affSColin Finck WebService->Finish("wine");
412c2c66affSColin Finck
413c2c66affSColin Finck /* Restore the original error mode */
414c2c66affSColin Finck if(!Configuration.IsInteractive())
415c2c66affSColin Finck SetErrorMode(ErrorMode);
416c2c66affSColin Finck }
417