зеркало из 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
|
else
|
||||||
{
|
{
|
||||||
buf = path;
|
buf = path;
|
||||||
buf += GetPathSeparator();
|
buf += GetPathSeparatorForEnvironmentVar();
|
||||||
buf += oldValue;
|
buf += oldValue;
|
||||||
newValue = buf.c_str();
|
newValue = buf.c_str();
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ bool SetCWD(const char *dirName);
|
||||||
bool SetEnvironmentVar(const char *variableName, const char *value);
|
bool SetEnvironmentVar(const char *variableName, const char *value);
|
||||||
bool UnsetEnvironmentVar(const char *variableName);
|
bool UnsetEnvironmentVar(const char *variableName);
|
||||||
std::string GetEnvironmentVar(const char *variableName);
|
std::string GetEnvironmentVar(const char *variableName);
|
||||||
const char *GetPathSeparator();
|
const char *GetPathSeparatorForEnvironmentVar();
|
||||||
bool PrependPathToEnvironmentVar(const char *variableName, const char *path);
|
bool PrependPathToEnvironmentVar(const char *variableName, const char *path);
|
||||||
bool IsDirectory(const char *filename);
|
bool IsDirectory(const char *filename);
|
||||||
|
|
||||||
|
|
|
@ -18,56 +18,6 @@
|
||||||
|
|
||||||
namespace angle
|
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()
|
Optional<std::string> GetCWD()
|
||||||
{
|
{
|
||||||
std::array<char, 4096> pathBuf;
|
std::array<char, 4096> pathBuf;
|
||||||
|
@ -100,117 +50,11 @@ std::string GetEnvironmentVar(const char *variableName)
|
||||||
return (value == nullptr ? std::string() : std::string(value));
|
return (value == nullptr ? std::string() : std::string(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GetPathSeparator()
|
const char *GetPathSeparatorForEnvironmentVar()
|
||||||
{
|
{
|
||||||
return ":";
|
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
|
class PosixLibrary : public Library
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
|
|
@ -9,46 +9,11 @@
|
||||||
#include "gtest/gtest.h"
|
#include "gtest/gtest.h"
|
||||||
|
|
||||||
#include "common/system_utils.h"
|
#include "common/system_utils.h"
|
||||||
#include "common/system_utils_unittest_helper.h"
|
|
||||||
|
|
||||||
using namespace angle;
|
using namespace angle;
|
||||||
|
|
||||||
namespace
|
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 getting the executable path
|
||||||
TEST(SystemUtils, ExecutablePath)
|
TEST(SystemUtils, ExecutablePath)
|
||||||
{
|
{
|
||||||
|
@ -95,48 +60,4 @@ TEST(SystemUtils, Environment)
|
||||||
readback = GetEnvironmentVar(kEnvVarName);
|
readback = GetEnvironmentVar(kEnvVarName);
|
||||||
EXPECT_EQ("", readback);
|
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
|
} // anonymous namespace
|
||||||
|
|
|
@ -15,56 +15,6 @@
|
||||||
|
|
||||||
namespace angle
|
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::string GetExecutablePath()
|
||||||
{
|
{
|
||||||
std::array<char, MAX_PATH> executableFileBuf;
|
std::array<char, MAX_PATH> executableFileBuf;
|
||||||
|
@ -126,7 +76,7 @@ std::string GetEnvironmentVar(const char *variableName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GetPathSeparator()
|
const char *GetPathSeparatorForEnvironmentVar()
|
||||||
{
|
{
|
||||||
return ";";
|
return ";";
|
||||||
}
|
}
|
||||||
|
@ -142,117 +92,6 @@ double GetCurrentTime()
|
||||||
return static_cast<double>(curTime.QuadPart) / frequency.QuadPart;
|
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
|
class Win32Library : public Library
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
|
@ -326,4 +165,5 @@ void BreakDebugger()
|
||||||
{
|
{
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace angle
|
} // namespace angle
|
||||||
|
|
|
@ -45,8 +45,8 @@ if (!build_with_chromium) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
angle_executable("angle_unittests_helper") {
|
angle_executable("test_utils_unittest_helper") {
|
||||||
sources = angle_unittests_helper_sources
|
sources = test_utils_unittest_helper_sources
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
"${angle_root}:angle_common",
|
"${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
|
# SystemUtils.RunApp, the only unittest using a helper binary, is not supported on these
|
||||||
# platforms yet.
|
# platforms yet.
|
||||||
data_deps = [
|
data_deps = [
|
||||||
":angle_unittests_helper",
|
":test_utils_unittest_helper",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,6 @@ angle_unittests_sources = [
|
||||||
"../common/matrix_utils_unittest.cpp",
|
"../common/matrix_utils_unittest.cpp",
|
||||||
"../common/string_utils_unittest.cpp",
|
"../common/string_utils_unittest.cpp",
|
||||||
"../common/system_utils_unittest.cpp",
|
"../common/system_utils_unittest.cpp",
|
||||||
"../common/system_utils_unittest_helper.h",
|
|
||||||
"../common/utilities_unittest.cpp",
|
"../common/utilities_unittest.cpp",
|
||||||
"../common/vector_utils_unittest.cpp",
|
"../common/vector_utils_unittest.cpp",
|
||||||
"../feature_support_util/feature_support_util_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.h",
|
||||||
"../tests/test_utils/ShaderCompileTreeTest.cpp",
|
"../tests/test_utils/ShaderCompileTreeTest.cpp",
|
||||||
"../tests/test_utils/ShaderExtensionTest.h",
|
"../tests/test_utils/ShaderExtensionTest.h",
|
||||||
|
"../../util/test_utils_unittest.cpp",
|
||||||
|
"../../util/test_utils_unittest_helper.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
# TODO(jmadill): should probably call this windows sources
|
# TODO(jmadill): should probably call this windows sources
|
||||||
|
@ -138,9 +139,9 @@ angle_unittests_hlsl_sources = [
|
||||||
"../tests/compiler_tests/UnrollFlatten_test.cpp",
|
"../tests/compiler_tests/UnrollFlatten_test.cpp",
|
||||||
]
|
]
|
||||||
|
|
||||||
angle_unittests_helper_sources = [
|
test_utils_unittest_helper_sources = [
|
||||||
"../common/system_utils_unittest_helper.cpp",
|
"../../util/test_utils_unittest_helper.cpp",
|
||||||
"../common/system_utils_unittest_helper.h",
|
"../../util/test_utils_unittest_helper.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
if (is_android) {
|
if (is_android) {
|
||||||
|
|
|
@ -151,10 +151,9 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
|
||||||
}
|
}
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|
||||||
std::string output;
|
ProcessHandle process(args, true, false);
|
||||||
int exitCode;
|
ASSERT_TRUE(process && process->started());
|
||||||
|
ASSERT_TRUE(process->finish());
|
||||||
bool success = RunApp(args, &output, nullptr, &exitCode);
|
|
||||||
|
|
||||||
// Restore the current working directory for the next tests.
|
// Restore the current working directory for the next tests.
|
||||||
if (cwd.valid())
|
if (cwd.valid())
|
||||||
|
@ -162,11 +161,11 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
|
||||||
SetCWD(cwd.value().c_str());
|
SetCWD(cwd.value().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_TRUE(success);
|
ASSERT_EQ(EXIT_SUCCESS, process->getExitCode());
|
||||||
ASSERT_EQ(EXIT_SUCCESS, exitCode);
|
|
||||||
|
|
||||||
if (!OneFrame())
|
if (!OneFrame())
|
||||||
{
|
{
|
||||||
|
std::string output = process->getStdout();
|
||||||
parseOutput(output, benchmarkName, completeRun);
|
parseOutput(output, benchmarkName, completeRun);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -320,15 +320,15 @@ bool RunSeparateProcessesForEachConfig(int *argc, char *argv[])
|
||||||
std::vector<const char *> childArgs = commonArgs;
|
std::vector<const char *> childArgs = commonArgs;
|
||||||
childArgs.push_back(configStr.c_str());
|
childArgs.push_back(configStr.c_str());
|
||||||
|
|
||||||
int exitCode = 0;
|
ProcessHandle process(childArgs, false, false);
|
||||||
if (!RunApp(childArgs, nullptr, nullptr, &exitCode))
|
if (!process->started() || !process->finish())
|
||||||
{
|
{
|
||||||
std::cerr << "Launching child config " << config << " failed.\n";
|
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
|
std::cerr << "Child config " << config << " failed with exit code "
|
||||||
<< ".\n";
|
<< process->getExitCode() << ".\n";
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,7 +481,7 @@ void ANGLETestBase::ANGLETestSetUp()
|
||||||
{
|
{
|
||||||
mSetUpCalled = true;
|
mSetUpCalled = true;
|
||||||
|
|
||||||
InitCrashHandler();
|
InitCrashHandler(nullptr);
|
||||||
|
|
||||||
gDefaultPlatformMethods.overrideWorkaroundsD3D = TestPlatform_overrideWorkaroundsD3D;
|
gDefaultPlatformMethods.overrideWorkaroundsD3D = TestPlatform_overrideWorkaroundsD3D;
|
||||||
gDefaultPlatformMethods.overrideFeaturesVk = TestPlatform_overrideFeaturesVk;
|
gDefaultPlatformMethods.overrideFeaturesVk = TestPlatform_overrideFeaturesVk;
|
||||||
|
|
|
@ -43,7 +43,7 @@ void PrintStackBacktrace()
|
||||||
// No implementations yet.
|
// No implementations yet.
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitCrashHandler()
|
void InitCrashHandler(CrashCallback *callback)
|
||||||
{
|
{
|
||||||
// No implementations yet.
|
// No implementations yet.
|
||||||
}
|
}
|
||||||
|
@ -140,7 +140,7 @@ static constexpr int kSignals[] = {
|
||||||
SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP,
|
SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP,
|
||||||
};
|
};
|
||||||
|
|
||||||
void InitCrashHandler()
|
void InitCrashHandler(CrashCallback *callback)
|
||||||
{
|
{
|
||||||
for (int sig : kSignals)
|
for (int sig : kSignals)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,10 +10,13 @@
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <sched.h>
|
#include <sched.h>
|
||||||
|
#include <signal.h>
|
||||||
#include <time.h>
|
#include <time.h>
|
||||||
#include <unistd.h>
|
#include <unistd.h>
|
||||||
#include <cstdarg>
|
#include <cstdarg>
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "common/debug.h"
|
||||||
#include "common/platform.h"
|
#include "common/platform.h"
|
||||||
|
|
||||||
#if !defined(ANGLE_PLATFORM_FUCHSIA)
|
#if !defined(ANGLE_PLATFORM_FUCHSIA)
|
||||||
|
@ -26,6 +29,222 @@
|
||||||
|
|
||||||
namespace angle
|
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)
|
void Sleep(unsigned int milliseconds)
|
||||||
{
|
{
|
||||||
// On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep
|
// On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep
|
||||||
|
@ -94,4 +313,68 @@ bool StabilizeCPUForBenchmarking()
|
||||||
return false;
|
return false;
|
||||||
#endif
|
#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
|
} // namespace angle
|
||||||
|
|
|
@ -4,33 +4,26 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "shader_utils.h"
|
#include "util/shader_utils.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "util/test_utils.h"
|
||||||
|
|
||||||
namespace
|
namespace
|
||||||
{
|
{
|
||||||
std::string ReadFileToString(const std::string &source)
|
bool ReadEntireFile(const std::string &filePath, std::string *contentsOut)
|
||||||
{
|
{
|
||||||
std::ifstream stream(source.c_str());
|
constexpr uint32_t kMaxBufferSize = 2000;
|
||||||
if (!stream)
|
char buffer[kMaxBufferSize] = {};
|
||||||
{
|
if (!angle::ReadEntireFileToString(filePath.c_str(), buffer, kMaxBufferSize) ||
|
||||||
std::cerr << "Failed to load shader file: " << source;
|
strlen(buffer) == 0)
|
||||||
return "";
|
return false;
|
||||||
}
|
*contentsOut = buffer;
|
||||||
|
return true;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
GLuint CompileProgramInternal(const char *vsSource,
|
GLuint CompileProgramInternal(const char *vsSource,
|
||||||
|
@ -124,9 +117,10 @@ GLuint CompileShader(GLenum type, const char *source)
|
||||||
|
|
||||||
GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath)
|
GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath)
|
||||||
{
|
{
|
||||||
std::string source = ReadFileToString(sourcePath);
|
std::string source;
|
||||||
if (source.empty())
|
if (!ReadEntireFile(sourcePath, &source))
|
||||||
{
|
{
|
||||||
|
std::cerr << "Error reading shader file: " << sourcePath << "\n";
|
||||||
return 0;
|
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)
|
GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath)
|
||||||
{
|
{
|
||||||
std::string vsSource = ReadFileToString(vsPath);
|
std::string vsSource;
|
||||||
std::string fsSource = ReadFileToString(fsPath);
|
if (!ReadEntireFile(vsPath, &vsSource))
|
||||||
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;
|
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_
|
#ifndef UTIL_TEST_UTILS_H_
|
||||||
#define UTIL_TEST_UTILS_H_
|
#define UTIL_TEST_UTILS_H_
|
||||||
|
|
||||||
|
#include <functional>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "common/angleutils.h"
|
||||||
|
#include "util/Timer.h"
|
||||||
#include "util/util_export.h"
|
#include "util/util_export.h"
|
||||||
|
|
||||||
namespace angle
|
namespace angle
|
||||||
|
@ -28,12 +31,96 @@ ANGLE_UTIL_EXPORT void WriteDebugMessage(const char *format, ...);
|
||||||
ANGLE_UTIL_EXPORT bool StabilizeCPUForBenchmarking();
|
ANGLE_UTIL_EXPORT bool StabilizeCPUForBenchmarking();
|
||||||
|
|
||||||
// Set a crash handler to print stack traces.
|
// 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();
|
ANGLE_UTIL_EXPORT void TerminateCrashHandler();
|
||||||
|
|
||||||
// Print a stack back trace.
|
// Print a stack back trace.
|
||||||
ANGLE_UTIL_EXPORT void PrintStackBacktrace();
|
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
|
} // namespace angle
|
||||||
|
|
||||||
#endif // UTIL_TEST_UTILS_H_
|
#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
|
// system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest
|
||||||
|
|
||||||
#include "common/system_utils_unittest_helper.h"
|
#include "test_utils_unittest_helper.h"
|
||||||
#include <string.h>
|
|
||||||
#include "common/system_utils.h"
|
#include "common/system_utils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
if (argc != 3 || strcmp(argv[1], kRunAppTestArg1) != 0 || strcmp(argv[2], kRunAppTestArg2) != 0)
|
if (argc != 3 || strcmp(argv[1], kRunAppTestArg1) != 0 || strcmp(argv[2], kRunAppTestArg2) != 0)
|
|
@ -12,6 +12,7 @@ util_sources = [
|
||||||
"util/random_utils.h",
|
"util/random_utils.h",
|
||||||
"util/shader_utils.cpp",
|
"util/shader_utils.cpp",
|
||||||
"util/shader_utils.h",
|
"util/shader_utils.h",
|
||||||
|
"util/test_utils.cpp",
|
||||||
"util/test_utils.h",
|
"util/test_utils.h",
|
||||||
"util/util_export.h",
|
"util/util_export.h",
|
||||||
"util/util_gl.h",
|
"util/util_gl.h",
|
||||||
|
|
|
@ -11,8 +11,14 @@
|
||||||
#include <stdarg.h>
|
#include <stdarg.h>
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
#include <iostream>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
|
#include <aclapi.h>
|
||||||
|
|
||||||
#include "common/angleutils.h"
|
#include "common/angleutils.h"
|
||||||
|
|
||||||
|
#include "anglebase/no_destructor.h"
|
||||||
#include "util/windows/third_party/StackWalker/src/StackWalker.h"
|
#include "util/windows/third_party/StackWalker/src/StackWalker.h"
|
||||||
|
|
||||||
namespace angle
|
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().
|
// The compiler wants us to return something. This is what we'd do if we didn't _exit().
|
||||||
return EXCEPTION_EXECUTE_HANDLER;
|
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
|
} // anonymous namespace
|
||||||
|
|
||||||
void Sleep(unsigned int milliseconds)
|
void Sleep(unsigned int milliseconds)
|
||||||
|
@ -127,13 +439,18 @@ void WriteDebugMessage(const char *format, ...)
|
||||||
OutputDebugStringA(buffer.data());
|
OutputDebugStringA(buffer.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitCrashHandler()
|
void InitCrashHandler(CrashCallback *callback)
|
||||||
{
|
{
|
||||||
SetUnhandledExceptionFilter(StackTraceCrashHandler);
|
if (callback)
|
||||||
|
{
|
||||||
|
gCrashHandlerCallback = callback;
|
||||||
|
}
|
||||||
|
SetUnhandledExceptionFilter(CrashHandler);
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerminateCrashHandler()
|
void TerminateCrashHandler()
|
||||||
{
|
{
|
||||||
|
gCrashHandlerCallback = nullptr;
|
||||||
SetUnhandledExceptionFilter(nullptr);
|
SetUnhandledExceptionFilter(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -144,4 +461,61 @@ void PrintStackBacktrace()
|
||||||
RtlCaptureContext(&context);
|
RtlCaptureContext(&context);
|
||||||
PrintBacktrace(&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
|
} // namespace angle
|
||||||
|
|
|
@ -11,9 +11,10 @@
|
||||||
#include <windows.h>
|
#include <windows.h>
|
||||||
#include <array>
|
#include <array>
|
||||||
|
|
||||||
|
#include "util/random_utils.h"
|
||||||
|
|
||||||
namespace angle
|
namespace angle
|
||||||
{
|
{
|
||||||
|
|
||||||
void SetLowPriorityProcess()
|
void SetLowPriorityProcess()
|
||||||
{
|
{
|
||||||
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
|
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
|
||||||
|
@ -21,15 +22,15 @@ void SetLowPriorityProcess()
|
||||||
|
|
||||||
bool StabilizeCPUForBenchmarking()
|
bool StabilizeCPUForBenchmarking()
|
||||||
{
|
{
|
||||||
if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0)
|
if (::SetThreadAffinityMask(::GetCurrentThread(), 1) == 0)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE)
|
if (::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE)
|
if (::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE)
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче