Something went wrong. Try again.
Reactos
Something went wrong. Try again.
13 kB · 460 lines
C++
at master
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461/* * PROJECT: ReactOS Automatic Testing Utility * LICENSE: GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+) * PURPOSE: Class implementing functions for handling Wine tests * COPYRIGHT: Copyright 2009-2019 Colin Finck (colin@reactos.org) */
#include "precomp.h"
static const DWORD ListTimeout = 10000;
// This value needs to be lower than the <timeout> configured in sysreg.xml! (usually 180000)// Otherwise, sysreg2 kills the VM before we can kill the process.static const DWORD ProcessActivityTimeout = 170000;
/** * Constructs a CWineTest object. */CWineTest::CWineTest() : m_hFind(NULL), m_ListBuffer(NULL){ WCHAR wszDirectory[MAX_PATH];
/* Set up m_TestPath */ if (GetEnvironmentVariableW(L"ROSAUTOTEST_DIR", wszDirectory, MAX_PATH)) { m_TestPath = wszDirectory; if (*m_TestPath.rbegin() != L'\\') m_TestPath += L'\\'; } else { if (!GetWindowsDirectoryW(wszDirectory, MAX_PATH)) FATAL("GetWindowsDirectoryW failed\n");
m_TestPath = wszDirectory; m_TestPath += L"\\bin\\"; }}
/** * Destructs a CWineTest object. */CWineTest::~CWineTest(){ if(m_hFind) FindClose(m_hFind);
if(m_ListBuffer) delete m_ListBuffer;}
/** * Gets the next module test file using the FindFirstFileW/FindNextFileW API. * * @return * true if we found a next file, otherwise false. */boolCWineTest::GetNextFile(){ bool FoundFile = false; WIN32_FIND_DATAW fd;
/* Did we already begin searching for files? */ if (m_hFind) { /* Then get the next file (if any) */ if (FindNextFileW(m_hFind, &fd)) { // printf("cFileName is '%S'.\n", fd.cFileName); /* If it was NOT rosautotest.exe then proceed as normal */ if (_wcsicmp(fd.cFileName, TestName) != 0) { FoundFile = true; } else { /* It was rosautotest.exe so get the next file (if any) */ if (FindNextFileW(m_hFind, &fd)) { FoundFile = true; } // printf("cFileName is '%S'.\n", fd.cFileName); } } } else { /* Start searching for test files */ wstring FindPath = m_TestPath;
/* Did the user specify a module? */ if(Configuration.GetModule().empty()) { /* No module, so search for all files in that directory */ FindPath += L"*.exe"; } else { /* Search for files with the pattern "modulename_*" */ FindPath += Configuration.GetModule(); FindPath += L"_*.exe"; }
/* Search for the first file and check whether we got one */ m_hFind = FindFirstFileW(FindPath.c_str(), &fd);
/* If we returned a good handle */ if (m_hFind != INVALID_HANDLE_VALUE) { // printf("cFileName is '%S'.\n", fd.cFileName); /* If it was NOT rosautotest.exe then proceed as normal */ if (_wcsicmp(fd.cFileName, TestName) != 0) { FoundFile = true; } else { /* It was rosautotest.exe so get the next file (if any) */ if (FindNextFileW(m_hFind, &fd)) { FoundFile = true; } // printf("cFileName is '%S'.\n", fd.cFileName); } } }
if(FoundFile) m_CurrentFile = fd.cFileName;
return FoundFile;}
/** * Executes the --list command of a module test file to get information about the available tests. * * @return * The number of bytes we read into the m_ListBuffer member variable by capturing the output of the --list command. */DWORDCWineTest::DoListCommand(){ DWORD BytesAvailable; DWORD Temp; wstring CommandLine; CPipe Pipe;
/* Build the command line */ CommandLine = m_TestPath; CommandLine += m_CurrentFile; CommandLine += L" --list";
{ /* Start the process for getting all available tests */ CPipedProcess Process(CommandLine, Pipe);
/* Wait till this process ended */ if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED) TESTEXCEPTION("WaitForSingleObject failed for the test list\n"); }
/* Read the output data into a buffer */ if(!Pipe.Peek(NULL, 0, NULL, &BytesAvailable)) TESTEXCEPTION("CPipe::Peek failed for the test list\n");
/* Check if we got any */ if(!BytesAvailable) { stringstream ss;
ss << "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile) << endl; TESTEXCEPTION(ss.str()); }
/* Read the data */ m_ListBuffer = new char[BytesAvailable];
if(Pipe.Read(m_ListBuffer, BytesAvailable, &Temp, INFINITE) != ERROR_SUCCESS) TESTEXCEPTION("CPipe::Read failed\n");
return BytesAvailable;}
/** * Gets the next test from m_ListBuffer, which was filled with information from the --list command. * * @return * true if a next test was found, otherwise false. */boolCWineTest::GetNextTest(){ PCHAR pEnd; static DWORD BufferSize; static PCHAR pStart;
if(!m_ListBuffer) { /* Perform the --list command */ BufferSize = DoListCommand();
/* Move the pointer to the first test */ pStart = strchr(m_ListBuffer, '\n'); pStart += 5; }
/* If we reach the buffer size, we finished analyzing the output of this test */ if(pStart >= (m_ListBuffer + BufferSize)) { /* Clear m_CurrentFile to indicate that */ m_CurrentFile.clear();
/* Also free the memory for the list buffer */ delete[] m_ListBuffer; m_ListBuffer = NULL;
return false; }
/* Get start and end of this test name */ pEnd = pStart;
while(*pEnd != '\r') ++pEnd;
/* Store the test name */ m_CurrentTest = string(pStart, pEnd);
/* Move the pointer to the next test */ pStart = pEnd + 6;
return true;}
/** * Interface to CTestList-derived classes for getting all information about the next test to be run. * * @return * Returns a pointer to a CTestInfo object containing all available information about the next test. */CTestInfo*CWineTest::GetNextTestInfo(){ while(!m_CurrentFile.empty() || GetNextFile()) { /* The user asked for a list of all modules */ if (Configuration.ListModulesOnly()) { std::stringstream ss; ss << "Module: " << UnicodeToAscii(m_CurrentFile) << endl; m_CurrentFile.clear(); StringOut(ss.str()); continue; }
try { while(GetNextTest()) { /* If the user specified a test through the command line, check this here */ if(!Configuration.GetTest().empty() && Configuration.GetTest() != m_CurrentTest) continue;
{ auto_ptr<CTestInfo> TestInfo(new CTestInfo()); size_t UnderscorePosition;
/* Build the command line */ TestInfo->CommandLine = m_TestPath; TestInfo->CommandLine += m_CurrentFile; TestInfo->CommandLine += ' '; TestInfo->CommandLine += AsciiToUnicode(m_CurrentTest);
/* Store the Module name */ UnderscorePosition = m_CurrentFile.find_last_of('_');
if(UnderscorePosition == m_CurrentFile.npos) { stringstream ss;
ss << "Invalid test file name: " << UnicodeToAscii(m_CurrentFile) << endl; SSEXCEPTION; }
TestInfo->Module = UnicodeToAscii(m_CurrentFile.substr(0, UnderscorePosition));
/* Store the test */ TestInfo->Test = m_CurrentTest;
return TestInfo.release(); } } } catch(CTestException& e) { stringstream ss;
ss << "An exception occurred trying to list tests for: " << UnicodeToAscii(m_CurrentFile) << endl; StringOut(ss.str()); StringOut(e.GetMessage()); StringOut("\n"); m_CurrentFile.clear(); delete[] m_ListBuffer; } }
return NULL;}
/** * Runs a Wine test and captures the output * * @param TestInfo * Pointer to a CTestInfo object containing information about the test. * Will contain the test log afterwards if the user wants to submit data. */voidCWineTest::RunTest(CTestInfo* TestInfo){ DWORD BytesAvailable; stringstream ss, ssFinish; DWORD StartTime; float TotalTime; string tailString; CPipe Pipe; char Buffer[1024];
ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl; StringOut(ss.str());
SetCurrentDirectoryW(m_TestPath.c_str());
StartTime = GetTickCount();
try { /* Execute the test */ CPipedProcess Process(TestInfo->CommandLine, Pipe);
/* Receive all the data from the pipe */ for (;;) { DWORD dwReadResult = Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable, ProcessActivityTimeout); if (dwReadResult == ERROR_SUCCESS) { /* Output text through StringOut, even while the test is still running */ Buffer[BytesAvailable] = 0; tailString = StringOut(tailString.append(string(Buffer)), false);
if (Configuration.DoSubmit()) TestInfo->Log += Buffer; } else if (dwReadResult == ERROR_BROKEN_PIPE) { // The process finished and has been terminated. break; } else if (dwReadResult == WAIT_TIMEOUT) { // The process activity timeout above has elapsed without any new data. TESTEXCEPTION("Timeout while waiting for the test process\n"); } else { // An unexpected error. TESTEXCEPTION("CPipe::Read failed for the test run\n"); } } } catch(CTestException& e) { if(!tailString.empty()) StringOut(tailString); tailString.clear(); StringOut(e.GetMessage()); TestInfo->Log += e.GetMessage(); }
/* Print what's left */ if(!tailString.empty()) StringOut(tailString);
TotalTime = ((float)GetTickCount() - StartTime)/1000; ssFinish << "Test " << TestInfo->Test << " completed in "; ssFinish << setprecision(2) << fixed << TotalTime << " seconds." << endl; StringOut(ssFinish.str()); TestInfo->Log += ssFinish.str();}
/** * Interface to other classes for running all desired Wine tests. */voidCWineTest::Run(){ auto_ptr<CTestList> TestList; auto_ptr<CWebService> WebService; CTestInfo* TestInfo; DWORD ErrorMode = 0;
/* The virtual test list is of course faster, so it should be preferred over the journaled one. Enable the journaled one only in case ... - we're running under ReactOS (as the journal is only useful in conjunction with sysreg2) - we shall keep information for Crash Recovery - and the user didn't specify a module (then doing Crash Recovery doesn't really make sense) */ if(Configuration.IsReactOS() && Configuration.DoCrashRecovery() && Configuration.GetModule().empty()) { /* Use a test list with a permanent journal */ TestList.reset(new CJournaledTestList(this)); } else { /* Use the fast virtual test list with no additional overhead */ TestList.reset(new CVirtualTestList(this)); }
/* Initialize the Web Service interface if required */ if (Configuration.DoSubmit()) { if (CWebServiceLibCurl::CanUseLibCurl()) { StringOut("[ROSAUTOTEST] Using libcurl\n"); WebService.reset(new CWebServiceLibCurl()); } else { StringOut("[ROSAUTOTEST] Using wininet\n"); WebService.reset(new CWebServiceWinInet()); } }
/* Disable error dialogs if we're running in non-interactive mode */ if(!Configuration.IsInteractive()) ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
/* Get information for each test to run */ while((TestInfo = TestList->GetNextTestInfo()) != 0) { auto_ptr<CTestInfo> TestInfoPtr(TestInfo);
RunTest(TestInfo);
if(Configuration.DoSubmit() && !TestInfo->Log.empty()) WebService->Submit("wine", TestInfo);
StringOut("\n\n"); }
/* We're done with all tests. Finish this run */ if(Configuration.DoSubmit()) WebService->Finish("wine");
/* Restore the original error mode */ if(!Configuration.IsInteractive()) SetErrorMode(ErrorMode);}