--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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);
+ }
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+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; }
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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)
+{
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+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; }
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#include "precomp.h"
+
+/**
+ * Constructs an empty CInvalidParameterException object, which is catched in wmain as an exception.
+ */
+CInvalidParameterException::CInvalidParameterException()
+{
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CInvalidParameterException
+{
+public:
+ CInvalidParameterException();
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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;
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CJournaledTestList : public CTestList
+{
+private:
+ HANDLE m_hJournal;
+ size_t m_ListIterator;
+ vector<CTestInfo> 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();
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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<WCHAR> 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);
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CProcess
+{
+private:
+ PROCESS_INFORMATION m_ProcessInfo;
+
+public:
+ CProcess(const wstring& CommandLine, LPSTARTUPINFOW StartupInfo);
+ ~CProcess();
+
+ HANDLE GetProcessHandle() const { return m_ProcessInfo.hProcess; }
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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)
+{
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CSimpleException
+{
+private:
+ string m_Message;
+
+public:
+ CSimpleException(const char* Message);
+
+ const string& GetMessage() const { return m_Message; }
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#include "precomp.h"
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+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;
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#include "precomp.h"
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CTestInfo
+{
+public:
+ wstring CommandLine;
+ string Module;
+ string Test;
+ string Log;
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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)
+{
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CTestList
+{
+protected:
+ CTest* m_Test;
+
+ CTestList(CTest* Test);
+
+public:
+ virtual CTestInfo* GetNextTestInfo() = 0;
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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();
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+class CVirtualTestList : public CTestList
+{
+public:
+ CVirtualTestList(CTest* Test);
+
+ CTestInfo* GetNextTestInfo();
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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<char> 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<char> 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<char> Response;
+ auto_array_ptr<char> 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");
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+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);
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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<CTestInfo> TestInfo(new CTestInfo());
+ size_t UnderscorePosition;
+
+ /* Build the command line */
+ TestInfo->CommandLine = m_TestPath;
+ TestInfo->CommandLine += m_CurrentFile;
+ TestInfo->CommandLine += ' ';
+ TestInfo->CommandLine += AsciiToUnicode(m_CurrentTest);
+
+ /* Store the Module name */
+ UnderscorePosition = m_CurrentFile.find('_');
+
+ 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<char> 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<CTestList> TestList;
+ auto_ptr<CWebService> 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<CTestInfo> TestInfoPtr(TestInfo);
+
+ RunTest(TestInfo);
+
+ if(Configuration.DoSubmit() && !TestInfo->Log.empty())
+ WebService->Submit("wine", TestInfo);
+
+ StringOut("\n\n");
+ }
+}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+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();
+};
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+template<typename Type>
+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<Type>& 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;
+ }
+};
+++ /dev/null
-/*
- * 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 <colin@reactos.org>
- */
-
-#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 <comment> - 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;
-}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#include "precomp.h"
+#include <cstdio>
+
+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 <comment> - 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;
+}
-/* Includes */
-#include <stdio.h>
+/* General includes */
+#include <iostream>
+#include <memory>
+#include <sstream>
+#include <string>
+#include <vector>
+using namespace std;
+
+#define WIN32_NO_STATUS
#include <windows.h>
+#include <ndk/rtlfuncs.h>
#include <reason.h>
+#include <shlobj.h>
#include <wininet.h>
+#include <ndk/rtlfuncs.h>
#include <reactos/buildno.h>
-/* Defines */
-#define BUFFER_BLOCKSIZE 2048
-#define BUILDNO_LENGTH 10
-#define PLATFORM_LENGTH 25
-#define SERVER_HOSTNAME L"localhost"
-#define SERVER_FILE L"testman/webservice/"
-
-/* 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;
+/* 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"
+
+/* 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);
-
-/* 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();
+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);
+
+
+/* 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
<?xml version="1.0"?>
<!DOCTYPE module SYSTEM "../../../tools/rbuild/project.dtd">
<module name="rosautotest" type="win32cui" installbase="system32" installname="rosautotest.exe" unicode="yes">
+ <compilerflag compiler="cpp">-fno-rtti</compilerflag>
<include base="rosautotest">.</include>
<library>advapi32</library>
<library>kernel32</library>
+ <library>shell32</library>
<library>user32</library>
<library>wininet</library>
- <file>main.c</file>
- <file>shutdown.c</file>
- <file>tools.c</file>
- <file>webservice.c</file>
- <file>winetests.c</file>
+ <file>CConfiguration.cpp</file>
+ <file>CFatalException.cpp</file>
+ <file>CInvalidParameterException.cpp</file>
+ <file>CJournaledTestList.cpp</file>
+ <file>CProcess.cpp</file>
+ <file>CSimpleException.cpp</file>
+ <file>CTest.cpp</file>
+ <file>CTestInfo.cpp</file>
+ <file>CTestList.cpp</file>
+ <file>CVirtualTestList.cpp</file>
+ <file>CWebService.cpp</file>
+ <file>CWineTest.cpp</file>
+ <file>main.cpp</file>
+ <file>shutdown.cpp</file>
+ <file>tools.cpp</file>
<pch>precomp.h</pch>
</module>
* 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;
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 */
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;
}
+++ /dev/null
-/*
- * 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 <colin@reactos.org>
- */
-
-#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);
-}
--- /dev/null
+/*
+ * 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 <colin@reactos.org>
+ */
+
+#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());
+}
+++ /dev/null
-/*
- * 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 <colin@reactos.org>
- */
-
-#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;
-}
+++ /dev/null
-/*
- * 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 <colin@reactos.org>
- */
-
-#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;
-}