Commit c63d9552 by Shahbaz Youssefi Committed by Commit Bot

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: 's avatarJamie Madill <jmadill@google.com>
parent f78131da
...@@ -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"
using namespace angle;
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";
// 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 = GetExecutablePath();
EXPECT_NE(executablePath, "");
std::vector<const char *> args = {executablePath.c_str(),
"--gtest_filter=SystemUtils.RunAppTestTarget", 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);
// Note that stdout includes gtest output as well.
EXPECT_NE(std::string::npos, NormalizeNewLines(stdoutOutput).find(kRunAppTestStdout));
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(kRunAppTestEnvVarValue, NormalizeNewLines(stderrOutput));
EXPECT_EQ(EXIT_SUCCESS, exitCode);
}
// Not an actual test. Used as an application with controlled output by the RunApp test.
TEST(SystemUtils, RunAppTestTarget)
{
std::string env = GetEnvironmentVar(kRunAppTestEnvVarName);
if (env == "")
{
printf("%s", kRunAppTestStdout);
fprintf(stderr, "%s", kRunAppTestStderr);
}
else
{
fprintf(stderr, "%s", env.c_str());
}
}
} // anonymous namespace
...@@ -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:
......
...@@ -13,6 +13,7 @@ angle_unittests_sources = [ ...@@ -13,6 +13,7 @@ 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/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",
......
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