Edit

kc3-lang/angle/util/windows/test_utils_win.cpp

Branch :

  • Show log

    Commit

  • Author : Shahbaz Youssefi
    Date : 2021-06-21 09:59:21
    Hash : 72d2bd0c
    Message : Allow capturing process stdout and stderr interleaved The test utils are enhanced to allow redirecting stderr to stdout. This is in preparation for a change that makes the test runner capture stderr together with stdout. Currently, on failure logs originating from UNIMPLEMENTED() and other such macros are not captured. Bug: angleproject:6077 Change-Id: I7a3c6c4732a59dac3ff0cc20a7835d5ed6f0f22e Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/2976183 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org>

  • util/windows/test_utils_win.cpp
  • //
    // Copyright 2014 The ANGLE Project Authors. All rights reserved.
    // Use of this source code is governed by a BSD-style license that can be
    // found in the LICENSE file.
    //
    
    // test_utils_win.cpp: Implementation of OS-specific functions for Windows
    
    #include "util/test_utils.h"
    
    #include <aclapi.h>
    #include <stdarg.h>
    #include <versionhelpers.h>
    #include <windows.h>
    #include <array>
    #include <iostream>
    #include <vector>
    
    #include "anglebase/no_destructor.h"
    #include "common/angleutils.h"
    
    namespace angle
    {
    namespace
    {
    struct ScopedPipe
    {
        ~ScopedPipe()
        {
            closeReadHandle();
            closeWriteHandle();
        }
        bool closeReadHandle()
        {
            if (readHandle)
            {
                if (::CloseHandle(readHandle) == FALSE)
                {
                    std::cerr << "Error closing write handle: " << GetLastError();
                    return false;
                }
                readHandle = nullptr;
            }
    
            return true;
        }
        bool closeWriteHandle()
        {
            if (writeHandle)
            {
                if (::CloseHandle(writeHandle) == FALSE)
                {
                    std::cerr << "Error closing write handle: " << GetLastError();
                    return false;
                }
                writeHandle = nullptr;
            }
    
            return true;
        }
    
        bool valid() const { return readHandle != nullptr || writeHandle != nullptr; }
    
        bool initPipe(SECURITY_ATTRIBUTES *securityAttribs)
        {
            if (::CreatePipe(&readHandle, &writeHandle, securityAttribs, 0) == FALSE)
            {
                std::cerr << "Error creating pipe: " << GetLastError() << "\n";
                return false;
            }
    
    #if !defined(ANGLE_ENABLE_WINDOWS_UWP)
            // Ensure the read handles to the pipes are not inherited.
            if (::SetHandleInformation(readHandle, HANDLE_FLAG_INHERIT, 0) == FALSE)
            {
                std::cerr << "Error setting handle info on pipe: " << GetLastError() << "\n";
                return false;
            }
    #endif  // !defined(ANGLE_ENABLE_WINDOWS_UWP)
    
            return true;
        }
    
        HANDLE readHandle  = nullptr;
        HANDLE writeHandle = nullptr;
    };
    
    // Returns false on EOF or error.
    void ReadFromFile(bool blocking, HANDLE handle, std::string *out)
    {
        char buffer[8192];
        DWORD bytesRead = 0;
    
        while (true)
        {
            if (!blocking)
            {
                BOOL success = ::PeekNamedPipe(handle, nullptr, 0, nullptr, &bytesRead, nullptr);
                if (success == FALSE || bytesRead == 0)
                    return;
            }
    
            BOOL success = ::ReadFile(handle, buffer, sizeof(buffer), &bytesRead, nullptr);
            if (success == FALSE || bytesRead == 0)
                return;
    
            out->append(buffer, bytesRead);
        }
    
        // unreachable.
    }
    
    // Returns the Win32 last error code or ERROR_SUCCESS if the last error code is
    // ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND. This is useful in cases where
    // the absence of a file or path is a success condition (e.g., when attempting
    // to delete an item in the filesystem).
    bool ReturnSuccessOnNotFound()
    {
        const DWORD error_code = ::GetLastError();
        return (error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_PATH_NOT_FOUND);
    }
    
    // Job objects seems to have problems on the Chromium CI and Windows 7.
    bool ShouldUseJobObjects()
    {
    #if defined(ANGLE_ENABLE_WINDOWS_UWP)
        return false;
    #else
        return (::IsWindows10OrGreater());
    #endif
    }
    
    class WindowsProcess : public Process
    {
      public:
        WindowsProcess(const std::vector<const char *> &commandLineArgs,
                       ProcessOutputCapture captureOutput)
        {
            mProcessInfo.hProcess = INVALID_HANDLE_VALUE;
            mProcessInfo.hThread  = INVALID_HANDLE_VALUE;
    
            std::vector<char> commandLineString;
            for (const char *arg : commandLineArgs)
            {
                if (arg)
                {
                    if (!commandLineString.empty())
                    {
                        commandLineString.push_back(' ');
                    }
                    commandLineString.insert(commandLineString.end(), arg, arg + strlen(arg));
                }
            }
            commandLineString.push_back('\0');
    
            // Set the bInheritHandle flag so pipe handles are inherited.
            SECURITY_ATTRIBUTES securityAttribs;
            securityAttribs.nLength              = sizeof(SECURITY_ATTRIBUTES);
            securityAttribs.bInheritHandle       = TRUE;
            securityAttribs.lpSecurityDescriptor = nullptr;
    
            STARTUPINFOA startInfo = {};
    
            const bool captureStdout = captureOutput != ProcessOutputCapture::Nothing;
            const bool captureStderr =
                captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved ||
                captureOutput == ProcessOutputCapture::StdoutAndStderrSeparately;
            const bool pipeStderrToStdout =
                captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved;
    
            // Create pipes for stdout and stderr.
            startInfo.cb        = sizeof(STARTUPINFOA);
            startInfo.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);
            if (captureStdout)
            {
                if (!mStdoutPipe.initPipe(&securityAttribs))
                {
                    return;
                }
                startInfo.hStdOutput = mStdoutPipe.writeHandle;
            }
            else
            {
                startInfo.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);
            }
    
            if (pipeStderrToStdout)
            {
                startInfo.hStdError = startInfo.hStdOutput;
            }
            else if (captureStderr)
            {
                if (!mStderrPipe.initPipe(&securityAttribs))
                {
                    return;
                }
                startInfo.hStdError = mStderrPipe.writeHandle;
            }
            else
            {
                startInfo.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);
            }
    
    #if !defined(ANGLE_ENABLE_WINDOWS_UWP)
            if (captureStdout || captureStderr)
            {
                startInfo.dwFlags |= STARTF_USESTDHANDLES;
            }
    
            if (ShouldUseJobObjects())
            {
                // Create job object. Job objects allow us to automatically force child processes to
                // exit if the parent process is unexpectedly killed. This should prevent ghost
                // processes from hanging around.
                mJobHandle = ::CreateJobObjectA(nullptr, nullptr);
                if (mJobHandle == NULL)
                {
                    std::cerr << "Error creating job object: " << GetLastError() << "\n";
                    return;
                }
    
                JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = {};
                limitInfo.BasicLimitInformation.LimitFlags     = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;
                if (::SetInformationJobObject(mJobHandle, JobObjectExtendedLimitInformation, &limitInfo,
                                              sizeof(limitInfo)) == FALSE)
                {
                    std::cerr << "Error setting job information: " << GetLastError() << "\n";
                    return;
                }
            }
    #endif  // !defined(ANGLE_ENABLE_WINDOWS_UWP)
    
            // Create the child process.
            if (::CreateProcessA(nullptr, commandLineString.data(), nullptr, nullptr,
                                 TRUE,  // Handles are inherited.
                                 0, nullptr, nullptr, &startInfo, &mProcessInfo) == FALSE)
            {
                std::cerr << "CreateProcessA Error code: " << GetLastError() << "\n";
                return;
            }
    
    #if !defined(ANGLE_ENABLE_WINDOWS_UWP)
            if (mJobHandle != nullptr)
            {
                if (::AssignProcessToJobObject(mJobHandle, mProcessInfo.hProcess) == FALSE)
                {
                    std::cerr << "AssignProcessToJobObject failed: " << GetLastError() << "\n";
                    return;
                }
            }
    #endif  // !defined(ANGLE_ENABLE_WINDOWS_UWP)
    
            // Close the write end of the pipes, so EOF can be generated when child exits.
            if (!mStdoutPipe.closeWriteHandle() || !mStderrPipe.closeWriteHandle())
                return;
    
            mStarted = true;
            mTimer.start();
        }
    
        ~WindowsProcess() override
        {
            if (mProcessInfo.hProcess != INVALID_HANDLE_VALUE)
            {
                ::CloseHandle(mProcessInfo.hProcess);
            }
            if (mProcessInfo.hThread != INVALID_HANDLE_VALUE)
            {
                ::CloseHandle(mProcessInfo.hThread);
            }
            if (mJobHandle != nullptr)
            {
                ::CloseHandle(mJobHandle);
            }
        }
    
        bool started() override { return mStarted; }
    
        bool finish() override
        {
            if (mStdoutPipe.valid())
            {
                ReadFromFile(true, mStdoutPipe.readHandle, &mStdout);
            }
    
            if (mStderrPipe.valid())
            {
                ReadFromFile(true, mStderrPipe.readHandle, &mStderr);
            }
    
            DWORD result = ::WaitForSingleObject(mProcessInfo.hProcess, INFINITE);
            mTimer.stop();
            return result == WAIT_OBJECT_0;
        }
    
        bool finished() override
        {
            if (!mStarted)
                return false;
    
            // Pipe stdin and stdout.
            if (mStdoutPipe.valid())
            {
                ReadFromFile(false, mStdoutPipe.readHandle, &mStdout);
            }
    
            if (mStderrPipe.valid())
            {
                ReadFromFile(false, mStderrPipe.readHandle, &mStderr);
            }
    
            DWORD result = ::WaitForSingleObject(mProcessInfo.hProcess, 0);
            if (result == WAIT_OBJECT_0)
            {
                mTimer.stop();
                return true;
            }
            if (result == WAIT_TIMEOUT)
                return false;
    
            mTimer.stop();
            std::cerr << "Unexpected result from WaitForSingleObject: " << result
                      << ". Last error: " << ::GetLastError() << "\n";
            return false;
        }
    
        int getExitCode() override
        {
            if (!mStarted)
                return -1;
    
            if (mProcessInfo.hProcess == INVALID_HANDLE_VALUE)
                return -1;
    
            DWORD exitCode = 0;
            if (::GetExitCodeProcess(mProcessInfo.hProcess, &exitCode) == FALSE)
                return -1;
    
            return static_cast<int>(exitCode);
        }
    
        bool kill() override
        {
            if (!mStarted)
                return true;
    
            HANDLE newHandle;
            if (::DuplicateHandle(::GetCurrentProcess(), mProcessInfo.hProcess, ::GetCurrentProcess(),
                                  &newHandle, PROCESS_ALL_ACCESS, false,
                                  DUPLICATE_CLOSE_SOURCE) == FALSE)
            {
                std::cerr << "Error getting permission to terminate process: " << ::GetLastError()
                          << "\n";
                return false;
            }
            mProcessInfo.hProcess = newHandle;
    
    #if !defined(ANGLE_ENABLE_WINDOWS_UWP)
            if (::TerminateThread(mProcessInfo.hThread, 1) == FALSE)
            {
                std::cerr << "TerminateThread failed: " << GetLastError() << "\n";
                return false;
            }
    #endif  // !defined(ANGLE_ENABLE_WINDOWS_UWP)
    
            if (::TerminateProcess(mProcessInfo.hProcess, 1) == FALSE)
            {
                std::cerr << "TerminateProcess failed: " << GetLastError() << "\n";
                return false;
            }
    
            mStarted = false;
            mTimer.stop();
            return true;
        }
    
      private:
        bool mStarted = false;
        ScopedPipe mStdoutPipe;
        ScopedPipe mStderrPipe;
        PROCESS_INFORMATION mProcessInfo = {};
        HANDLE mJobHandle                = nullptr;
    };
    }  // namespace
    
    void Sleep(unsigned int milliseconds)
    {
        ::Sleep(static_cast<DWORD>(milliseconds));
    }
    
    void WriteDebugMessage(const char *format, ...)
    {
        va_list args;
        va_start(args, format);
        int size = vsnprintf(nullptr, 0, format, args);
        va_end(args);
    
        std::vector<char> buffer(size + 2);
        va_start(args, format);
        vsnprintf(buffer.data(), size + 1, format, args);
        va_end(args);
    
        OutputDebugStringA(buffer.data());
    }
    
    Process *LaunchProcess(const std::vector<const char *> &args, ProcessOutputCapture captureOutput)
    {
        return new WindowsProcess(args, captureOutput);
    }
    
    bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen)
    {
        DWORD pathLen = ::GetTempPathA(maxDirNameLen, tempDirOut);
        // Strip last path character if present.
        if (pathLen > 0)
        {
            size_t lastChar = strlen(tempDirOut) - 1;
            if (tempDirOut[lastChar] == '\\')
            {
                tempDirOut[lastChar] = 0;
            }
        }
        return (pathLen < MAX_PATH && pathLen > 0);
    }
    
    bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen)
    {
        char fileName[MAX_PATH + 1];
        if (::GetTempFileNameA(dir, "ANGLE", 0, fileName) == 0)
            return false;
    
        strncpy(tempFileNameOut, fileName, maxFileNameLen);
        return true;
    }
    
    bool DeleteFile(const char *path)
    {
        if (strlen(path) >= MAX_PATH)
            return false;
    
        const DWORD attr = ::GetFileAttributesA(path);
        // Report success if the file or path does not exist.
        if (attr == INVALID_FILE_ATTRIBUTES)
        {
            return ReturnSuccessOnNotFound();
        }
    
        // Clear the read-only bit if it is set.
        if ((attr & FILE_ATTRIBUTE_READONLY) &&
            !::SetFileAttributesA(path, attr & ~FILE_ATTRIBUTE_READONLY))
        {
            // It's possible for |path| to be gone now under a race with other deleters.
            return ReturnSuccessOnNotFound();
        }
    
        // We don't handle directories right now.
        if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0)
        {
            return false;
        }
    
        return !!::DeleteFileA(path) ? true : ReturnSuccessOnNotFound();
    }
    
    const char *GetNativeEGLLibraryNameWithExtension()
    {
        return "libEGL.dll";
    }
    }  // namespace angle