Path: blob/main_old/util/windows/test_utils_win.cpp
1693 views
//1// Copyright 2014 The ANGLE Project Authors. All rights reserved.2// Use of this source code is governed by a BSD-style license that can be3// found in the LICENSE file.4//56// test_utils_win.cpp: Implementation of OS-specific functions for Windows78#include "util/test_utils.h"910#include <aclapi.h>11#include <stdarg.h>12#include <versionhelpers.h>13#include <windows.h>14#include <array>15#include <iostream>16#include <vector>1718#include "anglebase/no_destructor.h"19#include "common/angleutils.h"2021namespace angle22{23namespace24{25struct ScopedPipe26{27~ScopedPipe()28{29closeReadHandle();30closeWriteHandle();31}32bool closeReadHandle()33{34if (readHandle)35{36if (::CloseHandle(readHandle) == FALSE)37{38std::cerr << "Error closing write handle: " << GetLastError();39return false;40}41readHandle = nullptr;42}4344return true;45}46bool closeWriteHandle()47{48if (writeHandle)49{50if (::CloseHandle(writeHandle) == FALSE)51{52std::cerr << "Error closing write handle: " << GetLastError();53return false;54}55writeHandle = nullptr;56}5758return true;59}6061bool valid() const { return readHandle != nullptr || writeHandle != nullptr; }6263bool initPipe(SECURITY_ATTRIBUTES *securityAttribs)64{65if (::CreatePipe(&readHandle, &writeHandle, securityAttribs, 0) == FALSE)66{67std::cerr << "Error creating pipe: " << GetLastError() << "\n";68return false;69}7071#if !defined(ANGLE_ENABLE_WINDOWS_UWP)72// Ensure the read handles to the pipes are not inherited.73if (::SetHandleInformation(readHandle, HANDLE_FLAG_INHERIT, 0) == FALSE)74{75std::cerr << "Error setting handle info on pipe: " << GetLastError() << "\n";76return false;77}78#endif // !defined(ANGLE_ENABLE_WINDOWS_UWP)7980return true;81}8283HANDLE readHandle = nullptr;84HANDLE writeHandle = nullptr;85};8687// Returns false on EOF or error.88void ReadFromFile(bool blocking, HANDLE handle, std::string *out)89{90char buffer[8192];91DWORD bytesRead = 0;9293while (true)94{95if (!blocking)96{97BOOL success = ::PeekNamedPipe(handle, nullptr, 0, nullptr, &bytesRead, nullptr);98if (success == FALSE || bytesRead == 0)99return;100}101102BOOL success = ::ReadFile(handle, buffer, sizeof(buffer), &bytesRead, nullptr);103if (success == FALSE || bytesRead == 0)104return;105106out->append(buffer, bytesRead);107}108109// unreachable.110}111112// Returns the Win32 last error code or ERROR_SUCCESS if the last error code is113// ERROR_FILE_NOT_FOUND or ERROR_PATH_NOT_FOUND. This is useful in cases where114// the absence of a file or path is a success condition (e.g., when attempting115// to delete an item in the filesystem).116bool ReturnSuccessOnNotFound()117{118const DWORD error_code = ::GetLastError();119return (error_code == ERROR_FILE_NOT_FOUND || error_code == ERROR_PATH_NOT_FOUND);120}121122// Job objects seems to have problems on the Chromium CI and Windows 7.123bool ShouldUseJobObjects()124{125#if defined(ANGLE_ENABLE_WINDOWS_UWP)126return false;127#else128return (::IsWindows10OrGreater());129#endif130}131132class WindowsProcess : public Process133{134public:135WindowsProcess(const std::vector<const char *> &commandLineArgs,136ProcessOutputCapture captureOutput)137{138mProcessInfo.hProcess = INVALID_HANDLE_VALUE;139mProcessInfo.hThread = INVALID_HANDLE_VALUE;140141std::vector<char> commandLineString;142for (const char *arg : commandLineArgs)143{144if (arg)145{146if (!commandLineString.empty())147{148commandLineString.push_back(' ');149}150commandLineString.insert(commandLineString.end(), arg, arg + strlen(arg));151}152}153commandLineString.push_back('\0');154155// Set the bInheritHandle flag so pipe handles are inherited.156SECURITY_ATTRIBUTES securityAttribs;157securityAttribs.nLength = sizeof(SECURITY_ATTRIBUTES);158securityAttribs.bInheritHandle = TRUE;159securityAttribs.lpSecurityDescriptor = nullptr;160161STARTUPINFOA startInfo = {};162163const bool captureStdout = captureOutput != ProcessOutputCapture::Nothing;164const bool captureStderr =165captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved ||166captureOutput == ProcessOutputCapture::StdoutAndStderrSeparately;167const bool pipeStderrToStdout =168captureOutput == ProcessOutputCapture::StdoutAndStderrInterleaved;169170// Create pipes for stdout and stderr.171startInfo.cb = sizeof(STARTUPINFOA);172startInfo.hStdInput = ::GetStdHandle(STD_INPUT_HANDLE);173if (captureStdout)174{175if (!mStdoutPipe.initPipe(&securityAttribs))176{177return;178}179startInfo.hStdOutput = mStdoutPipe.writeHandle;180}181else182{183startInfo.hStdOutput = ::GetStdHandle(STD_OUTPUT_HANDLE);184}185186if (pipeStderrToStdout)187{188startInfo.hStdError = startInfo.hStdOutput;189}190else if (captureStderr)191{192if (!mStderrPipe.initPipe(&securityAttribs))193{194return;195}196startInfo.hStdError = mStderrPipe.writeHandle;197}198else199{200startInfo.hStdError = ::GetStdHandle(STD_ERROR_HANDLE);201}202203#if !defined(ANGLE_ENABLE_WINDOWS_UWP)204if (captureStdout || captureStderr)205{206startInfo.dwFlags |= STARTF_USESTDHANDLES;207}208209if (ShouldUseJobObjects())210{211// Create job object. Job objects allow us to automatically force child processes to212// exit if the parent process is unexpectedly killed. This should prevent ghost213// processes from hanging around.214mJobHandle = ::CreateJobObjectA(nullptr, nullptr);215if (mJobHandle == NULL)216{217std::cerr << "Error creating job object: " << GetLastError() << "\n";218return;219}220221JOBOBJECT_EXTENDED_LIMIT_INFORMATION limitInfo = {};222limitInfo.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_KILL_ON_JOB_CLOSE;223if (::SetInformationJobObject(mJobHandle, JobObjectExtendedLimitInformation, &limitInfo,224sizeof(limitInfo)) == FALSE)225{226std::cerr << "Error setting job information: " << GetLastError() << "\n";227return;228}229}230#endif // !defined(ANGLE_ENABLE_WINDOWS_UWP)231232// Create the child process.233if (::CreateProcessA(nullptr, commandLineString.data(), nullptr, nullptr,234TRUE, // Handles are inherited.2350, nullptr, nullptr, &startInfo, &mProcessInfo) == FALSE)236{237std::cerr << "CreateProcessA Error code: " << GetLastError() << "\n";238return;239}240241#if !defined(ANGLE_ENABLE_WINDOWS_UWP)242if (mJobHandle != nullptr)243{244if (::AssignProcessToJobObject(mJobHandle, mProcessInfo.hProcess) == FALSE)245{246std::cerr << "AssignProcessToJobObject failed: " << GetLastError() << "\n";247return;248}249}250#endif // !defined(ANGLE_ENABLE_WINDOWS_UWP)251252// Close the write end of the pipes, so EOF can be generated when child exits.253if (!mStdoutPipe.closeWriteHandle() || !mStderrPipe.closeWriteHandle())254return;255256mStarted = true;257mTimer.start();258}259260~WindowsProcess() override261{262if (mProcessInfo.hProcess != INVALID_HANDLE_VALUE)263{264::CloseHandle(mProcessInfo.hProcess);265}266if (mProcessInfo.hThread != INVALID_HANDLE_VALUE)267{268::CloseHandle(mProcessInfo.hThread);269}270if (mJobHandle != nullptr)271{272::CloseHandle(mJobHandle);273}274}275276bool started() override { return mStarted; }277278bool finish() override279{280if (mStdoutPipe.valid())281{282ReadFromFile(true, mStdoutPipe.readHandle, &mStdout);283}284285if (mStderrPipe.valid())286{287ReadFromFile(true, mStderrPipe.readHandle, &mStderr);288}289290DWORD result = ::WaitForSingleObject(mProcessInfo.hProcess, INFINITE);291mTimer.stop();292return result == WAIT_OBJECT_0;293}294295bool finished() override296{297if (!mStarted)298return false;299300// Pipe stdin and stdout.301if (mStdoutPipe.valid())302{303ReadFromFile(false, mStdoutPipe.readHandle, &mStdout);304}305306if (mStderrPipe.valid())307{308ReadFromFile(false, mStderrPipe.readHandle, &mStderr);309}310311DWORD result = ::WaitForSingleObject(mProcessInfo.hProcess, 0);312if (result == WAIT_OBJECT_0)313{314mTimer.stop();315return true;316}317if (result == WAIT_TIMEOUT)318return false;319320mTimer.stop();321std::cerr << "Unexpected result from WaitForSingleObject: " << result322<< ". Last error: " << ::GetLastError() << "\n";323return false;324}325326int getExitCode() override327{328if (!mStarted)329return -1;330331if (mProcessInfo.hProcess == INVALID_HANDLE_VALUE)332return -1;333334DWORD exitCode = 0;335if (::GetExitCodeProcess(mProcessInfo.hProcess, &exitCode) == FALSE)336return -1;337338return static_cast<int>(exitCode);339}340341bool kill() override342{343if (!mStarted)344return true;345346HANDLE newHandle;347if (::DuplicateHandle(::GetCurrentProcess(), mProcessInfo.hProcess, ::GetCurrentProcess(),348&newHandle, PROCESS_ALL_ACCESS, false,349DUPLICATE_CLOSE_SOURCE) == FALSE)350{351std::cerr << "Error getting permission to terminate process: " << ::GetLastError()352<< "\n";353return false;354}355mProcessInfo.hProcess = newHandle;356357#if !defined(ANGLE_ENABLE_WINDOWS_UWP)358if (::TerminateThread(mProcessInfo.hThread, 1) == FALSE)359{360std::cerr << "TerminateThread failed: " << GetLastError() << "\n";361return false;362}363#endif // !defined(ANGLE_ENABLE_WINDOWS_UWP)364365if (::TerminateProcess(mProcessInfo.hProcess, 1) == FALSE)366{367std::cerr << "TerminateProcess failed: " << GetLastError() << "\n";368return false;369}370371mStarted = false;372mTimer.stop();373return true;374}375376private:377bool mStarted = false;378ScopedPipe mStdoutPipe;379ScopedPipe mStderrPipe;380PROCESS_INFORMATION mProcessInfo = {};381HANDLE mJobHandle = nullptr;382};383} // namespace384385void Sleep(unsigned int milliseconds)386{387::Sleep(static_cast<DWORD>(milliseconds));388}389390void WriteDebugMessage(const char *format, ...)391{392va_list args;393va_start(args, format);394int size = vsnprintf(nullptr, 0, format, args);395va_end(args);396397std::vector<char> buffer(size + 2);398va_start(args, format);399vsnprintf(buffer.data(), size + 1, format, args);400va_end(args);401402OutputDebugStringA(buffer.data());403}404405Process *LaunchProcess(const std::vector<const char *> &args, ProcessOutputCapture captureOutput)406{407return new WindowsProcess(args, captureOutput);408}409410bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen)411{412DWORD pathLen = ::GetTempPathA(maxDirNameLen, tempDirOut);413// Strip last path character if present.414if (pathLen > 0)415{416size_t lastChar = strlen(tempDirOut) - 1;417if (tempDirOut[lastChar] == '\\')418{419tempDirOut[lastChar] = 0;420}421}422return (pathLen < MAX_PATH && pathLen > 0);423}424425bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen)426{427char fileName[MAX_PATH + 1];428if (::GetTempFileNameA(dir, "ANGLE", 0, fileName) == 0)429return false;430431strncpy(tempFileNameOut, fileName, maxFileNameLen);432return true;433}434435bool DeleteFile(const char *path)436{437if (strlen(path) >= MAX_PATH)438return false;439440const DWORD attr = ::GetFileAttributesA(path);441// Report success if the file or path does not exist.442if (attr == INVALID_FILE_ATTRIBUTES)443{444return ReturnSuccessOnNotFound();445}446447// Clear the read-only bit if it is set.448if ((attr & FILE_ATTRIBUTE_READONLY) &&449!::SetFileAttributesA(path, attr & ~FILE_ATTRIBUTE_READONLY))450{451// It's possible for |path| to be gone now under a race with other deleters.452return ReturnSuccessOnNotFound();453}454455// We don't handle directories right now.456if ((attr & FILE_ATTRIBUTE_DIRECTORY) != 0)457{458return false;459}460461return !!::DeleteFileA(path) ? true : ReturnSuccessOnNotFound();462}463464const char *GetNativeEGLLibraryNameWithExtension()465{466return "libEGL.dll";467}468} // namespace angle469470471