Commit a9f89313 by Shahbaz Youssefi Committed by Commit Bot

Reland "Add system util to execute app and retrieve its output"

This reverts commit fe14b2e5. Reason for revert: failing test is reworked not to run angle_unittests itself, but another binary. Previously, this test was calling angle_unittests itself, but with a different target. On the bots, that was in turn calling angle_unittests with even more arguments, including the addition of a log file location. Under some configurations, this separate process was thus trying to access files that were already opened by the parent process, leading to a test failure. In this CL, a new helper executable is created for the sake of this unittest. > Revert "Add system util to execute app and retrieve its output" > > This reverts commit c63d9552. > > Reason for revert: Test fails on Win7 > > Original change's description: > > Add system util to execute app and retrieve its output > > > > This will be useful to run external applications, such as benchmarks, > > and process their output. > > > > Bug: angleproject:3125 > > Change-Id: Ic13c69f2e034f4b47498fb2f299c62423c355c4a > > Reviewed-on: https://chromium-review.googlesource.com/c/1452534 > > Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> > > Reviewed-by: Jamie Madill <jmadill@google.com> Bug: angleproject:3125, angleproject:3168 Change-Id: I74815750484a79f33c36e0b4f941d4dd98f99aa5 Reviewed-on: https://chromium-review.googlesource.com/c/1487631 Commit-Queue: Shahbaz Youssefi <syoussefi@chromium.org> Reviewed-by: 's avatarJamie Madill <jmadill@google.com> Reviewed-by: 's avatarYuly Novikov <ynovikov@chromium.org>
parent 76bd848c
...@@ -25,6 +25,15 @@ std::string GetEnvironmentVar(const char *variableName); ...@@ -25,6 +25,15 @@ std::string GetEnvironmentVar(const char *variableName);
const char *GetPathSeparator(); const char *GetPathSeparator();
bool PrependPathToEnvironmentVar(const char *variableName, const char *path); bool PrependPathToEnvironmentVar(const char *variableName, const char *path);
// Run an application and get the output. Gets a nullptr-terminated set of args to execute the
// application with, and returns the stdout and stderr outputs as well as the exit code.
//
// Returns false if it fails to actually execute the application.
bool RunApp(const std::vector<const char *> &args,
std::string *stdoutOut,
std::string *stderrOut,
int *exitCodeOut);
class Library : angle::NonCopyable class Library : angle::NonCopyable
{ {
public: public:
......
...@@ -11,10 +11,68 @@ ...@@ -11,10 +11,68 @@
#include <array> #include <array>
#include <dlfcn.h> #include <dlfcn.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h> #include <unistd.h>
// On mac, environ is not declared anywhere:
// https://stackoverflow.com/a/31347357/912144
#if defined(ANGLE_PLATFORM_APPLE)
extern char **environ;
#endif
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;
...@@ -52,6 +110,112 @@ const char *GetPathSeparator() ...@@ -52,6 +110,112 @@ 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: execve 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.
execve(args[0], const_cast<char *const *>(args.data()), environ);
_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:
......
//
// 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_unittest.cpp: Unit tests for ANGLE's system utility functions
#include "gmock/gmock.h"
#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)
{
#if defined(ANGLE_PLATFORM_FUCHSIA)
// TODO: fuchsia support. http://anglebug.com/3161
return;
#endif
std::string executablePath = GetExecutablePath();
EXPECT_NE("", executablePath);
}
// Test getting the executable directory
TEST(SystemUtils, ExecutableDir)
{
#if defined(ANGLE_PLATFORM_FUCHSIA)
// TODO: fuchsia support. http://anglebug.com/3161
return;
#endif
std::string executableDir = GetExecutableDirectory();
EXPECT_NE("", executableDir);
std::string executablePath = GetExecutablePath();
EXPECT_LT(executableDir.size(), executablePath.size());
EXPECT_EQ(0, strncmp(executableDir.c_str(), executablePath.c_str(), executableDir.size()));
}
// Test setting environment variables
TEST(SystemUtils, Environment)
{
constexpr char kEnvVarName[] = "UNITTEST_ENV_VARIABLE";
constexpr char kEnvVarValue[] = "The quick brown fox jumps over the lazy dog";
bool setEnvDone = SetEnvironmentVar(kEnvVarName, kEnvVarValue);
EXPECT_TRUE(setEnvDone);
std::string readback = GetEnvironmentVar(kEnvVarName);
EXPECT_EQ(kEnvVarValue, readback);
bool unsetEnvDone = UnsetEnvironmentVar(kEnvVarName);
EXPECT_TRUE(unsetEnvDone);
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
//
// 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_unittest_helper.cpp: Helper to the SystemUtils.RunApp unittest
#include "common/system_utils_unittest_helper.h"
#include <string.h>
#include "common/system_utils.h"
int main(int argc, char **argv)
{
if (argc != 3 || strcmp(argv[1], kRunAppTestArg1) != 0 || strcmp(argv[2], kRunAppTestArg2) != 0)
{
fprintf(stderr, "Expected command line:\n%s %s %s\n", argv[0], kRunAppTestArg1,
kRunAppTestArg2);
return EXIT_FAILURE;
}
std::string env = angle::GetEnvironmentVar(kRunAppTestEnvVarName);
if (env == "")
{
printf("%s", kRunAppTestStdout);
fprintf(stderr, "%s", kRunAppTestStderr);
}
else
{
fprintf(stderr, "%s", env.c_str());
}
return EXIT_SUCCESS;
}
//
// 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_unittest_helper.h: Constants used by the SystemUtils.RunApp unittest
#ifndef COMMON_SYSTEM_UTILS_UNITTEST_HELPER_H_
#define COMMON_SYSTEM_UTILS_UNITTEST_HELPER_H_
namespace
{
constexpr char kRunAppTestEnvVarName[] = "RUN_APP_TEST_ENV";
constexpr char kRunAppTestEnvVarValue[] = "RunAppTest environment variable value\n";
constexpr char kRunAppTestStdout[] = "RunAppTest stdout test\n";
constexpr char kRunAppTestStderr[] = "RunAppTest stderr test\n .. that expands multiple lines\n";
constexpr char kRunAppTestArg1[] = "--expected-arg1";
constexpr char kRunAppTestArg2[] = "expected_arg2";
} // anonymous namespace
#endif // COMMON_SYSTEM_UTILS_UNITTEST_HELPER_H_
...@@ -15,6 +15,56 @@ ...@@ -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;
...@@ -81,6 +131,113 @@ const char *GetPathSeparator() ...@@ -81,6 +131,113 @@ const char *GetPathSeparator()
return ";"; return ";";
} }
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);
}
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:
......
...@@ -90,6 +90,14 @@ angle_tests_main("angle_perftests_main") { ...@@ -90,6 +90,14 @@ angle_tests_main("angle_perftests_main") {
main = "angle_perftests_main" main = "angle_perftests_main"
} }
angle_test("angle_unittests_helper") {
sources = angle_unittests_helper_sources
deps = [
"${angle_root}:angle_common",
]
}
angle_test("angle_unittests") { angle_test("angle_unittests") {
sources = angle_unittests_sources sources = angle_unittests_sources
...@@ -105,6 +113,13 @@ angle_test("angle_unittests") { ...@@ -105,6 +113,13 @@ angle_test("angle_unittests") {
"${angle_root}:libfeature_support${angle_libs_suffix}", "${angle_root}:libfeature_support${angle_libs_suffix}",
":angle_unittests_main", ":angle_unittests_main",
] ]
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",
]
}
} }
if (is_win || is_linux || is_mac || is_android || is_fuchsia) { if (is_win || is_linux || is_mac || is_android || is_fuchsia) {
......
...@@ -14,6 +14,8 @@ angle_unittests_sources = [ ...@@ -14,6 +14,8 @@ angle_unittests_sources = [
"../common/mathutil_unittest.cpp", "../common/mathutil_unittest.cpp",
"../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_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",
...@@ -133,3 +135,8 @@ angle_unittests_hlsl_sources = [ ...@@ -133,3 +135,8 @@ angle_unittests_hlsl_sources = [
"../tests/compiler_tests/HLSLOutput_test.cpp", "../tests/compiler_tests/HLSLOutput_test.cpp",
"../tests/compiler_tests/UnrollFlatten_test.cpp", "../tests/compiler_tests/UnrollFlatten_test.cpp",
] ]
angle_unittests_helper_sources = [
"../common/system_utils_unittest_helper.cpp",
"../common/system_utils_unittest_helper.h",
]
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