diff --git a/rostests/rosautotest/CConfiguration.cpp b/rostests/rosautotest/CConfiguration.cpp new file mode 100644 index 00000000000..9e577952b29 --- /dev/null +++ b/rostests/rosautotest/CConfiguration.cpp @@ -0,0 +1,207 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class for managing all the configuration parameters + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +#define CONFIGURATION_FILENAMEA "rosautotest.ini" +#define CONFIGURATION_FILENAMEW L"rosautotest.ini" + +typedef void (WINAPI *GETSYSINFO)(LPSYSTEM_INFO); + +/** + * Constructs an empty CConfiguration object + */ +CConfiguration::CConfiguration() +{ + WCHAR WindowsDirectory[MAX_PATH]; + + /* Zero-initialize variables */ + m_CrashRecovery = false; + m_Shutdown = false; + m_Submit = false; + + /* Check if we are running under ReactOS from the SystemRoot directory */ + if(!GetWindowsDirectoryW(WindowsDirectory, MAX_PATH)) + FATAL("GetWindowsDirectoryW failed"); + + m_IsReactOS = !_wcsnicmp(&WindowsDirectory[3], L"reactos", 7); +} + +/** + * Parses the passed parameters and sets the appropriate configuration settings. + * + * @param argc + * The number of parameters (argc parameter of the wmain function) + * + * @param argv + * Pointer to a wchar_t array containing the parameters (argv parameter of the wmain function) + */ +void +CConfiguration::ParseParameters(int argc, wchar_t* argv[]) +{ + /* Parse the command line arguments */ + for(int i = 1; i < argc; i++) + { + if(argv[i][0] == '-' || argv[i][0] == '/') + { + switch(argv[i][1]) + { + case 'c': + ++i; + m_Comment = UnicodeToAscii(argv[i]); + break; + + case 'r': + m_CrashRecovery = true; + break; + + case 's': + m_Shutdown = true; + break; + + case 'w': + m_Submit = true; + break; + + default: + throw CInvalidParameterException(); + } + } + else + { + /* Which parameter is this? */ + if(m_Module.empty()) + { + /* Copy the parameter */ + m_Module = argv[i]; + } + else if(m_Test.empty()) + { + /* Copy the parameter converted to ASCII */ + m_Test = UnicodeToAscii(argv[i]); + } + else + { + throw CInvalidParameterException(); + } + } + } + + /* The /r and /w options shouldn't be used in conjunction */ + if(m_CrashRecovery && m_Submit) + throw CInvalidParameterException(); +} + +/** + * Gets information about the running system and sets the appropriate configuration settings. + */ +void +CConfiguration::GetSystemInformation() +{ + char ProductType; + GETSYSINFO GetSysInfo; + HMODULE hKernel32; + OSVERSIONINFOEXW os; + stringstream ss; + SYSTEM_INFO si; + + /* Get the build from the define */ + ss << "&revision="; + ss << KERNEL_VERSION_BUILD_HEX; + + ss << "&platform="; + + if(m_IsReactOS) + { + ss << "reactos"; + } + else + { + /* No, then use the info from GetVersionExW */ + os.dwOSVersionInfoSize = sizeof(os); + + if(!GetVersionExW((LPOSVERSIONINFOW)&os)) + FATAL("GetVersionExW failed\n"); + + if(os.dwMajorVersion < 5) + EXCEPTION("Application requires at least Windows 2000!\n"); + + if(os.wProductType == VER_NT_WORKSTATION) + ProductType = 'w'; + else + ProductType = 's'; + + /* Print all necessary identification information into the Platform string */ + ss << os.dwMajorVersion << '.' + << os.dwMinorVersion << '.' + << os.dwBuildNumber << '.' + << os.wServicePackMajor << '.' + << os.wServicePackMinor << '.' + << ProductType << '.'; + } + + /* We also need to know about the processor architecture. + To retrieve this information accurately, check whether "GetNativeSystemInfo" is exported and use it then, otherwise fall back to "GetSystemInfo". */ + hKernel32 = GetModuleHandleW(L"KERNEL32.DLL"); + GetSysInfo = (GETSYSINFO)GetProcAddress(hKernel32, "GetNativeSystemInfo"); + + if(!GetSysInfo) + GetSysInfo = (GETSYSINFO)GetProcAddress(hKernel32, "GetSystemInfo"); + + GetSysInfo(&si); + ss << si.wProcessorArchitecture; + + m_SystemInfoRequestString = ss.str(); +} + +/** + * Reads additional configuration options from the INI file. + * + * ParseParameters should be called before this function to get the desired result. + */ +void +CConfiguration::GetConfigurationFromFile() +{ + DWORD Length; + string Value; + WCHAR ConfigFile[MAX_PATH]; + + /* Most values are only needed if we're going to submit anything */ + if(m_Submit) + { + /* Build the path to the configuration file from the application's path */ + GetModuleFileNameW(NULL, ConfigFile, MAX_PATH); + Length = wcsrchr(ConfigFile, '\\') - ConfigFile + 1; + wcscpy(&ConfigFile[Length], CONFIGURATION_FILENAMEW); + + /* Check if it exists */ + if(GetFileAttributesW(ConfigFile) == INVALID_FILE_ATTRIBUTES) + EXCEPTION("Missing \"" CONFIGURATION_FILENAMEA "\" configuration file!\n"); + + /* Get the user name */ + m_AuthenticationRequestString = "&username="; + Value = GetINIValue(L"Login", L"UserName", ConfigFile); + + if(Value.empty()) + EXCEPTION("UserName is missing in the configuration file\n"); + + m_AuthenticationRequestString += EscapeString(Value); + + /* Get the password */ + m_AuthenticationRequestString += "&password="; + Value = GetINIValue(L"Login", L"Password", ConfigFile); + + if(Value.empty()) + EXCEPTION("Password is missing in the configuration file\n"); + + m_AuthenticationRequestString += EscapeString(Value); + + /* If we don't have any Comment string yet, try to find one in the INI file */ + if(m_Comment.empty()) + m_Comment = GetINIValue(L"Submission", L"Comment", ConfigFile); + } +} diff --git a/rostests/rosautotest/CConfiguration.h b/rostests/rosautotest/CConfiguration.h new file mode 100644 index 00000000000..b65db7d6e5e --- /dev/null +++ b/rostests/rosautotest/CConfiguration.h @@ -0,0 +1,38 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class for managing all the configuration parameters + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CConfiguration +{ +private: + bool m_CrashRecovery; + bool m_IsReactOS; + bool m_Shutdown; + bool m_Submit; + string m_Comment; + wstring m_Module; + string m_Test; + + string m_AuthenticationRequestString; + string m_SystemInfoRequestString; + +public: + CConfiguration(); + void ParseParameters(int argc, wchar_t* argv[]); + void GetSystemInformation(); + void GetConfigurationFromFile(); + + bool DoCrashRecovery() const { return m_CrashRecovery; } + bool DoShutdown() const { return m_Shutdown; } + bool DoSubmit() const { return m_Submit; } + bool IsReactOS() const { return m_IsReactOS; } + const string& GetComment() const { return m_Comment; } + const wstring& GetModule() const { return m_Module; } + const string& GetTest() const { return m_Test; } + + const string& GetAuthenticationRequestString() const { return m_AuthenticationRequestString; } + const string& GetSystemInfoRequestString() const { return m_SystemInfoRequestString; } +}; diff --git a/rostests/rosautotest/CFatalException.cpp b/rostests/rosautotest/CFatalException.cpp new file mode 100644 index 00000000000..1541b5a68e4 --- /dev/null +++ b/rostests/rosautotest/CFatalException.cpp @@ -0,0 +1,26 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Fatal program exception with automatically added information + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +/** + * Constructs a CFatalException object, which is catched in wmain as an exception. + * You should always use the FATAL macro for throwing this exception. + * + * @param File + * Constant pointer to a char array with the source file where the exception occured (__FILE__) + * + * @param Line + * Constant pointer to a char array with the appropriate source line (#__LINE__) + * + * @param Message + * Constant pointer to a char array containing a short message about the exception + */ +CFatalException::CFatalException(const char* File, const char* Line, const char* Message) + : m_File(File), m_Line(Line), m_Message(Message) +{ +} diff --git a/rostests/rosautotest/CFatalException.h b/rostests/rosautotest/CFatalException.h new file mode 100644 index 00000000000..78d1b00b22e --- /dev/null +++ b/rostests/rosautotest/CFatalException.h @@ -0,0 +1,21 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Fatal program exception with automatically added information + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CFatalException +{ +private: + string m_File; + string m_Line; + string m_Message; + +public: + CFatalException(const char* File, const char* Line, const char* Message); + + const string& GetFile() const { return m_File; } + const string& GetLine() const { return m_Line; } + const string& GetMessage() const { return m_Message; } +}; diff --git a/rostests/rosautotest/CInvalidParameterException.cpp b/rostests/rosautotest/CInvalidParameterException.cpp new file mode 100644 index 00000000000..f28903f860f --- /dev/null +++ b/rostests/rosautotest/CInvalidParameterException.cpp @@ -0,0 +1,15 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Empty exception thrown when the parameter processor detects an invalid parameter + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +/** + * Constructs an empty CInvalidParameterException object, which is catched in wmain as an exception. + */ +CInvalidParameterException::CInvalidParameterException() +{ +} diff --git a/rostests/rosautotest/CInvalidParameterException.h b/rostests/rosautotest/CInvalidParameterException.h new file mode 100644 index 00000000000..f3a87575cc2 --- /dev/null +++ b/rostests/rosautotest/CInvalidParameterException.h @@ -0,0 +1,12 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Empty exception thrown when the parameter processor detects an invalid parameter + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CInvalidParameterException +{ +public: + CInvalidParameterException(); +}; diff --git a/rostests/rosautotest/CJournaledTestList.cpp b/rostests/rosautotest/CJournaledTestList.cpp new file mode 100644 index 00000000000..ed0a74a32a0 --- /dev/null +++ b/rostests/rosautotest/CJournaledTestList.cpp @@ -0,0 +1,291 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a journaled test list for the Crash Recovery feature + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +static const char szJournalHeader[] = "RAT_J-V1"; +static const WCHAR szJournalFileName[] = L"rosautotest.journal"; + +/** + * Constructs a CJournaledTestList object for an associated CTest-derived object. + * + * @param Test + * Pointer to a CTest-derived object, for which this test list shall serve. + */ +CJournaledTestList::CJournaledTestList(CTest* Test) + : CTestList(Test) +{ + WCHAR JournalFile[MAX_PATH]; + + m_hJournal = INVALID_HANDLE_VALUE; + + /* Build the path to the journal file */ + if(SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, SHGFP_TYPE_CURRENT, JournalFile) != S_OK) + FATAL("SHGetFolderPathW failed\n"); + + m_JournalFile = JournalFile; + m_JournalFile += L"\\rosautotest\\"; + + /* Create the directory if necessary */ + if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES) + CreateDirectoryW(m_JournalFile.c_str(), NULL); + + m_JournalFile += szJournalFileName; + + /* Check if the journal already exists */ + if(GetFileAttributesW(m_JournalFile.c_str()) == INVALID_FILE_ATTRIBUTES) + WriteInitialJournalFile(); + else + LoadJournalFile(); +} + +/** + * Destructs a CJournaledTestList object. + */ +CJournaledTestList::~CJournaledTestList() +{ + if(m_hJournal != INVALID_HANDLE_VALUE) + CloseHandle(m_hJournal); +} + +/** + * Opens the journal file through the CreateFileW API using the m_hJournal handle. + * + * @param DesiredAccess + * dwDesiredAccess parameter passed to CreateFileW + * + * @param CreateNew + * true if the journal file shall be created, false if an existing one shall be opened + */ +void +CJournaledTestList::OpenJournal(DWORD DesiredAccess, bool CreateNew) +{ + m_hJournal = CreateFileW(m_JournalFile.c_str(), DesiredAccess, 0, NULL, (CreateNew ? CREATE_ALWAYS : OPEN_EXISTING), FILE_ATTRIBUTE_NORMAL, NULL); + + if(m_hJournal == INVALID_HANDLE_VALUE) + FATAL("CreateFileW failed\n"); +} + +/** + * Serializes a std::string and writes it into the opened journal file. + * + * @param String + * The std::string to serialize + * + * @see UnserializeFromBuffer + */ +void +CJournaledTestList::SerializeIntoJournal(const string& String) +{ + DWORD BytesWritten; + WriteFile(m_hJournal, String.c_str(), String.size() + 1, &BytesWritten, NULL); +} + +/** + * Serializes a std::wstring and writes it into the opened journal file. + * + * @param String + * The std::wstring to serialize + * + * @see UnserializeFromBuffer + */ +void +CJournaledTestList::SerializeIntoJournal(const wstring& String) +{ + DWORD BytesWritten; + WriteFile(m_hJournal, String.c_str(), (String.size() + 1) * sizeof(WCHAR), &BytesWritten, NULL); +} + +/** + * Unserializes the next std::string from the journal buffer. + * The passed buffer pointer will point at the next element afterwards. + * + * @param Buffer + * Pointer to a pointer to a char array containing the journal buffer + * + * @param Output + * The std::string to unserialize the value into. + */ +void +CJournaledTestList::UnserializeFromBuffer(char** Buffer, string& Output) +{ + Output = string(*Buffer); + *Buffer += Output.size() + 1; +} + +/** + * Unserializes the next std::wstring from the journal buffer. + * The passed buffer pointer will point at the next element afterwards. + * + * @param Buffer + * Pointer to a pointer to a char array containing the journal buffer + * + * @param Output + * The std::wstring to unserialize the value into. + */ +void +CJournaledTestList::UnserializeFromBuffer(char** Buffer, wstring& Output) +{ + Output = wstring((PWSTR)*Buffer); + *Buffer += (Output.size() + 1) * sizeof(WCHAR); +} + +/** + * Gets all tests to be run and writes an initial journal file with this information. + */ +void +CJournaledTestList::WriteInitialJournalFile() +{ + char TerminatingNull = 0; + CTestInfo* TestInfo; + DWORD BytesWritten; + + StringOut("Writing initial journal file...\n\n"); + + m_ListIterator = 0; + + /* Store all command lines in the m_List vector */ + while((TestInfo = m_Test->GetNextTestInfo()) != 0) + { + m_List.push_back(*TestInfo); + delete TestInfo; + } + + /* Serialize the vector and the iterator into a file */ + OpenJournal(GENERIC_WRITE, true); + + WriteFile(m_hJournal, szJournalHeader, sizeof(szJournalHeader), &BytesWritten, NULL); + WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL); + + for(size_t i = 0; i < m_List.size(); i++) + { + SerializeIntoJournal(m_List[i].CommandLine); + SerializeIntoJournal(m_List[i].Module); + SerializeIntoJournal(m_List[i].Test); + } + + WriteFile(m_hJournal, &TerminatingNull, sizeof(TerminatingNull), &BytesWritten, NULL); + + CloseHandle(m_hJournal); + m_hJournal = INVALID_HANDLE_VALUE; + + /* m_ListIterator will be incremented before its first use */ + m_ListIterator = (size_t)-1; +} + +/** + * Loads the existing journal file and sets all members to the values saved in that file. + */ +void +CJournaledTestList::LoadJournalFile() +{ + char* Buffer; + char* pBuffer; + char FileHeader[sizeof(szJournalHeader)]; + DWORD BytesRead; + DWORD RemainingSize; + + StringOut("Loading journal file...\n\n"); + + OpenJournal(GENERIC_READ); + RemainingSize = GetFileSize(m_hJournal, NULL); + + /* Verify the header of the journal file */ + ReadFile(m_hJournal, FileHeader, sizeof(szJournalHeader), &BytesRead, NULL); + RemainingSize -= BytesRead; + + if(BytesRead != sizeof(szJournalHeader)) + EXCEPTION("Journal file contains no header!\n"); + + if(strcmp(FileHeader, szJournalHeader)) + EXCEPTION("Journal file has an unsupported header!\n"); + + /* Read the iterator */ + ReadFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesRead, NULL); + RemainingSize -= BytesRead; + + if(BytesRead != sizeof(m_ListIterator)) + EXCEPTION("Journal file contains no m_ListIterator member!\n"); + + /* Read the rest of the file into a buffer */ + Buffer = new char[RemainingSize]; + pBuffer = Buffer; + ReadFile(m_hJournal, Buffer, RemainingSize, &BytesRead, NULL); + + CloseHandle(m_hJournal); + m_hJournal = NULL; + + /* Now recreate the m_List vector out of that information */ + while(*pBuffer) + { + CTestInfo TestInfo; + + UnserializeFromBuffer(&pBuffer, TestInfo.CommandLine); + UnserializeFromBuffer(&pBuffer, TestInfo.Module); + UnserializeFromBuffer(&pBuffer, TestInfo.Test); + + m_List.push_back(TestInfo); + } + + delete[] Buffer; +} + +/** + * Writes the current m_ListIterator value into the journal. + */ +void +CJournaledTestList::UpdateJournal() +{ + DWORD BytesWritten; + + OpenJournal(GENERIC_WRITE); + + /* Skip the header */ + SetFilePointer(m_hJournal, sizeof(szJournalHeader), NULL, FILE_CURRENT); + + WriteFile(m_hJournal, &m_ListIterator, sizeof(m_ListIterator), &BytesWritten, NULL); + + CloseHandle(m_hJournal); + m_hJournal = NULL; +} + +/** + * Interface to other classes for receiving information about the next test to be run. + * + * @return + * A pointer to a CTestInfo object, which contains all available information about the next test. + * The caller needs to free that object. + */ +CTestInfo* +CJournaledTestList::GetNextTestInfo() +{ + CTestInfo* TestInfo; + + /* Always jump to the next test here. + - If we're at the beginning of a new test list, the iterator will be set to 0. + - If we started with a loaded one, we assume that the test m_ListIterator is currently set + to crashed, so we move to the next test. */ + ++m_ListIterator; + + /* Check whether the iterator would already exceed the number of stored elements */ + if(m_ListIterator == m_List.size()) + { + /* Delete the journal and return no pointer */ + DeleteFileW(m_JournalFile.c_str()); + + TestInfo = NULL; + } + else + { + /* Update the journal with the current iterator and return the test information */ + UpdateJournal(); + + TestInfo = new CTestInfo(m_List[m_ListIterator]); + } + + return TestInfo; +} diff --git a/rostests/rosautotest/CJournaledTestList.h b/rostests/rosautotest/CJournaledTestList.h new file mode 100644 index 00000000000..47900d3aa13 --- /dev/null +++ b/rostests/rosautotest/CJournaledTestList.h @@ -0,0 +1,30 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a journaled test list for the Crash Recovery feature + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CJournaledTestList : public CTestList +{ +private: + HANDLE m_hJournal; + size_t m_ListIterator; + vector m_List; + wstring m_JournalFile; + + void LoadJournalFile(); + void OpenJournal(DWORD DesiredAccess, bool CreateNew = false); + void SerializeIntoJournal(const string& String); + void SerializeIntoJournal(const wstring& String); + void UnserializeFromBuffer(char** Buffer, string& Output); + void UnserializeFromBuffer(char** Buffer, wstring& Output); + void UpdateJournal(); + void WriteInitialJournalFile(); + +public: + CJournaledTestList(CTest* Test); + ~CJournaledTestList(); + + CTestInfo* GetNextTestInfo(); +}; diff --git a/rostests/rosautotest/CProcess.cpp b/rostests/rosautotest/CProcess.cpp new file mode 100644 index 00000000000..6697318f349 --- /dev/null +++ b/rostests/rosautotest/CProcess.cpp @@ -0,0 +1,36 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class able to create a new process and closing its handles on destruction (exception-safe) + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +/** + * Constructs a CProcess object and uses the CreateProcessW function to start the process immediately. + * + * @param CommandLine + * A std::wstring containing the command line to run + * + * @param StartupInfo + * Pointer to a STARTUPINFOW structure containing process startup information + */ +CProcess::CProcess(const wstring& CommandLine, LPSTARTUPINFOW StartupInfo) +{ + auto_array_ptr CommandLinePtr(new WCHAR[CommandLine.size() + 1]); + + wcscpy(CommandLinePtr, CommandLine.c_str()); + + if(!CreateProcessW(NULL, CommandLinePtr, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &m_ProcessInfo)) + FATAL("CreateProcessW failed\n"); +} + +/** + * Destructs a CProcess object and closes all handles belonging to the process. + */ +CProcess::~CProcess() +{ + CloseHandle(m_ProcessInfo.hThread); + CloseHandle(m_ProcessInfo.hProcess); +} diff --git a/rostests/rosautotest/CProcess.h b/rostests/rosautotest/CProcess.h new file mode 100644 index 00000000000..a46310c2c7e --- /dev/null +++ b/rostests/rosautotest/CProcess.h @@ -0,0 +1,18 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class able to create a new process and closing its handles on destruction (exception-safe) + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CProcess +{ +private: + PROCESS_INFORMATION m_ProcessInfo; + +public: + CProcess(const wstring& CommandLine, LPSTARTUPINFOW StartupInfo); + ~CProcess(); + + HANDLE GetProcessHandle() const { return m_ProcessInfo.hProcess; } +}; diff --git a/rostests/rosautotest/CSimpleException.cpp b/rostests/rosautotest/CSimpleException.cpp new file mode 100644 index 00000000000..f7ae841dff0 --- /dev/null +++ b/rostests/rosautotest/CSimpleException.cpp @@ -0,0 +1,20 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Simple exception containing just a message + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +/** + * Constructs a CSimpleException object, which is catched in wmain as an exception. + * You should always use the EXCEPTION or SSEXCEPTION macro for throwing this exception. + * + * @param Message + * Constant pointer to a char array containing a short message about the exception + */ +CSimpleException::CSimpleException(const char* Message) + : m_Message(Message) +{ +} diff --git a/rostests/rosautotest/CSimpleException.h b/rostests/rosautotest/CSimpleException.h new file mode 100644 index 00000000000..08bd09cd809 --- /dev/null +++ b/rostests/rosautotest/CSimpleException.h @@ -0,0 +1,17 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Simple exception containing just a message + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CSimpleException +{ +private: + string m_Message; + +public: + CSimpleException(const char* Message); + + const string& GetMessage() const { return m_Message; } +}; diff --git a/rostests/rosautotest/CTest.cpp b/rostests/rosautotest/CTest.cpp new file mode 100644 index 00000000000..742130072da --- /dev/null +++ b/rostests/rosautotest/CTest.cpp @@ -0,0 +1,8 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a generic Test, needs to be used by a derived class + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" diff --git a/rostests/rosautotest/CTest.h b/rostests/rosautotest/CTest.h new file mode 100644 index 00000000000..1f559b336f1 --- /dev/null +++ b/rostests/rosautotest/CTest.h @@ -0,0 +1,19 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a generic Test, needs to be used by a derived class + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CTest +{ +private: + virtual CTestInfo* GetNextTestInfo() = 0; + +public: + virtual void Run() = 0; + + /* All CTestList-derived classes need to access the private GetNextTestInfo method */ + friend class CJournaledTestList; + friend class CVirtualTestList; +}; diff --git a/rostests/rosautotest/CTestInfo.cpp b/rostests/rosautotest/CTestInfo.cpp new file mode 100644 index 00000000000..bea24821475 --- /dev/null +++ b/rostests/rosautotest/CTestInfo.cpp @@ -0,0 +1,8 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a bucket for Test information + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" diff --git a/rostests/rosautotest/CTestInfo.h b/rostests/rosautotest/CTestInfo.h new file mode 100644 index 00000000000..791480540fc --- /dev/null +++ b/rostests/rosautotest/CTestInfo.h @@ -0,0 +1,15 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a bucket for Test information + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CTestInfo +{ +public: + wstring CommandLine; + string Module; + string Test; + string Log; +}; diff --git a/rostests/rosautotest/CTestList.cpp b/rostests/rosautotest/CTestList.cpp new file mode 100644 index 00000000000..17e31d9739d --- /dev/null +++ b/rostests/rosautotest/CTestList.cpp @@ -0,0 +1,19 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a generic Test list, needs to be used by a derived class + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +/** + * Constructor serving for CTestList-derived classes. + * + * @param Test + * Pointer to a CTest-derived object, for which this test list shall serve. + */ +CTestList::CTestList(CTest* Test) : + m_Test(Test) +{ +} diff --git a/rostests/rosautotest/CTestList.h b/rostests/rosautotest/CTestList.h new file mode 100644 index 00000000000..49a559ac999 --- /dev/null +++ b/rostests/rosautotest/CTestList.h @@ -0,0 +1,17 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a generic Test list, needs to be used by a derived class + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CTestList +{ +protected: + CTest* m_Test; + + CTestList(CTest* Test); + +public: + virtual CTestInfo* GetNextTestInfo() = 0; +}; diff --git a/rostests/rosautotest/CVirtualTestList.cpp b/rostests/rosautotest/CVirtualTestList.cpp new file mode 100644 index 00000000000..0aade7202fa --- /dev/null +++ b/rostests/rosautotest/CVirtualTestList.cpp @@ -0,0 +1,32 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a virtual test list for the tests to be ran + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +/** + * Constructs a CVirtualTestList object for an associated CTest-derived object. + * + * @param Test + * Pointer to a CTest-derived object, for which this test list shall serve. + */ +CVirtualTestList::CVirtualTestList(CTest* Test) + : CTestList(Test) +{ +} + +/** + * Interface to other classes for receiving information about the next test to be run. + * + * @return + * A pointer to a CTestInfo object, which contains all available information about the next test. + * The caller needs to free that object. + */ +CTestInfo* +CVirtualTestList::GetNextTestInfo() +{ + return m_Test->GetNextTestInfo(); +} diff --git a/rostests/rosautotest/CVirtualTestList.h b/rostests/rosautotest/CVirtualTestList.h new file mode 100644 index 00000000000..d9d6d603e95 --- /dev/null +++ b/rostests/rosautotest/CVirtualTestList.h @@ -0,0 +1,14 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing a virtual test list for the tests to be ran + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CVirtualTestList : public CTestList +{ +public: + CVirtualTestList(CTest* Test); + + CTestInfo* GetNextTestInfo(); +}; diff --git a/rostests/rosautotest/CWebService.cpp b/rostests/rosautotest/CWebService.cpp new file mode 100644 index 00000000000..fbf4900ef27 --- /dev/null +++ b/rostests/rosautotest/CWebService.cpp @@ -0,0 +1,214 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing the interface to the "testman" Web Service + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +static const WCHAR szHostname[] = L"localhost"; +static const WCHAR szServerFile[] = L"testman/webservice/"; + +/** + * Constructs a CWebService object and immediately establishes a connection to the "testman" Web Service. + */ +CWebService::CWebService() +{ + /* Zero-initialize variables */ + m_hHTTP = NULL; + m_hHTTPRequest = NULL; + m_TestID = NULL; + + /* Establish an internet connection to the "testman" server */ + m_hInet = InternetOpenW(L"rosautotest", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); + + if(!m_hInet) + FATAL("InternetOpenW failed\n"); + + m_hHTTP = InternetConnectW(m_hInet, szHostname, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); + + if(!m_hHTTP) + FATAL("InternetConnectW failed\n"); +} + +/** + * Destructs a CWebService object and closes all connections to the Web Service. + */ +CWebService::~CWebService() +{ + if(m_hInet) + InternetCloseHandle(m_hInet); + + if(m_hHTTP) + InternetCloseHandle(m_hHTTP); + + if(m_hHTTPRequest) + InternetCloseHandle(m_hHTTPRequest); + + if(m_TestID) + delete m_TestID; +} + +/** + * Sends data to the Web Service. + * + * @param InputData + * A std::string containing all the data, which is going to be submitted as HTTP POST data. + * + * @return + * Returns a pointer to a char array containing the data received from the Web Service. + * The caller needs to free that pointer. + */ +PCHAR +CWebService::DoRequest(const string& InputData) +{ + const WCHAR szHeaders[] = L"Content-Type: application/x-www-form-urlencoded"; + + auto_array_ptr Data; + DWORD DataLength; + + /* Post our test results to the web service */ + m_hHTTPRequest = HttpOpenRequestW(m_hHTTP, L"POST", szServerFile, NULL, NULL, NULL, INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0); + + if(!m_hHTTPRequest) + FATAL("HttpOpenRequestW failed\n"); + + Data.reset(new char[InputData.size() + 1]); + strcpy(Data, InputData.c_str()); + + if(!HttpSendRequestW(m_hHTTPRequest, szHeaders, wcslen(szHeaders), Data, InputData.size())) + FATAL("HttpSendRequestW failed\n"); + + /* Get the response */ + if(!InternetQueryDataAvailable(m_hHTTPRequest, &DataLength, 0, 0)) + FATAL("InternetQueryDataAvailable failed\n"); + + Data.reset(new char[DataLength + 1]); + + if(!InternetReadFile(m_hHTTPRequest, Data, DataLength, &DataLength)) + FATAL("InternetReadFile failed\n"); + + Data[DataLength] = 0; + + return Data.release(); +} + +/** + * Requests a Test ID from the Web Service for our test run. + * + * @param TestType + * Constant pointer to a char array containing the test type to be run (i.e. "wine") + */ +void +CWebService::GetTestID(const char* TestType) +{ + string Data; + + Data = "action=gettestid"; + Data += Configuration.GetAuthenticationRequestString(); + Data += Configuration.GetSystemInfoRequestString(); + Data += "&testtype="; + Data += TestType; + + if(!Configuration.GetComment().empty()) + { + Data += "&comment="; + Data += Configuration.GetComment(); + } + + m_TestID = DoRequest(Data); + + /* Verify that this is really a number */ + if(!IsNumber(m_TestID)) + { + stringstream ss; + + ss << "Expected Test ID, but received:" << endl << m_TestID << endl; + SSEXCEPTION; + } +} + +/** + * Gets a Suite ID from the Web Service for this module/test combination. + * + * @param TestType + * Constant pointer to a char array containing the test type to be run (i.e. "wine") + * + * @param TestInfo + * Pointer to a CTestInfo object containing information about the test + * + * @return + * Returns a pointer to a char array containing the Suite ID received from the Web Service. + * The caller needs to free that pointer. + */ +PCHAR +CWebService::GetSuiteID(const char* TestType, CTestInfo* TestInfo) +{ + auto_array_ptr SuiteID; + string Data; + + Data = "action=getsuiteid"; + Data += Configuration.GetAuthenticationRequestString(); + Data += "&testtype="; + Data += TestType; + Data += "&module="; + Data += TestInfo->Module; + Data += "&test="; + Data += TestInfo->Test; + + SuiteID.reset(DoRequest(Data)); + + /* Verify that this is really a number */ + if(!IsNumber(SuiteID)) + { + stringstream ss; + + ss << "Expected Suite ID, but received:" << endl << SuiteID << endl; + SSEXCEPTION; + } + + return SuiteID.release(); +} + +/** + * Interface to other classes for submitting a result of one test + * + * @param TestType + * Constant pointer to a char array containing the test type to be run (i.e. "wine") + * + * @param TestInfo + * Pointer to a CTestInfo object containing information about the test + */ +void +CWebService::Submit(const char* TestType, CTestInfo* TestInfo) +{ + auto_array_ptr Response; + auto_array_ptr SuiteID; + string Data; + stringstream ss; + + if(!m_TestID) + GetTestID(TestType); + + SuiteID.reset(GetSuiteID(TestType, TestInfo)); + + Data = "action=submit"; + Data += Configuration.GetAuthenticationRequestString(); + Data += "&testtype="; + Data += TestType; + Data += "&testid="; + Data += m_TestID; + Data += "&suiteid="; + Data += SuiteID; + Data += "&log="; + Data += TestInfo->Log; + + Response.reset(DoRequest(Data)); + + ss << "The server responded:" << endl << Response << endl; + StringOut(ss.str()); + + if(strcmp(Response, "OK")) + EXCEPTION("Aborted!\n"); +} diff --git a/rostests/rosautotest/CWebService.h b/rostests/rosautotest/CWebService.h new file mode 100644 index 00000000000..07dbf897707 --- /dev/null +++ b/rostests/rosautotest/CWebService.h @@ -0,0 +1,25 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing the interface to the "testman" Web Service + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CWebService +{ +private: + HINTERNET m_hInet; + HINTERNET m_hHTTP; + HINTERNET m_hHTTPRequest; + PCHAR m_TestID; + + PCHAR DoRequest(const string& InputData); + void GetTestID(const char* TestType); + PCHAR GetSuiteID(const char* TestType, CTestInfo* TestInfo); + +public: + CWebService(); + ~CWebService(); + + void Submit(const char* TestType, CTestInfo* TestInfo); +}; diff --git a/rostests/rosautotest/CWineTest.cpp b/rostests/rosautotest/CWineTest.cpp new file mode 100644 index 00000000000..471e6ab7e98 --- /dev/null +++ b/rostests/rosautotest/CWineTest.cpp @@ -0,0 +1,366 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing functions for handling Wine tests + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +#include "precomp.h" + +static const DWORD ListTimeout = 10000; + +/** + * Constructs a CWineTest object. + */ +CWineTest::CWineTest() +{ + WCHAR WindowsDirectory[MAX_PATH]; + + /* Zero-initialize variables */ + m_hFind = NULL; + m_hReadPipe = NULL; + m_hWritePipe = NULL; + m_ListBuffer = NULL; + memset(&m_StartupInfo, 0, sizeof(m_StartupInfo)); + + /* Set up m_TestPath */ + if(!GetWindowsDirectoryW(WindowsDirectory, MAX_PATH)) + FATAL("GetWindowsDirectoryW failed"); + + m_TestPath = WindowsDirectory; + m_TestPath += L"\\bin\\"; +} + +/** + * Destructs a CWineTest object. + */ +CWineTest::~CWineTest() +{ + if(m_hFind) + FindClose(m_hFind); + + if(m_hReadPipe) + CloseHandle(m_hReadPipe); + + if(m_hWritePipe) + CloseHandle(m_hWritePipe); + + 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. + */ +bool +CWineTest::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)) + FoundFile = true; + } + 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(m_hFind != INVALID_HANDLE_VALUE) + FoundFile = true; + } + + 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. + */ +DWORD +CWineTest::DoListCommand() +{ + DWORD BytesAvailable; + DWORD Temp; + wstring CommandLine; + + /* Build the command line */ + CommandLine = m_TestPath; + CommandLine += m_CurrentFile; + CommandLine += L" --list"; + + { + /* Start the process for getting all available tests */ + CProcess Process(CommandLine, &m_StartupInfo); + + /* Wait till this process ended */ + if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED) + FATAL("WaitForSingleObject failed for the test list\n"); + } + + /* Read the output data into a buffer */ + if(!PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL)) + FATAL("PeekNamedPipe 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; + SSEXCEPTION; + } + + /* Read the data */ + m_ListBuffer = new char[BytesAvailable]; + + if(!ReadFile(m_hReadPipe, m_ListBuffer, BytesAvailable, &Temp, NULL)) + FATAL("ReadPipe 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. + */ +bool +CWineTest::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()) + { + 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 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('_'); + + 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(); + } + } + } + + 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. + */ +void +CWineTest::RunTest(CTestInfo* TestInfo) +{ + bool BreakLoop = false; + DWORD BytesAvailable; + DWORD Temp; + stringstream ss; + + ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl; + StringOut(ss.str()); + + { + /* Execute the test */ + CProcess Process(TestInfo->CommandLine, &m_StartupInfo); + + /* Receive all the data from the pipe */ + do + { + /* When the application finished, make sure that we peek the pipe one more time, so that we get all data. + If the following condition would be the while() condition, we might hit a race condition: + - We check for data with PeekNamedPipe -> no data available + - The application outputs its data and finishes + - WaitForSingleObject reports that the application has finished and we break the loop without receiving any data + */ + if(WaitForSingleObject(Process.GetProcessHandle(), 0) != WAIT_TIMEOUT) + BreakLoop = true; + + if(!PeekNamedPipe(m_hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL)) + FATAL("PeekNamedPipe failed for the test run\n"); + + if(BytesAvailable) + { + /* There is data, so get it and output it */ + auto_array_ptr Buffer(new char[BytesAvailable + 1]); + + if(!ReadFile(m_hReadPipe, Buffer, BytesAvailable, &Temp, NULL)) + FATAL("ReadFile failed for the test run\n"); + + /* Output all test output through StringOut, even while the test is still running */ + Buffer[BytesAvailable] = 0; + StringOut(string(Buffer)); + + if(Configuration.DoSubmit()) + TestInfo->Log += Buffer; + } + } + while(!BreakLoop); + } +} + +/** + * Interface to other classes for running all desired Wine tests. + */ +void +CWineTest::Run() +{ + auto_ptr TestList; + auto_ptr WebService; + CTestInfo* TestInfo; + SECURITY_ATTRIBUTES SecurityAttributes; + + /* Create a pipe for getting the output of the tests */ + SecurityAttributes.nLength = sizeof(SecurityAttributes); + SecurityAttributes.bInheritHandle = TRUE; + SecurityAttributes.lpSecurityDescriptor = NULL; + + if(!CreatePipe(&m_hReadPipe, &m_hWritePipe, &SecurityAttributes, 0)) + FATAL("CreatePipe failed\n"); + + m_StartupInfo.cb = sizeof(m_StartupInfo); + m_StartupInfo.dwFlags = STARTF_USESTDHANDLES; + m_StartupInfo.hStdOutput = m_hWritePipe; + + /* 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()) + WebService.reset(new CWebService()); + + /* Get information for each test to run */ + while((TestInfo = TestList->GetNextTestInfo()) != 0) + { + auto_ptr TestInfoPtr(TestInfo); + + RunTest(TestInfo); + + if(Configuration.DoSubmit() && !TestInfo->Log.empty()) + WebService->Submit("wine", TestInfo); + + StringOut("\n\n"); + } +} diff --git a/rostests/rosautotest/CWineTest.h b/rostests/rosautotest/CWineTest.h new file mode 100644 index 00000000000..58ca5585e84 --- /dev/null +++ b/rostests/rosautotest/CWineTest.h @@ -0,0 +1,32 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Class implementing functions for handling Wine tests + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +class CWineTest : public CTest +{ +private: + HANDLE m_hFind; + HANDLE m_hReadPipe; + HANDLE m_hWritePipe; + PCHAR m_ListBuffer; + STARTUPINFOW m_StartupInfo; + string m_CurrentTest; + wstring m_CurrentFile; + wstring m_CurrentListCommand; + wstring m_TestPath; + + bool GetNextFile(); + bool GetNextTest(); + CTestInfo* GetNextTestInfo(); + DWORD DoListCommand(); + void RunTest(CTestInfo* TestInfo); + +public: + CWineTest(); + ~CWineTest(); + + void Run(); +}; diff --git a/rostests/rosautotest/auto_array_ptr.h b/rostests/rosautotest/auto_array_ptr.h new file mode 100644 index 00000000000..e1e3b291803 --- /dev/null +++ b/rostests/rosautotest/auto_array_ptr.h @@ -0,0 +1,64 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Template similar to std::auto_ptr for arrays + * COPYRIGHT: Copyright 2009 Colin Finck + */ + +template +class auto_array_ptr +{ +private: + Type* m_Ptr; + +public: + typedef Type element_type; + + /* Construct an auto_array_ptr from a pointer */ + explicit auto_array_ptr(Type* Ptr = 0) throw() + : m_Ptr(Ptr) + { + } + + /* Construct an auto_array_ptr from an existing auto_array_ptr */ + auto_array_ptr(auto_array_ptr& Right) throw() + : m_Ptr(Right.release()) + { + } + + /* Destruct the auto_array_ptr and remove the corresponding array from memory */ + ~auto_array_ptr() throw() + { + delete[] m_Ptr; + } + + /* Get the pointer address */ + Type* get() const throw() + { + return m_Ptr; + } + + /* Release the pointer */ + Type* release() throw() + { + Type* Tmp = m_Ptr; + m_Ptr = 0; + + return Tmp; + } + + /* Reset to a new pointer */ + void reset(Type* Ptr = 0) throw() + { + if(Ptr != m_Ptr) + delete[] m_Ptr; + + m_Ptr = Ptr; + } + + /* Simulate all the functionality of real arrays by casting the auto_array_ptr to Type* on demand */ + operator Type*() const throw() + { + return m_Ptr; + } +}; diff --git a/rostests/rosautotest/main.c b/rostests/rosautotest/main.c deleted file mode 100644 index 4bfc8bdfe58..00000000000 --- a/rostests/rosautotest/main.c +++ /dev/null @@ -1,358 +0,0 @@ -/* - * PROJECT: ReactOS Automatic Testing Utility - * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation - * PURPOSE: Main implementation file - * COPYRIGHT: Copyright 2008-2009 Colin Finck - */ - -#include "precomp.h" - -typedef void (WINAPI *GETSYSINFO)(LPSYSTEM_INFO); - -APP_OPTIONS AppOptions = {0}; -HANDLE hProcessHeap; -PCHAR AuthenticationRequestString = NULL; -PCHAR SystemInfoRequestString = NULL; - -/** - * Gets a value from a specified INI file and returns it converted to ASCII. - * - * @param AppName - * The INI section to look in (lpAppName parameter passed to GetPrivateProfileStringW) - * - * @param KeyName - * The key to look for in the specified section (lpKeyName parameter passed to GetPrivateProfileStringW) - * - * @param FileName - * The path to the INI file - * - * @param ReturnedValue - * Pointer to a CHAR pointer, which will receive the read and converted value. - * The caller needs to HeapFree that value manually. - * - * @return - * Returns the string length of the read value (in characters) or zero if we didn't get any value. - */ -static DWORD -IntGetINIValueA(PCWCH AppName, PCWCH KeyName, PCWCH FileName, char** ReturnedValue) -{ - DWORD Length; - WCHAR Buffer[2048]; - - /* Load the value into a temporary Unicode buffer */ - Length = GetPrivateProfileStringW(AppName, KeyName, NULL, Buffer, sizeof(Buffer) / sizeof(WCHAR), FileName); - - if(!Length) - return 0; - - /* Convert the string to ANSI charset */ - *ReturnedValue = HeapAlloc(hProcessHeap, 0, Length + 1); - WideCharToMultiByte(CP_ACP, 0, Buffer, Length + 1, *ReturnedValue, Length + 1, NULL, NULL); - - return Length; -} - -/** - * Gets the username and password from the "rosautotest.ini" file if the user enabled submitting the results to the web service. - * The "rosautotest.ini" file should look like this: - * - * [Login] - * UserName=TestMan - * Password=TestPassword - */ -static BOOL -IntGetConfigurationValues() -{ - const CHAR PasswordProp[] = "&password="; - const CHAR UserNameProp[] = "&username="; - - BOOL ReturnValue = FALSE; - DWORD DataLength; - DWORD Length; - PCHAR Password = NULL; - PCHAR UserName = NULL; - WCHAR ConfigFile[MAX_PATH]; - - /* Most values are only needed if we're going to submit */ - if(AppOptions.Submit) - { - /* Build the path to the configuration file from the application's path */ - GetModuleFileNameW(NULL, ConfigFile, MAX_PATH); - Length = wcsrchr(ConfigFile, '\\') - ConfigFile; - wcscpy(&ConfigFile[Length], L"\\rosautotest.ini"); - - /* Check if it exists */ - if(GetFileAttributesW(ConfigFile) == INVALID_FILE_ATTRIBUTES) - { - StringOut("Missing \"rosautotest.ini\" configuration file!\n"); - goto Cleanup; - } - - /* Get the required length of the authentication request string */ - DataLength = sizeof(UserNameProp) - 1; - Length = IntGetINIValueA(L"Login", L"UserName", ConfigFile, &UserName); - - if(!Length) - { - StringOut("UserName is missing in the configuration file\n"); - goto Cleanup; - } - - /* Some characters might need to be escaped and an escaped character takes 3 bytes */ - DataLength += 3 * Length; - - DataLength += sizeof(PasswordProp) - 1; - Length = IntGetINIValueA(L"Login", L"Password", ConfigFile, &Password); - - if(!Length) - { - StringOut("Password is missing in the configuration file\n"); - goto Cleanup; - } - - DataLength += 3 * Length; - - /* Build the string */ - AuthenticationRequestString = HeapAlloc(hProcessHeap, 0, DataLength + 1); - - strcpy(AuthenticationRequestString, UserNameProp); - EscapeString(&AuthenticationRequestString[strlen(AuthenticationRequestString)], UserName); - - strcat(AuthenticationRequestString, PasswordProp); - EscapeString(&AuthenticationRequestString[strlen(AuthenticationRequestString)], Password); - - /* If we don't have any Comment string yet, try to find one in the INI file */ - if(!AppOptions.Comment) - IntGetINIValueA(L"Submission", L"Comment", ConfigFile, &AppOptions.Comment); - } - - ReturnValue = TRUE; - -Cleanup: - if(UserName) - HeapFree(hProcessHeap, 0, UserName); - - if(Password) - HeapFree(hProcessHeap, 0, Password); - - return ReturnValue; -} - -/** - * Determines on which platform we're running on. - * Prepares the appropriate request strings needed if we want to submit test results to the web service. - */ -static BOOL -IntGetBuildAndPlatform() -{ - const CHAR PlatformProp[] = "&platform="; - const CHAR RevisionProp[] = "&revision="; - - CHAR BuildNo[BUILDNO_LENGTH]; - CHAR Platform[PLATFORM_LENGTH]; - CHAR PlatformArchitecture[3]; - CHAR ProductType; - DWORD DataLength; - GETSYSINFO GetSysInfo; - HANDLE hKernel32; - OSVERSIONINFOEXW os; - SYSTEM_INFO si; - WCHAR WindowsDirectory[MAX_PATH]; - - /* Get the build from the define */ - _ultoa(KERNEL_VERSION_BUILD_HEX, BuildNo, 10); - - /* Check if we are running under ReactOS from the SystemRoot directory */ - GetWindowsDirectoryW(WindowsDirectory, MAX_PATH); - - if(!_wcsnicmp(&WindowsDirectory[3], L"reactos", 7)) - { - /* Yes, we are most-probably under ReactOS */ - strcpy(Platform, "reactos"); - } - else - { - /* No, then use the info from GetVersionExW */ - os.dwOSVersionInfoSize = sizeof(os); - - if(!GetVersionExW((LPOSVERSIONINFOW)&os)) - { - StringOut("GetVersionExW failed\n"); - return FALSE; - } - - if(os.dwMajorVersion < 5) - { - StringOut("Application requires at least Windows 2000!\n"); - return FALSE; - } - - if(os.wProductType == VER_NT_WORKSTATION) - ProductType = 'w'; - else - ProductType = 's'; - - /* Print all necessary identification information into the Platform string */ - sprintf(Platform, "%lu.%lu.%lu.%u.%u.%c", os.dwMajorVersion, os.dwMinorVersion, os.dwBuildNumber, os.wServicePackMajor, os.wServicePackMinor, ProductType); - } - - /* We also need to know about the processor architecture. - To retrieve this information accurately, check whether "GetNativeSystemInfo" is exported and use it then, otherwise fall back to "GetSystemInfo". */ - hKernel32 = GetModuleHandleW(L"KERNEL32.DLL"); - GetSysInfo = (GETSYSINFO)GetProcAddress(hKernel32, "GetNativeSystemInfo"); - - if(!GetSysInfo) - GetSysInfo = (GETSYSINFO)GetProcAddress(hKernel32, "GetSystemInfo"); - - GetSysInfo(&si); - - PlatformArchitecture[0] = '.'; - _ultoa(si.wProcessorArchitecture, &PlatformArchitecture[1], 10); - PlatformArchitecture[2] = 0; - strcat(Platform, PlatformArchitecture); - - /* Get the required length of the system info request string */ - DataLength = sizeof(RevisionProp) - 1; - DataLength += strlen(BuildNo); - DataLength += sizeof(PlatformProp) - 1; - DataLength += strlen(Platform); - - /* Now build the string */ - SystemInfoRequestString = HeapAlloc(hProcessHeap, 0, DataLength + 1); - strcpy(SystemInfoRequestString, RevisionProp); - strcat(SystemInfoRequestString, BuildNo); - strcat(SystemInfoRequestString, PlatformProp); - strcat(SystemInfoRequestString, Platform); - - return TRUE; -} - -/** - * Prints the application usage. - */ -static VOID -IntPrintUsage() -{ - printf("rosautotest - ReactOS Automatic Testing Utility\n"); - printf("Usage: rosautotest [options] [module] [test]\n"); - printf(" options:\n"); - printf(" /? - Shows this help\n"); - printf(" /c - Specifies the comment to be submitted to the Web Service.\n"); - printf(" Skips the comment set in the configuration file (if any).\n"); - printf(" Only has an effect when /w is also used.\n"); - printf(" /s - Shut down the system after finishing the tests\n"); - printf(" /w - Submit the results to the webservice\n"); - printf(" Requires a \"rosautotest.ini\" with valid login data.\n"); - printf("\n"); - printf(" module:\n"); - printf(" The module to be tested (i.e. \"advapi32\")\n"); - printf(" If this parameter is specified without any test parameter,\n"); - printf(" all tests of the specified module are run.\n"); - printf("\n"); - printf(" test:\n"); - printf(" The test to be run. Needs to be a test of the specified module.\n"); -} - -/** - * Main entry point - */ -int -wmain(int argc, wchar_t* argv[]) -{ - int ReturnValue = 0; - size_t Length; - UINT i; - - hProcessHeap = GetProcessHeap(); - - /* Parse the command line arguments */ - for(i = 1; i < (UINT)argc; i++) - { - if(argv[i][0] == '-' || argv[i][0] == '/') - { - switch(argv[i][1]) - { - case 'c': - ++i; - - /* Copy the parameter converted to ASCII */ - Length = WideCharToMultiByte(CP_ACP, 0, argv[i], -1, NULL, 0, NULL, NULL); - AppOptions.Comment = HeapAlloc(hProcessHeap, 0, Length); - WideCharToMultiByte(CP_ACP, 0, argv[i], -1, AppOptions.Comment, Length, NULL, NULL); - - break; - - case 's': - AppOptions.Shutdown = TRUE; - break; - - case 'w': - AppOptions.Submit = TRUE; - break; - - default: - ReturnValue = 1; - /* Fall through */ - - case '?': - IntPrintUsage(); - goto Cleanup; - } - } - else - { - /* Which parameter is this? */ - if(!AppOptions.Module) - { - /* Copy the parameter */ - Length = (wcslen(argv[i]) + 1) * sizeof(WCHAR); - AppOptions.Module = HeapAlloc(hProcessHeap, 0, Length); - memcpy(AppOptions.Module, argv[i], Length); - } - else if(!AppOptions.Test) - { - /* Copy the parameter converted to ASCII */ - Length = WideCharToMultiByte(CP_ACP, 0, argv[i], -1, NULL, 0, NULL, NULL); - AppOptions.Test = HeapAlloc(hProcessHeap, 0, Length); - WideCharToMultiByte(CP_ACP, 0, argv[i], -1, AppOptions.Test, Length, NULL, NULL); - } - else - { - ReturnValue = 1; - IntPrintUsage(); - goto Cleanup; - } - } - } - - if(!IntGetConfigurationValues() || !IntGetBuildAndPlatform() || !RunWineTests()) - { - ReturnValue = 1; - goto Cleanup; - } - - /* For sysreg */ - OutputDebugStringA("SYSREG_CHECKPOINT:THIRDBOOT_COMPLETE\n"); - -Cleanup: - if(AppOptions.Comment) - HeapFree(hProcessHeap, 0, AppOptions.Comment); - - if(AppOptions.Module) - HeapFree(hProcessHeap, 0, AppOptions.Module); - - if(AppOptions.Test) - HeapFree(hProcessHeap, 0, AppOptions.Test); - - if(AuthenticationRequestString) - HeapFree(hProcessHeap, 0, AuthenticationRequestString); - - if(SystemInfoRequestString) - HeapFree(hProcessHeap, 0, SystemInfoRequestString); - - /* Shut down the system if requested */ - if(AppOptions.Shutdown && !ShutdownSystem()) - ReturnValue = 1; - - return ReturnValue; -} diff --git a/rostests/rosautotest/main.cpp b/rostests/rosautotest/main.cpp new file mode 100644 index 00000000000..6b54ac87d60 --- /dev/null +++ b/rostests/rosautotest/main.cpp @@ -0,0 +1,92 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Main implementation file + * COPYRIGHT: Copyright 2008-2009 Colin Finck + */ + +#include "precomp.h" +#include + +CConfiguration Configuration; + +/** + * Prints the application usage. + */ +static void +IntPrintUsage() +{ + cout << "rosautotest - ReactOS Automatic Testing Utility" << endl + << "Usage: rosautotest [options] [module] [test]" << endl + << " options:" << endl + << " /? - Shows this help." << endl + << " /c - Specifies the comment to be submitted to the Web Service." << endl + << " Skips the comment set in the configuration file (if any)." << endl + << " Only has an effect when /w is also used." << endl + << " /r - Maintain information to resume from ReactOS crashes" << endl + << " Can only be run under ReactOS and relies on sysreg2," << endl + << " so incompatible with /w" << endl + << " /s - Shut down the system after finishing the tests." << endl + << " /w - Submit the results to the webservice." << endl + << " Requires a \"rosautotest.ini\" with valid login data." << endl + << " Incompatible with the /r option." << endl + << endl + << " module:" << endl + << " The module to be tested (i.e. \"advapi32\")" << endl + << " If this parameter is specified without any test parameter," << endl + << " all tests of the specified module are run." << endl + << endl + << " test:" << endl + << " The test to be run. Needs to be a test of the specified module." << endl; +} + +/** + * Main entry point + */ +extern "C" int +wmain(int argc, wchar_t* argv[]) +{ + CWineTest WineTest; + int ReturnValue = 1; + + try + { + /* Set up the configuration */ + Configuration.ParseParameters(argc, argv); + Configuration.GetSystemInformation(); + Configuration.GetConfigurationFromFile(); + + /* Run the tests */ + WineTest.Run(); + + /* For sysreg */ + DbgPrint("SYSREG_CHECKPOINT:THIRDBOOT_COMPLETE\n"); + + ReturnValue = 0; + } + catch(CInvalidParameterException) + { + IntPrintUsage(); + } + catch(CSimpleException& e) + { + StringOut(e.GetMessage()); + } + catch(CFatalException& e) + { + stringstream ss; + + ss << "An exception occured in rosautotest." << endl + << "Message: " << e.GetMessage() << endl + << "File: " << e.GetFile() << endl + << "Line: " << e.GetLine() << endl + << "Last Win32 Error: " << GetLastError() << endl; + StringOut(ss.str()); + } + + /* Shut down the system if requested, also in case of an exception above */ + if(Configuration.DoShutdown() && !ShutdownSystem()) + ReturnValue = 1; + + return ReturnValue; +} diff --git a/rostests/rosautotest/precomp.h b/rostests/rosautotest/precomp.h index 9b8d6d75edf..57acaf835df 100644 --- a/rostests/rosautotest/precomp.h +++ b/rostests/rosautotest/precomp.h @@ -1,82 +1,63 @@ -/* Includes */ -#include +/* General includes */ +#include +#include +#include +#include +#include +using namespace std; + +#define WIN32_NO_STATUS #include +#include #include +#include #include +#include #include -/* Defines */ -#define BUFFER_BLOCKSIZE 2048 -#define BUILDNO_LENGTH 10 -#define PLATFORM_LENGTH 25 -#define SERVER_HOSTNAME L"localhost" -#define SERVER_FILE L"testman/webservice/" +/* Class includes */ +#include "auto_array_ptr.h" +#include "CConfiguration.h" +#include "CFatalException.h" +#include "CInvalidParameterException.h" +#include "CProcess.h" +#include "CSimpleException.h" +#include "CTestInfo.h" +#include "CTest.h" +#include "CTestList.h" +#include "CJournaledTestList.h" +#include "CVirtualTestList.h" +#include "CWebService.h" +#include "CWineTest.h" -/* Enums */ -typedef enum _TESTTYPES -{ - WineTest -} -TESTTYPES; - -/* Structs */ -typedef struct _APP_OPTIONS -{ - BOOL Shutdown; - BOOL Submit; - PCHAR Comment; - PWSTR Module; - PCHAR Test; -} -APP_OPTIONS, *PAPP_OPTIONS; - -typedef struct _WINE_GETSUITEID_DATA -{ - PCHAR Module; - PCHAR Test; -} -WINE_GETSUITEID_DATA, *PWINE_GETSUITEID_DATA; - -typedef struct _GENERAL_SUBMIT_DATA -{ - PCHAR TestID; - PCHAR SuiteID; -} -GENERAL_SUBMIT_DATA, *PGENERAL_SUBMIT_DATA; - -typedef struct _WINE_SUBMIT_DATA -{ - GENERAL_SUBMIT_DATA General; - PCHAR Log; -} -WINE_SUBMIT_DATA, *PWINE_SUBMIT_DATA; - -typedef struct _GENERAL_FINISH_DATA -{ - PCHAR TestID; -} -GENERAL_FINISH_DATA, *PGENERAL_FINISH_DATA; +/* Useful macros */ +#define STRINGIZER(Value) #Value +#define EXCEPTION(Message) throw CSimpleException(Message) +#define FATAL(Message) throw CFatalException(__FILE__, STRINGIZER(__LINE__), Message) +#define SSEXCEPTION throw CSimpleException(ss.str().c_str()) /* main.c */ -extern APP_OPTIONS AppOptions; -extern HANDLE hProcessHeap; -extern PCHAR AuthenticationRequestString; -extern PCHAR SystemInfoRequestString; +extern CConfiguration Configuration; /* shutdown.c */ -BOOL ShutdownSystem(); +bool ShutdownSystem(); /* tools.c */ -VOID EscapeString(PCHAR Output, PCHAR Input); -VOID StringOut(PCHAR String); +wstring AsciiToUnicode(const char* AsciiString); +wstring AsciiToUnicode(const string& AsciiString); +string EscapeString(const char* Input); +string EscapeString(const string& Input); +string GetINIValue(PCWCH AppName, PCWCH KeyName, PCWCH FileName); +bool IsNumber(const char* Input); +void StringOut(const string& String); +string UnicodeToAscii(PCWSTR UnicodeString); +string UnicodeToAscii(const wstring& UnicodeString); -/* webservice.c */ -PCHAR GetTestID(TESTTYPES TestType); -PCHAR GetSuiteID(TESTTYPES TestType, const PVOID TestData); -BOOL Submit(TESTTYPES TestType, const PVOID TestData); -BOOL Finish(TESTTYPES TestType, const PVOID TestData); -/* winetests.c */ -BOOL RunWineTests(); +/* Lazy HACK to allow compiling/debugging with MSVC while we lack support + for linking against "debugsup_ntdll" in MSVC */ +#ifdef _MSC_VER + #define DbgPrint +#endif diff --git a/rostests/rosautotest/rosautotest.rbuild b/rostests/rosautotest/rosautotest.rbuild index b372227fc8a..7213c05b89f 100644 --- a/rostests/rosautotest/rosautotest.rbuild +++ b/rostests/rosautotest/rosautotest.rbuild @@ -1,15 +1,27 @@ + -fno-rtti . advapi32 kernel32 + shell32 user32 wininet - main.c - shutdown.c - tools.c - webservice.c - winetests.c + CConfiguration.cpp + CFatalException.cpp + CInvalidParameterException.cpp + CJournaledTestList.cpp + CProcess.cpp + CSimpleException.cpp + CTest.cpp + CTestInfo.cpp + CTestList.cpp + CVirtualTestList.cpp + CWebService.cpp + CWineTest.cpp + main.cpp + shutdown.cpp + tools.cpp precomp.h diff --git a/rostests/rosautotest/shutdown.c b/rostests/rosautotest/shutdown.cpp similarity index 87% rename from rostests/rosautotest/shutdown.c rename to rostests/rosautotest/shutdown.cpp index 7493d7962f7..dc243fa7afe 100644 --- a/rostests/rosautotest/shutdown.c +++ b/rostests/rosautotest/shutdown.cpp @@ -11,9 +11,9 @@ * Shuts down the system. * * @return - * TRUE if everything went well, FALSE if there was a problem while trying to shut down the system. + * true if everything went well, false if there was a problem while trying to shut down the system. */ -BOOL ShutdownSystem() +bool ShutdownSystem() { HANDLE hToken; TOKEN_PRIVILEGES Privileges; @@ -21,14 +21,14 @@ BOOL ShutdownSystem() if (!OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES, &hToken)) { StringOut("OpenProcessToken failed\n"); - return FALSE; + return false; } /* Get the LUID for the Shutdown privilege */ if (!LookupPrivilegeValueW(NULL, SE_SHUTDOWN_NAME, &Privileges.Privileges[0].Luid)) { StringOut("LookupPrivilegeValue failed\n"); - return FALSE; + return false; } /* Assign the Shutdown privilege to our process */ @@ -38,15 +38,15 @@ BOOL ShutdownSystem() if (!AdjustTokenPrivileges(hToken, FALSE, &Privileges, 0, NULL, NULL)) { StringOut("AdjustTokenPrivileges failed\n"); - return FALSE; + return false; } /* Finally shut down the system */ if(!ExitWindowsEx(EWX_POWEROFF, SHTDN_REASON_MAJOR_OTHER | SHTDN_REASON_MINOR_OTHER | SHTDN_REASON_FLAG_PLANNED)) { StringOut("ExitWindowsEx failed\n"); - return FALSE; + return false; } - return TRUE; + return true; } diff --git a/rostests/rosautotest/tools.c b/rostests/rosautotest/tools.c deleted file mode 100644 index adb7b730d70..00000000000 --- a/rostests/rosautotest/tools.c +++ /dev/null @@ -1,94 +0,0 @@ -/* - * PROJECT: ReactOS Automatic Testing Utility - * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation - * PURPOSE: Various helper functions - * COPYRIGHT: Copyright 2008-2009 Colin Finck - */ - -#include "precomp.h" - -/** - * Escapes a string according to RFC 1738. - * Required for passing parameters to the web service. - * - * @param Output - * Pointer to a CHAR array, which will receive the escaped string. - * The output buffer must be large enough to hold the full escaped string. You're on the safe side - * if you make the output buffer three times as large as the input buffer. - * - * @param Input - * Pointer to a CHAR array, which contains the input buffer to escape. - */ -VOID -EscapeString(PCHAR Output, PCHAR Input) -{ - const CHAR HexCharacters[] = "0123456789ABCDEF"; - - do - { - if(strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~", *Input) ) - { - /* It's a character we don't need to escape, just add it to the output string */ - *Output++ = *Input; - } - else - { - /* We need to escape this character */ - *Output++ = '%'; - *Output++ = HexCharacters[((UCHAR)*Input >> 4) % 16]; - *Output++ = HexCharacters[(UCHAR)*Input % 16]; - } - } - while(*++Input); - - *Output = 0; -} - -/** - * Outputs a string through the standard output and the debug output. - * The string may have LF or CRLF line endings. - * - * @param String - * The string to output - */ -VOID -StringOut(PCHAR String) -{ - PCHAR NewString; - PCHAR pNewString; - size_t Length; - - /* The piped output of the tests may use CRLF line endings, so convert them to LF. - As both printf and OutputDebugStringA operate in text mode, the line-endings will be properly converted again later. */ - Length = strlen(String); - NewString = HeapAlloc(hProcessHeap, 0, Length + 1); - pNewString = NewString; - - do - { - /* If this is a CRLF line-ending, only copy a \n to the new string and skip the next character */ - if(*String == '\r' && *(String + 1) == '\n') - { - *pNewString = '\n'; - ++String; - } - else - { - /* Otherwise copy the string */ - *pNewString = *String; - } - - ++pNewString; - } - while(*++String); - - /* Null-terminate it */ - *pNewString = 0; - - /* Output it */ - printf(NewString); - OutputDebugStringA(NewString); - - /* Cleanup */ - HeapFree(hProcessHeap, 0, NewString); -} diff --git a/rostests/rosautotest/tools.cpp b/rostests/rosautotest/tools.cpp new file mode 100644 index 00000000000..6b1165c5bd5 --- /dev/null +++ b/rostests/rosautotest/tools.cpp @@ -0,0 +1,257 @@ +/* + * PROJECT: ReactOS Automatic Testing Utility + * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation + * PURPOSE: Various helper functions + * COPYRIGHT: Copyright 2008-2009 Colin Finck + */ + +#include "precomp.h" + +#define DBGPRINT_BUFSIZE 511 +static const char HexCharacters[] = "0123456789ABCDEF"; + +/** + * Escapes a string according to RFC 1738. + * Required for passing parameters to the web service. + * + * @param Input + * Constant pointer to a char array, which contains the input buffer to escape. + * + * @return + * The escaped string as std::string. + */ +string +EscapeString(const char* Input) +{ + string ReturnedString; + + do + { + if(strchr("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_.~", *Input)) + { + /* It's a character we don't need to escape, just add it to the output string */ + ReturnedString += *Input; + } + else + { + /* We need to escape this character */ + ReturnedString += '%'; + ReturnedString += HexCharacters[((UCHAR)*Input >> 4) % 16]; + ReturnedString += HexCharacters[(UCHAR)*Input % 16]; + } + } + while(*++Input); + + return ReturnedString; +} + +/** + * Escapes a string according to RFC 1738. + * Required for passing parameters to the web service. + * + * @param Input + * Pointer to a std::string, which contains the input buffer to escape. + * + * @return + * The escaped string as std::string. + */ +string +EscapeString(const string& Input) +{ + return EscapeString(Input.c_str()); +} + +/** + * Determines whether a string contains entirely numeric values. + * + * @param Input + * Constant pointer to a char array containing the input to check. + * + * @return + * true if the string is entirely numeric, false otherwise. + */ +bool +IsNumber(const char* Input) +{ + do + { + if(!isdigit(*Input)) + return false; + + ++Input; + } + while(*Input); + + return true; +} + +/** + * Outputs a string through the standard output and the debug output. + * The string may have LF or CRLF line endings. + * + * @param String + * The std::string to output + */ +void +StringOut(const string& String) +{ + char DbgString[DBGPRINT_BUFSIZE + 1]; + size_t i; + string NewString; + + /* Unify the line endings (the piped output of the tests may use CRLF) */ + for(i = 0; i < String.size(); i++) + { + /* If this is a CRLF line-ending, only copy a \n to the new string and skip the next character */ + if(String[i] == '\r' && String[i + 1] == '\n') + { + NewString += '\n'; + ++i; + } + else + { + /* Otherwise copy the string */ + NewString += String[i]; + } + } + + /* Output the string. + For DbgPrint, this must be done in chunks of 512 bytes. */ + cout << NewString; + + for(i = 0; i < NewString.size(); i += DBGPRINT_BUFSIZE) + { + size_t BytesToCopy; + + if(NewString.size() - i > DBGPRINT_BUFSIZE) + BytesToCopy = DBGPRINT_BUFSIZE; + else + BytesToCopy = NewString.size() - i; + + memcpy(DbgString, NewString.c_str() + i, BytesToCopy); + DbgString[BytesToCopy] = 0; + + DbgPrint(DbgString); + } +} + +/** + * Gets a value from a specified INI file and returns it converted to ASCII. + * + * @param AppName + * Constant pointer to a WCHAR array with the INI section to look in (lpAppName parameter passed to GetPrivateProfileStringW) + * + * @param KeyName + * Constant pointer to a WCHAR array containing the key to look for in the specified section (lpKeyName parameter passed to GetPrivateProfileStringW) + * + * @param FileName + * Constant pointer to a WCHAR array containing the path to the INI file + * + * @return + * Returns the data of the value as std::string or an empty string if no data could be retrieved. + */ +string +GetINIValue(PCWCH AppName, PCWCH KeyName, PCWCH FileName) +{ + DWORD Length; + PCHAR AsciiBuffer; + string ReturnedString; + WCHAR Buffer[2048]; + + /* Load the value into a temporary Unicode buffer */ + Length = GetPrivateProfileStringW(AppName, KeyName, NULL, Buffer, sizeof(Buffer) / sizeof(WCHAR), FileName); + + if(Length) + { + /* Convert the string to ASCII charset */ + AsciiBuffer = new char[Length + 1]; + WideCharToMultiByte(CP_ACP, 0, Buffer, Length + 1, AsciiBuffer, Length + 1, NULL, NULL); + + ReturnedString = AsciiBuffer; + delete AsciiBuffer; + } + + return ReturnedString; +} + +/** + * Converts an ASCII string to a Unicode one. + * + * @param AsciiString + * Constant pointer to a char array containing the ASCII string + * + * @return + * The Unicode string as std::wstring + */ +wstring +AsciiToUnicode(const char* AsciiString) +{ + DWORD Length; + PWSTR UnicodeString; + wstring ReturnString; + + Length = MultiByteToWideChar(CP_ACP, 0, AsciiString, -1, NULL, 0); + + UnicodeString = new WCHAR[Length]; + MultiByteToWideChar(CP_ACP, 0, AsciiString, -1, UnicodeString, Length); + ReturnString = UnicodeString; + delete UnicodeString; + + return ReturnString; +} + +/** + * Converts an ASCII string to a Unicode one. + * + * @param AsciiString + * Pointer to a std::string containing the ASCII string + * + * @return + * The Unicode string as std::wstring + */ +wstring +AsciiToUnicode(const string& AsciiString) +{ + return AsciiToUnicode(AsciiString.c_str()); +} + +/** + * Converts a Unicode string to an ASCII one. + * + * @param UnicodeString + * Constant pointer to a WCHAR array containing the Unicode string + * + * @return + * The ASCII string as std::string + */ +string +UnicodeToAscii(PCWSTR UnicodeString) +{ + DWORD Length; + PCHAR AsciiString; + string ReturnString; + + Length = WideCharToMultiByte(CP_ACP, 0, UnicodeString, -1, NULL, 0, NULL, NULL); + + AsciiString = new char[Length]; + WideCharToMultiByte(CP_ACP, 0, UnicodeString, -1, AsciiString, Length, NULL, NULL); + ReturnString = AsciiString; + delete AsciiString; + + return ReturnString; +} + +/** + * Converts a Unicode string to an ASCII one. + * + * @param UnicodeString + * Pointer to a std::wstring containing the Unicode string + * + * @return + * The ASCII string as std::string + */ +string +UnicodeToAscii(const wstring& UnicodeString) +{ + return UnicodeToAscii(UnicodeString.c_str()); +} diff --git a/rostests/rosautotest/webservice.c b/rostests/rosautotest/webservice.c deleted file mode 100644 index 852ee3d598a..00000000000 --- a/rostests/rosautotest/webservice.c +++ /dev/null @@ -1,464 +0,0 @@ -/* - * PROJECT: ReactOS Automatic Testing Utility - * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation - * PURPOSE: Submitting test results to the Web Service - * COPYRIGHT: Copyright 2008-2009 Colin Finck - */ - -#include "precomp.h" - -static const CHAR ActionProp[] = "action="; -static const CHAR TestIDProp[] = "&testid="; -static const CHAR TestTypeProp[] = "&testtype="; -static const CHAR WineTestType[] = "wine"; - -/** - * Sends data to the ReactOS Web Test Manager web service. - * - * @param Data - * Pointer to a CHAR pointer, which contains the data to submit as HTTP POST data. - * The buffer behind this pointer had to be allocated with HeapAlloc. - * Returns the data received by the web service after the call. - * - * @param DataLength - * Pointer to a DWORD, which contains the length of the data to submit (in bytes). - * Returns the length of the data received by the web service after the call (in bytes). - * - * @return - * TRUE if everything went well, FALSE if an error occured while submitting the request. - * In case of an error, the function will output an appropriate error message through StringOut. - */ -static BOOL -IntDoRequest(char** Data, PDWORD DataLength) -{ - const WCHAR Headers[] = L"Content-Type: application/x-www-form-urlencoded"; - - BOOL ReturnValue = FALSE; - HINTERNET hHTTP = NULL; - HINTERNET hHTTPRequest = NULL; - HINTERNET hInet = NULL; - - /* Establish an internet connection to the "testman" server */ - hInet = InternetOpenW(L"rosautotest", INTERNET_OPEN_TYPE_PRECONFIG, NULL, NULL, 0); - - if(!hInet) - { - StringOut("InternetOpenW failed\n"); - goto Cleanup; - } - - hHTTP = InternetConnectW(hInet, SERVER_HOSTNAME, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); - - if(!hHTTP) - { - StringOut("InternetConnectW failed\n"); - goto Cleanup; - } - - /* Post our test results to the web service */ - hHTTPRequest = HttpOpenRequestW(hHTTP, L"POST", SERVER_FILE, NULL, NULL, NULL, INTERNET_FLAG_NO_COOKIES | INTERNET_FLAG_RELOAD | INTERNET_FLAG_NO_CACHE_WRITE, 0); - - if(!hHTTPRequest) - { - StringOut("HttpOpenRequestW failed\n"); - goto Cleanup; - } - - if(!HttpSendRequestW(hHTTPRequest, Headers, wcslen(Headers), *Data, *DataLength)) - { - StringOut("HttpSendRequestW failed\n"); - goto Cleanup; - } - - HeapFree(hProcessHeap, 0, *Data); - *Data = NULL; - - /* Get the response */ - if(!InternetQueryDataAvailable(hHTTPRequest, DataLength, 0, 0)) - { - StringOut("InternetQueryDataAvailable failed\n"); - goto Cleanup; - } - - *Data = HeapAlloc(hProcessHeap, 0, *DataLength + 1); - - if(!InternetReadFile(hHTTPRequest, *Data, *DataLength, DataLength)) - { - StringOut("InternetReadFile failed\n"); - goto Cleanup; - } - - (*Data)[*DataLength] = 0; - ReturnValue = TRUE; - -Cleanup: - if(hHTTPRequest) - InternetCloseHandle(hHTTPRequest); - - if(hHTTP) - InternetCloseHandle(hHTTP); - - if(hInet) - InternetCloseHandle(hInet); - - return ReturnValue; -} - -/** - * Determines whether a string contains entirely numeric values. - * - * @param Input - * The string to check. - * - * @return - * TRUE if the string is entirely numeric, FALSE otherwise. - */ -static BOOL -IsNumber(PCHAR Input) -{ - do - { - if(!isdigit(*Input)) - return FALSE; - - ++Input; - } - while(*Input); - - return TRUE; -} - -/** - * Requests a Test ID from the web service for our test run. - * - * @param TestType - * Value from the TESTTYPES enum indicating the type of test we are about to submit. - * - * @return - * Returns the Test ID as a CHAR array if successful or NULL otherwise. - * The caller needs to HeapFree the returned pointer in case of success. - */ -PCHAR -GetTestID(TESTTYPES TestType) -{ - const CHAR GetTestIDAction[] = "gettestid"; - const CHAR CommentProp[] = "&comment="; - - DWORD DataLength; - PCHAR Data; - PCHAR ReturnValue = NULL; - - /* Build the full request string */ - DataLength = sizeof(ActionProp) - 1 + sizeof(GetTestIDAction) - 1; - DataLength += strlen(AuthenticationRequestString) + strlen(SystemInfoRequestString); - - if(AppOptions.Comment) - DataLength += sizeof(CommentProp) - 1 + strlen(AppOptions.Comment); - - DataLength += sizeof(TestTypeProp) - 1; - - switch(TestType) - { - case WineTest: - DataLength += sizeof(WineTestType) - 1; - break; - } - - Data = HeapAlloc(hProcessHeap, 0, DataLength + 1); - strcpy(Data, ActionProp); - strcat(Data, GetTestIDAction); - strcat(Data, AuthenticationRequestString); - strcat(Data, SystemInfoRequestString); - - if(AppOptions.Comment) - { - strcat(Data, CommentProp); - strcat(Data, AppOptions.Comment); - } - - strcat(Data, TestTypeProp); - - switch(TestType) - { - case WineTest: - strcat(Data, WineTestType); - break; - } - - if(!IntDoRequest(&Data, &DataLength)) - goto Cleanup; - - /* Verify that this is really a number */ - if(!IsNumber(Data)) - { - StringOut("Expected Test ID, but received:\n"); - StringOut(Data); - StringOut("\n"); - goto Cleanup; - } - - ReturnValue = Data; - -Cleanup: - if(Data && ReturnValue != Data) - HeapFree(hProcessHeap, 0, Data); - - return ReturnValue; -} - -/** - * Requests a Suite ID from the web service for our module/test combination. - * - * @param TestType - * Value from the TESTTYPES enum indicating the type of test we are about to submit. - * - * @param TestData - * Pointer to a *_GETSUITEID_DATA structure appropriate for our selected test type. - * Contains other input information for this request. - * - * @return - * Returns the Suite ID as a CHAR array if successful or NULL otherwise. - * The caller needs to HeapFree the returned pointer in case of success. - */ -PCHAR -GetSuiteID(TESTTYPES TestType, const PVOID TestData) -{ - const CHAR GetSuiteIDAction[] = "getsuiteid"; - const CHAR ModuleProp[] = "&module="; - const CHAR TestProp[] = "&test="; - - DWORD DataLength; - PCHAR Data; - PCHAR ReturnValue = NULL; - PWINE_GETSUITEID_DATA WineData; - - DataLength = sizeof(ActionProp) - 1 + sizeof(GetSuiteIDAction) - 1; - DataLength += strlen(AuthenticationRequestString); - DataLength += sizeof(TestTypeProp) - 1; - - switch(TestType) - { - case WineTest: - DataLength += sizeof(WineTestType) - 1; - - WineData = (PWINE_GETSUITEID_DATA)TestData; - DataLength += sizeof(ModuleProp) - 1; - DataLength += strlen(WineData->Module); - DataLength += sizeof(TestProp) - 1; - DataLength += strlen(WineData->Test); - - break; - } - - Data = HeapAlloc(hProcessHeap, 0, DataLength + 1); - strcpy(Data, ActionProp); - strcat(Data, GetSuiteIDAction); - strcat(Data, AuthenticationRequestString); - strcat(Data, TestTypeProp); - - switch(TestType) - { - case WineTest: - strcat(Data, WineTestType); - - /* Stupid GCC and MSVC: WineData is already initialized above, still it's reported as a potentially uninitialized variable :-( */ - WineData = (PWINE_GETSUITEID_DATA)TestData; - strcat(Data, ModuleProp); - strcat(Data, WineData->Module); - strcat(Data, TestProp); - strcat(Data, WineData->Test); - - break; - } - - if(!IntDoRequest(&Data, &DataLength)) - goto Cleanup; - - /* Verify that this is really a number */ - if(!IsNumber(Data)) - { - StringOut("Expected Suite ID, but received:\n"); - StringOut(Data); - StringOut("\n"); - goto Cleanup; - } - - ReturnValue = Data; - -Cleanup: - if(Data && ReturnValue != Data) - HeapFree(hProcessHeap, 0, Data); - - return ReturnValue; -} - -/** - * Submits the result of one test call to the web service. - * - * @param TestType - * Value from the TESTTYPES enum indicating the type of test we are about to submit. - * - * @param TestData - * Pointer to a *_SUBMIT_DATA structure appropriate for our selected test type. - * Contains other input information for this request. - * - * @return - * TRUE if everything went well, FALSE otherwise. - */ -BOOL -Submit(TESTTYPES TestType, const PVOID TestData) -{ - const CHAR SubmitAction[] = "submit"; - const CHAR SuiteIDProp[] = "&suiteid="; - const CHAR LogProp[] = "&log="; - - BOOL ReturnValue = FALSE; - DWORD DataLength; - PCHAR Data; - PCHAR pData; - PGENERAL_SUBMIT_DATA GeneralData; - PWINE_SUBMIT_DATA WineData; - - /* Compute the full length of the POST data */ - DataLength = sizeof(ActionProp) - 1 + sizeof(SubmitAction) - 1; - DataLength += strlen(AuthenticationRequestString); - - GeneralData = (PGENERAL_SUBMIT_DATA)TestData; - DataLength += sizeof(TestIDProp) - 1; - DataLength += strlen(GeneralData->TestID); - DataLength += sizeof(SuiteIDProp) - 1; - DataLength += strlen(GeneralData->SuiteID); - - /* The rest of the POST data depends on the test type */ - DataLength += sizeof(TestTypeProp) - 1; - - switch(TestType) - { - case WineTest: - DataLength += sizeof(WineTestType) - 1; - - WineData = (PWINE_SUBMIT_DATA)TestData; - DataLength += sizeof(LogProp) - 1; - DataLength += 3 * strlen(WineData->Log); - - break; - } - - /* Now collect all the POST data */ - Data = HeapAlloc(hProcessHeap, 0, DataLength + 1); - strcpy(Data, ActionProp); - strcat(Data, SubmitAction); - strcat(Data, AuthenticationRequestString); - - strcat(Data, TestIDProp); - strcat(Data, GeneralData->TestID); - strcat(Data, SuiteIDProp); - strcat(Data, GeneralData->SuiteID); - - strcat(Data, TestTypeProp); - - switch(TestType) - { - case WineTest: - strcat(Data, WineTestType); - - /* Stupid GCC and MSVC: WineData is already initialized above, still it's reported as a potentially uninitialized variable :-( */ - WineData = (PWINE_SUBMIT_DATA)TestData; - - strcat(Data, LogProp); - pData = Data + strlen(Data); - EscapeString(pData, WineData->Log); - - break; - } - - /* DataLength still contains the maximum length of the buffer, but not the actual data length we need for the request. - Determine that one now. */ - DataLength = strlen(Data); - - /* Send all the stuff */ - if(!IntDoRequest(&Data, &DataLength)) - goto Cleanup; - - /* Output the response */ - StringOut("The server responded:\n"); - StringOut(Data); - StringOut("\n"); - - if(!strcmp(Data, "OK")) - ReturnValue = TRUE; - -Cleanup: - if(Data) - HeapFree(hProcessHeap, 0, Data); - - return ReturnValue; -} - -/** - * Finishes a test run for the web service. - * - * @param TestType - * Value from the TESTTYPES enum indicating the type of test we are about to submit. - * - * @param TestData - * Pointer to a *_FINISH_DATA structure appropriate for our selected test type. - * Contains other input information for this request. - * - * @return - * TRUE if everything went well, FALSE otherwise. - */ -BOOL -Finish(TESTTYPES TestType, const PVOID TestData) -{ - const CHAR FinishAction[] = "finish"; - - BOOL ReturnValue = FALSE; - DWORD DataLength; - PCHAR Data; - PGENERAL_FINISH_DATA GeneralData; - - /* Build the full request string */ - DataLength = sizeof(ActionProp) - 1 + sizeof(FinishAction) - 1; - DataLength += strlen(AuthenticationRequestString); - - GeneralData = (PGENERAL_FINISH_DATA)TestData; - DataLength += sizeof(TestIDProp) - 1; - DataLength += strlen(GeneralData->TestID); - - DataLength += sizeof(TestTypeProp) - 1; - - switch(TestType) - { - case WineTest: - DataLength += sizeof(WineTestType) - 1; - break; - } - - Data = HeapAlloc(hProcessHeap, 0, DataLength + 1); - strcpy(Data, ActionProp); - strcat(Data, FinishAction); - strcat(Data, AuthenticationRequestString); - strcat(Data, TestIDProp); - strcat(Data, GeneralData->TestID); - strcat(Data, TestTypeProp); - - switch(TestType) - { - case WineTest: - strcat(Data, WineTestType); - break; - } - - if(!IntDoRequest(&Data, &DataLength)) - goto Cleanup; - - if(!strcmp(Data, "OK")) - ReturnValue = TRUE; - -Cleanup: - if(Data) - HeapFree(hProcessHeap, 0, Data); - - return ReturnValue; -} diff --git a/rostests/rosautotest/winetests.c b/rostests/rosautotest/winetests.c deleted file mode 100644 index b1c62c7b83c..00000000000 --- a/rostests/rosautotest/winetests.c +++ /dev/null @@ -1,453 +0,0 @@ -/* - * PROJECT: ReactOS Automatic Testing Utility - * LICENSE: GNU GPLv2 or any later version as published by the Free Software Foundation - * PURPOSE: Running Wine Tests automatically - * COPYRIGHT: Copyright 2008-2009 Colin Finck - */ - -#include "precomp.h" - -/** - * Runs a specific test for a specific module. - * If we get results for a test, they are submitted to the Web Service. - * - * @param CommandLine - * Command line to run (should be the path to a module's test suite together with a parameter for the specified test) - * - * @param hReadPipe - * Handle to the Read Pipe set up in RunWineTests. - * - * @param StartupInfo - * Pointer to the StartupInfo structure set up in RunWineTests. - * - * @param GetSuiteIDData - * Pointer to the GetSuiteIDData structure set up in IntRunModuleTests. - * - * @param SubmitData - * Pointer to the SubmitData structure set up in RunWineTests. - * - * @return - * TRUE if everything went well, FALSE otherwise. - */ -static BOOL -IntRunTest(PWSTR CommandLine, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_GETSUITEID_DATA GetSuiteIDData, PWINE_SUBMIT_DATA SubmitData) -{ - BOOL BreakLoop = FALSE; - BOOL ReturnValue = FALSE; - DWORD BytesAvailable; - DWORD LogAvailable = 0; - DWORD LogLength = 0; - DWORD LogPosition = 0; - DWORD Temp; - PCHAR Buffer = NULL; - PROCESS_INFORMATION ProcessInfo = {0}; - - if(AppOptions.Submit) - { - /* Allocate one block for the log */ - SubmitData->Log = HeapAlloc(hProcessHeap, 0, BUFFER_BLOCKSIZE); - LogAvailable = BUFFER_BLOCKSIZE; - LogLength = BUFFER_BLOCKSIZE; - } - - /* Allocate a buffer with the exact size of the output string. - We have to output this string in one call to prevent a race condition, when another application also outputs a string over the debug line. */ - Buffer = HeapAlloc(hProcessHeap, 0, 27 + strlen(GetSuiteIDData->Module) + 8 + strlen(GetSuiteIDData->Test) + 2); - sprintf(Buffer, "Running Wine Test, Module: %s, Test: %s\n", GetSuiteIDData->Module, GetSuiteIDData->Test); - StringOut(Buffer); - HeapFree(hProcessHeap, 0, Buffer); - Buffer = NULL; - - /* Execute the test */ - if(!CreateProcessW(NULL, CommandLine, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo)) - { - StringOut("CreateProcessW for running the test failed\n"); - goto Cleanup; - } - - /* Receive all the data from the pipe */ - do - { - /* When the application finished, make sure that we peek the pipe one more time, so that we get all data. - If the following condition would be the while() condition, we might hit a race condition: - - We check for data with PeekNamedPipe -> no data available - - The application outputs its data and finishes - - WaitForSingleObject reports that the application has finished and we break the loop without receiving any data - */ - if(WaitForSingleObject(ProcessInfo.hProcess, 0) == WAIT_OBJECT_0) - BreakLoop = TRUE; - - if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL)) - { - StringOut("PeekNamedPipe failed for the test run\n"); - goto Cleanup; - } - - if(BytesAvailable) - { - /* There is data, so get it and output it */ - Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable + 1); - - if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL)) - { - StringOut("ReadFile failed for the test run\n"); - goto Cleanup; - } - - /* Output all test output through StringOut, even while the test is still running */ - Buffer[BytesAvailable] = 0; - StringOut(Buffer); - - if(AppOptions.Submit) - { - /* Also store it in the buffer */ - if(BytesAvailable > LogAvailable) - { - /* Allocate enough new blocks to hold all available data */ - Temp = ((BytesAvailable - LogAvailable) / BUFFER_BLOCKSIZE + 1) * BUFFER_BLOCKSIZE; - LogAvailable += Temp; - LogLength += Temp; - SubmitData->Log = HeapReAlloc(hProcessHeap, 0, SubmitData->Log, LogLength); - } - - memcpy(&SubmitData->Log[LogPosition], Buffer, BytesAvailable); - LogPosition += BytesAvailable; - LogAvailable -= BytesAvailable; - } - - HeapFree(hProcessHeap, 0, Buffer); - Buffer = NULL; - } - } - while(!BreakLoop); - - if(AppOptions.Submit) - { - SubmitData->Log[LogLength - LogAvailable] = 0; - - /* If we got any output, submit it to the web service */ - if(*SubmitData->Log) - { - /* We don't want to waste any ID's, so only request them if we can be sure that we have results to submit. */ - - /* Get a Test ID if we don't have one yet */ - if(!SubmitData->General.TestID) - { - SubmitData->General.TestID = GetTestID(WineTest); - - if(!SubmitData->General.TestID) - goto Cleanup; - } - - /* Get a Suite ID for this combination */ - SubmitData->General.SuiteID = GetSuiteID(WineTest, GetSuiteIDData); - - if(!SubmitData->General.SuiteID) - goto Cleanup; - - /* Submit the stuff */ - Submit(WineTest, SubmitData); - } - } - - StringOut("\n\n"); - - ReturnValue = TRUE; - -Cleanup: - if(Buffer) - HeapFree(hProcessHeap, 0, Buffer); - - if(ProcessInfo.hProcess) - HeapFree(hProcessHeap, 0, ProcessInfo.hProcess); - - if(ProcessInfo.hThread) - HeapFree(hProcessHeap, 0, ProcessInfo.hThread); - - if(SubmitData->General.SuiteID) - { - HeapFree(hProcessHeap, 0, SubmitData->General.SuiteID); - SubmitData->General.SuiteID = NULL; - } - - if(SubmitData->Log) - { - HeapFree(hProcessHeap, 0, SubmitData->Log); - SubmitData->Log = NULL; - } - - return ReturnValue; -} - -/** - * Runs the desired tests for a specified module. - * - * @param File - * The file name of the module's test suite. - * - * @param FilePath - * The full path to the file of the module's test suite. - * - * @param hReadPipe - * Handle to the Read Pipe set up in RunWineTests. - * - * @param StartupInfo - * Pointer to the StartupInfo structure set up in RunWineTests. - * - * @param SubmitData - * Pointer to the SubmitData structure set up in RunWineTests. - * - * @return - * TRUE if everything went well, FALSE otherwise. - */ -static BOOL -IntRunModuleTests(PWSTR File, PWSTR FilePath, HANDLE hReadPipe, LPSTARTUPINFOW StartupInfo, PWINE_SUBMIT_DATA SubmitData) -{ - BOOL ReturnValue = FALSE; - DWORD BytesAvailable; - DWORD Length; - DWORD Temp; - PCHAR Buffer = NULL; - PCHAR pStart; - PCHAR pEnd; - PROCESS_INFORMATION ProcessInfo = {0}; - PWSTR pUnderscore; - size_t FilePosition; - WINE_GETSUITEID_DATA GetSuiteIDData = {0}; - - /* Build the full command line */ - FilePosition = wcslen(FilePath); - FilePath[FilePosition++] = ' '; - FilePath[FilePosition] = 0; - wcscat(FilePath, L"--list"); - - /* Find the underscore in the file name */ - pUnderscore = wcschr(File, L'_'); - - if(!pUnderscore) - { - StringOut("Invalid test file name: "); - - Length = wcslen(File); - Buffer = HeapAlloc(hProcessHeap, 0, Length + 1); - WideCharToMultiByte(CP_ACP, 0, File, Length + 1, Buffer, Length + 1, NULL, NULL); - - StringOut(Buffer); - StringOut("\n"); - - goto Cleanup; - } - - /* Store the tested module name */ - Length = pUnderscore - File; - GetSuiteIDData.Module = HeapAlloc(hProcessHeap, 0, Length + 1); - WideCharToMultiByte(CP_ACP, 0, File, Length, GetSuiteIDData.Module, Length, NULL, NULL); - GetSuiteIDData.Module[Length] = 0; - - /* Start the process for getting all available tests */ - if(!CreateProcessW(NULL, FilePath, NULL, NULL, TRUE, NORMAL_PRIORITY_CLASS, NULL, NULL, StartupInfo, &ProcessInfo)) - { - StringOut("CreateProcessW for getting the available tests failed\n"); - goto Cleanup; - } - - /* Wait till this process ended */ - if(WaitForSingleObject(ProcessInfo.hProcess, INFINITE) == WAIT_FAILED) - { - StringOut("WaitForSingleObject failed for the test list\n"); - goto Cleanup; - } - - /* Read the output data into a buffer */ - if(!PeekNamedPipe(hReadPipe, NULL, 0, NULL, &BytesAvailable, NULL)) - { - StringOut("PeekNamedPipe failed for the test list\n"); - goto Cleanup; - } - - /* Check if we got any */ - if(!BytesAvailable) - { - StringOut("The --list command did not return any data for "); - - Length = wcslen(File); - Buffer = HeapAlloc(hProcessHeap, 0, Length + 1); - WideCharToMultiByte(CP_ACP, 0, File, Length + 1, Buffer, Length + 1, NULL, NULL); - - StringOut(Buffer); - StringOut("\n"); - - goto Cleanup; - } - - /* Read the data */ - Buffer = HeapAlloc(hProcessHeap, 0, BytesAvailable); - - if(!ReadFile(hReadPipe, Buffer, BytesAvailable, &Temp, NULL)) - { - StringOut("ReadFile failed\n"); - goto Cleanup; - } - - /* Jump to the first available test */ - pStart = strchr(Buffer, '\n'); - pStart += 5; - - while(pStart < (Buffer + BytesAvailable)) - { - /* Get start and end of this test name */ - pEnd = pStart; - - while(*pEnd != '\r') - ++pEnd; - - /* Store the test name */ - GetSuiteIDData.Test = HeapAlloc(hProcessHeap, 0, pEnd - pStart + 1); - memcpy(GetSuiteIDData.Test, pStart, pEnd - pStart); - GetSuiteIDData.Test[pEnd - pStart] = 0; - - /* If the user gave us a test to run, we check whether the module's test suite really provides this test. */ - if(!AppOptions.Test || (AppOptions.Test && !strcmp(AppOptions.Test, GetSuiteIDData.Test))) - { - /* Build the command line for this test */ - Length = MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, NULL, 0); - MultiByteToWideChar(CP_ACP, 0, pStart, pEnd - pStart, &FilePath[FilePosition], Length * sizeof(WCHAR)); - FilePath[FilePosition + Length] = 0; - - if(!IntRunTest(FilePath, hReadPipe, StartupInfo, &GetSuiteIDData, SubmitData)) - goto Cleanup; - } - - /* Cleanup */ - HeapFree(hProcessHeap, 0, GetSuiteIDData.Test); - GetSuiteIDData.Test = NULL; - - /* Move to the next test */ - pStart = pEnd + 6; - } - - ReturnValue = TRUE; - -Cleanup: - if(GetSuiteIDData.Module) - HeapFree(hProcessHeap, 0, GetSuiteIDData.Module); - - if(GetSuiteIDData.Test) - HeapFree(hProcessHeap, 0, GetSuiteIDData.Test); - - if(Buffer) - HeapFree(hProcessHeap, 0, Buffer); - - if(ProcessInfo.hProcess) - CloseHandle(ProcessInfo.hProcess); - - if(ProcessInfo.hThread) - CloseHandle(ProcessInfo.hThread); - - return ReturnValue; -} - -/** - * Runs the Wine tests according to the options specified by the parameters. - * - * @return - * TRUE if everything went well, FALSE otherwise. - */ -BOOL -RunWineTests() -{ - BOOL ReturnValue = FALSE; - GENERAL_FINISH_DATA FinishData; - HANDLE hFind = NULL; - HANDLE hReadPipe = NULL; - HANDLE hWritePipe = NULL; - SECURITY_ATTRIBUTES SecurityAttributes; - STARTUPINFOW StartupInfo = {0}; - size_t PathPosition; - WCHAR FilePath[MAX_PATH]; - WIN32_FIND_DATAW fd; - WINE_SUBMIT_DATA SubmitData = { {0} }; - - /* Create a pipe for getting the output of the tests */ - SecurityAttributes.nLength = sizeof(SecurityAttributes); - SecurityAttributes.bInheritHandle = TRUE; - SecurityAttributes.lpSecurityDescriptor = NULL; - - if(!CreatePipe(&hReadPipe, &hWritePipe, &SecurityAttributes, 0)) - { - StringOut("CreatePipe failed\n"); - goto Cleanup; - } - - StartupInfo.cb = sizeof(StartupInfo); - StartupInfo.dwFlags = STARTF_USESTDHANDLES; - StartupInfo.hStdOutput = hWritePipe; - - /* Build the path for finding the tests */ - if(GetWindowsDirectoryW(FilePath, MAX_PATH) > MAX_PATH - 60) - { - StringOut("Windows directory path is too long\n"); - goto Cleanup; - } - - wcscat(FilePath, L"\\bin\\"); - PathPosition = wcslen(FilePath); - - if(AppOptions.Module) - { - /* Find a test belonging to this module */ - wcscat(FilePath, AppOptions.Module); - wcscat(FilePath, L"_*.exe"); - } - else - { - /* Find all tests */ - wcscat(FilePath, L"*.exe"); - } - - hFind = FindFirstFileW(FilePath, &fd); - - if(hFind == INVALID_HANDLE_VALUE) - { - StringOut("FindFirstFileW failed\n"); - goto Cleanup; - } - - /* Run the tests */ - do - { - /* Build the full path to the test suite */ - wcscpy(&FilePath[PathPosition], fd.cFileName); - - /* Run it */ - if(!IntRunModuleTests(fd.cFileName, FilePath, hReadPipe, &StartupInfo, &SubmitData)) - goto Cleanup; - } - while(FindNextFileW(hFind, &fd)); - - /* Close this test run if necessary */ - if(SubmitData.General.TestID) - { - FinishData.TestID = SubmitData.General.TestID; - - if(!Finish(WineTest, &FinishData)) - goto Cleanup; - } - - ReturnValue = TRUE; - -Cleanup: - if(SubmitData.General.TestID) - HeapFree(hProcessHeap, 0, SubmitData.General.TestID); - - if(hFind && hFind != INVALID_HANDLE_VALUE) - FindClose(hFind); - - if(hReadPipe) - CloseHandle(hReadPipe); - - if(hWritePipe) - CloseHandle(hWritePipe); - - return ReturnValue; -}