зеркало из https://github.com/AvaloniaUI/angle.git
Revert "Reland "Add more test_utils functions.""
This reverts commit7923e234bc
. Reason for revert: Fails compilation on rollers: [2097/6048] SOLINK ./lib_angle_unittests__library.cr.so FAILED: lib_angle_unittests__library.cr.so lib_angle_unittests__library.cr.so.TOC lib.unstripped/lib_angle_unittests__library.cr.so python "../../build/toolchain/gcc_solink_wrapper.py" --readelf="../../third_party/android_ndk/toolch...(too long) ld.lld: error: undefined symbol: glCreateShader >>> referenced by shader_utils.cpp >>> angle_util_static/shader_utils.o:(CompileShader(unsigned int, char const*)) in archive obj/third_party/angle/libangle_util_static.a ld.lld: error: undefined symbol: glShaderSource Original change's description: > Reland "Add more test_utils functions." > > This is a reland of5fcfcea4a9
> > Re-land uses static linking with angle_util. The root cause > of the CFI error wasn't solved. Static linking works around > the problem by not using any export rules. > > Original change's description: > > Add more test_utils functions. > > > > Includes methods for creating temporary files, deleting files, and > > reading files into a string. Also renames GetPathSeparator to mention > > it's only used for environment variables. Includes a new virtual type > > angle::Process that will be used to implement cross-platform async > > Process launching for tests. Also includes a way to specify a custom > > crash handler callback. > > > > Also adds a few unit tests for the new functionality. They are disabled > > on Android because the functions are not needed by the new test runner. > > > > Bug: angleproject:3162 > > Change-Id: I3e2c2e9837608884c98379fa0f78c9ffbe158d73 > > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1821940 > > Commit-Queue: Jamie Madill <jmadill@chromium.org> > > Reviewed-by: Jonah Ryan-Davis <jonahr@google.com> > > Bug: chromium:1015810 > Bug: angleproject:3162 > Change-Id: I6a2c1e7b585a13ca846759f32da0777c00d7f7e6 > Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1869541 > Commit-Queue: Jamie Madill <jmadill@chromium.org> > Reviewed-by: Jamie Madill <jmadill@chromium.org> TBR=ynovikov@chromium.org,jonahr@google.com,jmadill@chromium.org Change-Id: I975b2214411906cb981bffa04fa50e0a65ff8b4e No-Presubmit: true No-Tree-Checks: true No-Try: true Bug: chromium:1015810, angleproject:3162 Reviewed-on: https://chromium-review.googlesource.com/c/angle/angle/+/1870811 Reviewed-by: Jamie Madill <jmadill@chromium.org> Commit-Queue: Jamie Madill <jmadill@chromium.org>
This commit is contained in:
Родитель
7923e234bc
Коммит
f939cb63fd
|
@ -22,7 +22,7 @@ bool PrependPathToEnvironmentVar(const char *variableName, const char *path)
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
buf = path;
|
buf = path;
|
||||||
buf += GetPathSeparatorForEnvironmentVar();
|
buf += GetPathSeparator();
|
||||||
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 *GetPathSeparatorForEnvironmentVar();
|
const char *GetPathSeparator();
|
||||||
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,6 +18,56 @@
|
||||||
|
|
||||||
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;
|
||||||
|
@ -50,11 +100,117 @@ std::string GetEnvironmentVar(const char *variableName)
|
||||||
return (value == nullptr ? std::string() : std::string(value));
|
return (value == nullptr ? std::string() : std::string(value));
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GetPathSeparatorForEnvironmentVar()
|
const char *GetPathSeparator()
|
||||||
{
|
{
|
||||||
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,11 +9,46 @@
|
||||||
#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)
|
||||||
{
|
{
|
||||||
|
@ -60,4 +95,48 @@ 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
|
||||||
|
|
|
@ -5,11 +5,9 @@
|
||||||
|
|
||||||
// system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest
|
// system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest
|
||||||
|
|
||||||
#include "test_utils_unittest_helper.h"
|
#include "common/system_utils_unittest_helper.h"
|
||||||
|
|
||||||
#include "common/system_utils.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include "common/system_utils.h"
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
|
@ -15,6 +15,56 @@
|
||||||
|
|
||||||
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;
|
||||||
|
@ -76,7 +126,7 @@ std::string GetEnvironmentVar(const char *variableName)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *GetPathSeparatorForEnvironmentVar()
|
const char *GetPathSeparator()
|
||||||
{
|
{
|
||||||
return ";";
|
return ";";
|
||||||
}
|
}
|
||||||
|
@ -92,6 +142,117 @@ 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:
|
||||||
|
@ -165,5 +326,4 @@ void BreakDebugger()
|
||||||
{
|
{
|
||||||
__debugbreak();
|
__debugbreak();
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace angle
|
} // namespace angle
|
||||||
|
|
|
@ -45,8 +45,8 @@ if (!build_with_chromium) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
angle_executable("test_utils_unittest_helper") {
|
angle_executable("angle_unittests_helper") {
|
||||||
sources = test_utils_unittest_helper_sources
|
sources = angle_unittests_helper_sources
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
"${angle_root}:angle_common",
|
"${angle_root}:angle_common",
|
||||||
|
@ -73,8 +73,9 @@ config("angle_test_expectations_config") {
|
||||||
angle_static_library("angle_test_expectations") {
|
angle_static_library("angle_test_expectations") {
|
||||||
public_configs += [ ":angle_test_expectations_config" ]
|
public_configs += [ ":angle_test_expectations_config" ]
|
||||||
public_deps = [
|
public_deps = [
|
||||||
"$angle_root:angle_common",
|
"${angle_root}:angle_common",
|
||||||
"$angle_root:angle_gpu_info_util",
|
"${angle_root}:angle_gpu_info_util",
|
||||||
|
"${angle_root}:angle_util",
|
||||||
]
|
]
|
||||||
sources = test_expectations_sources
|
sources = test_expectations_sources
|
||||||
if (is_mac) {
|
if (is_mac) {
|
||||||
|
@ -94,17 +95,16 @@ angle_test("angle_unittests") {
|
||||||
|
|
||||||
deps = [
|
deps = [
|
||||||
":angle_test_expectations",
|
":angle_test_expectations",
|
||||||
"$angle_root:angle_util_static",
|
"${angle_root}:libANGLE",
|
||||||
"$angle_root:libANGLE",
|
"${angle_root}:libfeature_support",
|
||||||
"$angle_root:libfeature_support",
|
"${angle_root}:preprocessor",
|
||||||
"$angle_root:preprocessor",
|
"${angle_root}:translator",
|
||||||
"$angle_root:translator",
|
|
||||||
]
|
]
|
||||||
if (!is_android && !is_fuchsia) {
|
if (!is_android && !is_fuchsia) {
|
||||||
# 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 = [
|
||||||
":test_utils_unittest_helper",
|
":angle_unittests_helper",
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ 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",
|
||||||
|
@ -129,8 +130,6 @@ 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
|
||||||
|
@ -139,9 +138,9 @@ angle_unittests_hlsl_sources = [
|
||||||
"../tests/compiler_tests/UnrollFlatten_test.cpp",
|
"../tests/compiler_tests/UnrollFlatten_test.cpp",
|
||||||
]
|
]
|
||||||
|
|
||||||
test_utils_unittest_helper_sources = [
|
angle_unittests_helper_sources = [
|
||||||
"../../util/test_utils_unittest_helper.cpp",
|
"../common/system_utils_unittest_helper.cpp",
|
||||||
"../../util/test_utils_unittest_helper.h",
|
"../common/system_utils_unittest_helper.h",
|
||||||
]
|
]
|
||||||
|
|
||||||
if (is_android) {
|
if (is_android) {
|
||||||
|
|
|
@ -151,9 +151,10 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
|
||||||
}
|
}
|
||||||
args.push_back(nullptr);
|
args.push_back(nullptr);
|
||||||
|
|
||||||
ProcessHandle process(args, true, false);
|
std::string output;
|
||||||
ASSERT_TRUE(process && process->started());
|
int exitCode;
|
||||||
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())
|
||||||
|
@ -161,11 +162,11 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
|
||||||
SetCWD(cwd.value().c_str());
|
SetCWD(cwd.value().c_str());
|
||||||
}
|
}
|
||||||
|
|
||||||
ASSERT_EQ(EXIT_SUCCESS, process->getExitCode());
|
ASSERT_TRUE(success);
|
||||||
|
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());
|
||||||
|
|
||||||
ProcessHandle process(childArgs, false, false);
|
int exitCode = 0;
|
||||||
if (!process->started() || !process->finish())
|
if (!RunApp(childArgs, nullptr, nullptr, &exitCode))
|
||||||
{
|
{
|
||||||
std::cerr << "Launching child config " << config << " failed.\n";
|
std::cerr << "Launching child config " << config << " failed.\n";
|
||||||
}
|
}
|
||||||
else if (process->getExitCode() != 0)
|
else if (exitCode != 0)
|
||||||
{
|
{
|
||||||
std::cerr << "Child config " << config << " failed with exit code "
|
std::cerr << "Child config " << config << " failed with exit code " << exitCode
|
||||||
<< process->getExitCode() << ".\n";
|
<< ".\n";
|
||||||
success = false;
|
success = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -481,7 +481,7 @@ void ANGLETestBase::ANGLETestSetUp()
|
||||||
{
|
{
|
||||||
mSetUpCalled = true;
|
mSetUpCalled = true;
|
||||||
|
|
||||||
InitCrashHandler(nullptr);
|
InitCrashHandler();
|
||||||
|
|
||||||
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(CrashCallback *callback)
|
void InitCrashHandler()
|
||||||
{
|
{
|
||||||
// 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(CrashCallback *callback)
|
void InitCrashHandler()
|
||||||
{
|
{
|
||||||
for (int sig : kSignals)
|
for (int sig : kSignals)
|
||||||
{
|
{
|
||||||
|
|
|
@ -10,13 +10,10 @@
|
||||||
|
|
||||||
#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)
|
||||||
|
@ -29,222 +26,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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
||||||
|
@ -313,68 +94,4 @@ 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,26 +4,33 @@
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
//
|
//
|
||||||
|
|
||||||
#include "util/shader_utils.h"
|
#include "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
|
||||||
{
|
{
|
||||||
bool ReadEntireFile(const std::string &filePath, std::string *contentsOut)
|
std::string ReadFileToString(const std::string &source)
|
||||||
{
|
{
|
||||||
constexpr uint32_t kMaxBufferSize = 2000;
|
std::ifstream stream(source.c_str());
|
||||||
char buffer[kMaxBufferSize] = {};
|
if (!stream)
|
||||||
if (!angle::ReadEntireFileToString(filePath.c_str(), buffer, kMaxBufferSize) ||
|
{
|
||||||
strlen(buffer) == 0)
|
std::cerr << "Failed to load shader file: " << source;
|
||||||
return false;
|
return "";
|
||||||
*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,
|
||||||
|
@ -117,10 +124,9 @@ 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;
|
std::string source = ReadFileToString(sourcePath);
|
||||||
if (!ReadEntireFile(sourcePath, &source))
|
if (source.empty())
|
||||||
{
|
{
|
||||||
std::cerr << "Error reading shader file: " << sourcePath << "\n";
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -208,17 +214,10 @@ GLuint CompileProgramWithGS(const char *vsSource, const char *gsSource, const ch
|
||||||
|
|
||||||
GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath)
|
GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath)
|
||||||
{
|
{
|
||||||
std::string vsSource;
|
std::string vsSource = ReadFileToString(vsPath);
|
||||||
if (!ReadEntireFile(vsPath, &vsSource))
|
std::string fsSource = ReadFileToString(fsPath);
|
||||||
|
if (vsSource.empty() || fsSource.empty())
|
||||||
{
|
{
|
||||||
std::cerr << "Error reading shader: " << vsPath << "\n";
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
std::string fsSource;
|
|
||||||
if (!ReadEntireFile(fsPath, &fsSource))
|
|
||||||
{
|
|
||||||
std::cerr << "Error reading shader: " << fsPath << "\n";
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,97 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
//
|
|
||||||
|
|
||||||
// system_utils: Defines common utility functions
|
|
||||||
|
|
||||||
#include "util/test_utils.h"
|
|
||||||
|
|
||||||
#include <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,12 +9,9 @@
|
||||||
#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
|
||||||
|
@ -31,96 +28,12 @@ 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.
|
||||||
using CrashCallback = std::function<void()>;
|
ANGLE_UTIL_EXPORT void InitCrashHandler();
|
||||||
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_
|
||||||
|
|
|
@ -1,184 +0,0 @@
|
||||||
//
|
|
||||||
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
|
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
|
||||||
// found in the LICENSE file.
|
|
||||||
|
|
||||||
// test_utils_unittest.cpp: Unit tests for ANGLE's test utility functions
|
|
||||||
|
|
||||||
#include "gtest/gtest.h"
|
|
||||||
|
|
||||||
#include "common/system_utils.h"
|
|
||||||
#include "util/Timer.h"
|
|
||||||
#include "util/test_utils.h"
|
|
||||||
#include "util/test_utils_unittest_helper.h"
|
|
||||||
|
|
||||||
using namespace angle;
|
|
||||||
|
|
||||||
namespace
|
|
||||||
{
|
|
||||||
#if defined(ANGLE_PLATFORM_WINDOWS)
|
|
||||||
constexpr char kRunAppHelperExecutable[] = "test_utils_unittest_helper.exe";
|
|
||||||
#else
|
|
||||||
constexpr char kRunAppHelperExecutable[] = "test_utils_unittest_helper";
|
|
||||||
#endif
|
|
||||||
|
|
||||||
// Transforms various line endings into C/Unix line endings:
|
|
||||||
//
|
|
||||||
// - A\nB -> A\nB
|
|
||||||
// - A\rB -> A\nB
|
|
||||||
// - A\r\nB -> A\nB
|
|
||||||
std::string NormalizeNewLines(const std::string &str)
|
|
||||||
{
|
|
||||||
std::string result;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < str.size(); ++i)
|
|
||||||
{
|
|
||||||
if (str[i] == '\r')
|
|
||||||
{
|
|
||||||
if (i + 1 < str.size() && str[i + 1] == '\n')
|
|
||||||
{
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
result += '\n';
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
result += str[i];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests that Sleep() actually waits some time.
|
|
||||||
TEST(TestUtils, Sleep)
|
|
||||||
{
|
|
||||||
Timer timer;
|
|
||||||
timer.start();
|
|
||||||
angle::Sleep(500);
|
|
||||||
timer.stop();
|
|
||||||
|
|
||||||
// Use a slightly fuzzy range
|
|
||||||
EXPECT_GT(timer.getElapsedTime(), 0.48);
|
|
||||||
}
|
|
||||||
|
|
||||||
constexpr uint32_t kMaxPath = 1000;
|
|
||||||
|
|
||||||
// Temporary file creation is not supported on Android right now.
|
|
||||||
#if defined(ANGLE_PLATFORM_ANDROID)
|
|
||||||
# define MAYBE_CreateAndDeleteTemporaryFile DISABLED_CreateAndDeleteTemporaryFile
|
|
||||||
# define MAYBE_CreateAndDeleteFileInTempDir DISABLED_CreateAndDeleteFileInTempDir
|
|
||||||
#else
|
|
||||||
# define MAYBE_CreateAndDeleteTemporaryFile CreateAndDeleteTemporaryFile
|
|
||||||
# define MAYBE_CreateAndDeleteFileInTempDir CreateAndDeleteFileInTempDir
|
|
||||||
#endif // defined(ANGLE_PLATFORM_ANDROID)
|
|
||||||
|
|
||||||
// Test creating and deleting temporary file.
|
|
||||||
TEST(TestUtils, MAYBE_CreateAndDeleteTemporaryFile)
|
|
||||||
{
|
|
||||||
char path[kMaxPath] = {};
|
|
||||||
ASSERT_TRUE(CreateTemporaryFile(path, kMaxPath));
|
|
||||||
ASSERT_TRUE(strlen(path) > 0);
|
|
||||||
|
|
||||||
const char kOutputString[] = "test output";
|
|
||||||
|
|
||||||
FILE *fp = fopen(path, "wt");
|
|
||||||
ASSERT_NE(fp, nullptr);
|
|
||||||
int retval = fputs(kOutputString, fp);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
EXPECT_GE(retval, 0);
|
|
||||||
|
|
||||||
// Test ReadEntireFileToString
|
|
||||||
char actualString[kMaxPath];
|
|
||||||
EXPECT_TRUE(ReadEntireFileToString(path, actualString, kMaxPath));
|
|
||||||
EXPECT_EQ(strcmp(actualString, kOutputString), 0);
|
|
||||||
|
|
||||||
// Delete the temporary file.
|
|
||||||
EXPECT_TRUE(angle::DeleteFile(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Tests creating and deleting a file in the system temp dir.
|
|
||||||
TEST(TestUtils, MAYBE_CreateAndDeleteFileInTempDir)
|
|
||||||
{
|
|
||||||
char tempDir[kMaxPath];
|
|
||||||
ASSERT_TRUE(GetTempDir(tempDir, kMaxPath));
|
|
||||||
|
|
||||||
char path[kMaxPath] = {};
|
|
||||||
ASSERT_TRUE(CreateTemporaryFileInDir(tempDir, path, kMaxPath));
|
|
||||||
ASSERT_TRUE(strlen(path) > 0);
|
|
||||||
|
|
||||||
const char kOutputString[] = "test output";
|
|
||||||
|
|
||||||
FILE *fp = fopen(path, "wt");
|
|
||||||
ASSERT_NE(fp, nullptr);
|
|
||||||
int retval = fputs(kOutputString, fp);
|
|
||||||
fclose(fp);
|
|
||||||
|
|
||||||
EXPECT_GE(retval, 0);
|
|
||||||
|
|
||||||
// Test ReadEntireFileToString
|
|
||||||
char actualString[kMaxPath];
|
|
||||||
EXPECT_TRUE(ReadEntireFileToString(path, actualString, kMaxPath));
|
|
||||||
EXPECT_EQ(strcmp(actualString, kOutputString), 0);
|
|
||||||
|
|
||||||
// Delete the temporary file.
|
|
||||||
EXPECT_TRUE(angle::DeleteFile(path));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Test running an external application and receiving its output
|
|
||||||
TEST(TestUtils, RunApp)
|
|
||||||
{
|
|
||||||
#if defined(ANGLE_PLATFORM_ANDROID)
|
|
||||||
// TODO: android support. http://anglebug.com/3125
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
#if defined(ANGLE_PLATFORM_FUCHSIA)
|
|
||||||
// TODO: fuchsia support. http://anglebug.com/3161
|
|
||||||
return;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
std::string executablePath = GetExecutableDirectory();
|
|
||||||
EXPECT_NE(executablePath, "");
|
|
||||||
executablePath += "/";
|
|
||||||
executablePath += kRunAppHelperExecutable;
|
|
||||||
|
|
||||||
std::vector<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
|
|
|
@ -12,7 +12,6 @@ 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,14 +11,8 @@
|
||||||
#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
|
||||||
|
@ -111,312 +105,6 @@ LONG WINAPI StackTraceCrashHandler(EXCEPTION_POINTERS *e)
|
||||||
// The compiler wants us to return something. This is what we'd do if we didn't _exit().
|
// 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)
|
||||||
|
@ -439,18 +127,13 @@ void WriteDebugMessage(const char *format, ...)
|
||||||
OutputDebugStringA(buffer.data());
|
OutputDebugStringA(buffer.data());
|
||||||
}
|
}
|
||||||
|
|
||||||
void InitCrashHandler(CrashCallback *callback)
|
void InitCrashHandler()
|
||||||
{
|
{
|
||||||
if (callback)
|
SetUnhandledExceptionFilter(StackTraceCrashHandler);
|
||||||
{
|
|
||||||
gCrashHandlerCallback = callback;
|
|
||||||
}
|
|
||||||
SetUnhandledExceptionFilter(CrashHandler);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void TerminateCrashHandler()
|
void TerminateCrashHandler()
|
||||||
{
|
{
|
||||||
gCrashHandlerCallback = nullptr;
|
|
||||||
SetUnhandledExceptionFilter(nullptr);
|
SetUnhandledExceptionFilter(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -461,61 +144,4 @@ 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,10 +11,9 @@
|
||||||
#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);
|
||||||
|
@ -22,15 +21,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;
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Ссылка в новой задаче