From 5fcfcea4a9379633a83a67fc1d94938cb31f2a9c Mon Sep 17 00:00:00 2001 From: Jamie Madill Date: Wed, 9 Oct 2019 10:26:25 -0400 Subject: [PATCH] Add more test_utils functions. Includes methods for creating temporary files, deleting files, and reading files into a string. Also renames GetPathSeparator to mention it's only used for environment variables. Includes a new virtual type angle::Process that will be used to implement cross-platform async Process launching for tests. Also includes a way to specify a custom crash handler callback. Also adds a few unit tests for the new functionality. They are disabled on Android because the functions are not needed by the new test runner. Bug: angleproject:3162 Change-Id: I3e2c2e9837608884c98379fa0f78c9ffbe158d73 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1821940 Commit-Queue: Jamie Madill Reviewed-by: Jonah Ryan-Davis --- src/common/system_utils.cpp | 2 +- src/common/system_utils.h | 2 +- src/common/system_utils_posix.cpp | 158 +------- src/common/system_utils_unittest.cpp | 79 ---- src/common/system_utils_win.cpp | 164 +------- src/tests/BUILD.gn | 6 +- src/tests/angle_unittests.gni | 9 +- src/tests/perf_tests/glmark2Benchmark.cpp | 11 +- src/tests/test_utils/ANGLETest.cpp | 12 +- util/posix/crash_handler_posix.cpp | 4 +- util/posix/test_utils_posix.cpp | 283 +++++++++++++ util/shader_utils.cpp | 47 +-- util/test_utils.cpp | 97 +++++ util/test_utils.h | 89 ++++- util/test_utils_unittest.cpp | 184 +++++++++ .../test_utils_unittest_helper.cpp | 6 +- .../test_utils_unittest_helper.h | 0 util/util.gni | 1 + util/windows/test_utils_win.cpp | 378 +++++++++++++++++- util/windows/win32/test_utils_win32.cpp | 9 +- 20 files changed, 1088 insertions(+), 453 deletions(-) create mode 100644 util/test_utils.cpp create mode 100644 util/test_utils_unittest.cpp rename src/common/system_utils_unittest_helper.cpp => util/test_utils_unittest_helper.cpp (94%) rename src/common/system_utils_unittest_helper.h => util/test_utils_unittest_helper.h (100%) diff --git a/src/common/system_utils.cpp b/src/common/system_utils.cpp index 257fc42c2..54693b3d5 100644 --- a/src/common/system_utils.cpp +++ b/src/common/system_utils.cpp @@ -22,7 +22,7 @@ bool PrependPathToEnvironmentVar(const char *variableName, const char *path) else { buf = path; - buf += GetPathSeparator(); + buf += GetPathSeparatorForEnvironmentVar(); buf += oldValue; newValue = buf.c_str(); } diff --git a/src/common/system_utils.h b/src/common/system_utils.h index 1191fac22..ccb4e9df8 100644 --- a/src/common/system_utils.h +++ b/src/common/system_utils.h @@ -22,7 +22,7 @@ bool SetCWD(const char *dirName); bool SetEnvironmentVar(const char *variableName, const char *value); bool UnsetEnvironmentVar(const char *variableName); std::string GetEnvironmentVar(const char *variableName); -const char *GetPathSeparator(); +const char *GetPathSeparatorForEnvironmentVar(); bool PrependPathToEnvironmentVar(const char *variableName, const char *path); bool IsDirectory(const char *filename); diff --git a/src/common/system_utils_posix.cpp b/src/common/system_utils_posix.cpp index c8cb68a92..d71a07324 100644 --- a/src/common/system_utils_posix.cpp +++ b/src/common/system_utils_posix.cpp @@ -18,56 +18,6 @@ namespace angle { - -namespace -{ -struct ScopedPipe -{ - ~ScopedPipe() - { - closeEndPoint(0); - closeEndPoint(1); - } - void closeEndPoint(int index) - { - if (fds[index] >= 0) - { - close(fds[index]); - fds[index] = -1; - } - } - int fds[2] = { - -1, - -1, - }; -}; - -void ReadEntireFile(int fd, std::string *out) -{ - out->clear(); - - while (true) - { - char buffer[256]; - ssize_t bytesRead = read(fd, buffer, sizeof(buffer)); - - // If interrupted, retry. - if (bytesRead < 0 && errno == EINTR) - { - continue; - } - - // If failed, or nothing to read, we are done. - if (bytesRead <= 0) - { - break; - } - - out->append(buffer, bytesRead); - } -} -} // anonymous namespace - Optional GetCWD() { std::array pathBuf; @@ -100,117 +50,11 @@ std::string GetEnvironmentVar(const char *variableName) return (value == nullptr ? std::string() : std::string(value)); } -const char *GetPathSeparator() +const char *GetPathSeparatorForEnvironmentVar() { return ":"; } -bool RunApp(const std::vector &args, - std::string *stdoutOut, - std::string *stderrOut, - int *exitCodeOut) -{ - if (args.size() == 0 || args.back() != nullptr) - { - return false; - } - - ScopedPipe stdoutPipe; - ScopedPipe stderrPipe; - - // Create pipes for stdout and stderr. - if (stdoutOut && pipe(stdoutPipe.fds) != 0) - { - return false; - } - if (stderrOut && pipe(stderrPipe.fds) != 0) - { - return false; - } - - pid_t pid = fork(); - if (pid < 0) - { - return false; - } - - if (pid == 0) - { - // Child. Execute the application. - - // Redirect stdout and stderr to the pipe fds. - if (stdoutOut) - { - if (dup2(stdoutPipe.fds[1], STDOUT_FILENO) < 0) - { - _exit(errno); - } - } - if (stderrOut) - { - if (dup2(stderrPipe.fds[1], STDERR_FILENO) < 0) - { - _exit(errno); - } - } - - // Execute the application, which doesn't return unless failed. Note: execv takes argv as - // `char * const *` for historical reasons. It is safe to const_cast it: - // - // http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html - // - // > The statement about argv[] and envp[] being constants is included to make explicit to - // future writers of language bindings that these objects are completely constant. Due to a - // limitation of the ISO C standard, it is not possible to state that idea in standard C. - // Specifying two levels of const- qualification for the argv[] and envp[] parameters for - // the exec functions may seem to be the natural choice, given that these functions do not - // modify either the array of pointers or the characters to which the function points, but - // this would disallow existing correct code. Instead, only the array of pointers is noted - // as constant. - execv(args[0], const_cast(args.data())); - _exit(errno); - } - - // Parent. Read child output from the pipes and clean it up. - - // Close the write end of the pipes, so EOF can be generated when child exits. - stdoutPipe.closeEndPoint(1); - stderrPipe.closeEndPoint(1); - - // Read back the output of the child. - if (stdoutOut) - { - ReadEntireFile(stdoutPipe.fds[0], stdoutOut); - } - if (stderrOut) - { - ReadEntireFile(stderrPipe.fds[0], stderrOut); - } - - // Cleanup the child. - int status = 0; - do - { - pid_t changedPid = waitpid(pid, &status, 0); - if (changedPid < 0 && errno == EINTR) - { - continue; - } - if (changedPid < 0) - { - return false; - } - } while (!WIFEXITED(status) && !WIFSIGNALED(status)); - - // Retrieve the error code. - if (exitCodeOut) - { - *exitCodeOut = WEXITSTATUS(status); - } - - return true; -} - class PosixLibrary : public Library { public: diff --git a/src/common/system_utils_unittest.cpp b/src/common/system_utils_unittest.cpp index 7a5948a49..7b189b957 100644 --- a/src/common/system_utils_unittest.cpp +++ b/src/common/system_utils_unittest.cpp @@ -9,46 +9,11 @@ #include "gtest/gtest.h" #include "common/system_utils.h" -#include "common/system_utils_unittest_helper.h" using namespace angle; namespace { -#if defined(ANGLE_PLATFORM_WINDOWS) -constexpr char kRunAppHelperExecutable[] = "angle_unittests_helper.exe"; -#else -constexpr char kRunAppHelperExecutable[] = "angle_unittests_helper"; -#endif - -// Transforms various line endings into C/Unix line endings: -// -// - A\nB -> A\nB -// - A\rB -> A\nB -// - A\r\nB -> A\nB -std::string NormalizeNewLines(const std::string &str) -{ - std::string result; - - for (size_t i = 0; i < str.size(); ++i) - { - if (str[i] == '\r') - { - if (i + 1 < str.size() && str[i + 1] == '\n') - { - ++i; - } - result += '\n'; - } - else - { - result += str[i]; - } - } - - return result; -} - // Test getting the executable path TEST(SystemUtils, ExecutablePath) { @@ -95,48 +60,4 @@ TEST(SystemUtils, Environment) readback = GetEnvironmentVar(kEnvVarName); EXPECT_EQ("", readback); } - -// Test running an external application and receiving its output -TEST(SystemUtils, RunApp) -{ -#if defined(ANGLE_PLATFORM_ANDROID) - // TODO: android support. http://anglebug.com/3125 - return; -#endif - -#if defined(ANGLE_PLATFORM_FUCHSIA) - // TODO: fuchsia support. http://anglebug.com/3161 - return; -#endif - - std::string executablePath = GetExecutableDirectory(); - EXPECT_NE(executablePath, ""); - executablePath += "/"; - executablePath += kRunAppHelperExecutable; - - std::vector args = {executablePath.c_str(), kRunAppTestArg1, kRunAppTestArg2, - nullptr}; - - std::string stdoutOutput; - std::string stderrOutput; - int exitCode = EXIT_FAILURE; - - // Test that the application can be executed. - bool ranApp = RunApp(args, &stdoutOutput, &stderrOutput, &exitCode); - EXPECT_TRUE(ranApp); - EXPECT_EQ(kRunAppTestStdout, NormalizeNewLines(stdoutOutput)); - EXPECT_EQ(kRunAppTestStderr, NormalizeNewLines(stderrOutput)); - EXPECT_EQ(EXIT_SUCCESS, exitCode); - - // Test that environment variables reach the cild. - bool setEnvDone = SetEnvironmentVar(kRunAppTestEnvVarName, kRunAppTestEnvVarValue); - EXPECT_TRUE(setEnvDone); - - ranApp = RunApp(args, &stdoutOutput, &stderrOutput, &exitCode); - EXPECT_TRUE(ranApp); - EXPECT_EQ("", stdoutOutput); - EXPECT_EQ(kRunAppTestEnvVarValue, NormalizeNewLines(stderrOutput)); - EXPECT_EQ(EXIT_SUCCESS, exitCode); -} - } // anonymous namespace diff --git a/src/common/system_utils_win.cpp b/src/common/system_utils_win.cpp index 6aa196ce7..48e26c96d 100644 --- a/src/common/system_utils_win.cpp +++ b/src/common/system_utils_win.cpp @@ -15,56 +15,6 @@ namespace angle { -namespace -{ -struct ScopedPipe -{ - ~ScopedPipe() - { - closeReadHandle(); - closeWriteHandle(); - } - void closeReadHandle() - { - if (readHandle) - { - CloseHandle(readHandle); - readHandle = nullptr; - } - } - void closeWriteHandle() - { - if (writeHandle) - { - CloseHandle(writeHandle); - writeHandle = nullptr; - } - } - HANDLE readHandle = nullptr; - HANDLE writeHandle = nullptr; -}; - -void ReadEntireFile(HANDLE handle, std::string *out) -{ - out->clear(); - - while (true) - { - char buffer[256]; - DWORD bytesRead; - - BOOL success = ReadFile(handle, buffer, sizeof(buffer), &bytesRead, nullptr); - - if (!success || bytesRead == 0) - { - break; - } - - out->append(buffer, bytesRead); - } -} -} // anonymous namespace - std::string GetExecutablePath() { std::array executableFileBuf; @@ -126,7 +76,7 @@ std::string GetEnvironmentVar(const char *variableName) } } -const char *GetPathSeparator() +const char *GetPathSeparatorForEnvironmentVar() { return ";"; } @@ -142,117 +92,6 @@ double GetCurrentTime() return static_cast(curTime.QuadPart) / frequency.QuadPart; } -bool RunApp(const std::vector &args, - std::string *stdoutOut, - std::string *stderrOut, - int *exitCodeOut) -{ - ScopedPipe stdoutPipe; - ScopedPipe stderrPipe; - - SECURITY_ATTRIBUTES sa_attr; - // Set the bInheritHandle flag so pipe handles are inherited. - sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES); - sa_attr.bInheritHandle = TRUE; - sa_attr.lpSecurityDescriptor = nullptr; - - // Create pipes for stdout and stderr. Ensure the read handles to the pipes are not inherited. - if (stdoutOut && !CreatePipe(&stdoutPipe.readHandle, &stdoutPipe.writeHandle, &sa_attr, 0) && - !SetHandleInformation(stdoutPipe.readHandle, HANDLE_FLAG_INHERIT, 0)) - { - return false; - } - if (stderrOut && !CreatePipe(&stderrPipe.readHandle, &stderrPipe.writeHandle, &sa_attr, 0) && - !SetHandleInformation(stderrPipe.readHandle, HANDLE_FLAG_INHERIT, 0)) - { - return false; - } - - // Concat the nicely separated arguments into one string so the application has to reparse it. - // We don't support quotation and spaces in arguments currently. - std::vector commandLineString; - for (const char *arg : args) - { - if (arg) - { - if (!commandLineString.empty()) - { - commandLineString.push_back(' '); - } - commandLineString.insert(commandLineString.end(), arg, arg + strlen(arg)); - } - } - commandLineString.push_back('\0'); - - STARTUPINFOA startInfo = {}; - - startInfo.cb = sizeof(STARTUPINFOA); - startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE); - if (stdoutOut) - { - startInfo.hStdOutput = stdoutPipe.writeHandle; - } - else - { - startInfo.hStdError = GetStdHandle(STD_OUTPUT_HANDLE); - } - if (stderrOut) - { - startInfo.hStdError = stderrPipe.writeHandle; - } - else - { - startInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE); - } - - if (stderrOut || stdoutOut) - { - startInfo.dwFlags |= STARTF_USESTDHANDLES; - } - - // Create the child process. - PROCESS_INFORMATION processInfo = {}; - if (!CreateProcessA(nullptr, commandLineString.data(), nullptr, nullptr, - TRUE, // Handles are inherited. - 0, nullptr, nullptr, &startInfo, &processInfo)) - { - return false; - } - - // Close the write end of the pipes, so EOF can be generated when child exits. - stdoutPipe.closeWriteHandle(); - stderrPipe.closeWriteHandle(); - - // Read back the output of the child. - if (stdoutOut) - { - ReadEntireFile(stdoutPipe.readHandle, stdoutOut); - } - if (stderrOut) - { - ReadEntireFile(stderrPipe.readHandle, stderrOut); - } - - // Cleanup the child. - bool success = WaitForSingleObject(processInfo.hProcess, INFINITE) == WAIT_OBJECT_0; - - if (success) - { - DWORD exitCode = 0; - success = GetExitCodeProcess(processInfo.hProcess, &exitCode); - - if (success) - { - *exitCodeOut = static_cast(exitCode); - } - } - - CloseHandle(processInfo.hProcess); - CloseHandle(processInfo.hThread); - - return success; -} - class Win32Library : public Library { public: @@ -326,4 +165,5 @@ void BreakDebugger() { __debugbreak(); } + } // namespace angle diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index 1ac3b58f2..d0d5dec5b 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -45,8 +45,8 @@ if (!build_with_chromium) { } } -angle_executable("angle_unittests_helper") { - sources = angle_unittests_helper_sources +angle_executable("test_utils_unittest_helper") { + sources = test_utils_unittest_helper_sources deps = [ "${angle_root}:angle_common", @@ -104,7 +104,7 @@ angle_test("angle_unittests") { # SystemUtils.RunApp, the only unittest using a helper binary, is not supported on these # platforms yet. data_deps = [ - ":angle_unittests_helper", + ":test_utils_unittest_helper", ] } } diff --git a/src/tests/angle_unittests.gni b/src/tests/angle_unittests.gni index b1686cc84..d6a86a37e 100644 --- a/src/tests/angle_unittests.gni +++ b/src/tests/angle_unittests.gni @@ -15,7 +15,6 @@ angle_unittests_sources = [ "../common/matrix_utils_unittest.cpp", "../common/string_utils_unittest.cpp", "../common/system_utils_unittest.cpp", - "../common/system_utils_unittest_helper.h", "../common/utilities_unittest.cpp", "../common/vector_utils_unittest.cpp", "../feature_support_util/feature_support_util_unittest.cpp", @@ -130,6 +129,8 @@ angle_unittests_sources = [ "../tests/test_utils/ShaderCompileTreeTest.h", "../tests/test_utils/ShaderCompileTreeTest.cpp", "../tests/test_utils/ShaderExtensionTest.h", + "../../util/test_utils_unittest.cpp", + "../../util/test_utils_unittest_helper.h", ] # TODO(jmadill): should probably call this windows sources @@ -138,9 +139,9 @@ angle_unittests_hlsl_sources = [ "../tests/compiler_tests/UnrollFlatten_test.cpp", ] -angle_unittests_helper_sources = [ - "../common/system_utils_unittest_helper.cpp", - "../common/system_utils_unittest_helper.h", +test_utils_unittest_helper_sources = [ + "../../util/test_utils_unittest_helper.cpp", + "../../util/test_utils_unittest_helper.h", ] if (is_android) { diff --git a/src/tests/perf_tests/glmark2Benchmark.cpp b/src/tests/perf_tests/glmark2Benchmark.cpp index 2d5e46388..174fcc673 100644 --- a/src/tests/perf_tests/glmark2Benchmark.cpp +++ b/src/tests/perf_tests/glmark2Benchmark.cpp @@ -151,10 +151,9 @@ class GLMark2Benchmark : public testing::TestWithParamstarted()); + ASSERT_TRUE(process->finish()); // Restore the current working directory for the next tests. if (cwd.valid()) @@ -162,11 +161,11 @@ class GLMark2Benchmark : public testing::TestWithParamgetExitCode()); if (!OneFrame()) { + std::string output = process->getStdout(); parseOutput(output, benchmarkName, completeRun); } } diff --git a/src/tests/test_utils/ANGLETest.cpp b/src/tests/test_utils/ANGLETest.cpp index 993185b0f..c0a268e8c 100644 --- a/src/tests/test_utils/ANGLETest.cpp +++ b/src/tests/test_utils/ANGLETest.cpp @@ -320,15 +320,15 @@ bool RunSeparateProcessesForEachConfig(int *argc, char *argv[]) std::vector childArgs = commonArgs; childArgs.push_back(configStr.c_str()); - int exitCode = 0; - if (!RunApp(childArgs, nullptr, nullptr, &exitCode)) + ProcessHandle process(childArgs, false, false); + if (!process->started() || !process->finish()) { std::cerr << "Launching child config " << config << " failed.\n"; } - else if (exitCode != 0) + else if (process->getExitCode() != 0) { - std::cerr << "Child config " << config << " failed with exit code " << exitCode - << ".\n"; + std::cerr << "Child config " << config << " failed with exit code " + << process->getExitCode() << ".\n"; success = false; } } @@ -481,7 +481,7 @@ void ANGLETestBase::ANGLETestSetUp() { mSetUpCalled = true; - InitCrashHandler(); + InitCrashHandler(nullptr); gDefaultPlatformMethods.overrideWorkaroundsD3D = TestPlatform_overrideWorkaroundsD3D; gDefaultPlatformMethods.overrideFeaturesVk = TestPlatform_overrideFeaturesVk; diff --git a/util/posix/crash_handler_posix.cpp b/util/posix/crash_handler_posix.cpp index e40c4b551..64c31a78a 100644 --- a/util/posix/crash_handler_posix.cpp +++ b/util/posix/crash_handler_posix.cpp @@ -43,7 +43,7 @@ void PrintStackBacktrace() // No implementations yet. } -void InitCrashHandler() +void InitCrashHandler(CrashCallback *callback) { // No implementations yet. } @@ -140,7 +140,7 @@ static constexpr int kSignals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, }; -void InitCrashHandler() +void InitCrashHandler(CrashCallback *callback) { for (int sig : kSignals) { diff --git a/util/posix/test_utils_posix.cpp b/util/posix/test_utils_posix.cpp index 9d6c82fd0..82c5a7079 100644 --- a/util/posix/test_utils_posix.cpp +++ b/util/posix/test_utils_posix.cpp @@ -10,10 +10,13 @@ #include #include +#include #include #include #include +#include +#include "common/debug.h" #include "common/platform.h" #if !defined(ANGLE_PLATFORM_FUCHSIA) @@ -26,6 +29,222 @@ namespace angle { +namespace +{ +struct ScopedPipe +{ + ~ScopedPipe() + { + closeEndPoint(0); + closeEndPoint(1); + } + + void closeEndPoint(int index) + { + if (fds[index] >= 0) + { + close(fds[index]); + fds[index] = -1; + } + } + + bool valid() const { return fds[0] != -1 || fds[1] != -1; } + + int fds[2] = { + -1, + -1, + }; +}; + +void ReadEntireFile(int fd, std::string *out) +{ + out->clear(); + + while (true) + { + char buffer[256]; + ssize_t bytesRead = read(fd, buffer, sizeof(buffer)); + + // If interrupted, retry. + if (bytesRead < 0 && errno == EINTR) + { + continue; + } + + // If failed, or nothing to read, we are done. + if (bytesRead <= 0) + { + break; + } + + out->append(buffer, bytesRead); + } +} + +class PosixProcess : public Process +{ + public: + PosixProcess(const std::vector &commandLineArgs, + bool captureStdOut, + bool captureStdErr) + { +#if defined(ANGLE_PLATFORM_FUCHSIA) + ANGLE_UNUSED_VARIABLE(ReadEntireFile); + ANGLE_UNUSED_VARIABLE(mExitCode); + ANGLE_UNUSED_VARIABLE(mPID); +#else + if (commandLineArgs.empty() || commandLineArgs.back() != nullptr) + { + return; + } + + // Create pipes for stdout and stderr. + if (captureStdOut && pipe(mStdoutPipe.fds) != 0) + { + return; + } + if (captureStdErr && pipe(mStderrPipe.fds) != 0) + { + return; + } + + mPID = fork(); + if (mPID < 0) + { + return; + } + + mStarted = true; + + if (mPID == 0) + { + // Child. Execute the application. + + // Redirect stdout and stderr to the pipe fds. + if (captureStdOut) + { + if (dup2(mStdoutPipe.fds[1], STDOUT_FILENO) < 0) + { + _exit(errno); + } + } + if (captureStdErr) + { + if (dup2(mStderrPipe.fds[1], STDERR_FILENO) < 0) + { + _exit(errno); + } + } + + // Execute the application, which doesn't return unless failed. Note: execv takes argv + // as `char * const *` for historical reasons. It is safe to const_cast it: + // + // http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html + // + // > The statement about argv[] and envp[] being constants is included to make explicit + // to future writers of language bindings that these objects are completely constant. + // Due to a limitation of the ISO C standard, it is not possible to state that idea in + // standard C. Specifying two levels of const- qualification for the argv[] and envp[] + // parameters for the exec functions may seem to be the natural choice, given that these + // functions do not modify either the array of pointers or the characters to which the + // function points, but this would disallow existing correct code. Instead, only the + // array of pointers is noted as constant. + execv(commandLineArgs[0], const_cast(commandLineArgs.data())); + _exit(errno); + } + // Parent continues execution. +#endif // defined(ANGLE_PLATFORM_FUCHSIA) + } + + ~PosixProcess() override {} + + bool started() override { return mStarted; } + + bool finish() override + { + if (!mStarted) + { + return false; + } + +#if defined(ANGLE_PLATFORM_FUCHSIA) + return false; +#else + // Close the write end of the pipes, so EOF can be generated when child exits. + // Then read back the output of the child. + if (mStdoutPipe.valid()) + { + mStdoutPipe.closeEndPoint(1); + ReadEntireFile(mStdoutPipe.fds[0], &mStdout); + } + if (mStderrPipe.valid()) + { + mStderrPipe.closeEndPoint(1); + ReadEntireFile(mStderrPipe.fds[0], &mStderr); + } + + // Cleanup the child. + int status = 0; + do + { + pid_t changedPid = waitpid(mPID, &status, 0); + if (changedPid < 0 && errno == EINTR) + { + continue; + } + if (changedPid < 0) + { + return false; + } + } while (!WIFEXITED(status) && !WIFSIGNALED(status)); + + // Retrieve the error code. + mExitCode = WEXITSTATUS(status); + return true; +#endif // defined(ANGLE_PLATFORM_FUCHSIA) + } + + bool finished() override + { + if (!mStarted) + { + return false; + } + + return (::kill(mPID, 0) != 0); + } + + int getExitCode() override { return 0; } + + bool kill() override + { + if (!mStarted) + { + return false; + } + + if (finished()) + { + return true; + } + + return (::kill(mPID, SIGTERM) == 0); + } + + private: + bool mStarted = false; + ScopedPipe mStdoutPipe; + ScopedPipe mStderrPipe; + int mExitCode = 0; + pid_t mPID = -1; +}; + +std::string TempFileName() +{ + return std::string(".angle.XXXXXX"); +} +} // anonymous namespace + void Sleep(unsigned int milliseconds) { // On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep @@ -94,4 +313,68 @@ bool StabilizeCPUForBenchmarking() return false; #endif } + +bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen) +{ + const char *tmp = getenv("TMPDIR"); + if (tmp) + { + strncpy(tempDirOut, tmp, maxDirNameLen); + return true; + } + +#if defined(ANGLE_PLATFORM_ANDROID) + // TODO(jmadill): Android support. http://anglebug.com/3162 + // return PathService::Get(DIR_CACHE, path); + return false; +#else + strncpy(tempDirOut, "/tmp", maxDirNameLen); + return true; +#endif +} + +bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen) +{ + std::string tempFile = TempFileName(); + sprintf(tempFileNameOut, "%s/%s", dir, tempFile.c_str()); + int fd = mkstemp(tempFileNameOut); + close(fd); + return fd != -1; +} + +bool DeleteFile(const char *path) +{ + return unlink(path) == 0; +} + +Process *LaunchProcess(const std::vector &args, + bool captureStdout, + bool captureStderr) +{ + return new PosixProcess(args, captureStdout, captureStderr); +} + +int NumberOfProcessors() +{ + // sysconf returns the number of "logical" (not "physical") processors on both + // Mac and Linux. So we get the number of max available "logical" processors. + // + // Note that the number of "currently online" processors may be fewer than the + // returned value of NumberOfProcessors(). On some platforms, the kernel may + // make some processors offline intermittently, to save power when system + // loading is low. + // + // One common use case that needs to know the processor count is to create + // optimal number of threads for optimization. It should make plan according + // to the number of "max available" processors instead of "currently online" + // ones. The kernel should be smart enough to make all processors online when + // it has sufficient number of threads waiting to run. + long res = sysconf(_SC_NPROCESSORS_CONF); + if (res == -1) + { + return 1; + } + + return static_cast(res); +} } // namespace angle diff --git a/util/shader_utils.cpp b/util/shader_utils.cpp index 518608eaf..17b8c85ae 100644 --- a/util/shader_utils.cpp +++ b/util/shader_utils.cpp @@ -4,33 +4,26 @@ // found in the LICENSE file. // -#include "shader_utils.h" +#include "util/shader_utils.h" #include #include #include #include +#include "util/test_utils.h" + namespace { -std::string ReadFileToString(const std::string &source) +bool ReadEntireFile(const std::string &filePath, std::string *contentsOut) { - std::ifstream stream(source.c_str()); - if (!stream) - { - std::cerr << "Failed to load shader file: " << source; - return ""; - } - - std::string result; - - stream.seekg(0, std::ios::end); - result.reserve(static_cast(stream.tellg())); - stream.seekg(0, std::ios::beg); - - result.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); - - return result; + constexpr uint32_t kMaxBufferSize = 2000; + char buffer[kMaxBufferSize] = {}; + if (!angle::ReadEntireFileToString(filePath.c_str(), buffer, kMaxBufferSize) || + strlen(buffer) == 0) + return false; + *contentsOut = buffer; + return true; } GLuint CompileProgramInternal(const char *vsSource, @@ -124,9 +117,10 @@ GLuint CompileShader(GLenum type, const char *source) GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath) { - std::string source = ReadFileToString(sourcePath); - if (source.empty()) + std::string source; + if (!ReadEntireFile(sourcePath, &source)) { + std::cerr << "Error reading shader file: " << sourcePath << "\n"; return 0; } @@ -214,10 +208,17 @@ GLuint CompileProgramWithGS(const char *vsSource, const char *gsSource, const ch GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath) { - std::string vsSource = ReadFileToString(vsPath); - std::string fsSource = ReadFileToString(fsPath); - if (vsSource.empty() || fsSource.empty()) + std::string vsSource; + if (!ReadEntireFile(vsPath, &vsSource)) { + std::cerr << "Error reading shader: " << vsPath << "\n"; + return 0; + } + + std::string fsSource; + if (!ReadEntireFile(fsPath, &fsSource)) + { + std::cerr << "Error reading shader: " << fsPath << "\n"; return 0; } diff --git a/util/test_utils.cpp b/util/test_utils.cpp new file mode 100644 index 000000000..ac4dc8f24 --- /dev/null +++ b/util/test_utils.cpp @@ -0,0 +1,97 @@ +// +// Copyright 2019 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. +// + +// system_utils: Defines common utility functions + +#include "util/test_utils.h" + +#include +#include + +namespace angle +{ +bool CreateTemporaryFile(char *tempFileNameOut, uint32_t maxFileNameLen) +{ + constexpr uint32_t kMaxPath = 1000u; + char tempPath[kMaxPath]; + + if (!GetTempDir(tempPath, kMaxPath)) + return false; + + return CreateTemporaryFileInDir(tempPath, tempFileNameOut, maxFileNameLen); +} + +bool GetFileSize(const char *filePath, uint32_t *sizeOut) +{ + std::ifstream stream(filePath); + if (!stream) + { + return false; + } + + stream.seekg(0, std::ios::end); + *sizeOut = static_cast(stream.tellg()); + return true; +} + +bool ReadEntireFileToString(const char *filePath, char *contentsOut, uint32_t maxLen) +{ + std::ifstream stream(filePath); + if (!stream) + { + return false; + } + + std::string contents; + + stream.seekg(0, std::ios::end); + contents.reserve(static_cast(stream.tellg())); + stream.seekg(0, std::ios::beg); + + contents.assign((std::istreambuf_iterator(stream)), std::istreambuf_iterator()); + + strncpy(contentsOut, contents.c_str(), maxLen); + return true; +} + +// static +Process::~Process() = default; + +ProcessHandle::ProcessHandle() : mProcess(nullptr) {} + +ProcessHandle::ProcessHandle(Process *process) : mProcess(process) {} + +ProcessHandle::ProcessHandle(const std::vector &args, + bool captureStdout, + bool captureStderr) + : mProcess(LaunchProcess(args, captureStdout, captureStderr)) +{} + +ProcessHandle::~ProcessHandle() +{ + reset(); +} + +ProcessHandle::ProcessHandle(ProcessHandle &&other) : mProcess(other.mProcess) +{ + other.mProcess = nullptr; +} + +ProcessHandle &ProcessHandle::operator=(ProcessHandle &&rhs) +{ + std::swap(mProcess, rhs.mProcess); + return *this; +} + +void ProcessHandle::reset() +{ + if (mProcess) + { + delete mProcess; + mProcess = nullptr; + } +} +} // namespace angle diff --git a/util/test_utils.h b/util/test_utils.h index 6a4951d77..fe8a96923 100644 --- a/util/test_utils.h +++ b/util/test_utils.h @@ -9,9 +9,12 @@ #ifndef UTIL_TEST_UTILS_H_ #define UTIL_TEST_UTILS_H_ +#include #include #include +#include "common/angleutils.h" +#include "util/Timer.h" #include "util/util_export.h" namespace angle @@ -28,12 +31,96 @@ ANGLE_UTIL_EXPORT void WriteDebugMessage(const char *format, ...); ANGLE_UTIL_EXPORT bool StabilizeCPUForBenchmarking(); // Set a crash handler to print stack traces. -ANGLE_UTIL_EXPORT void InitCrashHandler(); +using CrashCallback = std::function; +ANGLE_UTIL_EXPORT void InitCrashHandler(CrashCallback *callback); ANGLE_UTIL_EXPORT void TerminateCrashHandler(); // Print a stack back trace. ANGLE_UTIL_EXPORT void PrintStackBacktrace(); +// Get temporary directory. +ANGLE_UTIL_EXPORT bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen); + +// Creates a temporary file. The full path is placed in |path|, and the +// function returns true if was successful in creating the file. The file will +// be empty and all handles closed after this function returns. +ANGLE_UTIL_EXPORT bool CreateTemporaryFile(char *tempFileNameOut, uint32_t maxFileNameLen); + +// Same as CreateTemporaryFile but the file is created in |dir|. +ANGLE_UTIL_EXPORT bool CreateTemporaryFileInDir(const char *dir, + char *tempFileNameOut, + uint32_t maxFileNameLen); + +// Deletes a file or directory. +ANGLE_UTIL_EXPORT bool DeleteFile(const char *path); + +// Reads a file contents into a string. +ANGLE_UTIL_EXPORT bool ReadEntireFileToString(const char *filePath, + char *contentsOut, + uint32_t maxLen); + +// Compute a file's size. +ANGLE_UTIL_EXPORT bool GetFileSize(const char *filePath, uint32_t *sizeOut); + +class ProcessHandle; + +class ANGLE_UTIL_EXPORT Process : angle::NonCopyable +{ + public: + virtual bool started() = 0; + virtual bool finished() = 0; + virtual bool finish() = 0; + virtual bool kill() = 0; + virtual int getExitCode() = 0; + + double getElapsedTimeSeconds() const { return mTimer.getElapsedTime(); } + const std::string &getStdout() const { return mStdout; } + const std::string &getStderr() const { return mStderr; } + + protected: + friend class ProcessHandle; + virtual ~Process(); + + Timer mTimer; + std::string mStdout; + std::string mStderr; +}; + +class ANGLE_UTIL_EXPORT ProcessHandle final : angle::NonCopyable +{ + public: + ProcessHandle(); + ProcessHandle(Process *process); + ProcessHandle(const std::vector &args, bool captureStdout, bool captureStderr); + ~ProcessHandle(); + ProcessHandle(ProcessHandle &&other); + ProcessHandle &operator=(ProcessHandle &&rhs); + + Process *operator->() { return mProcess; } + const Process *operator->() const { return mProcess; } + + operator bool() const { return mProcess != nullptr; } + + void reset(); + + private: + Process *mProcess; +}; + +// Launch a process and optionally get the output. Uses a vector of c strings as command line +// arguments to the child process. Returns a Process handle which can be used to retrieve +// the stdout and stderr outputs as well as the exit code. +// +// Pass false for stdoutOut/stderrOut if you don't need to capture them. +// +// On success, returns a Process pointer with started() == true. +// On failure, returns a Process pointer with started() == false. +ANGLE_UTIL_EXPORT Process *LaunchProcess(const std::vector &args, + bool captureStdout, + bool captureStderr); + +ANGLE_UTIL_EXPORT int NumberOfProcessors(); + } // namespace angle #endif // UTIL_TEST_UTILS_H_ diff --git a/util/test_utils_unittest.cpp b/util/test_utils_unittest.cpp new file mode 100644 index 000000000..380d43fc3 --- /dev/null +++ b/util/test_utils_unittest.cpp @@ -0,0 +1,184 @@ +// +// Copyright 2019 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_unittest.cpp: Unit tests for ANGLE's test utility functions + +#include "gtest/gtest.h" + +#include "common/system_utils.h" +#include "util/Timer.h" +#include "util/test_utils.h" +#include "util/test_utils_unittest_helper.h" + +using namespace angle; + +namespace +{ +#if defined(ANGLE_PLATFORM_WINDOWS) +constexpr char kRunAppHelperExecutable[] = "test_utils_unittest_helper.exe"; +#else +constexpr char kRunAppHelperExecutable[] = "test_utils_unittest_helper"; +#endif + +// Transforms various line endings into C/Unix line endings: +// +// - A\nB -> A\nB +// - A\rB -> A\nB +// - A\r\nB -> A\nB +std::string NormalizeNewLines(const std::string &str) +{ + std::string result; + + for (size_t i = 0; i < str.size(); ++i) + { + if (str[i] == '\r') + { + if (i + 1 < str.size() && str[i + 1] == '\n') + { + ++i; + } + result += '\n'; + } + else + { + result += str[i]; + } + } + + return result; +} + +// Tests that Sleep() actually waits some time. +TEST(TestUtils, Sleep) +{ + Timer timer; + timer.start(); + angle::Sleep(500); + timer.stop(); + + // Use a slightly fuzzy range + EXPECT_GT(timer.getElapsedTime(), 0.48); +} + +constexpr uint32_t kMaxPath = 1000; + +// Temporary file creation is not supported on Android right now. +#if defined(ANGLE_PLATFORM_ANDROID) +# define MAYBE_CreateAndDeleteTemporaryFile DISABLED_CreateAndDeleteTemporaryFile +# define MAYBE_CreateAndDeleteFileInTempDir DISABLED_CreateAndDeleteFileInTempDir +#else +# define MAYBE_CreateAndDeleteTemporaryFile CreateAndDeleteTemporaryFile +# define MAYBE_CreateAndDeleteFileInTempDir CreateAndDeleteFileInTempDir +#endif // defined(ANGLE_PLATFORM_ANDROID) + +// Test creating and deleting temporary file. +TEST(TestUtils, MAYBE_CreateAndDeleteTemporaryFile) +{ + char path[kMaxPath] = {}; + ASSERT_TRUE(CreateTemporaryFile(path, kMaxPath)); + ASSERT_TRUE(strlen(path) > 0); + + const char kOutputString[] = "test output"; + + FILE *fp = fopen(path, "wt"); + ASSERT_NE(fp, nullptr); + int retval = fputs(kOutputString, fp); + fclose(fp); + + EXPECT_GE(retval, 0); + + // Test ReadEntireFileToString + char actualString[kMaxPath]; + EXPECT_TRUE(ReadEntireFileToString(path, actualString, kMaxPath)); + EXPECT_EQ(strcmp(actualString, kOutputString), 0); + + // Delete the temporary file. + EXPECT_TRUE(angle::DeleteFile(path)); +} + +// Tests creating and deleting a file in the system temp dir. +TEST(TestUtils, MAYBE_CreateAndDeleteFileInTempDir) +{ + char tempDir[kMaxPath]; + ASSERT_TRUE(GetTempDir(tempDir, kMaxPath)); + + char path[kMaxPath] = {}; + ASSERT_TRUE(CreateTemporaryFileInDir(tempDir, path, kMaxPath)); + ASSERT_TRUE(strlen(path) > 0); + + const char kOutputString[] = "test output"; + + FILE *fp = fopen(path, "wt"); + ASSERT_NE(fp, nullptr); + int retval = fputs(kOutputString, fp); + fclose(fp); + + EXPECT_GE(retval, 0); + + // Test ReadEntireFileToString + char actualString[kMaxPath]; + EXPECT_TRUE(ReadEntireFileToString(path, actualString, kMaxPath)); + EXPECT_EQ(strcmp(actualString, kOutputString), 0); + + // Delete the temporary file. + EXPECT_TRUE(angle::DeleteFile(path)); +} + +// Test running an external application and receiving its output +TEST(TestUtils, RunApp) +{ +#if defined(ANGLE_PLATFORM_ANDROID) + // TODO: android support. http://anglebug.com/3125 + return; +#endif + +#if defined(ANGLE_PLATFORM_FUCHSIA) + // TODO: fuchsia support. http://anglebug.com/3161 + return; +#endif + + std::string executablePath = GetExecutableDirectory(); + EXPECT_NE(executablePath, ""); + executablePath += "/"; + executablePath += kRunAppHelperExecutable; + + std::vector args = {executablePath.c_str(), kRunAppTestArg1, kRunAppTestArg2, + nullptr}; + + // Test that the application can be executed. + { + ProcessHandle process(args, true, true); + EXPECT_TRUE(process->started()); + EXPECT_TRUE(process->finish()); + EXPECT_TRUE(process->finished()); + + EXPECT_EQ(kRunAppTestStdout, NormalizeNewLines(process->getStdout())); + EXPECT_EQ(kRunAppTestStderr, NormalizeNewLines(process->getStderr())); + EXPECT_EQ(EXIT_SUCCESS, process->getExitCode()); + } + + // Test that environment variables reach the cild. + { + bool setEnvDone = SetEnvironmentVar(kRunAppTestEnvVarName, kRunAppTestEnvVarValue); + EXPECT_TRUE(setEnvDone); + + ProcessHandle process(LaunchProcess(args, true, true)); + EXPECT_TRUE(process->started()); + EXPECT_TRUE(process->finish()); + + EXPECT_EQ("", process->getStdout()); + EXPECT_EQ(kRunAppTestEnvVarValue, NormalizeNewLines(process->getStderr())); + EXPECT_EQ(EXIT_SUCCESS, process->getExitCode()); + } +} + +// Verify that NumberOfProcessors returns something sane. +TEST(TestUtils, NumberOfProcessors) +{ + int numProcs = angle::NumberOfProcessors(); + EXPECT_GT(numProcs, 0); + EXPECT_LT(numProcs, 1000); +} +} // namespace diff --git a/src/common/system_utils_unittest_helper.cpp b/util/test_utils_unittest_helper.cpp similarity index 94% rename from src/common/system_utils_unittest_helper.cpp rename to util/test_utils_unittest_helper.cpp index f1bfa69b4..2604f11c5 100644 --- a/src/common/system_utils_unittest_helper.cpp +++ b/util/test_utils_unittest_helper.cpp @@ -5,10 +5,12 @@ // system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest -#include "common/system_utils_unittest_helper.h" -#include +#include "test_utils_unittest_helper.h" + #include "common/system_utils.h" +#include + int main(int argc, char **argv) { if (argc != 3 || strcmp(argv[1], kRunAppTestArg1) != 0 || strcmp(argv[2], kRunAppTestArg2) != 0) diff --git a/src/common/system_utils_unittest_helper.h b/util/test_utils_unittest_helper.h similarity index 100% rename from src/common/system_utils_unittest_helper.h rename to util/test_utils_unittest_helper.h diff --git a/util/util.gni b/util/util.gni index 4304cae8b..fa1b33610 100644 --- a/util/util.gni +++ b/util/util.gni @@ -12,6 +12,7 @@ util_sources = [ "util/random_utils.h", "util/shader_utils.cpp", "util/shader_utils.h", + "util/test_utils.cpp", "util/test_utils.h", "util/util_export.h", "util/util_gl.h", diff --git a/util/windows/test_utils_win.cpp b/util/windows/test_utils_win.cpp index 05a44c842..3349d1a00 100644 --- a/util/windows/test_utils_win.cpp +++ b/util/windows/test_utils_win.cpp @@ -11,8 +11,14 @@ #include #include #include +#include +#include + +#include #include "common/angleutils.h" + +#include "anglebase/no_destructor.h" #include "util/windows/third_party/StackWalker/src/StackWalker.h" namespace angle @@ -105,6 +111,312 @@ LONG WINAPI StackTraceCrashHandler(EXCEPTION_POINTERS *e) // The compiler wants us to return something. This is what we'd do if we didn't _exit(). return EXCEPTION_EXECUTE_HANDLER; } + +CrashCallback *gCrashHandlerCallback; + +LONG WINAPI CrashHandler(EXCEPTION_POINTERS *e) +{ + if (gCrashHandlerCallback) + { + (*gCrashHandlerCallback)(); + } + return StackTraceCrashHandler(e); +} + +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; + } + + // 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; + } + + 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); +} + +class WindowsProcess : public Process +{ + public: + WindowsProcess(const std::vector &commandLineArgs, + bool captureStdOut, + bool captureStdErr) + { + mProcessInfo.hProcess = INVALID_HANDLE_VALUE; + mProcessInfo.hThread = INVALID_HANDLE_VALUE; + + std::vector 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 = {}; + + // 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 (captureStdErr) + { + if (!mStderrPipe.initPipe(&securityAttribs)) + { + return; + } + startInfo.hStdError = mStderrPipe.writeHandle; + } + else + { + startInfo.hStdError = ::GetStdHandle(STD_ERROR_HANDLE); + } + + if (captureStdOut || captureStdErr) + { + startInfo.dwFlags |= STARTF_USESTDHANDLES; + } + + // 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; + } + + // 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); + } + } + + 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(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 (::TerminateThread(mProcessInfo.hThread, 1) == FALSE) + { + std::cerr << "TerminateThread failed: " << GetLastError() << "\n"; + return false; + } + + 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 = {}; +}; } // anonymous namespace void Sleep(unsigned int milliseconds) @@ -127,13 +439,18 @@ void WriteDebugMessage(const char *format, ...) OutputDebugStringA(buffer.data()); } -void InitCrashHandler() +void InitCrashHandler(CrashCallback *callback) { - SetUnhandledExceptionFilter(StackTraceCrashHandler); + if (callback) + { + gCrashHandlerCallback = callback; + } + SetUnhandledExceptionFilter(CrashHandler); } void TerminateCrashHandler() { + gCrashHandlerCallback = nullptr; SetUnhandledExceptionFilter(nullptr); } @@ -144,4 +461,61 @@ void PrintStackBacktrace() RtlCaptureContext(&context); PrintBacktrace(&context); } + +Process *LaunchProcess(const std::vector &args, + bool captureStdout, + bool captureStderr) +{ + return new WindowsProcess(args, captureStdout, captureStderr); +} + +bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen) +{ + DWORD pathLen = ::GetTempPathA(maxDirNameLen, tempDirOut); + 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(); +} + +int NumberOfProcessors() +{ + return ::GetActiveProcessorCount(ALL_PROCESSOR_GROUPS); +} } // namespace angle diff --git a/util/windows/win32/test_utils_win32.cpp b/util/windows/win32/test_utils_win32.cpp index 4f4aa8d97..9e08b23ca 100644 --- a/util/windows/win32/test_utils_win32.cpp +++ b/util/windows/win32/test_utils_win32.cpp @@ -11,9 +11,10 @@ #include #include +#include "util/random_utils.h" + namespace angle { - void SetLowPriorityProcess() { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); @@ -21,15 +22,15 @@ void SetLowPriorityProcess() bool StabilizeCPUForBenchmarking() { - if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0) + if (::SetThreadAffinityMask(::GetCurrentThread(), 1) == 0) { return false; } - if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE) + if (::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE) { return false; } - if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE) + if (::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE) { return false; }