Git conversion: Make reactos the root directory, move rosapps, rostests, wallpapers...
[reactos.git] / modules / rostests / rosautotest / CWineTest.cpp
diff --git a/modules/rostests/rosautotest/CWineTest.cpp b/modules/rostests/rosautotest/CWineTest.cpp
new file mode 100644 (file)
index 0000000..baf68a6
--- /dev/null
@@ -0,0 +1,382 @@
+/*
+ * PROJECT:     ReactOS Automatic Testing Utility
+ * LICENSE:     GPL-2.0+ (https://spdx.org/licenses/GPL-2.0+)
+ * PURPOSE:     Class implementing functions for handling Wine tests
+ * COPYRIGHT:   Copyright 2009-2015 Colin Finck (colin@reactos.org)
+ */
+
+#include "precomp.h"
+
+static const DWORD ListTimeout = 10000;
+
+/**
+ * Constructs a CWineTest object.
+ */
+CWineTest::CWineTest()
+    : m_hFind(NULL), m_ListBuffer(NULL)
+{
+    WCHAR wszDirectory[MAX_PATH];
+
+    /* Set up m_TestPath */
+    if (GetEnvironmentVariableW(L"ROSAUTOTEST_DIR", wszDirectory, MAX_PATH))
+    {
+        m_TestPath = wszDirectory;
+        if (*m_TestPath.rbegin() != L'\\')
+            m_TestPath += L'\\';
+    }
+    else
+    {
+        if (!GetWindowsDirectoryW(wszDirectory, MAX_PATH))
+            FATAL("GetWindowsDirectoryW failed");
+
+        m_TestPath = wszDirectory;
+        m_TestPath += L"\\bin\\";
+    }
+}
+
+/**
+ * Destructs a CWineTest object.
+ */
+CWineTest::~CWineTest()
+{
+    if(m_hFind)
+        FindClose(m_hFind);
+
+    if(m_ListBuffer)
+        delete m_ListBuffer;
+}
+
+/**
+ * Gets the next module test file using the FindFirstFileW/FindNextFileW API.
+ *
+ * @return
+ * true if we found a next file, otherwise false.
+ */
+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;
+    CPipe Pipe;
+
+    /* Build the command line */
+    CommandLine = m_TestPath;
+    CommandLine += m_CurrentFile;
+    CommandLine += L" --list";
+
+    {
+        /* Start the process for getting all available tests */
+        CPipedProcess Process(CommandLine, Pipe);
+
+        /* Wait till this process ended */
+        if(WaitForSingleObject(Process.GetProcessHandle(), ListTimeout) == WAIT_FAILED)
+            TESTEXCEPTION("WaitForSingleObject failed for the test list\n");
+    }
+
+    /* Read the output data into a buffer */
+    if(!Pipe.Peek(NULL, 0, NULL, &BytesAvailable))
+        TESTEXCEPTION("CPipe::Peek failed for the test list\n");
+
+    /* Check if we got any */
+    if(!BytesAvailable)
+    {
+        stringstream ss;
+
+        ss << "The --list command did not return any data for " << UnicodeToAscii(m_CurrentFile) << endl;
+        TESTEXCEPTION(ss.str());
+    }
+
+    /* Read the data */
+    m_ListBuffer = new char[BytesAvailable];
+
+    if(!Pipe.Read(m_ListBuffer, BytesAvailable, &Temp))
+        TESTEXCEPTION("CPipe::Read failed\n");
+
+    return BytesAvailable;
+}
+
+/**
+ * Gets the next test from m_ListBuffer, which was filled with information from the --list command.
+ *
+ * @return
+ * true if a next test was found, otherwise false.
+ */
+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())
+    {
+        try
+        {
+            while(GetNextTest())
+            {
+                /* If the user specified a test through the command line, check this here */
+                if(!Configuration.GetTest().empty() && Configuration.GetTest() != m_CurrentTest)
+                    continue;
+
+                {
+                    auto_ptr<CTestInfo> TestInfo(new CTestInfo());
+                    size_t UnderscorePosition;
+
+                    /* Build the command line */
+                    TestInfo->CommandLine = m_TestPath;
+                    TestInfo->CommandLine += m_CurrentFile;
+                    TestInfo->CommandLine += ' ';
+                    TestInfo->CommandLine += AsciiToUnicode(m_CurrentTest);
+
+                    /* Store the Module name */
+                    UnderscorePosition = m_CurrentFile.find_last_of('_');
+
+                    if(UnderscorePosition == m_CurrentFile.npos)
+                    {
+                        stringstream ss;
+
+                        ss << "Invalid test file name: " << UnicodeToAscii(m_CurrentFile) << endl;
+                        SSEXCEPTION;
+                    }
+
+                    TestInfo->Module = UnicodeToAscii(m_CurrentFile.substr(0, UnderscorePosition));
+
+                    /* Store the test */
+                    TestInfo->Test = m_CurrentTest;
+
+                    return TestInfo.release();
+                }
+            }
+        }
+        catch(CTestException& e)
+        {
+            stringstream ss;
+
+            ss << "An exception occurred trying to list tests for: " << UnicodeToAscii(m_CurrentFile) << endl;
+            StringOut(ss.str());
+            StringOut(e.GetMessage());
+            StringOut("\n");
+            m_CurrentFile.clear();
+            delete[] m_ListBuffer;
+        }
+    }
+
+    return NULL;
+}
+
+/**
+ * Runs a Wine test and captures the output
+ *
+ * @param TestInfo
+ * Pointer to a CTestInfo object containing information about the test.
+ * Will contain the test log afterwards if the user wants to submit data.
+ */
+void
+CWineTest::RunTest(CTestInfo* TestInfo)
+{
+    DWORD BytesAvailable;
+    stringstream ss, ssFinish;
+    DWORD StartTime;
+    float TotalTime;
+    string tailString;
+    CPipe Pipe;
+    char Buffer[1024];
+
+    ss << "Running Wine Test, Module: " << TestInfo->Module << ", Test: " << TestInfo->Test << endl;
+    StringOut(ss.str());
+
+    StartTime = GetTickCount();
+
+    try
+    {
+        /* Execute the test */
+        CPipedProcess Process(TestInfo->CommandLine, Pipe);
+
+        /* Receive all the data from the pipe */
+        while(Pipe.Read(Buffer, sizeof(Buffer) - 1, &BytesAvailable) && BytesAvailable)
+        {
+            /* Output text through StringOut, even while the test is still running */
+            Buffer[BytesAvailable] = 0;
+            tailString = StringOut(tailString.append(string(Buffer)), false);
+
+            if(Configuration.DoSubmit())
+                TestInfo->Log += Buffer;
+        }
+        if(GetLastError() != ERROR_BROKEN_PIPE)
+            TESTEXCEPTION("CPipe::Read failed for the test run\n");
+    }
+    catch(CTestException& e)
+    {
+        if(!tailString.empty())
+            StringOut(tailString);
+        tailString.clear();
+        StringOut(e.GetMessage());
+        TestInfo->Log += e.GetMessage();
+    }
+
+    /* Print what's left */
+    if(!tailString.empty())
+        StringOut(tailString);
+
+    TotalTime = ((float)GetTickCount() - StartTime)/1000;
+    ssFinish << "Test " << TestInfo->Test << " completed in ";
+    ssFinish << setprecision(2) << fixed << TotalTime << " seconds." << endl;
+    StringOut(ssFinish.str());
+    TestInfo->Log += ssFinish.str();
+}
+
+/**
+ * Interface to other classes for running all desired Wine tests.
+ */
+void
+CWineTest::Run()
+{
+    auto_ptr<CTestList> TestList;
+    auto_ptr<CWebService> WebService;
+    CTestInfo* TestInfo;
+    DWORD ErrorMode;
+
+    /* 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());
+
+    /* Disable error dialogs if we're running in non-interactive mode */
+    if(!Configuration.IsInteractive())
+        ErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS | SEM_NOGPFAULTERRORBOX);
+
+    /* Get information for each test to run */
+    while((TestInfo = TestList->GetNextTestInfo()) != 0)
+    {
+        auto_ptr<CTestInfo> TestInfoPtr(TestInfo);
+
+        RunTest(TestInfo);
+
+        if(Configuration.DoSubmit() && !TestInfo->Log.empty())
+            WebService->Submit("wine", TestInfo);
+
+        StringOut("\n\n");
+    }
+
+    /* We're done with all tests. Finish this run */
+    if(Configuration.DoSubmit())
+        WebService->Finish("wine");
+
+    /* Restore the original error mode */
+    if(!Configuration.IsInteractive())
+        SetErrorMode(ErrorMode);
+}