From 28abe1bf4d8c16af92e1ef3dc1f267399f0690b9 Mon Sep 17 00:00:00 2001 From: Oliver Reiche Date: Mon, 7 Mar 2022 18:23:53 +0100 Subject: SystemCommand: Move to new module "src/buildtool/system" --- src/buildtool/execution_api/local/TARGETS | 2 +- src/buildtool/execution_api/local/local_action.cpp | 2 +- src/buildtool/file_system/TARGETS | 11 -- src/buildtool/file_system/system_command.hpp | 202 --------------------- src/buildtool/system/TARGETS | 12 ++ src/buildtool/system/system_command.hpp | 202 +++++++++++++++++++++ test/buildtool/TARGETS | 1 + test/buildtool/file_system/TARGETS | 15 +- test/buildtool/file_system/system_command.test.cpp | 115 ------------ test/buildtool/system/TARGETS | 13 ++ test/buildtool/system/system_command.test.cpp | 115 ++++++++++++ 11 files changed, 347 insertions(+), 343 deletions(-) delete mode 100644 src/buildtool/file_system/system_command.hpp create mode 100644 src/buildtool/system/TARGETS create mode 100644 src/buildtool/system/system_command.hpp delete mode 100644 test/buildtool/file_system/system_command.test.cpp create mode 100644 test/buildtool/system/TARGETS create mode 100644 test/buildtool/system/system_command.test.cpp diff --git a/src/buildtool/execution_api/local/TARGETS b/src/buildtool/execution_api/local/TARGETS index b3e54597..398f00e4 100644 --- a/src/buildtool/execution_api/local/TARGETS +++ b/src/buildtool/execution_api/local/TARGETS @@ -27,8 +27,8 @@ , ["src/buildtool/execution_api/common", "common"] , ["src/buildtool/execution_api/bazel_msg", "bazel_msg_factory"] , ["src/buildtool/file_system", "file_system_manager"] - , ["src/buildtool/file_system", "system_command"] , ["src/buildtool/file_system", "object_type"] + , ["src/buildtool/system", "system_command"] , ["src/buildtool/logging", "logging"] ] , "stage": ["src", "buildtool", "execution_api", "local"] diff --git a/src/buildtool/execution_api/local/local_action.cpp b/src/buildtool/execution_api/local/local_action.cpp index eac6ede8..37c3c085 100644 --- a/src/buildtool/execution_api/local/local_action.cpp +++ b/src/buildtool/execution_api/local/local_action.cpp @@ -8,7 +8,7 @@ #include "src/buildtool/execution_api/local/local_response.hpp" #include "src/buildtool/file_system/file_system_manager.hpp" #include "src/buildtool/file_system/object_type.hpp" -#include "src/buildtool/file_system/system_command.hpp" +#include "src/buildtool/system/system_command.hpp" namespace { diff --git a/src/buildtool/file_system/TARGETS b/src/buildtool/file_system/TARGETS index 478b5903..ef817912 100644 --- a/src/buildtool/file_system/TARGETS +++ b/src/buildtool/file_system/TARGETS @@ -15,17 +15,6 @@ ] , "stage": ["src", "buildtool", "file_system"] } -, "system_command": - { "type": ["@", "rules", "CC", "library"] - , "name": ["system_command"] - , "hdrs": ["system_command.hpp"] - , "deps": - [ "file_system_manager" - , ["src/buildtool/logging", "logging"] - , ["@", "gsl-lite", "", "gsl-lite"] - ] - , "stage": ["src", "buildtool", "file_system"] - } , "jsonfs": { "type": ["@", "rules", "CC", "library"] , "name": ["jsonfs"] diff --git a/src/buildtool/file_system/system_command.hpp b/src/buildtool/file_system/system_command.hpp deleted file mode 100644 index 66470ade..00000000 --- a/src/buildtool/file_system/system_command.hpp +++ /dev/null @@ -1,202 +0,0 @@ -#ifndef INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_EXECUTION_SYSTEM_HPP -#define INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_EXECUTION_SYSTEM_HPP - -#include -#include -#include // for strerror() -#include -#include -#include -#include -#include -#include - -#include -#include - -#include "gsl-lite/gsl-lite.hpp" -#include "src/buildtool/file_system/file_system_manager.hpp" -#include "src/buildtool/logging/logger.hpp" - -/// \brief Execute system commands and obtain stdout, stderr and return value. -/// Subsequent commands are context free and are not affected by previous -/// commands. This class is not thread-safe. -class SystemCommand { - public: - struct ExecOutput { - int return_value{}; - std::filesystem::path stdout_file{}; - std::filesystem::path stderr_file{}; - }; - - /// \brief Create execution system with name. - explicit SystemCommand(std::string name) : logger_{std::move(name)} {} - - /// \brief Execute command and arguments. - /// \param argv argv vector with the command to execute - /// \param env Environment variables set for execution. - /// \param cwd Working directory for execution. - /// \param tmpdir Temporary directory for storing stdout/stderr files. - /// \returns std::nullopt if there was an error in the execution setup - /// outside running the command itself, SystemCommand::ExecOutput otherwise. - [[nodiscard]] auto Execute(std::vector argv, - std::map env, - std::filesystem::path const& cwd, - std::filesystem::path const& tmpdir) noexcept - -> std::optional { - if (not FileSystemManager::IsDirectory(tmpdir)) { - logger_.Emit(LogLevel::Error, - "Temporary directory does not exist {}", - tmpdir.string()); - return std::nullopt; - } - - if (argv.empty()) { - logger_.Emit(LogLevel::Error, "Command cannot be empty."); - return std::nullopt; - } - - std::vector cmd = UnwrapStrings(&argv); - - std::vector env_string{}; - std::transform(std::begin(env), - std::end(env), - std::back_inserter(env_string), - [](auto& name_value) { - return name_value.first + "=" + name_value.second; - }); - std::vector envp = UnwrapStrings(&env_string); - return ExecuteCommand(cmd.data(), envp.data(), cwd, tmpdir); - } - - private: - Logger logger_; - - /// \brief Open file exclusively as write-only. - [[nodiscard]] static auto OpenFile( - std::filesystem::path const& file_path) noexcept { - static auto file_closer = [](gsl::owner f) { - if (f != nullptr) { - std::fclose(f); - } - }; - return std::unique_ptr( - std::fopen(file_path.c_str(), "wx"), file_closer); - } - - /// \brief Execute command and arguments. - /// \param cmd Command arguments as char pointer array. - /// \param envp Environment variables as char pointer array. - /// \param cwd Working directory for execution. - /// \param tmpdir Temporary directory for storing stdout/stderr files. - /// \returns ExecOutput if command was successfully submitted to the system. - /// \returns std::nullopt on internal failure. - [[nodiscard]] auto ExecuteCommand( - char* const* cmd, - char* const* envp, - std::filesystem::path const& cwd, - std::filesystem::path const& tmpdir) noexcept - -> std::optional { - auto stdout_file = tmpdir / "stdout"; - auto stderr_file = tmpdir / "stderr"; - if (auto const out = OpenFile(stdout_file)) { - if (auto const err = OpenFile(stderr_file)) { - if (auto retval = ForkAndExecute( - cmd, envp, cwd, fileno(out.get()), fileno(err.get()))) { - return ExecOutput{*retval, - std::move(stdout_file), - std::move(stderr_file)}; - } - } - else { - logger_.Emit(LogLevel::Error, - "Failed to open stderr file '{}' with error: {}", - stderr_file.string(), - strerror(errno)); - } - } - else { - logger_.Emit(LogLevel::Error, - "Failed to open stdout file '{}' with error: {}", - stdout_file.string(), - strerror(errno)); - } - - return std::nullopt; - } - - /// \brief Fork process and exec command. - /// \param cmd Command arguments as char pointer array. - /// \param envp Environment variables as char pointer array. - /// \param cwd Working directory for execution. - /// \param out_fd File descriptor to standard output file. - /// \param err_fd File descriptor to standard erro file. - /// \returns return code if command was successfully submitted to system. - /// \returns std::nullopt if fork or exec failed. - [[nodiscard]] auto ForkAndExecute(char* const* cmd, - char* const* envp, - std::filesystem::path const& cwd, - int out_fd, - int err_fd) const noexcept - -> std::optional { - // fork child process - pid_t pid = ::fork(); - if (-1 == pid) { - logger_.Emit(LogLevel::Error, - "Failed to execute '{}': cannot fork a child process.", - *cmd); - return std::nullopt; - } - - // dispatch child/parent process - if (pid == 0) { - // some executables require an open (possibly seekable) stdin, and - // therefore, we use an open temporary file that does not appear - // on the file system and will be removed automatically once the - // descriptor is closed. - gsl::owner in_file = std::tmpfile(); - auto in_fd = fileno(in_file); - - // redirect and close fds - ::dup2(in_fd, STDIN_FILENO); - ::dup2(out_fd, STDOUT_FILENO); - ::dup2(err_fd, STDERR_FILENO); - ::close(in_fd); - ::close(out_fd); - ::close(err_fd); - - [[maybe_unused]] auto anchor = - FileSystemManager::ChangeDirectory(cwd); - - // execute command in child process and exit - ::execvpe(*cmd, cmd, envp); - - // report error and terminate child process if ::execvp did not exit - logger_.Emit(LogLevel::Error, - "Failed to execute '{}' with error: {}", - *cmd, - strerror(errno)); - - std::exit(EXIT_FAILURE); - } - - // wait for child to finish and obtain return value - int status{}; - ::waitpid(pid, &status, 0); - // NOLINTNEXTLINE(hicpp-signed-bitwise) - return WEXITSTATUS(status); - } - - static auto UnwrapStrings(std::vector* v) noexcept - -> std::vector { - std::vector raw{}; - std::transform(std::begin(*v), - std::end(*v), - std::back_inserter(raw), - [](auto& str) { return str.data(); }); - raw.push_back(nullptr); - return raw; - } -}; - -#endif // INCLUDED_SRC_BUILDTOOL_FILE_SYSTEM_EXECUTION_SYSTEM_HPP diff --git a/src/buildtool/system/TARGETS b/src/buildtool/system/TARGETS new file mode 100644 index 00000000..c5db3f83 --- /dev/null +++ b/src/buildtool/system/TARGETS @@ -0,0 +1,12 @@ +{ "system_command": + { "type": ["@", "rules", "CC", "library"] + , "name": ["system_command"] + , "hdrs": ["system_command.hpp"] + , "deps": + [ ["src/buildtool/file_system", "file_system_manager"] + , ["src/buildtool/logging", "logging"] + , ["@", "gsl-lite", "", "gsl-lite"] + ] + , "stage": ["src", "buildtool", "system"] + } +} diff --git a/src/buildtool/system/system_command.hpp b/src/buildtool/system/system_command.hpp new file mode 100644 index 00000000..53c92f21 --- /dev/null +++ b/src/buildtool/system/system_command.hpp @@ -0,0 +1,202 @@ +#ifndef INCLUDED_SRC_BUILDTOOL_SYSTEM_SYSTEM_COMMAND_HPP +#define INCLUDED_SRC_BUILDTOOL_SYSTEM_SYSTEM_COMMAND_HPP + +#include +#include +#include // for strerror() +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "gsl-lite/gsl-lite.hpp" +#include "src/buildtool/file_system/file_system_manager.hpp" +#include "src/buildtool/logging/logger.hpp" + +/// \brief Execute system commands and obtain stdout, stderr and return value. +/// Subsequent commands are context free and are not affected by previous +/// commands. This class is not thread-safe. +class SystemCommand { + public: + struct ExecOutput { + int return_value{}; + std::filesystem::path stdout_file{}; + std::filesystem::path stderr_file{}; + }; + + /// \brief Create execution system with name. + explicit SystemCommand(std::string name) : logger_{std::move(name)} {} + + /// \brief Execute command and arguments. + /// \param argv argv vector with the command to execute + /// \param env Environment variables set for execution. + /// \param cwd Working directory for execution. + /// \param tmpdir Temporary directory for storing stdout/stderr files. + /// \returns std::nullopt if there was an error in the execution setup + /// outside running the command itself, SystemCommand::ExecOutput otherwise. + [[nodiscard]] auto Execute(std::vector argv, + std::map env, + std::filesystem::path const& cwd, + std::filesystem::path const& tmpdir) noexcept + -> std::optional { + if (not FileSystemManager::IsDirectory(tmpdir)) { + logger_.Emit(LogLevel::Error, + "Temporary directory does not exist {}", + tmpdir.string()); + return std::nullopt; + } + + if (argv.empty()) { + logger_.Emit(LogLevel::Error, "Command cannot be empty."); + return std::nullopt; + } + + std::vector cmd = UnwrapStrings(&argv); + + std::vector env_string{}; + std::transform(std::begin(env), + std::end(env), + std::back_inserter(env_string), + [](auto& name_value) { + return name_value.first + "=" + name_value.second; + }); + std::vector envp = UnwrapStrings(&env_string); + return ExecuteCommand(cmd.data(), envp.data(), cwd, tmpdir); + } + + private: + Logger logger_; + + /// \brief Open file exclusively as write-only. + [[nodiscard]] static auto OpenFile( + std::filesystem::path const& file_path) noexcept { + static auto file_closer = [](gsl::owner f) { + if (f != nullptr) { + std::fclose(f); + } + }; + return std::unique_ptr( + std::fopen(file_path.c_str(), "wx"), file_closer); + } + + /// \brief Execute command and arguments. + /// \param cmd Command arguments as char pointer array. + /// \param envp Environment variables as char pointer array. + /// \param cwd Working directory for execution. + /// \param tmpdir Temporary directory for storing stdout/stderr files. + /// \returns ExecOutput if command was successfully submitted to the system. + /// \returns std::nullopt on internal failure. + [[nodiscard]] auto ExecuteCommand( + char* const* cmd, + char* const* envp, + std::filesystem::path const& cwd, + std::filesystem::path const& tmpdir) noexcept + -> std::optional { + auto stdout_file = tmpdir / "stdout"; + auto stderr_file = tmpdir / "stderr"; + if (auto const out = OpenFile(stdout_file)) { + if (auto const err = OpenFile(stderr_file)) { + if (auto retval = ForkAndExecute( + cmd, envp, cwd, fileno(out.get()), fileno(err.get()))) { + return ExecOutput{*retval, + std::move(stdout_file), + std::move(stderr_file)}; + } + } + else { + logger_.Emit(LogLevel::Error, + "Failed to open stderr file '{}' with error: {}", + stderr_file.string(), + strerror(errno)); + } + } + else { + logger_.Emit(LogLevel::Error, + "Failed to open stdout file '{}' with error: {}", + stdout_file.string(), + strerror(errno)); + } + + return std::nullopt; + } + + /// \brief Fork process and exec command. + /// \param cmd Command arguments as char pointer array. + /// \param envp Environment variables as char pointer array. + /// \param cwd Working directory for execution. + /// \param out_fd File descriptor to standard output file. + /// \param err_fd File descriptor to standard erro file. + /// \returns return code if command was successfully submitted to system. + /// \returns std::nullopt if fork or exec failed. + [[nodiscard]] auto ForkAndExecute(char* const* cmd, + char* const* envp, + std::filesystem::path const& cwd, + int out_fd, + int err_fd) const noexcept + -> std::optional { + // fork child process + pid_t pid = ::fork(); + if (-1 == pid) { + logger_.Emit(LogLevel::Error, + "Failed to execute '{}': cannot fork a child process.", + *cmd); + return std::nullopt; + } + + // dispatch child/parent process + if (pid == 0) { + // some executables require an open (possibly seekable) stdin, and + // therefore, we use an open temporary file that does not appear + // on the file system and will be removed automatically once the + // descriptor is closed. + gsl::owner in_file = std::tmpfile(); + auto in_fd = fileno(in_file); + + // redirect and close fds + ::dup2(in_fd, STDIN_FILENO); + ::dup2(out_fd, STDOUT_FILENO); + ::dup2(err_fd, STDERR_FILENO); + ::close(in_fd); + ::close(out_fd); + ::close(err_fd); + + [[maybe_unused]] auto anchor = + FileSystemManager::ChangeDirectory(cwd); + + // execute command in child process and exit + ::execvpe(*cmd, cmd, envp); + + // report error and terminate child process if ::execvp did not exit + logger_.Emit(LogLevel::Error, + "Failed to execute '{}' with error: {}", + *cmd, + strerror(errno)); + + std::exit(EXIT_FAILURE); + } + + // wait for child to finish and obtain return value + int status{}; + ::waitpid(pid, &status, 0); + // NOLINTNEXTLINE(hicpp-signed-bitwise) + return WEXITSTATUS(status); + } + + static auto UnwrapStrings(std::vector* v) noexcept + -> std::vector { + std::vector raw{}; + std::transform(std::begin(*v), + std::end(*v), + std::back_inserter(raw), + [](auto& str) { return str.data(); }); + raw.push_back(nullptr); + return raw; + } +}; + +#endif // INCLUDED_SRC_BUILDTOOL_SYSTEM_SYSTEM_COMMAND_HPP diff --git a/test/buildtool/TARGETS b/test/buildtool/TARGETS index 3e70f462..a07928af 100644 --- a/test/buildtool/TARGETS +++ b/test/buildtool/TARGETS @@ -11,6 +11,7 @@ , [["./", "graph_traverser", "TESTS"], "graph_traverser"] , [["./", "logging", "TESTS"], "logging"] , [["./", "multithreading", "TESTS"], "multithreading"] + , [["./", "system", "TESTS"], "system"] ] } } diff --git a/test/buildtool/file_system/TARGETS b/test/buildtool/file_system/TARGETS index fdb2296d..c7fe3bda 100644 --- a/test/buildtool/file_system/TARGETS +++ b/test/buildtool/file_system/TARGETS @@ -10,17 +10,6 @@ ] , "stage": ["test", "buildtool", "file_system"] } -, "system_command": - { "type": ["@", "rules", "CC/test", "test"] - , "name": ["system_command"] - , "srcs": ["system_command.test.cpp"] - , "deps": - [ ["@", "catch2", "", "catch2"] - , ["test", "catch-main"] - , ["src/buildtool/file_system", "system_command"] - ] - , "stage": ["test", "buildtool", "file_system"] - } , "git_tree": { "type": ["@", "rules", "CC/test", "test"] , "name": ["git_tree"] @@ -57,6 +46,6 @@ , "TESTS": { "type": "install" , "tainted": ["test"] - , "deps": ["file_root", "file_system_manager", "git_tree", "system_command"] + , "deps": ["file_root", "file_system_manager", "git_tree"] } -} \ No newline at end of file +} diff --git a/test/buildtool/file_system/system_command.test.cpp b/test/buildtool/file_system/system_command.test.cpp deleted file mode 100644 index 81b4e2b6..00000000 --- a/test/buildtool/file_system/system_command.test.cpp +++ /dev/null @@ -1,115 +0,0 @@ -#include -#include - -#include "catch2/catch.hpp" -#include "src/buildtool/file_system/system_command.hpp" - -namespace { -[[nodiscard]] auto GetTestDir() -> std::filesystem::path { - auto* tmp_dir = std::getenv("TEST_TMPDIR"); - if (tmp_dir != nullptr) { - return tmp_dir; - } - return FileSystemManager::GetCurrentDirectory() / - "test/buildtool/file_system"; -} -} // namespace - -TEST_CASE("SystemCommand", "[filesystem]") { - using Catch::Matchers::Contains; - using Catch::Matchers::StartsWith; - - std::string name{"ExecutorTest"}; - SystemCommand system{name}; - - auto const testdir = GetTestDir(); - - SECTION("empty command") { - auto tmpdir = testdir / "empty"; - REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); - auto output = system.Execute( - {}, {}, FileSystemManager::GetCurrentDirectory(), tmpdir); - CHECK(not output.has_value()); - } - - SECTION("simple command, no arguments, no env variables") { - auto tmpdir = testdir / "simple_noargs"; - REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); - auto output = system.Execute( - {"echo"}, {}, FileSystemManager::GetCurrentDirectory(), tmpdir); - REQUIRE(output.has_value()); - CHECK(output->return_value == 0); - CHECK(*FileSystemManager::ReadFile(output->stdout_file) == "\n"); - CHECK(FileSystemManager::ReadFile(output->stderr_file)->empty()); - } - - SECTION( - "simple command, env variables are expanded only when wrapped with " - "/bin/sh") { - auto tmpdir = testdir / "simple_env0"; - REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); - auto output = system.Execute({"echo", "${MY_MESSAGE}"}, - {{"MY_MESSAGE", "hello"}}, - FileSystemManager::GetCurrentDirectory(), - tmpdir); - REQUIRE(output.has_value()); - CHECK(output->return_value == 0); - CHECK(*FileSystemManager::ReadFile(output->stdout_file) == - "${MY_MESSAGE}\n"); - CHECK(FileSystemManager::ReadFile(output->stderr_file)->empty()); - - tmpdir = testdir / "simple_env1"; - REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); - auto output_wrapped = - system.Execute({"/bin/sh", "-c", "set -e\necho ${MY_MESSAGE}"}, - {{"MY_MESSAGE", "hello"}}, - FileSystemManager::GetCurrentDirectory(), - tmpdir); - REQUIRE(output_wrapped.has_value()); - CHECK(output_wrapped->return_value == 0); - CHECK(*FileSystemManager::ReadFile(output_wrapped->stdout_file) == - "hello\n"); - CHECK( - FileSystemManager::ReadFile(output_wrapped->stderr_file)->empty()); - } - - SECTION("executable, producing std output, std error and return value") { - auto tmpdir = testdir / "exe_output"; - REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); - auto output = system.Execute( - {"/bin/sh", - "-c", - "set -e\necho this is stdout; echo this is stderr >&2; exit 5"}, - {}, - FileSystemManager::GetCurrentDirectory(), - tmpdir); - REQUIRE(output.has_value()); - CHECK(output->return_value == 5); - CHECK(*FileSystemManager::ReadFile(output->stdout_file) == - "this is stdout\n"); - CHECK(*FileSystemManager::ReadFile(output->stderr_file) == - "this is stderr\n"); - } - - SECTION( - "executable dependent on env, producing std output, std error and " - "return value") { - auto tmpdir = testdir / "exe_output_from_env"; - REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); - std::string const stdout = "this is stdout from env var"; - std::string const stderr = "this is stderr from env var"; - auto output = system.Execute( - {"/bin/sh", - "-c", - "set -e\necho ${MY_STDOUT}; echo ${MY_STDERR} >&2; exit 5"}, - {{"MY_STDOUT", stdout}, {"MY_STDERR", stderr}}, - FileSystemManager::GetCurrentDirectory(), - tmpdir); - REQUIRE(output.has_value()); - CHECK(output->return_value == 5); - CHECK(*FileSystemManager::ReadFile(output->stdout_file) == - stdout + '\n'); - CHECK(*FileSystemManager::ReadFile(output->stderr_file) == - stderr + '\n'); - } -} diff --git a/test/buildtool/system/TARGETS b/test/buildtool/system/TARGETS new file mode 100644 index 00000000..97101bae --- /dev/null +++ b/test/buildtool/system/TARGETS @@ -0,0 +1,13 @@ +{ "system_command": + { "type": ["@", "rules", "CC/test", "test"] + , "name": ["system_command"] + , "srcs": ["system_command.test.cpp"] + , "deps": + [ ["@", "catch2", "", "catch2"] + , ["test", "catch-main"] + , ["src/buildtool/system", "system_command"] + ] + , "stage": ["test", "buildtool", "system"] + } +, "TESTS": {"type": "install", "tainted": ["test"], "deps": ["system_command"]} +} diff --git a/test/buildtool/system/system_command.test.cpp b/test/buildtool/system/system_command.test.cpp new file mode 100644 index 00000000..6b3777ee --- /dev/null +++ b/test/buildtool/system/system_command.test.cpp @@ -0,0 +1,115 @@ +#include +#include + +#include "catch2/catch.hpp" +#include "src/buildtool/system/system_command.hpp" + +namespace { +[[nodiscard]] auto GetTestDir() -> std::filesystem::path { + auto* tmp_dir = std::getenv("TEST_TMPDIR"); + if (tmp_dir != nullptr) { + return tmp_dir; + } + return FileSystemManager::GetCurrentDirectory() / + "test/buildtool/file_system"; +} +} // namespace + +TEST_CASE("SystemCommand", "[filesystem]") { + using Catch::Matchers::Contains; + using Catch::Matchers::StartsWith; + + std::string name{"ExecutorTest"}; + SystemCommand system{name}; + + auto const testdir = GetTestDir(); + + SECTION("empty command") { + auto tmpdir = testdir / "empty"; + REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); + auto output = system.Execute( + {}, {}, FileSystemManager::GetCurrentDirectory(), tmpdir); + CHECK(not output.has_value()); + } + + SECTION("simple command, no arguments, no env variables") { + auto tmpdir = testdir / "simple_noargs"; + REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); + auto output = system.Execute( + {"echo"}, {}, FileSystemManager::GetCurrentDirectory(), tmpdir); + REQUIRE(output.has_value()); + CHECK(output->return_value == 0); + CHECK(*FileSystemManager::ReadFile(output->stdout_file) == "\n"); + CHECK(FileSystemManager::ReadFile(output->stderr_file)->empty()); + } + + SECTION( + "simple command, env variables are expanded only when wrapped with " + "/bin/sh") { + auto tmpdir = testdir / "simple_env0"; + REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); + auto output = system.Execute({"echo", "${MY_MESSAGE}"}, + {{"MY_MESSAGE", "hello"}}, + FileSystemManager::GetCurrentDirectory(), + tmpdir); + REQUIRE(output.has_value()); + CHECK(output->return_value == 0); + CHECK(*FileSystemManager::ReadFile(output->stdout_file) == + "${MY_MESSAGE}\n"); + CHECK(FileSystemManager::ReadFile(output->stderr_file)->empty()); + + tmpdir = testdir / "simple_env1"; + REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); + auto output_wrapped = + system.Execute({"/bin/sh", "-c", "set -e\necho ${MY_MESSAGE}"}, + {{"MY_MESSAGE", "hello"}}, + FileSystemManager::GetCurrentDirectory(), + tmpdir); + REQUIRE(output_wrapped.has_value()); + CHECK(output_wrapped->return_value == 0); + CHECK(*FileSystemManager::ReadFile(output_wrapped->stdout_file) == + "hello\n"); + CHECK( + FileSystemManager::ReadFile(output_wrapped->stderr_file)->empty()); + } + + SECTION("executable, producing std output, std error and return value") { + auto tmpdir = testdir / "exe_output"; + REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); + auto output = system.Execute( + {"/bin/sh", + "-c", + "set -e\necho this is stdout; echo this is stderr >&2; exit 5"}, + {}, + FileSystemManager::GetCurrentDirectory(), + tmpdir); + REQUIRE(output.has_value()); + CHECK(output->return_value == 5); + CHECK(*FileSystemManager::ReadFile(output->stdout_file) == + "this is stdout\n"); + CHECK(*FileSystemManager::ReadFile(output->stderr_file) == + "this is stderr\n"); + } + + SECTION( + "executable dependent on env, producing std output, std error and " + "return value") { + auto tmpdir = testdir / "exe_output_from_env"; + REQUIRE(FileSystemManager::CreateDirectoryExclusive(tmpdir)); + std::string const stdout = "this is stdout from env var"; + std::string const stderr = "this is stderr from env var"; + auto output = system.Execute( + {"/bin/sh", + "-c", + "set -e\necho ${MY_STDOUT}; echo ${MY_STDERR} >&2; exit 5"}, + {{"MY_STDOUT", stdout}, {"MY_STDERR", stderr}}, + FileSystemManager::GetCurrentDirectory(), + tmpdir); + REQUIRE(output.has_value()); + CHECK(output->return_value == 5); + CHECK(*FileSystemManager::ReadFile(output->stdout_file) == + stdout + '\n'); + CHECK(*FileSystemManager::ReadFile(output->stderr_file) == + stderr + '\n'); + } +} -- cgit v1.2.3