зеркало из https://github.com/AvaloniaUI/angle.git
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 <jmadill@chromium.org> Reviewed-by: Jonah Ryan-Davis <jonahr@google.com>
This commit is contained in:
Родитель
7e23efd3a5
Коммит
5fcfcea4a9
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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<std::string> GetCWD()
|
||||
{
|
||||
std::array<char, 4096> 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<const char *> &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<char *const *>(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:
|
||||
|
|
|
@ -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<const char *> 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
|
||||
|
|
|
@ -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<char, MAX_PATH> 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<double>(curTime.QuadPart) / frequency.QuadPart;
|
||||
}
|
||||
|
||||
bool RunApp(const std::vector<const char *> &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<char> 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<int>(exitCode);
|
||||
}
|
||||
}
|
||||
|
||||
CloseHandle(processInfo.hProcess);
|
||||
CloseHandle(processInfo.hThread);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
class Win32Library : public Library
|
||||
{
|
||||
public:
|
||||
|
@ -326,4 +165,5 @@ void BreakDebugger()
|
|||
{
|
||||
__debugbreak();
|
||||
}
|
||||
|
||||
} // namespace angle
|
||||
|
|
|
@ -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",
|
||||
]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -151,10 +151,9 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
|
|||
}
|
||||
args.push_back(nullptr);
|
||||
|
||||
std::string output;
|
||||
int exitCode;
|
||||
|
||||
bool success = RunApp(args, &output, nullptr, &exitCode);
|
||||
ProcessHandle process(args, true, false);
|
||||
ASSERT_TRUE(process && process->started());
|
||||
ASSERT_TRUE(process->finish());
|
||||
|
||||
// Restore the current working directory for the next tests.
|
||||
if (cwd.valid())
|
||||
|
@ -162,11 +161,11 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
|
|||
SetCWD(cwd.value().c_str());
|
||||
}
|
||||
|
||||
ASSERT_TRUE(success);
|
||||
ASSERT_EQ(EXIT_SUCCESS, exitCode);
|
||||
ASSERT_EQ(EXIT_SUCCESS, process->getExitCode());
|
||||
|
||||
if (!OneFrame())
|
||||
{
|
||||
std::string output = process->getStdout();
|
||||
parseOutput(output, benchmarkName, completeRun);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -320,15 +320,15 @@ bool RunSeparateProcessesForEachConfig(int *argc, char *argv[])
|
|||
std::vector<const char *> 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;
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -10,10 +10,13 @@
|
|||
|
||||
#include <errno.h>
|
||||
#include <sched.h>
|
||||
#include <signal.h>
|
||||
#include <time.h>
|
||||
#include <unistd.h>
|
||||
#include <cstdarg>
|
||||
#include <cstring>
|
||||
|
||||
#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<const char *> &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<char *const *>(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<const char *> &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<int>(res);
|
||||
}
|
||||
} // namespace angle
|
||||
|
|
|
@ -4,33 +4,26 @@
|
|||
// found in the LICENSE file.
|
||||
//
|
||||
|
||||
#include "shader_utils.h"
|
||||
#include "util/shader_utils.h"
|
||||
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#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<unsigned int>(stream.tellg()));
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
result.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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 <cstring>
|
||||
#include <fstream>
|
||||
|
||||
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<uint32_t>(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<unsigned int>(stream.tellg()));
|
||||
stream.seekg(0, std::ios::beg);
|
||||
|
||||
contents.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
|
||||
|
||||
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<const char *> &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
|
|
@ -9,9 +9,12 @@
|
|||
#ifndef UTIL_TEST_UTILS_H_
|
||||
#define UTIL_TEST_UTILS_H_
|
||||
|
||||
#include <functional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#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<void()>;
|
||||
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<const char *> &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<const char *> &args,
|
||||
bool captureStdout,
|
||||
bool captureStderr);
|
||||
|
||||
ANGLE_UTIL_EXPORT int NumberOfProcessors();
|
||||
|
||||
} // namespace angle
|
||||
|
||||
#endif // UTIL_TEST_UTILS_H_
|
||||
|
|
|
@ -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<const char *> 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
|
|
@ -5,10 +5,12 @@
|
|||
|
||||
// system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest
|
||||
|
||||
#include "common/system_utils_unittest_helper.h"
|
||||
#include <string.h>
|
||||
#include "test_utils_unittest_helper.h"
|
||||
|
||||
#include "common/system_utils.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
int main(int argc, char **argv)
|
||||
{
|
||||
if (argc != 3 || strcmp(argv[1], kRunAppTestArg1) != 0 || strcmp(argv[2], kRunAppTestArg2) != 0)
|
|
@ -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",
|
||||
|
|
|
@ -11,8 +11,14 @@
|
|||
#include <stdarg.h>
|
||||
#include <windows.h>
|
||||
#include <array>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <aclapi.h>
|
||||
|
||||
#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<const char *> &commandLineArgs,
|
||||
bool captureStdOut,
|
||||
bool captureStdErr)
|
||||
{
|
||||
mProcessInfo.hProcess = INVALID_HANDLE_VALUE;
|
||||
mProcessInfo.hThread = INVALID_HANDLE_VALUE;
|
||||
|
||||
std::vector<char> commandLineString;
|
||||
for (const char *arg : commandLineArgs)
|
||||
{
|
||||
if (arg)
|
||||
{
|
||||
if (!commandLineString.empty())
|
||||
{
|
||||
commandLineString.push_back(' ');
|
||||
}
|
||||
commandLineString.insert(commandLineString.end(), arg, arg + strlen(arg));
|
||||
}
|
||||
}
|
||||
commandLineString.push_back('\0');
|
||||
|
||||
// Set the bInheritHandle flag so pipe handles are inherited.
|
||||
SECURITY_ATTRIBUTES securityAttribs;
|
||||
securityAttribs.nLength = sizeof(SECURITY_ATTRIBUTES);
|
||||
securityAttribs.bInheritHandle = TRUE;
|
||||
securityAttribs.lpSecurityDescriptor = nullptr;
|
||||
|
||||
STARTUPINFOA startInfo = {};
|
||||
|
||||
// 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<int>(exitCode);
|
||||
}
|
||||
|
||||
bool kill() override
|
||||
{
|
||||
if (!mStarted)
|
||||
return true;
|
||||
|
||||
HANDLE newHandle;
|
||||
if (::DuplicateHandle(::GetCurrentProcess(), mProcessInfo.hProcess, ::GetCurrentProcess(),
|
||||
&newHandle, PROCESS_ALL_ACCESS, false,
|
||||
DUPLICATE_CLOSE_SOURCE) == FALSE)
|
||||
{
|
||||
std::cerr << "Error getting permission to terminate process: " << ::GetLastError()
|
||||
<< "\n";
|
||||
return false;
|
||||
}
|
||||
mProcessInfo.hProcess = newHandle;
|
||||
|
||||
if (::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<const char *> &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
|
||||
|
|
|
@ -11,9 +11,10 @@
|
|||
#include <windows.h>
|
||||
#include <array>
|
||||
|
||||
#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;
|
||||
}
|
||||
|
|
Загрузка…
Ссылка в новой задаче