test: drop transport runners (ssh/sshwin/wsl) and the Shell-quoting enum
All checks were successful
CI / build-test-release (pull_request) Successful in 9m56s
All checks were successful
CI / build-test-release (pull_request) Successful in 9m56s
With test.toml + ForTarget covering the cross-arch + Windows-on-Linux
cases, the env-var-driven transport runners are dead weight. This commit
removes them and the retired tests that exercised the env-var plumbing:
- TestRunner::Ssh / SshWin / Wsl factories and their copy/exec/cleanup
template machinery.
- TestRunner::Shell enum (Host/Sh/Cmd) and the ShellQuoteSh helper —
only Host shell quoting is needed once the remote shells are gone.
- TestRunner::copy / cleanup / remoteDir / argsShell fields.
- WindowsPathToWsl and the {remote_bundle}/{bin_win}/{bundle_wsl}
placeholder substitution in RunSingleTest's transport branch.
- ParseRunnerSpec narrowed from {local, cmd, ssh, sshwin, wsl} to
{local, cmd} — the override hatch is preserved, just simpler.
- tests/SshRunner, tests/WindowsViaSsh, tests/QemuUser: these tested
the CRAFTER_BUILD_RUNNER_<target> → runner plumbing that has been
replaced by ForTarget. The runner derivation is exercised every
time CrossArchAarch64 / Wasi / WindowsViaWine runs.
- tests/UnitLib: ssh/sshwin spec assertions become "throws on bogus
spec" assertions.
Refs issue #8.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
406a406420
commit
8de93aaf06
15 changed files with 35 additions and 516 deletions
|
|
@ -109,15 +109,11 @@ namespace {
|
|||
#endif
|
||||
}
|
||||
|
||||
std::string JoinAndQuoteArgs(std::span<const std::string> args, TestRunner::Shell shell) {
|
||||
std::string JoinAndQuoteArgs(std::span<const std::string> args) {
|
||||
std::string out;
|
||||
for (const auto& a : args) {
|
||||
if (!out.empty()) out.push_back(' ');
|
||||
switch (shell) {
|
||||
case TestRunner::Shell::Sh: out += ShellQuoteSh(a); break;
|
||||
case TestRunner::Shell::Cmd: out += ShellQuoteCmd(a); break;
|
||||
case TestRunner::Shell::Host: out += ShellQuoteHost(a); break;
|
||||
}
|
||||
out += ShellQuoteHost(a);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
|
@ -241,69 +237,12 @@ Configuration* Crafter::ParentLib(std::string_view name) {
|
|||
TestRunner TestRunner::Local() {
|
||||
TestRunner r;
|
||||
r.name = "local";
|
||||
r.argsShell = Shell::Host;
|
||||
return r;
|
||||
}
|
||||
|
||||
TestRunner TestRunner::Ssh(std::string host, std::string remoteDir) {
|
||||
TestRunner r;
|
||||
r.name = std::format("ssh:{}", host);
|
||||
r.remoteDir = std::move(remoteDir);
|
||||
r.argsShell = Shell::Sh;
|
||||
// Outer "..." (not '...') so the wrapper survives both sh (Linux host)
|
||||
// and cmd (Windows host). cmd doesn't honor single quotes, which would
|
||||
// cause it to split on the inner '&&'. Args inside use POSIX quoting
|
||||
// because they reach a remote bash regardless of which host issued them.
|
||||
r.copy = std::format("ssh -q {0} \"mkdir -p {{remote_bundle}}\" && scp -r -q {{bundle}}/. {0}:{{remote_bundle}}/", host);
|
||||
r.exec = std::format("ssh -q {} \"cd {{remote_bundle}} && ./{{bin_name}} {{args}}\"", host);
|
||||
r.cleanup = std::format("ssh -q {} \"rm -rf {{remote_bundle}}\"", host);
|
||||
// RunCommandChecked captures stdout+stderr internally — no shell redirect
|
||||
// needed in the probe spec, which means it works the same way under cmd
|
||||
// (Windows host) and sh (Linux host) since `> /dev/null` doesn't translate.
|
||||
r.probe = std::format("ssh -q -o BatchMode=yes -o ConnectTimeout=5 {} true", host);
|
||||
return r;
|
||||
}
|
||||
|
||||
TestRunner TestRunner::SshWin(std::string host, std::string remoteDir) {
|
||||
TestRunner r;
|
||||
r.name = std::format("sshwin:{}", host);
|
||||
r.remoteDir = std::move(remoteDir);
|
||||
r.argsShell = Shell::Cmd;
|
||||
// cmd.exe-friendly templates. Use backslash variants of remote paths because
|
||||
// cmd's mkdir won't auto-create intermediate directories with forward
|
||||
// slashes. scp tolerates either, so we keep forward slashes for the scp
|
||||
// dest (which `{remote_bundle}` provides). 2>nul + `& rem ok` swallows
|
||||
// mkdir's "already exists" error and forces exit code 0.
|
||||
r.copy = std::format("ssh -q {0} \"mkdir {{remote_bundle_win}} 2>nul & rem ok\" && scp -r -q {{bundle}}/. {0}:{{remote_bundle}}/", host);
|
||||
r.exec = std::format("ssh -q {} \"{{bin_win}} {{args}}\"", host);
|
||||
r.cleanup = std::format("ssh -q {} \"rd /s /q {{remote_bundle_win}} 2>nul & rem ok\"", host);
|
||||
// No shell redirect in the probe — see TestRunner::Ssh for rationale.
|
||||
r.probe = std::format("ssh -q -o BatchMode=yes -o ConnectTimeout=5 {} \"ver\"", host);
|
||||
return r;
|
||||
}
|
||||
|
||||
TestRunner TestRunner::Wsl(std::string remoteDir) {
|
||||
TestRunner r;
|
||||
r.name = "wsl";
|
||||
r.argsShell = Shell::Sh;
|
||||
r.remoteDir = std::move(remoteDir);
|
||||
// Transport runner: stage the test bundle into WSL's native filesystem
|
||||
// (faster than executing in-place from /mnt/c) then run via bash. {bundle_wsl}
|
||||
// is the local Windows path translated to /mnt/<drive>/... form so wsl cp
|
||||
// can read it. Args are POSIX-quoted because they reach a Linux shell.
|
||||
r.copy = "wsl mkdir -p {remote_bundle} && wsl cp -r {bundle_wsl}/. {remote_bundle}/";
|
||||
r.exec = "wsl bash -c \"cd {remote_bundle} && ./{bin_name} {args}\"";
|
||||
r.cleanup = "wsl rm -rf {remote_bundle}";
|
||||
// `where wsl` matches the host-shell convention; on a non-Windows host
|
||||
// it fails (no `where` command), which correctly skips this runner.
|
||||
r.probe = "where wsl";
|
||||
return r;
|
||||
}
|
||||
|
||||
TestRunner TestRunner::Cmd(std::string command) {
|
||||
TestRunner r;
|
||||
r.name = std::format("cmd:{}", command);
|
||||
r.argsShell = Shell::Host;
|
||||
r.exec = std::format("{} {{bin}} {{args}}", command);
|
||||
#ifdef _WIN32
|
||||
r.probe = std::format("where {}", command);
|
||||
|
|
@ -316,7 +255,6 @@ TestRunner TestRunner::Cmd(std::string command) {
|
|||
TestRunner TestRunner::Wine() {
|
||||
TestRunner r;
|
||||
r.name = "wine";
|
||||
r.argsShell = Shell::Host;
|
||||
r.exec = "wine {bin} {args}";
|
||||
#ifdef _WIN32
|
||||
r.probe = "where wine";
|
||||
|
|
@ -385,56 +323,18 @@ namespace {
|
|||
return out;
|
||||
}
|
||||
|
||||
// Spec grammar: "local" | "cmd:<binary>". Used by CRAFTER_BUILD_RUNNER_*
|
||||
// env vars and --runner=. The set used to include ssh/sshwin/wsl variants;
|
||||
// those were removed when transport-style runners were dropped (issue #8).
|
||||
std::optional<TestRunner> ParseRunnerSpec(std::string_view spec) {
|
||||
if (spec.empty()) return std::nullopt;
|
||||
std::vector<std::string> parts;
|
||||
for (auto piece : std::views::split(spec, ':')) {
|
||||
parts.emplace_back(std::string_view(piece.begin(), piece.end()));
|
||||
}
|
||||
if (parts.empty()) return std::nullopt;
|
||||
if (parts[0] == "local") return TestRunner::Local();
|
||||
if (parts[0] == "cmd" && parts.size() == 2) return TestRunner::Cmd(parts[1]);
|
||||
if (parts[0] == "ssh" && parts.size() == 2) return TestRunner::Ssh(parts[1]);
|
||||
if (parts[0] == "ssh" && parts.size() >= 3) {
|
||||
std::string remote;
|
||||
for (std::size_t i = 2; i < parts.size(); ++i) {
|
||||
if (i > 2) remote.push_back(':');
|
||||
remote += parts[i];
|
||||
}
|
||||
return TestRunner::Ssh(parts[1], remote);
|
||||
}
|
||||
if (parts[0] == "sshwin" && parts.size() == 2) return TestRunner::SshWin(parts[1]);
|
||||
if (parts[0] == "sshwin" && parts.size() >= 3) {
|
||||
std::string remote;
|
||||
for (std::size_t i = 2; i < parts.size(); ++i) {
|
||||
if (i > 2) remote.push_back(':');
|
||||
remote += parts[i];
|
||||
}
|
||||
return TestRunner::SshWin(parts[1], remote);
|
||||
}
|
||||
if (parts[0] == "wsl" && parts.size() == 1) return TestRunner::Wsl();
|
||||
if (parts[0] == "wsl" && parts.size() >= 2) {
|
||||
std::string remote;
|
||||
for (std::size_t i = 1; i < parts.size(); ++i) {
|
||||
if (i > 1) remote.push_back(':');
|
||||
remote += parts[i];
|
||||
}
|
||||
return TestRunner::Wsl(remote);
|
||||
if (spec == "local") return TestRunner::Local();
|
||||
if (spec.starts_with("cmd:") && spec.size() > 4) {
|
||||
return TestRunner::Cmd(std::string(spec.substr(4)));
|
||||
}
|
||||
throw std::runtime_error(std::format(
|
||||
"TestRunner::FromSpec: unrecognized runner spec '{}'", spec));
|
||||
}
|
||||
|
||||
// C:\Users\jorij -> /mnt/c/Users/jorij. Idempotent on paths that don't
|
||||
// start with a drive letter, so callers can compute it unconditionally.
|
||||
std::string WindowsPathToWsl(std::string_view p) {
|
||||
if (p.size() < 2 || p[1] != ':') return std::string(p);
|
||||
char drive = static_cast<char>(std::tolower(static_cast<unsigned char>(p[0])));
|
||||
std::string out = std::format("/mnt/{}", drive);
|
||||
for (std::size_t i = 2; i < p.size(); ++i) {
|
||||
out.push_back(p[i] == '\\' ? '/' : p[i]);
|
||||
}
|
||||
return out;
|
||||
"TestRunner::FromSpec: unrecognized runner spec '{}' "
|
||||
"(expected 'local' or 'cmd:<binary>')", spec));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -451,60 +351,24 @@ TestRunner TestRunner::FromEnv(std::string_view target, TestRunner fallback) {
|
|||
}
|
||||
|
||||
TestResult Crafter::RunSingleTest(const Test& test, const fs::path& binary, std::chrono::seconds timeout) {
|
||||
using namespace std::chrono_literals;
|
||||
TestResult result;
|
||||
result.name = test.config.name;
|
||||
|
||||
std::map<std::string, std::string> ph;
|
||||
ph["{args}"] = JoinAndQuoteArgs(test.args, test.runner.argsShell);
|
||||
ph["{bin_name}"] = binary.filename().string();
|
||||
ph["{bundle}"] = binary.parent_path().string();
|
||||
ph["{args}"] = JoinAndQuoteArgs(test.args);
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
CommandResult r;
|
||||
|
||||
if (test.runner.exec.empty()) {
|
||||
// Pure-local runner: spawn the binary directly through the host shell.
|
||||
// Local runner: spawn the binary directly through the host shell.
|
||||
std::string cmd = std::format("{} {}", ShellQuoteHost(binary.string()), ph["{args}"]);
|
||||
r = RunCommandWithTimeout(cmd, timeout);
|
||||
} else if (test.runner.copy.empty()) {
|
||||
// Prefix runner (qemu-user, wsl, ...): templated exec wraps a local binary.
|
||||
} else {
|
||||
// Prefix runner (qemu-user, wasmtime, wine, ...): templated exec
|
||||
// wraps the local binary.
|
||||
ph["{bin}"] = binary.string();
|
||||
r = RunCommandWithTimeout(Substitute(test.runner.exec, ph), timeout);
|
||||
} else {
|
||||
// Transport runner (ssh, ...): copy bundle → exec remotely → cleanup.
|
||||
std::string unique = std::format("{}-{}-{}",
|
||||
test.config.name,
|
||||
std::hash<std::thread::id>{}(std::this_thread::get_id()),
|
||||
std::chrono::steady_clock::now().time_since_epoch().count());
|
||||
ph["{remote_bundle}"] = std::format("{}/{}", test.runner.remoteDir, unique);
|
||||
ph["{bin}"] = std::format("{}/{}", ph["{remote_bundle}"], ph["{bin_name}"]);
|
||||
// Backslash variants for cmd.exe-shell remotes (cmd's mkdir won't
|
||||
// auto-create intermediate directories when path uses forward slashes).
|
||||
ph["{remote_bundle_win}"] = ph["{remote_bundle}"];
|
||||
std::replace(ph["{remote_bundle_win}"].begin(), ph["{remote_bundle_win}"].end(), '/', '\\');
|
||||
ph["{bin_win}"] = ph["{bin}"];
|
||||
std::replace(ph["{bin_win}"].begin(), ph["{bin_win}"].end(), '/', '\\');
|
||||
// WSL form of the local bundle path: C:\foo -> /mnt/c/foo. Used by
|
||||
// the Wsl runner's `wsl cp -r` step to read the test bundle out of
|
||||
// the Windows host's filesystem.
|
||||
ph["{bundle_wsl}"] = WindowsPathToWsl(ph["{bundle}"]);
|
||||
|
||||
CommandResult cp = RunCommandWithTimeout(Substitute(test.runner.copy, ph), 5min);
|
||||
if (cp.exitCode != 0) {
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
result.duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
|
||||
result.outcome = TestOutcome::Fail;
|
||||
result.exitCode = cp.exitCode;
|
||||
result.output = std::format("copy step failed (exit {}):\n{}", cp.exitCode, cp.output);
|
||||
return result;
|
||||
}
|
||||
|
||||
r = RunCommandWithTimeout(Substitute(test.runner.exec, ph), timeout);
|
||||
|
||||
if (!test.runner.cleanup.empty()) {
|
||||
std::system(Substitute(test.runner.cleanup, ph).c_str());
|
||||
}
|
||||
}
|
||||
|
||||
auto end = std::chrono::steady_clock::now();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue