From f939cb63fdde376a9aecaa9a768fb4d2989a8dd6 Mon Sep 17 00:00:00 2001 From: Jamie Madill Date: Sun, 20 Oct 2019 21:44:36 +0000 Subject: [PATCH] Revert "Reland "Add more test_utils functions."" This reverts commit 7923e234bc458eda5936b8cb4f09fa19e28c0452. Reason for revert: Fails compilation on rollers: [2097/6048] SOLINK ./lib_angle_unittests__library.cr.so FAILED: lib_angle_unittests__library.cr.so lib_angle_unittests__library.cr.so.TOC lib.unstripped/lib_angle_unittests__library.cr.so python "../../build/toolchain/gcc_solink_wrapper.py" --readelf="../../third_party/android_ndk/toolch...(too long) ld.lld: error: undefined symbol: glCreateShader >>> referenced by shader_utils.cpp >>> angle_util_static/shader_utils.o:(CompileShader(unsigned int, char const*)) in archive obj/third_party/angle/libangle_util_static.a ld.lld: error: undefined symbol: glShaderSource Original change's description: > Reland "Add more test_utils functions." > > This is a reland of 5fcfcea4a9379633a83a67fc1d94938cb31f2a9c > > Re-land uses static linking with angle_util. The root cause > of the CFI error wasn't solved. Static linking works around > the problem by not using any export rules. > > Original change's description: > > 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 > > Bug: chromium:1015810 > Bug: angleproject:3162 > Change-Id: I6a2c1e7b585a13ca846759f32da0777c00d7f7e6 > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1869541 > Commit-Queue: Jamie Madill > Reviewed-by: Jamie Madill TBR=ynovikov@chromium.org,jonahr@google.com,jmadill@chromium.org Change-Id: I975b2214411906cb981bffa04fa50e0a65ff8b4e No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: chromium:1015810, angleproject:3162 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1870811 Reviewed-by: Jamie Madill Commit-Queue: Jamie Madill --- 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 ++++ .../common/system_utils_unittest_helper.cpp | 6 +- .../common/system_utils_unittest_helper.h | 0 src/common/system_utils_win.cpp | 164 +++++++- src/tests/BUILD.gn | 20 +- 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 --------- util/util.gni | 1 - util/windows/test_utils_win.cpp | 378 +----------------- util/windows/win32/test_utils_win32.cpp | 9 +- 20 files changed, 460 insertions(+), 1095 deletions(-) rename util/test_utils_unittest_helper.cpp => src/common/system_utils_unittest_helper.cpp (94%) rename util/test_utils_unittest_helper.h => src/common/system_utils_unittest_helper.h (100%) delete mode 100644 util/test_utils.cpp delete mode 100644 util/test_utils_unittest.cpp diff --git a/src/common/system_utils.cpp b/src/common/system_utils.cpp index 54693b3d5..257fc42c2 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 += GetPathSeparatorForEnvironmentVar(); + buf += GetPathSeparator(); buf += oldValue; newValue = buf.c_str(); } diff --git a/src/common/system_utils.h b/src/common/system_utils.h index ccb4e9df8..1191fac22 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 *GetPathSeparatorForEnvironmentVar(); +const char *GetPathSeparator(); 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 d71a07324..c8cb68a92 100644 --- a/src/common/system_utils_posix.cpp +++ b/src/common/system_utils_posix.cpp @@ -18,6 +18,56 @@ 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; @@ -50,11 +100,117 @@ std::string GetEnvironmentVar(const char *variableName) return (value == nullptr ? std::string() : std::string(value)); } -const char *GetPathSeparatorForEnvironmentVar() +const char *GetPathSeparator() { 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 7b189b957..7a5948a49 100644 --- a/src/common/system_utils_unittest.cpp +++ b/src/common/system_utils_unittest.cpp @@ -9,11 +9,46 @@ #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) { @@ -60,4 +95,48 @@ 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/util/test_utils_unittest_helper.cpp b/src/common/system_utils_unittest_helper.cpp similarity index 94% rename from util/test_utils_unittest_helper.cpp rename to src/common/system_utils_unittest_helper.cpp index 2604f11c5..f1bfa69b4 100644 --- a/util/test_utils_unittest_helper.cpp +++ b/src/common/system_utils_unittest_helper.cpp @@ -5,11 +5,9 @@ // system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest -#include "test_utils_unittest_helper.h" - -#include "common/system_utils.h" - +#include "common/system_utils_unittest_helper.h" #include +#include "common/system_utils.h" int main(int argc, char **argv) { diff --git a/util/test_utils_unittest_helper.h b/src/common/system_utils_unittest_helper.h similarity index 100% rename from util/test_utils_unittest_helper.h rename to src/common/system_utils_unittest_helper.h diff --git a/src/common/system_utils_win.cpp b/src/common/system_utils_win.cpp index 48e26c96d..6aa196ce7 100644 --- a/src/common/system_utils_win.cpp +++ b/src/common/system_utils_win.cpp @@ -15,6 +15,56 @@ 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; @@ -76,7 +126,7 @@ std::string GetEnvironmentVar(const char *variableName) } } -const char *GetPathSeparatorForEnvironmentVar() +const char *GetPathSeparator() { return ";"; } @@ -92,6 +142,117 @@ 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: @@ -165,5 +326,4 @@ void BreakDebugger() { __debugbreak(); } - } // namespace angle diff --git a/src/tests/BUILD.gn b/src/tests/BUILD.gn index 79f6e3035..1ac3b58f2 100644 --- a/src/tests/BUILD.gn +++ b/src/tests/BUILD.gn @@ -45,8 +45,8 @@ if (!build_with_chromium) { } } -angle_executable("test_utils_unittest_helper") { - sources = test_utils_unittest_helper_sources +angle_executable("angle_unittests_helper") { + sources = angle_unittests_helper_sources deps = [ "${angle_root}:angle_common", @@ -73,8 +73,9 @@ config("angle_test_expectations_config") { angle_static_library("angle_test_expectations") { public_configs += [ ":angle_test_expectations_config" ] public_deps = [ - "$angle_root:angle_common", - "$angle_root:angle_gpu_info_util", + "${angle_root}:angle_common", + "${angle_root}:angle_gpu_info_util", + "${angle_root}:angle_util", ] sources = test_expectations_sources if (is_mac) { @@ -94,17 +95,16 @@ angle_test("angle_unittests") { deps = [ ":angle_test_expectations", - "$angle_root:angle_util_static", - "$angle_root:libANGLE", - "$angle_root:libfeature_support", - "$angle_root:preprocessor", - "$angle_root:translator", + "${angle_root}:libANGLE", + "${angle_root}:libfeature_support", + "${angle_root}:preprocessor", + "${angle_root}:translator", ] if (!is_android && !is_fuchsia) { # SystemUtils.RunApp, the only unittest using a helper binary, is not supported on these # platforms yet. data_deps = [ - ":test_utils_unittest_helper", + ":angle_unittests_helper", ] } } diff --git a/src/tests/angle_unittests.gni b/src/tests/angle_unittests.gni index d6a86a37e..b1686cc84 100644 --- a/src/tests/angle_unittests.gni +++ b/src/tests/angle_unittests.gni @@ -15,6 +15,7 @@ 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", @@ -129,8 +130,6 @@ 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 @@ -139,9 +138,9 @@ angle_unittests_hlsl_sources = [ "../tests/compiler_tests/UnrollFlatten_test.cpp", ] -test_utils_unittest_helper_sources = [ - "../../util/test_utils_unittest_helper.cpp", - "../../util/test_utils_unittest_helper.h", +angle_unittests_helper_sources = [ + "../common/system_utils_unittest_helper.cpp", + "../common/system_utils_unittest_helper.h", ] if (is_android) { diff --git a/src/tests/perf_tests/glmark2Benchmark.cpp b/src/tests/perf_tests/glmark2Benchmark.cpp index 174fcc673..2d5e46388 100644 --- a/src/tests/perf_tests/glmark2Benchmark.cpp +++ b/src/tests/perf_tests/glmark2Benchmark.cpp @@ -151,9 +151,10 @@ class GLMark2Benchmark : public testing::TestWithParamstarted()); - ASSERT_TRUE(process->finish()); + std::string output; + int exitCode; + + bool success = RunApp(args, &output, nullptr, &exitCode); // Restore the current working directory for the next tests. if (cwd.valid()) @@ -161,11 +162,11 @@ class GLMark2Benchmark : public testing::TestWithParamgetExitCode()); + ASSERT_TRUE(success); + ASSERT_EQ(EXIT_SUCCESS, exitCode); 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 c0a268e8c..993185b0f 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()); - ProcessHandle process(childArgs, false, false); - if (!process->started() || !process->finish()) + int exitCode = 0; + if (!RunApp(childArgs, nullptr, nullptr, &exitCode)) { std::cerr << "Launching child config " << config << " failed.\n"; } - else if (process->getExitCode() != 0) + else if (exitCode != 0) { - std::cerr << "Child config " << config << " failed with exit code " - << process->getExitCode() << ".\n"; + std::cerr << "Child config " << config << " failed with exit code " << exitCode + << ".\n"; success = false; } } @@ -481,7 +481,7 @@ void ANGLETestBase::ANGLETestSetUp() { mSetUpCalled = true; - InitCrashHandler(nullptr); + InitCrashHandler(); 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 64c31a78a..e40c4b551 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(CrashCallback *callback) +void InitCrashHandler() { // No implementations yet. } @@ -140,7 +140,7 @@ static constexpr int kSignals[] = { SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP, }; -void InitCrashHandler(CrashCallback *callback) +void InitCrashHandler() { for (int sig : kSignals) { diff --git a/util/posix/test_utils_posix.cpp b/util/posix/test_utils_posix.cpp index 82c5a7079..9d6c82fd0 100644 --- a/util/posix/test_utils_posix.cpp +++ b/util/posix/test_utils_posix.cpp @@ -10,13 +10,10 @@ #include #include -#include #include #include #include -#include -#include "common/debug.h" #include "common/platform.h" #if !defined(ANGLE_PLATFORM_FUCHSIA) @@ -29,222 +26,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; - } - } - - 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 @@ -313,68 +94,4 @@ 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 17b8c85ae..518608eaf 100644 --- a/util/shader_utils.cpp +++ b/util/shader_utils.cpp @@ -4,26 +4,33 @@ // found in the LICENSE file. // -#include "util/shader_utils.h" +#include "shader_utils.h" #include #include #include #include -#include "util/test_utils.h" - namespace { -bool ReadEntireFile(const std::string &filePath, std::string *contentsOut) +std::string ReadFileToString(const std::string &source) { - 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; + 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; } GLuint CompileProgramInternal(const char *vsSource, @@ -117,10 +124,9 @@ GLuint CompileShader(GLenum type, const char *source) GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath) { - std::string source; - if (!ReadEntireFile(sourcePath, &source)) + std::string source = ReadFileToString(sourcePath); + if (source.empty()) { - std::cerr << "Error reading shader file: " << sourcePath << "\n"; return 0; } @@ -208,17 +214,10 @@ GLuint CompileProgramWithGS(const char *vsSource, const char *gsSource, const ch GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath) { - std::string vsSource; - if (!ReadEntireFile(vsPath, &vsSource)) + std::string vsSource = ReadFileToString(vsPath); + std::string fsSource = ReadFileToString(fsPath); + if (vsSource.empty() || fsSource.empty()) { - 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 deleted file mode 100644 index ac4dc8f24..000000000 --- a/util/test_utils.cpp +++ /dev/null @@ -1,97 +0,0 @@ -// -// 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 fe8a96923..6a4951d77 100644 --- a/util/test_utils.h +++ b/util/test_utils.h @@ -9,12 +9,9 @@ #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 @@ -31,96 +28,12 @@ ANGLE_UTIL_EXPORT void WriteDebugMessage(const char *format, ...); ANGLE_UTIL_EXPORT bool StabilizeCPUForBenchmarking(); // Set a crash handler to print stack traces. -using CrashCallback = std::function; -ANGLE_UTIL_EXPORT void InitCrashHandler(CrashCallback *callback); +ANGLE_UTIL_EXPORT void InitCrashHandler(); 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 deleted file mode 100644 index 380d43fc3..000000000 --- a/util/test_utils_unittest.cpp +++ /dev/null @@ -1,184 +0,0 @@ -// -// 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/util/util.gni b/util/util.gni index fa1b33610..4304cae8b 100644 --- a/util/util.gni +++ b/util/util.gni @@ -12,7 +12,6 @@ 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 3349d1a00..05a44c842 100644 --- a/util/windows/test_utils_win.cpp +++ b/util/windows/test_utils_win.cpp @@ -11,14 +11,8 @@ #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 @@ -111,312 +105,6 @@ 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) @@ -439,18 +127,13 @@ void WriteDebugMessage(const char *format, ...) OutputDebugStringA(buffer.data()); } -void InitCrashHandler(CrashCallback *callback) +void InitCrashHandler() { - if (callback) - { - gCrashHandlerCallback = callback; - } - SetUnhandledExceptionFilter(CrashHandler); + SetUnhandledExceptionFilter(StackTraceCrashHandler); } void TerminateCrashHandler() { - gCrashHandlerCallback = nullptr; SetUnhandledExceptionFilter(nullptr); } @@ -461,61 +144,4 @@ 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 9e08b23ca..4f4aa8d97 100644 --- a/util/windows/win32/test_utils_win32.cpp +++ b/util/windows/win32/test_utils_win32.cpp @@ -11,10 +11,9 @@ #include #include -#include "util/random_utils.h" - namespace angle { + void SetLowPriorityProcess() { SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS); @@ -22,15 +21,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; }