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