From 8de93aaf069b2ed9b5219bb14cdeb0ecb2a9db2a Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 27 May 2026 18:07:33 +0200 Subject: [PATCH] test: drop transport runners (ssh/sshwin/wsl) and the Shell-quoting enum MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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_ → 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) --- implementations/Crafter.Build-Test.cpp | 166 +++---------------------- interfaces/Crafter.Build-Clang.cppm | 25 ++-- tests/QemuUser/QemuUser.cpp | 58 --------- tests/QemuUser/inner/project.cpp | 27 ---- tests/QemuUser/inner/tests/Hello.cpp | 6 - tests/QemuUser/project.cpp | 20 --- tests/SshRunner/SshRunner.cpp | 52 -------- tests/SshRunner/inner/project.cpp | 27 ---- tests/SshRunner/inner/tests/Hello.cpp | 6 - tests/SshRunner/project.cpp | 20 --- tests/UnitLib/main.cpp | 22 ++-- tests/WindowsViaSsh/WindowsViaSsh.cpp | 68 ---------- tests/WindowsViaSsh/inner/main.cpp | 6 - tests/WindowsViaSsh/inner/project.cpp | 28 ----- tests/WindowsViaSsh/project.cpp | 20 --- 15 files changed, 35 insertions(+), 516 deletions(-) delete mode 100644 tests/QemuUser/QemuUser.cpp delete mode 100644 tests/QemuUser/inner/project.cpp delete mode 100644 tests/QemuUser/inner/tests/Hello.cpp delete mode 100644 tests/QemuUser/project.cpp delete mode 100644 tests/SshRunner/SshRunner.cpp delete mode 100644 tests/SshRunner/inner/project.cpp delete mode 100644 tests/SshRunner/inner/tests/Hello.cpp delete mode 100644 tests/SshRunner/project.cpp delete mode 100644 tests/WindowsViaSsh/WindowsViaSsh.cpp delete mode 100644 tests/WindowsViaSsh/inner/main.cpp delete mode 100644 tests/WindowsViaSsh/inner/project.cpp delete mode 100644 tests/WindowsViaSsh/project.cpp diff --git a/implementations/Crafter.Build-Test.cpp b/implementations/Crafter.Build-Test.cpp index b2cf3f9..23ef72d 100644 --- a/implementations/Crafter.Build-Test.cpp +++ b/implementations/Crafter.Build-Test.cpp @@ -109,15 +109,11 @@ namespace { #endif } - std::string JoinAndQuoteArgs(std::span args, TestRunner::Shell shell) { + std::string JoinAndQuoteArgs(std::span 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//... 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:". 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 ParseRunnerSpec(std::string_view spec) { if (spec.empty()) return std::nullopt; - std::vector 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(std::tolower(static_cast(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:')", 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 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::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(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(); diff --git a/interfaces/Crafter.Build-Clang.cppm b/interfaces/Crafter.Build-Clang.cppm index a2d4ac2..45e8809 100644 --- a/interfaces/Crafter.Build-Clang.cppm +++ b/interfaces/Crafter.Build-Clang.cppm @@ -53,35 +53,34 @@ export namespace Crafter { struct Test; struct TestRunner { - // Quoting style for {args} when this runner is used. Args reach - // whichever shell the runner ultimately delivers them to: Host for - // Local + Cmd-prefix (host shell parses), Sh for Ssh (remote bash), - // Cmd for SshWin (remote cmd.exe). - enum class Shell { Host, Sh, Cmd }; - - std::string copy; + // Command template the harness executes to run the test binary. + // Local runners leave this empty; prefix runners (Cmd, Wine) set it to + // a template like "wine {bin} {args}" or "qemu-aarch64 {bin} {args}". std::string exec; - std::string cleanup; - std::string remoteDir; + // Display name; also the cache key for the availability probe. std::string name; // Runs once per RunTests invocation (cached by `name`). Exit 0 = runner // is available; non-zero = skip every Test using this runner with a // "runner unavailable" message. Empty = always available (e.g., Local). std::string probe; - Shell argsShell = Shell::Host; bool IsLocal() const { return exec.empty(); } static CRAFTER_API TestRunner Local(); - static CRAFTER_API TestRunner Ssh(std::string host, std::string remoteDir = "/tmp/crafter-tests"); - static CRAFTER_API TestRunner SshWin(std::string host, std::string remoteDir = "C:/temp/crafter-tests"); - static CRAFTER_API TestRunner Wsl(std::string remoteDir = "/tmp/crafter-tests-wsl"); + // Prefix runner: wraps the local binary in ` {bin} {args}`. + // Used for qemu-user, wasmtime, and similar single-binary wrappers. static CRAFTER_API TestRunner Cmd(std::string command); // Run a Windows .exe through Wine. Probes `wine` on PATH; on a Windows // host the wine wrapper is pointless, so callers should route to Local // before reaching here. static CRAFTER_API TestRunner Wine(); + // Parse a `[:]` spec used by CRAFTER_BUILD_RUNNER_ + // and --runner=. Supported: "local", "cmd:". Returns nullopt + // for an empty string; throws on a non-empty unrecognized spec. static CRAFTER_API std::optional FromSpec(std::string_view spec); + // Honor CRAFTER_BUILD_RUNNER_ (power-user override). Triple + // dashes/dots become underscores so they're valid in env-var names. + // Returns `fallback` when the env var is unset. static CRAFTER_API TestRunner FromEnv(std::string_view target, TestRunner fallback = Local()); // Derive a runner from a Configuration's target triple + sysroot. // Returns Local() when target equals the host, Wine() for Windows diff --git a/tests/QemuUser/QemuUser.cpp b/tests/QemuUser/QemuUser.cpp deleted file mode 100644 index 66ee83a..0000000 --- a/tests/QemuUser/QemuUser.cpp +++ /dev/null @@ -1,58 +0,0 @@ -/* -Crafter® Build -Copyright (C) 2026 Catcrafts® -Catcrafts.net - -LGPL-3.0-only. -*/ - -import std; -import Crafter.Build; -#include "../_shared/TestUtil.h" -namespace fs = std::filesystem; -using namespace TestUtil; -using namespace Crafter; - -namespace { - std::string PickQemu() { - if (const char* v = std::getenv("CRAFTER_TEST_QEMU"); v && *v) return v; - return "qemu-x86_64"; - } - bool QemuPresent(const std::string& qemu) { - return std::system(std::format("which {} > /dev/null 2>&1", qemu).c_str()) == 0; - } -} - -int main() { - try { - std::string qemu = PickQemu(); - if (!QemuPresent(qemu)) Skip(std::format("{} not on PATH", qemu)); - - std::string spec = std::format("cmd:{}", qemu); - ::setenv("CRAFTER_BUILD_RUNNER_x86_64_pc_linux_gnu", spec.c_str(), 1); - - // Verify env-var translation independently of RunTests. - auto runner = TestRunner::FromEnv("x86_64-pc-linux-gnu", TestRunner::Local()); - if (runner.name != spec) { - std::println(std::cerr, "FromEnv produced '{}', expected '{}'", runner.name, spec); - return 1; - } - - fs::path src = fs::current_path() / "tests" / "QemuUser" / "inner"; - Configuration cfg = LoadFixture("QemuUser", src); - RunTestsOptions opts; - TestSummary summary = RunTests(cfg, opts); - - if (summary.passed != 1 || summary.failed != 0 || summary.crashed != 0 || - summary.timedOut != 0 || summary.skipped != 0) { - std::println(std::cerr, - "outcome counts mismatch: passed={} failed={} crashed={} timedOut={} skipped={}", - summary.passed, summary.failed, summary.crashed, summary.timedOut, summary.skipped); - return 1; - } - return 0; - } catch (const std::exception& e) { - std::println(std::cerr, "test exception: {}", e.what()); - return 1; - } -} diff --git a/tests/QemuUser/inner/project.cpp b/tests/QemuUser/inner/project.cpp deleted file mode 100644 index 63f60ad..0000000 --- a/tests/QemuUser/inner/project.cpp +++ /dev/null @@ -1,27 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span) { - Configuration cfg; - cfg.path = "./"; - cfg.name = "qemu-meta"; - cfg.outputName = "qemu-meta"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.type = ConfigurationType::Executable; - - Test t; - t.config.path = "./"; - t.config.name = "Hello"; - t.config.outputName = "Hello"; - t.config.target = "x86_64-pc-linux-gnu"; - t.config.type = ConfigurationType::Executable; - std::array ifaces = {}; - std::array impls = { "tests/Hello" }; - t.config.GetInterfacesAndImplementations(ifaces, impls); - t.runner = TestRunner::FromEnv(t.config.target); - cfg.tests.push_back(std::move(t)); - - return cfg; -} diff --git a/tests/QemuUser/inner/tests/Hello.cpp b/tests/QemuUser/inner/tests/Hello.cpp deleted file mode 100644 index 3849425..0000000 --- a/tests/QemuUser/inner/tests/Hello.cpp +++ /dev/null @@ -1,6 +0,0 @@ -import std; - -int main() { - std::println("hello-from-qemu"); - return 0; -} diff --git a/tests/QemuUser/project.cpp b/tests/QemuUser/project.cpp deleted file mode 100644 index ad47121..0000000 --- a/tests/QemuUser/project.cpp +++ /dev/null @@ -1,20 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span) { - Configuration cfg; - cfg.path = "tests/QemuUser/"; - cfg.name = "QemuUser"; - cfg.outputName = "QemuUser"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.type = ConfigurationType::Executable; - cfg.dependencies = { ParentLib("crafter.build-lib") }; - cfg.linkFlags.push_back("-Wl,--export-dynamic"); - cfg.linkFlags.push_back("-ldl"); - std::array ifaces = {}; - std::array impls = { "QemuUser" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - return cfg; -} diff --git a/tests/SshRunner/SshRunner.cpp b/tests/SshRunner/SshRunner.cpp deleted file mode 100644 index d32ec7a..0000000 --- a/tests/SshRunner/SshRunner.cpp +++ /dev/null @@ -1,52 +0,0 @@ -/* -Crafter® Build -Copyright (C) 2026 Catcrafts® -Catcrafts.net - -LGPL-3.0-only. -*/ - -import std; -import Crafter.Build; -#include "../_shared/TestUtil.h" -namespace fs = std::filesystem; -using namespace TestUtil; -using namespace Crafter; - -int main() { - try { - const char* hostEnv = std::getenv("CRAFTER_TEST_SSH_HOST"); - if (!hostEnv || !*hostEnv) Skip("set CRAFTER_TEST_SSH_HOST to enable"); - std::string host = hostEnv; - - std::string probe = std::format("ssh -o BatchMode=yes -o ConnectTimeout=5 {} true > /dev/null 2>&1", host); - if (std::system(probe.c_str()) != 0) Skip(std::format("ssh {} not reachable", host)); - - std::string remoteDir = "/tmp/crafter-test-ssh-runner"; - std::string spec = std::format("ssh:{}:{}", host, remoteDir); - ::setenv("CRAFTER_BUILD_RUNNER_x86_64_pc_linux_gnu", spec.c_str(), 1); - - auto runner = TestRunner::FromEnv("x86_64-pc-linux-gnu", TestRunner::Local()); - if (runner.name != std::format("ssh:{}", host)) { - std::println(std::cerr, "FromEnv produced '{}', expected 'ssh:{}'", runner.name, host); - return 1; - } - - fs::path src = fs::current_path() / "tests" / "SshRunner" / "inner"; - Configuration cfg = LoadFixture("SshRunner", src); - RunTestsOptions opts; - TestSummary summary = RunTests(cfg, opts); - - if (summary.passed != 1 || summary.failed != 0 || summary.crashed != 0 || - summary.timedOut != 0 || summary.skipped != 0) { - std::println(std::cerr, - "outcome counts mismatch: passed={} failed={} crashed={} timedOut={} skipped={}", - summary.passed, summary.failed, summary.crashed, summary.timedOut, summary.skipped); - return 1; - } - return 0; - } catch (const std::exception& e) { - std::println(std::cerr, "test exception: {}", e.what()); - return 1; - } -} diff --git a/tests/SshRunner/inner/project.cpp b/tests/SshRunner/inner/project.cpp deleted file mode 100644 index 578e07a..0000000 --- a/tests/SshRunner/inner/project.cpp +++ /dev/null @@ -1,27 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span) { - Configuration cfg; - cfg.path = "./"; - cfg.name = "ssh-meta"; - cfg.outputName = "ssh-meta"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.type = ConfigurationType::Executable; - - Test t; - t.config.path = "./"; - t.config.name = "Hello"; - t.config.outputName = "Hello"; - t.config.target = "x86_64-pc-linux-gnu"; - t.config.type = ConfigurationType::Executable; - std::array ifaces = {}; - std::array impls = { "tests/Hello" }; - t.config.GetInterfacesAndImplementations(ifaces, impls); - t.runner = TestRunner::FromEnv(t.config.target); - cfg.tests.push_back(std::move(t)); - - return cfg; -} diff --git a/tests/SshRunner/inner/tests/Hello.cpp b/tests/SshRunner/inner/tests/Hello.cpp deleted file mode 100644 index f275c9d..0000000 --- a/tests/SshRunner/inner/tests/Hello.cpp +++ /dev/null @@ -1,6 +0,0 @@ -import std; - -int main() { - std::println("hello-from-ssh"); - return 0; -} diff --git a/tests/SshRunner/project.cpp b/tests/SshRunner/project.cpp deleted file mode 100644 index b9d86da..0000000 --- a/tests/SshRunner/project.cpp +++ /dev/null @@ -1,20 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span) { - Configuration cfg; - cfg.path = "tests/SshRunner/"; - cfg.name = "SshRunner"; - cfg.outputName = "SshRunner"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.type = ConfigurationType::Executable; - cfg.dependencies = { ParentLib("crafter.build-lib") }; - cfg.linkFlags.push_back("-Wl,--export-dynamic"); - cfg.linkFlags.push_back("-ldl"); - std::array ifaces = {}; - std::array impls = { "SshRunner" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - return cfg; -} diff --git a/tests/UnitLib/main.cpp b/tests/UnitLib/main.cpp index 470b5de..91c6d70 100644 --- a/tests/UnitLib/main.cpp +++ b/tests/UnitLib/main.cpp @@ -13,21 +13,15 @@ int main() { auto local = TestRunner::FromSpec("local"); if (!local || local->name != "local") return 1; - auto ssh = TestRunner::FromSpec("ssh:somehost"); - if (!ssh || ssh->name != "ssh:somehost") return 1; - - auto sshWithDir = TestRunner::FromSpec("ssh:somehost:/var/tmp/x"); - if (!sshWithDir || sshWithDir->remoteDir != "/var/tmp/x") return 1; - - auto sshWin = TestRunner::FromSpec("sshwin:winhost"); - if (!sshWin || sshWin->name != "sshwin:winhost") return 1; - - // Empty input returns nullopt; bogus prefix throws. + // Empty input returns nullopt; anything else unrecognized throws. + // ssh/sshwin/wsl used to be valid specs; they're now bogus (issue #8). if (TestRunner::FromSpec("")) return 1; - try { - TestRunner::FromSpec("nonsense:thing"); - return 1; - } catch (const std::exception&) {} + for (auto bogus : { "nonsense:thing", "ssh:somehost", "sshwin:winhost", "wsl" }) { + try { + TestRunner::FromSpec(bogus); + return 1; + } catch (const std::exception&) {} + } return 0; } diff --git a/tests/WindowsViaSsh/WindowsViaSsh.cpp b/tests/WindowsViaSsh/WindowsViaSsh.cpp deleted file mode 100644 index 428bc15..0000000 --- a/tests/WindowsViaSsh/WindowsViaSsh.cpp +++ /dev/null @@ -1,68 +0,0 @@ -/* -Crafter® Build -Copyright (C) 2026 Catcrafts® -Catcrafts.net - -LGPL-3.0-only. - -End-to-end Linux→Windows via SSH: -the inner fixture cross-compiles main.cpp for x86_64-w64-mingw32, the runner -specified via CRAFTER_BUILD_RUNNER_x86_64_w64_mingw32 scp's it to a Windows -host (winvm by default) and runs the .exe under cmd.exe via ssh. Gated on: - - mingw cross-toolchain installed (x86_64-w64-mingw32-g++) - - CRAFTER_TEST_WIN_SSH_HOST env var set - - the host reachable via ssh -*/ - -import std; -import Crafter.Build; -#include "../_shared/TestUtil.h" -namespace fs = std::filesystem; -using namespace TestUtil; -using namespace Crafter; - -namespace { - bool ToolPresent(std::string_view name) { - return std::system(std::format("which {} > /dev/null 2>&1", name).c_str()) == 0; - } -} - -int main() { - try { - const char* hostEnv = std::getenv("CRAFTER_TEST_WIN_SSH_HOST"); - if (!hostEnv || !*hostEnv) Skip("set CRAFTER_TEST_WIN_SSH_HOST to enable, e.g. winvm"); - std::string host = hostEnv; - - if (!ToolPresent("x86_64-w64-mingw32-g++")) Skip("mingw cross-toolchain not on PATH"); - - std::string probe = std::format("ssh -o BatchMode=yes -o ConnectTimeout=5 {} \"ver\" > /dev/null 2>&1", host); - if (std::system(probe.c_str()) != 0) Skip(std::format("ssh {} not reachable", host)); - - std::string remoteDir = "C:/temp/crafter-test-winhello"; - std::string spec = std::format("sshwin:{}:{}", host, remoteDir); - ::setenv("CRAFTER_BUILD_RUNNER_x86_64_w64_mingw32", spec.c_str(), 1); - - auto runner = TestRunner::FromEnv("x86_64-w64-mingw32", TestRunner::Local()); - if (runner.name != std::format("sshwin:{}", host)) { - std::println(std::cerr, "FromEnv produced '{}', expected 'sshwin:{}'", runner.name, host); - return 1; - } - - fs::path src = fs::current_path() / "tests" / "WindowsViaSsh" / "inner"; - Configuration cfg = LoadFixture("WindowsViaSsh", src); - RunTestsOptions opts; - TestSummary summary = RunTests(cfg, opts); - - if (summary.passed != 1 || summary.failed != 0 || summary.crashed != 0 || - summary.timedOut != 0 || summary.skipped != 0) { - std::println(std::cerr, - "outcome counts mismatch: passed={} failed={} crashed={} timedOut={} skipped={}", - summary.passed, summary.failed, summary.crashed, summary.timedOut, summary.skipped); - return 1; - } - return 0; - } catch (const std::exception& e) { - std::println(std::cerr, "test exception: {}", e.what()); - return 1; - } -} diff --git a/tests/WindowsViaSsh/inner/main.cpp b/tests/WindowsViaSsh/inner/main.cpp deleted file mode 100644 index 44e22e0..0000000 --- a/tests/WindowsViaSsh/inner/main.cpp +++ /dev/null @@ -1,6 +0,0 @@ -import std; - -int main() { - std::println("hi from windows"); - return 0; -} diff --git a/tests/WindowsViaSsh/inner/project.cpp b/tests/WindowsViaSsh/inner/project.cpp deleted file mode 100644 index 059f7a9..0000000 --- a/tests/WindowsViaSsh/inner/project.cpp +++ /dev/null @@ -1,28 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span) { - Configuration cfg; - cfg.path = "./"; - cfg.name = "winhello-meta"; - cfg.outputName = "winhello-meta"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.type = ConfigurationType::Executable; - - Test t; - t.config.path = "./"; - t.config.name = "winhello"; - t.config.outputName = "winhello"; - t.config.target = "x86_64-w64-mingw32"; - t.config.type = ConfigurationType::Executable; - std::array ifaces = {}; - std::array impls = { "main" }; - t.config.GetInterfacesAndImplementations(ifaces, impls); - - t.runner = TestRunner::FromEnv(t.config.target); - - cfg.tests.push_back(std::move(t)); - return cfg; -} diff --git a/tests/WindowsViaSsh/project.cpp b/tests/WindowsViaSsh/project.cpp deleted file mode 100644 index 7acd28b..0000000 --- a/tests/WindowsViaSsh/project.cpp +++ /dev/null @@ -1,20 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span) { - Configuration cfg; - cfg.path = "tests/WindowsViaSsh/"; - cfg.name = "WindowsViaSsh"; - cfg.outputName = "WindowsViaSsh"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.type = ConfigurationType::Executable; - cfg.dependencies = { ParentLib("crafter.build-lib") }; - cfg.linkFlags.push_back("-Wl,--export-dynamic"); - cfg.linkFlags.push_back("-ldl"); - std::array ifaces = {}; - std::array impls = { "WindowsViaSsh" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - return cfg; -}