Commit 7923e234 by Jamie Madill Committed by Commit Bot

Reland "Add more test_utils functions."

This is a reland of 5fcfcea4 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: 's avatarJamie Madill <jmadill@chromium.org>
parent beacd8c8
......@@ -22,7 +22,7 @@ bool PrependPathToEnvironmentVar(const char *variableName, const char *path)
else
{
buf = path;
buf += GetPathSeparator();
buf += GetPathSeparatorForEnvironmentVar();
buf += oldValue;
newValue = buf.c_str();
}
......
......@@ -22,7 +22,7 @@ bool SetCWD(const char *dirName);
bool SetEnvironmentVar(const char *variableName, const char *value);
bool UnsetEnvironmentVar(const char *variableName);
std::string GetEnvironmentVar(const char *variableName);
const char *GetPathSeparator();
const char *GetPathSeparatorForEnvironmentVar();
bool PrependPathToEnvironmentVar(const char *variableName, const char *path);
bool IsDirectory(const char *filename);
......
......@@ -18,56 +18,6 @@
namespace angle
{
namespace
{
struct ScopedPipe
{
~ScopedPipe()
{
closeEndPoint(0);
closeEndPoint(1);
}
void closeEndPoint(int index)
{
if (fds[index] >= 0)
{
close(fds[index]);
fds[index] = -1;
}
}
int fds[2] = {
-1,
-1,
};
};
void ReadEntireFile(int fd, std::string *out)
{
out->clear();
while (true)
{
char buffer[256];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
// If interrupted, retry.
if (bytesRead < 0 && errno == EINTR)
{
continue;
}
// If failed, or nothing to read, we are done.
if (bytesRead <= 0)
{
break;
}
out->append(buffer, bytesRead);
}
}
} // anonymous namespace
Optional<std::string> GetCWD()
{
std::array<char, 4096> pathBuf;
......@@ -100,117 +50,11 @@ std::string GetEnvironmentVar(const char *variableName)
return (value == nullptr ? std::string() : std::string(value));
}
const char *GetPathSeparator()
const char *GetPathSeparatorForEnvironmentVar()
{
return ":";
}
bool RunApp(const std::vector<const char *> &args,
std::string *stdoutOut,
std::string *stderrOut,
int *exitCodeOut)
{
if (args.size() == 0 || args.back() != nullptr)
{
return false;
}
ScopedPipe stdoutPipe;
ScopedPipe stderrPipe;
// Create pipes for stdout and stderr.
if (stdoutOut && pipe(stdoutPipe.fds) != 0)
{
return false;
}
if (stderrOut && pipe(stderrPipe.fds) != 0)
{
return false;
}
pid_t pid = fork();
if (pid < 0)
{
return false;
}
if (pid == 0)
{
// Child. Execute the application.
// Redirect stdout and stderr to the pipe fds.
if (stdoutOut)
{
if (dup2(stdoutPipe.fds[1], STDOUT_FILENO) < 0)
{
_exit(errno);
}
}
if (stderrOut)
{
if (dup2(stderrPipe.fds[1], STDERR_FILENO) < 0)
{
_exit(errno);
}
}
// Execute the application, which doesn't return unless failed. Note: execv takes argv as
// `char * const *` for historical reasons. It is safe to const_cast it:
//
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
//
// > The statement about argv[] and envp[] being constants is included to make explicit to
// future writers of language bindings that these objects are completely constant. Due to a
// limitation of the ISO C standard, it is not possible to state that idea in standard C.
// Specifying two levels of const- qualification for the argv[] and envp[] parameters for
// the exec functions may seem to be the natural choice, given that these functions do not
// modify either the array of pointers or the characters to which the function points, but
// this would disallow existing correct code. Instead, only the array of pointers is noted
// as constant.
execv(args[0], const_cast<char *const *>(args.data()));
_exit(errno);
}
// Parent. Read child output from the pipes and clean it up.
// Close the write end of the pipes, so EOF can be generated when child exits.
stdoutPipe.closeEndPoint(1);
stderrPipe.closeEndPoint(1);
// Read back the output of the child.
if (stdoutOut)
{
ReadEntireFile(stdoutPipe.fds[0], stdoutOut);
}
if (stderrOut)
{
ReadEntireFile(stderrPipe.fds[0], stderrOut);
}
// Cleanup the child.
int status = 0;
do
{
pid_t changedPid = waitpid(pid, &status, 0);
if (changedPid < 0 && errno == EINTR)
{
continue;
}
if (changedPid < 0)
{
return false;
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
// Retrieve the error code.
if (exitCodeOut)
{
*exitCodeOut = WEXITSTATUS(status);
}
return true;
}
class PosixLibrary : public Library
{
public:
......
......@@ -9,46 +9,11 @@
#include "gtest/gtest.h"
#include "common/system_utils.h"
#include "common/system_utils_unittest_helper.h"
using namespace angle;
namespace
{
#if defined(ANGLE_PLATFORM_WINDOWS)
constexpr char kRunAppHelperExecutable[] = "angle_unittests_helper.exe";
#else
constexpr char kRunAppHelperExecutable[] = "angle_unittests_helper";
#endif
// Transforms various line endings into C/Unix line endings:
//
// - A\nB -> A\nB
// - A\rB -> A\nB
// - A\r\nB -> A\nB
std::string NormalizeNewLines(const std::string &str)
{
std::string result;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == '\r')
{
if (i + 1 < str.size() && str[i + 1] == '\n')
{
++i;
}
result += '\n';
}
else
{
result += str[i];
}
}
return result;
}
// Test getting the executable path
TEST(SystemUtils, ExecutablePath)
{
......@@ -95,48 +60,4 @@ TEST(SystemUtils, Environment)
readback = GetEnvironmentVar(kEnvVarName);
EXPECT_EQ("", readback);
}
// Test running an external application and receiving its output
TEST(SystemUtils, RunApp)
{
#if defined(ANGLE_PLATFORM_ANDROID)
// TODO: android support. http://anglebug.com/3125
return;
#endif
#if defined(ANGLE_PLATFORM_FUCHSIA)
// TODO: fuchsia support. http://anglebug.com/3161
return;
#endif
std::string executablePath = GetExecutableDirectory();
EXPECT_NE(executablePath, "");
executablePath += "/";
executablePath += kRunAppHelperExecutable;
std::vector<const char *> args = {executablePath.c_str(), kRunAppTestArg1, kRunAppTestArg2,
nullptr};
std::string stdoutOutput;
std::string stderrOutput;
int exitCode = EXIT_FAILURE;
// Test that the application can be executed.
bool ranApp = RunApp(args, &stdoutOutput, &stderrOutput, &exitCode);
EXPECT_TRUE(ranApp);
EXPECT_EQ(kRunAppTestStdout, NormalizeNewLines(stdoutOutput));
EXPECT_EQ(kRunAppTestStderr, NormalizeNewLines(stderrOutput));
EXPECT_EQ(EXIT_SUCCESS, exitCode);
// Test that environment variables reach the cild.
bool setEnvDone = SetEnvironmentVar(kRunAppTestEnvVarName, kRunAppTestEnvVarValue);
EXPECT_TRUE(setEnvDone);
ranApp = RunApp(args, &stdoutOutput, &stderrOutput, &exitCode);
EXPECT_TRUE(ranApp);
EXPECT_EQ("", stdoutOutput);
EXPECT_EQ(kRunAppTestEnvVarValue, NormalizeNewLines(stderrOutput));
EXPECT_EQ(EXIT_SUCCESS, exitCode);
}
} // anonymous namespace
......@@ -15,56 +15,6 @@
namespace angle
{
namespace
{
struct ScopedPipe
{
~ScopedPipe()
{
closeReadHandle();
closeWriteHandle();
}
void closeReadHandle()
{
if (readHandle)
{
CloseHandle(readHandle);
readHandle = nullptr;
}
}
void closeWriteHandle()
{
if (writeHandle)
{
CloseHandle(writeHandle);
writeHandle = nullptr;
}
}
HANDLE readHandle = nullptr;
HANDLE writeHandle = nullptr;
};
void ReadEntireFile(HANDLE handle, std::string *out)
{
out->clear();
while (true)
{
char buffer[256];
DWORD bytesRead;
BOOL success = ReadFile(handle, buffer, sizeof(buffer), &bytesRead, nullptr);
if (!success || bytesRead == 0)
{
break;
}
out->append(buffer, bytesRead);
}
}
} // anonymous namespace
std::string GetExecutablePath()
{
std::array<char, MAX_PATH> executableFileBuf;
......@@ -126,7 +76,7 @@ std::string GetEnvironmentVar(const char *variableName)
}
}
const char *GetPathSeparator()
const char *GetPathSeparatorForEnvironmentVar()
{
return ";";
}
......@@ -142,117 +92,6 @@ double GetCurrentTime()
return static_cast<double>(curTime.QuadPart) / frequency.QuadPart;
}
bool RunApp(const std::vector<const char *> &args,
std::string *stdoutOut,
std::string *stderrOut,
int *exitCodeOut)
{
ScopedPipe stdoutPipe;
ScopedPipe stderrPipe;
SECURITY_ATTRIBUTES sa_attr;
// Set the bInheritHandle flag so pipe handles are inherited.
sa_attr.nLength = sizeof(SECURITY_ATTRIBUTES);
sa_attr.bInheritHandle = TRUE;
sa_attr.lpSecurityDescriptor = nullptr;
// Create pipes for stdout and stderr. Ensure the read handles to the pipes are not inherited.
if (stdoutOut && !CreatePipe(&stdoutPipe.readHandle, &stdoutPipe.writeHandle, &sa_attr, 0) &&
!SetHandleInformation(stdoutPipe.readHandle, HANDLE_FLAG_INHERIT, 0))
{
return false;
}
if (stderrOut && !CreatePipe(&stderrPipe.readHandle, &stderrPipe.writeHandle, &sa_attr, 0) &&
!SetHandleInformation(stderrPipe.readHandle, HANDLE_FLAG_INHERIT, 0))
{
return false;
}
// Concat the nicely separated arguments into one string so the application has to reparse it.
// We don't support quotation and spaces in arguments currently.
std::vector<char> commandLineString;
for (const char *arg : args)
{
if (arg)
{
if (!commandLineString.empty())
{
commandLineString.push_back(' ');
}
commandLineString.insert(commandLineString.end(), arg, arg + strlen(arg));
}
}
commandLineString.push_back('\0');
STARTUPINFOA startInfo = {};
startInfo.cb = sizeof(STARTUPINFOA);
startInfo.hStdInput = GetStdHandle(STD_INPUT_HANDLE);
if (stdoutOut)
{
startInfo.hStdOutput = stdoutPipe.writeHandle;
}
else
{
startInfo.hStdError = GetStdHandle(STD_OUTPUT_HANDLE);
}
if (stderrOut)
{
startInfo.hStdError = stderrPipe.writeHandle;
}
else
{
startInfo.hStdError = GetStdHandle(STD_ERROR_HANDLE);
}
if (stderrOut || stdoutOut)
{
startInfo.dwFlags |= STARTF_USESTDHANDLES;
}
// Create the child process.
PROCESS_INFORMATION processInfo = {};
if (!CreateProcessA(nullptr, commandLineString.data(), nullptr, nullptr,
TRUE, // Handles are inherited.
0, nullptr, nullptr, &startInfo, &processInfo))
{
return false;
}
// Close the write end of the pipes, so EOF can be generated when child exits.
stdoutPipe.closeWriteHandle();
stderrPipe.closeWriteHandle();
// Read back the output of the child.
if (stdoutOut)
{
ReadEntireFile(stdoutPipe.readHandle, stdoutOut);
}
if (stderrOut)
{
ReadEntireFile(stderrPipe.readHandle, stderrOut);
}
// Cleanup the child.
bool success = WaitForSingleObject(processInfo.hProcess, INFINITE) == WAIT_OBJECT_0;
if (success)
{
DWORD exitCode = 0;
success = GetExitCodeProcess(processInfo.hProcess, &exitCode);
if (success)
{
*exitCodeOut = static_cast<int>(exitCode);
}
}
CloseHandle(processInfo.hProcess);
CloseHandle(processInfo.hThread);
return success;
}
class Win32Library : public Library
{
public:
......@@ -326,4 +165,5 @@ void BreakDebugger()
{
__debugbreak();
}
} // namespace angle
......@@ -45,8 +45,8 @@ if (!build_with_chromium) {
}
}
angle_executable("angle_unittests_helper") {
sources = angle_unittests_helper_sources
angle_executable("test_utils_unittest_helper") {
sources = test_utils_unittest_helper_sources
deps = [
"${angle_root}:angle_common",
......@@ -73,9 +73,8 @@ config("angle_test_expectations_config") {
angle_static_library("angle_test_expectations") {
public_configs += [ ":angle_test_expectations_config" ]
public_deps = [
"${angle_root}:angle_common",
"${angle_root}:angle_gpu_info_util",
"${angle_root}:angle_util",
"$angle_root:angle_common",
"$angle_root:angle_gpu_info_util",
]
sources = test_expectations_sources
if (is_mac) {
......@@ -95,16 +94,17 @@ angle_test("angle_unittests") {
deps = [
":angle_test_expectations",
"${angle_root}:libANGLE",
"${angle_root}:libfeature_support",
"${angle_root}:preprocessor",
"${angle_root}:translator",
"$angle_root:angle_util_static",
"$angle_root:libANGLE",
"$angle_root:libfeature_support",
"$angle_root:preprocessor",
"$angle_root:translator",
]
if (!is_android && !is_fuchsia) {
# SystemUtils.RunApp, the only unittest using a helper binary, is not supported on these
# platforms yet.
data_deps = [
":angle_unittests_helper",
":test_utils_unittest_helper",
]
}
}
......
......@@ -15,7 +15,6 @@ angle_unittests_sources = [
"../common/matrix_utils_unittest.cpp",
"../common/string_utils_unittest.cpp",
"../common/system_utils_unittest.cpp",
"../common/system_utils_unittest_helper.h",
"../common/utilities_unittest.cpp",
"../common/vector_utils_unittest.cpp",
"../feature_support_util/feature_support_util_unittest.cpp",
......@@ -130,6 +129,8 @@ angle_unittests_sources = [
"../tests/test_utils/ShaderCompileTreeTest.h",
"../tests/test_utils/ShaderCompileTreeTest.cpp",
"../tests/test_utils/ShaderExtensionTest.h",
"../../util/test_utils_unittest.cpp",
"../../util/test_utils_unittest_helper.h",
]
# TODO(jmadill): should probably call this windows sources
......@@ -138,9 +139,9 @@ angle_unittests_hlsl_sources = [
"../tests/compiler_tests/UnrollFlatten_test.cpp",
]
angle_unittests_helper_sources = [
"../common/system_utils_unittest_helper.cpp",
"../common/system_utils_unittest_helper.h",
test_utils_unittest_helper_sources = [
"../../util/test_utils_unittest_helper.cpp",
"../../util/test_utils_unittest_helper.h",
]
if (is_android) {
......
......@@ -151,10 +151,9 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
}
args.push_back(nullptr);
std::string output;
int exitCode;
bool success = RunApp(args, &output, nullptr, &exitCode);
ProcessHandle process(args, true, false);
ASSERT_TRUE(process && process->started());
ASSERT_TRUE(process->finish());
// Restore the current working directory for the next tests.
if (cwd.valid())
......@@ -162,11 +161,11 @@ class GLMark2Benchmark : public testing::TestWithParam<GLMark2BenchmarkTestParam
SetCWD(cwd.value().c_str());
}
ASSERT_TRUE(success);
ASSERT_EQ(EXIT_SUCCESS, exitCode);
ASSERT_EQ(EXIT_SUCCESS, process->getExitCode());
if (!OneFrame())
{
std::string output = process->getStdout();
parseOutput(output, benchmarkName, completeRun);
}
}
......
......@@ -320,15 +320,15 @@ bool RunSeparateProcessesForEachConfig(int *argc, char *argv[])
std::vector<const char *> childArgs = commonArgs;
childArgs.push_back(configStr.c_str());
int exitCode = 0;
if (!RunApp(childArgs, nullptr, nullptr, &exitCode))
ProcessHandle process(childArgs, false, false);
if (!process->started() || !process->finish())
{
std::cerr << "Launching child config " << config << " failed.\n";
}
else if (exitCode != 0)
else if (process->getExitCode() != 0)
{
std::cerr << "Child config " << config << " failed with exit code " << exitCode
<< ".\n";
std::cerr << "Child config " << config << " failed with exit code "
<< process->getExitCode() << ".\n";
success = false;
}
}
......@@ -481,7 +481,7 @@ void ANGLETestBase::ANGLETestSetUp()
{
mSetUpCalled = true;
InitCrashHandler();
InitCrashHandler(nullptr);
gDefaultPlatformMethods.overrideWorkaroundsD3D = TestPlatform_overrideWorkaroundsD3D;
gDefaultPlatformMethods.overrideFeaturesVk = TestPlatform_overrideFeaturesVk;
......
......@@ -43,7 +43,7 @@ void PrintStackBacktrace()
// No implementations yet.
}
void InitCrashHandler()
void InitCrashHandler(CrashCallback *callback)
{
// No implementations yet.
}
......@@ -140,7 +140,7 @@ static constexpr int kSignals[] = {
SIGABRT, SIGBUS, SIGFPE, SIGILL, SIGSEGV, SIGTRAP,
};
void InitCrashHandler()
void InitCrashHandler(CrashCallback *callback)
{
for (int sig : kSignals)
{
......
......@@ -10,10 +10,13 @@
#include <errno.h>
#include <sched.h>
#include <signal.h>
#include <time.h>
#include <unistd.h>
#include <cstdarg>
#include <cstring>
#include "common/debug.h"
#include "common/platform.h"
#if !defined(ANGLE_PLATFORM_FUCHSIA)
......@@ -26,6 +29,222 @@
namespace angle
{
namespace
{
struct ScopedPipe
{
~ScopedPipe()
{
closeEndPoint(0);
closeEndPoint(1);
}
void closeEndPoint(int index)
{
if (fds[index] >= 0)
{
close(fds[index]);
fds[index] = -1;
}
}
bool valid() const { return fds[0] != -1 || fds[1] != -1; }
int fds[2] = {
-1,
-1,
};
};
void ReadEntireFile(int fd, std::string *out)
{
out->clear();
while (true)
{
char buffer[256];
ssize_t bytesRead = read(fd, buffer, sizeof(buffer));
// If interrupted, retry.
if (bytesRead < 0 && errno == EINTR)
{
continue;
}
// If failed, or nothing to read, we are done.
if (bytesRead <= 0)
{
break;
}
out->append(buffer, bytesRead);
}
}
class PosixProcess : public Process
{
public:
PosixProcess(const std::vector<const char *> &commandLineArgs,
bool captureStdOut,
bool captureStdErr)
{
#if defined(ANGLE_PLATFORM_FUCHSIA)
ANGLE_UNUSED_VARIABLE(ReadEntireFile);
ANGLE_UNUSED_VARIABLE(mExitCode);
ANGLE_UNUSED_VARIABLE(mPID);
#else
if (commandLineArgs.empty() || commandLineArgs.back() != nullptr)
{
return;
}
// Create pipes for stdout and stderr.
if (captureStdOut && pipe(mStdoutPipe.fds) != 0)
{
return;
}
if (captureStdErr && pipe(mStderrPipe.fds) != 0)
{
return;
}
mPID = fork();
if (mPID < 0)
{
return;
}
mStarted = true;
if (mPID == 0)
{
// Child. Execute the application.
// Redirect stdout and stderr to the pipe fds.
if (captureStdOut)
{
if (dup2(mStdoutPipe.fds[1], STDOUT_FILENO) < 0)
{
_exit(errno);
}
}
if (captureStdErr)
{
if (dup2(mStderrPipe.fds[1], STDERR_FILENO) < 0)
{
_exit(errno);
}
}
// Execute the application, which doesn't return unless failed. Note: execv takes argv
// as `char * const *` for historical reasons. It is safe to const_cast it:
//
// http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
//
// > The statement about argv[] and envp[] being constants is included to make explicit
// to future writers of language bindings that these objects are completely constant.
// Due to a limitation of the ISO C standard, it is not possible to state that idea in
// standard C. Specifying two levels of const- qualification for the argv[] and envp[]
// parameters for the exec functions may seem to be the natural choice, given that these
// functions do not modify either the array of pointers or the characters to which the
// function points, but this would disallow existing correct code. Instead, only the
// array of pointers is noted as constant.
execv(commandLineArgs[0], const_cast<char *const *>(commandLineArgs.data()));
_exit(errno);
}
// Parent continues execution.
#endif // defined(ANGLE_PLATFORM_FUCHSIA)
}
~PosixProcess() override {}
bool started() override { return mStarted; }
bool finish() override
{
if (!mStarted)
{
return false;
}
#if defined(ANGLE_PLATFORM_FUCHSIA)
return false;
#else
// Close the write end of the pipes, so EOF can be generated when child exits.
// Then read back the output of the child.
if (mStdoutPipe.valid())
{
mStdoutPipe.closeEndPoint(1);
ReadEntireFile(mStdoutPipe.fds[0], &mStdout);
}
if (mStderrPipe.valid())
{
mStderrPipe.closeEndPoint(1);
ReadEntireFile(mStderrPipe.fds[0], &mStderr);
}
// Cleanup the child.
int status = 0;
do
{
pid_t changedPid = waitpid(mPID, &status, 0);
if (changedPid < 0 && errno == EINTR)
{
continue;
}
if (changedPid < 0)
{
return false;
}
} while (!WIFEXITED(status) && !WIFSIGNALED(status));
// Retrieve the error code.
mExitCode = WEXITSTATUS(status);
return true;
#endif // defined(ANGLE_PLATFORM_FUCHSIA)
}
bool finished() override
{
if (!mStarted)
{
return false;
}
return (::kill(mPID, 0) != 0);
}
int getExitCode() override { return 0; }
bool kill() override
{
if (!mStarted)
{
return false;
}
if (finished())
{
return true;
}
return (::kill(mPID, SIGTERM) == 0);
}
private:
bool mStarted = false;
ScopedPipe mStdoutPipe;
ScopedPipe mStderrPipe;
int mExitCode = 0;
pid_t mPID = -1;
};
std::string TempFileName()
{
return std::string(".angle.XXXXXX");
}
} // anonymous namespace
void Sleep(unsigned int milliseconds)
{
// On Windows Sleep(0) yields while it isn't guaranteed by Posix's sleep
......@@ -94,4 +313,68 @@ bool StabilizeCPUForBenchmarking()
return false;
#endif
}
bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen)
{
const char *tmp = getenv("TMPDIR");
if (tmp)
{
strncpy(tempDirOut, tmp, maxDirNameLen);
return true;
}
#if defined(ANGLE_PLATFORM_ANDROID)
// TODO(jmadill): Android support. http://anglebug.com/3162
// return PathService::Get(DIR_CACHE, path);
return false;
#else
strncpy(tempDirOut, "/tmp", maxDirNameLen);
return true;
#endif
}
bool CreateTemporaryFileInDir(const char *dir, char *tempFileNameOut, uint32_t maxFileNameLen)
{
std::string tempFile = TempFileName();
sprintf(tempFileNameOut, "%s/%s", dir, tempFile.c_str());
int fd = mkstemp(tempFileNameOut);
close(fd);
return fd != -1;
}
bool DeleteFile(const char *path)
{
return unlink(path) == 0;
}
Process *LaunchProcess(const std::vector<const char *> &args,
bool captureStdout,
bool captureStderr)
{
return new PosixProcess(args, captureStdout, captureStderr);
}
int NumberOfProcessors()
{
// sysconf returns the number of "logical" (not "physical") processors on both
// Mac and Linux. So we get the number of max available "logical" processors.
//
// Note that the number of "currently online" processors may be fewer than the
// returned value of NumberOfProcessors(). On some platforms, the kernel may
// make some processors offline intermittently, to save power when system
// loading is low.
//
// One common use case that needs to know the processor count is to create
// optimal number of threads for optimization. It should make plan according
// to the number of "max available" processors instead of "currently online"
// ones. The kernel should be smart enough to make all processors online when
// it has sufficient number of threads waiting to run.
long res = sysconf(_SC_NPROCESSORS_CONF);
if (res == -1)
{
return 1;
}
return static_cast<int>(res);
}
} // namespace angle
......@@ -4,33 +4,26 @@
// found in the LICENSE file.
//
#include "shader_utils.h"
#include "util/shader_utils.h"
#include <cstring>
#include <fstream>
#include <iostream>
#include <vector>
#include "util/test_utils.h"
namespace
{
std::string ReadFileToString(const std::string &source)
bool ReadEntireFile(const std::string &filePath, std::string *contentsOut)
{
std::ifstream stream(source.c_str());
if (!stream)
{
std::cerr << "Failed to load shader file: " << source;
return "";
}
std::string result;
stream.seekg(0, std::ios::end);
result.reserve(static_cast<unsigned int>(stream.tellg()));
stream.seekg(0, std::ios::beg);
result.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
return result;
constexpr uint32_t kMaxBufferSize = 2000;
char buffer[kMaxBufferSize] = {};
if (!angle::ReadEntireFileToString(filePath.c_str(), buffer, kMaxBufferSize) ||
strlen(buffer) == 0)
return false;
*contentsOut = buffer;
return true;
}
GLuint CompileProgramInternal(const char *vsSource,
......@@ -124,9 +117,10 @@ GLuint CompileShader(GLenum type, const char *source)
GLuint CompileShaderFromFile(GLenum type, const std::string &sourcePath)
{
std::string source = ReadFileToString(sourcePath);
if (source.empty())
std::string source;
if (!ReadEntireFile(sourcePath, &source))
{
std::cerr << "Error reading shader file: " << sourcePath << "\n";
return 0;
}
......@@ -214,10 +208,17 @@ GLuint CompileProgramWithGS(const char *vsSource, const char *gsSource, const ch
GLuint CompileProgramFromFiles(const std::string &vsPath, const std::string &fsPath)
{
std::string vsSource = ReadFileToString(vsPath);
std::string fsSource = ReadFileToString(fsPath);
if (vsSource.empty() || fsSource.empty())
std::string vsSource;
if (!ReadEntireFile(vsPath, &vsSource))
{
std::cerr << "Error reading shader: " << vsPath << "\n";
return 0;
}
std::string fsSource;
if (!ReadEntireFile(fsPath, &fsSource))
{
std::cerr << "Error reading shader: " << fsPath << "\n";
return 0;
}
......
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
//
// system_utils: Defines common utility functions
#include "util/test_utils.h"
#include <cstring>
#include <fstream>
namespace angle
{
bool CreateTemporaryFile(char *tempFileNameOut, uint32_t maxFileNameLen)
{
constexpr uint32_t kMaxPath = 1000u;
char tempPath[kMaxPath];
if (!GetTempDir(tempPath, kMaxPath))
return false;
return CreateTemporaryFileInDir(tempPath, tempFileNameOut, maxFileNameLen);
}
bool GetFileSize(const char *filePath, uint32_t *sizeOut)
{
std::ifstream stream(filePath);
if (!stream)
{
return false;
}
stream.seekg(0, std::ios::end);
*sizeOut = static_cast<uint32_t>(stream.tellg());
return true;
}
bool ReadEntireFileToString(const char *filePath, char *contentsOut, uint32_t maxLen)
{
std::ifstream stream(filePath);
if (!stream)
{
return false;
}
std::string contents;
stream.seekg(0, std::ios::end);
contents.reserve(static_cast<unsigned int>(stream.tellg()));
stream.seekg(0, std::ios::beg);
contents.assign((std::istreambuf_iterator<char>(stream)), std::istreambuf_iterator<char>());
strncpy(contentsOut, contents.c_str(), maxLen);
return true;
}
// static
Process::~Process() = default;
ProcessHandle::ProcessHandle() : mProcess(nullptr) {}
ProcessHandle::ProcessHandle(Process *process) : mProcess(process) {}
ProcessHandle::ProcessHandle(const std::vector<const char *> &args,
bool captureStdout,
bool captureStderr)
: mProcess(LaunchProcess(args, captureStdout, captureStderr))
{}
ProcessHandle::~ProcessHandle()
{
reset();
}
ProcessHandle::ProcessHandle(ProcessHandle &&other) : mProcess(other.mProcess)
{
other.mProcess = nullptr;
}
ProcessHandle &ProcessHandle::operator=(ProcessHandle &&rhs)
{
std::swap(mProcess, rhs.mProcess);
return *this;
}
void ProcessHandle::reset()
{
if (mProcess)
{
delete mProcess;
mProcess = nullptr;
}
}
} // namespace angle
......@@ -9,9 +9,12 @@
#ifndef UTIL_TEST_UTILS_H_
#define UTIL_TEST_UTILS_H_
#include <functional>
#include <string>
#include <vector>
#include "common/angleutils.h"
#include "util/Timer.h"
#include "util/util_export.h"
namespace angle
......@@ -28,12 +31,96 @@ ANGLE_UTIL_EXPORT void WriteDebugMessage(const char *format, ...);
ANGLE_UTIL_EXPORT bool StabilizeCPUForBenchmarking();
// Set a crash handler to print stack traces.
ANGLE_UTIL_EXPORT void InitCrashHandler();
using CrashCallback = std::function<void()>;
ANGLE_UTIL_EXPORT void InitCrashHandler(CrashCallback *callback);
ANGLE_UTIL_EXPORT void TerminateCrashHandler();
// Print a stack back trace.
ANGLE_UTIL_EXPORT void PrintStackBacktrace();
// Get temporary directory.
ANGLE_UTIL_EXPORT bool GetTempDir(char *tempDirOut, uint32_t maxDirNameLen);
// Creates a temporary file. The full path is placed in |path|, and the
// function returns true if was successful in creating the file. The file will
// be empty and all handles closed after this function returns.
ANGLE_UTIL_EXPORT bool CreateTemporaryFile(char *tempFileNameOut, uint32_t maxFileNameLen);
// Same as CreateTemporaryFile but the file is created in |dir|.
ANGLE_UTIL_EXPORT bool CreateTemporaryFileInDir(const char *dir,
char *tempFileNameOut,
uint32_t maxFileNameLen);
// Deletes a file or directory.
ANGLE_UTIL_EXPORT bool DeleteFile(const char *path);
// Reads a file contents into a string.
ANGLE_UTIL_EXPORT bool ReadEntireFileToString(const char *filePath,
char *contentsOut,
uint32_t maxLen);
// Compute a file's size.
ANGLE_UTIL_EXPORT bool GetFileSize(const char *filePath, uint32_t *sizeOut);
class ProcessHandle;
class ANGLE_UTIL_EXPORT Process : angle::NonCopyable
{
public:
virtual bool started() = 0;
virtual bool finished() = 0;
virtual bool finish() = 0;
virtual bool kill() = 0;
virtual int getExitCode() = 0;
double getElapsedTimeSeconds() const { return mTimer.getElapsedTime(); }
const std::string &getStdout() const { return mStdout; }
const std::string &getStderr() const { return mStderr; }
protected:
friend class ProcessHandle;
virtual ~Process();
Timer mTimer;
std::string mStdout;
std::string mStderr;
};
class ANGLE_UTIL_EXPORT ProcessHandle final : angle::NonCopyable
{
public:
ProcessHandle();
ProcessHandle(Process *process);
ProcessHandle(const std::vector<const char *> &args, bool captureStdout, bool captureStderr);
~ProcessHandle();
ProcessHandle(ProcessHandle &&other);
ProcessHandle &operator=(ProcessHandle &&rhs);
Process *operator->() { return mProcess; }
const Process *operator->() const { return mProcess; }
operator bool() const { return mProcess != nullptr; }
void reset();
private:
Process *mProcess;
};
// Launch a process and optionally get the output. Uses a vector of c strings as command line
// arguments to the child process. Returns a Process handle which can be used to retrieve
// the stdout and stderr outputs as well as the exit code.
//
// Pass false for stdoutOut/stderrOut if you don't need to capture them.
//
// On success, returns a Process pointer with started() == true.
// On failure, returns a Process pointer with started() == false.
ANGLE_UTIL_EXPORT Process *LaunchProcess(const std::vector<const char *> &args,
bool captureStdout,
bool captureStderr);
ANGLE_UTIL_EXPORT int NumberOfProcessors();
} // namespace angle
#endif // UTIL_TEST_UTILS_H_
//
// Copyright 2019 The ANGLE Project Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// test_utils_unittest.cpp: Unit tests for ANGLE's test utility functions
#include "gtest/gtest.h"
#include "common/system_utils.h"
#include "util/Timer.h"
#include "util/test_utils.h"
#include "util/test_utils_unittest_helper.h"
using namespace angle;
namespace
{
#if defined(ANGLE_PLATFORM_WINDOWS)
constexpr char kRunAppHelperExecutable[] = "test_utils_unittest_helper.exe";
#else
constexpr char kRunAppHelperExecutable[] = "test_utils_unittest_helper";
#endif
// Transforms various line endings into C/Unix line endings:
//
// - A\nB -> A\nB
// - A\rB -> A\nB
// - A\r\nB -> A\nB
std::string NormalizeNewLines(const std::string &str)
{
std::string result;
for (size_t i = 0; i < str.size(); ++i)
{
if (str[i] == '\r')
{
if (i + 1 < str.size() && str[i + 1] == '\n')
{
++i;
}
result += '\n';
}
else
{
result += str[i];
}
}
return result;
}
// Tests that Sleep() actually waits some time.
TEST(TestUtils, Sleep)
{
Timer timer;
timer.start();
angle::Sleep(500);
timer.stop();
// Use a slightly fuzzy range
EXPECT_GT(timer.getElapsedTime(), 0.48);
}
constexpr uint32_t kMaxPath = 1000;
// Temporary file creation is not supported on Android right now.
#if defined(ANGLE_PLATFORM_ANDROID)
# define MAYBE_CreateAndDeleteTemporaryFile DISABLED_CreateAndDeleteTemporaryFile
# define MAYBE_CreateAndDeleteFileInTempDir DISABLED_CreateAndDeleteFileInTempDir
#else
# define MAYBE_CreateAndDeleteTemporaryFile CreateAndDeleteTemporaryFile
# define MAYBE_CreateAndDeleteFileInTempDir CreateAndDeleteFileInTempDir
#endif // defined(ANGLE_PLATFORM_ANDROID)
// Test creating and deleting temporary file.
TEST(TestUtils, MAYBE_CreateAndDeleteTemporaryFile)
{
char path[kMaxPath] = {};
ASSERT_TRUE(CreateTemporaryFile(path, kMaxPath));
ASSERT_TRUE(strlen(path) > 0);
const char kOutputString[] = "test output";
FILE *fp = fopen(path, "wt");
ASSERT_NE(fp, nullptr);
int retval = fputs(kOutputString, fp);
fclose(fp);
EXPECT_GE(retval, 0);
// Test ReadEntireFileToString
char actualString[kMaxPath];
EXPECT_TRUE(ReadEntireFileToString(path, actualString, kMaxPath));
EXPECT_EQ(strcmp(actualString, kOutputString), 0);
// Delete the temporary file.
EXPECT_TRUE(angle::DeleteFile(path));
}
// Tests creating and deleting a file in the system temp dir.
TEST(TestUtils, MAYBE_CreateAndDeleteFileInTempDir)
{
char tempDir[kMaxPath];
ASSERT_TRUE(GetTempDir(tempDir, kMaxPath));
char path[kMaxPath] = {};
ASSERT_TRUE(CreateTemporaryFileInDir(tempDir, path, kMaxPath));
ASSERT_TRUE(strlen(path) > 0);
const char kOutputString[] = "test output";
FILE *fp = fopen(path, "wt");
ASSERT_NE(fp, nullptr);
int retval = fputs(kOutputString, fp);
fclose(fp);
EXPECT_GE(retval, 0);
// Test ReadEntireFileToString
char actualString[kMaxPath];
EXPECT_TRUE(ReadEntireFileToString(path, actualString, kMaxPath));
EXPECT_EQ(strcmp(actualString, kOutputString), 0);
// Delete the temporary file.
EXPECT_TRUE(angle::DeleteFile(path));
}
// Test running an external application and receiving its output
TEST(TestUtils, RunApp)
{
#if defined(ANGLE_PLATFORM_ANDROID)
// TODO: android support. http://anglebug.com/3125
return;
#endif
#if defined(ANGLE_PLATFORM_FUCHSIA)
// TODO: fuchsia support. http://anglebug.com/3161
return;
#endif
std::string executablePath = GetExecutableDirectory();
EXPECT_NE(executablePath, "");
executablePath += "/";
executablePath += kRunAppHelperExecutable;
std::vector<const char *> args = {executablePath.c_str(), kRunAppTestArg1, kRunAppTestArg2,
nullptr};
// Test that the application can be executed.
{
ProcessHandle process(args, true, true);
EXPECT_TRUE(process->started());
EXPECT_TRUE(process->finish());
EXPECT_TRUE(process->finished());
EXPECT_EQ(kRunAppTestStdout, NormalizeNewLines(process->getStdout()));
EXPECT_EQ(kRunAppTestStderr, NormalizeNewLines(process->getStderr()));
EXPECT_EQ(EXIT_SUCCESS, process->getExitCode());
}
// Test that environment variables reach the cild.
{
bool setEnvDone = SetEnvironmentVar(kRunAppTestEnvVarName, kRunAppTestEnvVarValue);
EXPECT_TRUE(setEnvDone);
ProcessHandle process(LaunchProcess(args, true, true));
EXPECT_TRUE(process->started());
EXPECT_TRUE(process->finish());
EXPECT_EQ("", process->getStdout());
EXPECT_EQ(kRunAppTestEnvVarValue, NormalizeNewLines(process->getStderr()));
EXPECT_EQ(EXIT_SUCCESS, process->getExitCode());
}
}
// Verify that NumberOfProcessors returns something sane.
TEST(TestUtils, NumberOfProcessors)
{
int numProcs = angle::NumberOfProcessors();
EXPECT_GT(numProcs, 0);
EXPECT_LT(numProcs, 1000);
}
} // namespace
......@@ -5,10 +5,12 @@
// system_utils_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest
#include "common/system_utils_unittest_helper.h"
#include <string.h>
#include "test_utils_unittest_helper.h"
#include "common/system_utils.h"
#include <string.h>
int main(int argc, char **argv)
{
if (argc != 3 || strcmp(argv[1], kRunAppTestArg1) != 0 || strcmp(argv[2], kRunAppTestArg2) != 0)
......
......@@ -12,6 +12,7 @@ util_sources = [
"util/random_utils.h",
"util/shader_utils.cpp",
"util/shader_utils.h",
"util/test_utils.cpp",
"util/test_utils.h",
"util/util_export.h",
"util/util_gl.h",
......
......@@ -11,9 +11,10 @@
#include <windows.h>
#include <array>
#include "util/random_utils.h"
namespace angle
{
void SetLowPriorityProcess()
{
SetPriorityClass(GetCurrentProcess(), BELOW_NORMAL_PRIORITY_CLASS);
......@@ -21,15 +22,15 @@ void SetLowPriorityProcess()
bool StabilizeCPUForBenchmarking()
{
if (SetThreadAffinityMask(GetCurrentThread(), 1) == 0)
if (::SetThreadAffinityMask(::GetCurrentThread(), 1) == 0)
{
return false;
}
if (SetPriorityClass(GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE)
if (::SetPriorityClass(::GetCurrentProcess(), REALTIME_PRIORITY_CLASS) == FALSE)
{
return false;
}
if (SetThreadPriority(GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE)
if (::SetThreadPriority(::GetCurrentThread(), THREAD_PRIORITY_TIME_CRITICAL) == FALSE)
{
return false;
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment