[M4] add exec::run subprocess wrapper

This commit is contained in:
2026-05-09 23:54:01 +00:00
parent 219254a1dd
commit 807158b8cc
6 changed files with 264 additions and 1 deletions

View File

@@ -9,6 +9,7 @@ endfunction()
cargoxx_add_test(util_error)
cargoxx_add_test(semver_satisfies)
cargoxx_add_test(exec_run)
cargoxx_add_test(manifest_parse)
cargoxx_add_test(manifest_write)
cargoxx_add_test(layout_discovery)

78
tests/exec_run.cpp Normal file
View File

@@ -0,0 +1,78 @@
#include <catch2/catch_test_macros.hpp>
import cargoxx.exec;
import cargoxx.util;
import std;
using cargoxx::exec::ExecOptions;
using cargoxx::exec::ExecResult;
using cargoxx::exec::run;
using cargoxx::util::ErrorCode;
TEST_CASE("run captures stdout from echo", "[exec]") {
auto r = run("echo", {"hello", "world"});
REQUIRE(r.has_value());
REQUIRE(r->exit_code == 0);
REQUIRE(r->stdout_text == "hello world\n");
REQUIRE(r->stderr_text.empty());
}
TEST_CASE("run returns 0 for true", "[exec]") {
auto r = run("true", {});
REQUIRE(r.has_value());
REQUIRE(r->exit_code == 0);
}
TEST_CASE("run returns non-zero for false", "[exec]") {
auto r = run("false", {});
REQUIRE(r.has_value());
REQUIRE(r->exit_code != 0);
}
TEST_CASE("run reports ExecToolNotFound for missing program", "[exec]") {
auto r = run("definitely-not-a-real-command-xyz-12345", {});
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ExecToolNotFound);
}
TEST_CASE("run honors cwd", "[exec]") {
auto cwd = std::filesystem::temp_directory_path();
auto r = run("pwd", {}, ExecOptions{.cwd = cwd});
REQUIRE(r.has_value());
REQUIRE(r->exit_code == 0);
// pwd may print the canonical path; at minimum the result starts with /tmp
// (or whatever the temp dir's prefix is)
REQUIRE_FALSE(r->stdout_text.empty());
}
TEST_CASE("run captures stderr", "[exec]") {
auto r = run("ls", {"/this/path/does/not/exist/cargoxx-xyz"});
REQUIRE(r.has_value());
REQUIRE(r->exit_code != 0);
REQUIRE_FALSE(r->stderr_text.empty());
}
TEST_CASE("run propagates env_overrides", "[exec]") {
auto r = run("env", {}, ExecOptions{
.env_overrides = {{"CARGOXX_TEST_VAR", "lemonade"}},
});
REQUIRE(r.has_value());
REQUIRE(r->exit_code == 0);
REQUIRE(r->stdout_text.find("CARGOXX_TEST_VAR=lemonade") != std::string::npos);
}
TEST_CASE("run enforces a timeout", "[exec]") {
auto r = run("sleep", {"5"},
ExecOptions{.timeout = std::chrono::seconds{1}});
REQUIRE_FALSE(r.has_value());
REQUIRE(r.error().code == ErrorCode::ExecCommandFailed);
}
TEST_CASE("run handles many output bytes without deadlocking", "[exec]") {
// 64 KiB of zeros — exercises pipe-pumping past the typical 4 KiB buffer
// without producing unbounded output.
auto r = run("dd", {"if=/dev/zero", "bs=1024", "count=64", "status=none"});
REQUIRE(r.has_value());
REQUIRE(r->exit_code == 0);
REQUIRE(r->stdout_text.size() == 64 * 1024);
}