// SPDX-License-Identifier: LGPL-3.0-only // SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts® import std; import Crafter.Build; using namespace Crafter; // POSIX env helpers — libc++ exports only std::getenv via `import std`, so // forward-declare the setters we need against the C library directly. extern "C" int setenv(const char* name, const char* value, int overwrite); extern "C" int unsetenv(const char* name); namespace { std::int32_t Failures = 0; void Check(bool cond, std::string_view msg) { if (!cond) { std::println(std::cerr, "FAIL: {}", msg); ++Failures; } } } // Pure-data tests for TestRunner factories. No tests are actually executed; // we just confirm the routing tables in ForTarget / FromSpec / FromEnv. int main() { // FromSpec: empty -> nullopt; recognized -> populated; garbage -> throws. { auto empty = TestRunner::FromSpec(""); Check(!empty.has_value(), "FromSpec(\"\") returns nullopt"); } { auto local = TestRunner::FromSpec("local"); Check(local.has_value() && local->IsLocal(), "FromSpec(\"local\") returns Local"); Check(local.has_value() && local->name == "local", "Local name is 'local'"); } { auto cmd = TestRunner::FromSpec("cmd:wasmtime"); Check(cmd.has_value(), "FromSpec(\"cmd:wasmtime\") returns a runner"); Check(cmd.has_value() && cmd->name == "cmd:wasmtime", "Cmd name carries the binary"); Check(cmd.has_value() && cmd->exec.find("wasmtime") != std::string::npos, "Cmd exec carries the binary"); Check(cmd.has_value() && !cmd->probe.empty(), "Cmd probe is set so harness can detect missing tools"); } { bool threw = false; try { (void)TestRunner::FromSpec("garbage"); } catch (const std::exception&) { threw = true; } Check(threw, "FromSpec rejects unrecognized non-empty specs"); } // ForTarget routing. { Configuration cfg; cfg.target = HostTarget(); TestRunner r = TestRunner::ForTarget(cfg); Check(r.IsLocal(), "ForTarget(host) is Local"); } { Configuration cfg; cfg.target = "wasm32-wasip1"; TestRunner r = TestRunner::ForTarget(cfg); Check(r.name == "cmd:wasmtime", "ForTarget(wasm32-wasip1) routes through wasmtime"); } { Configuration cfg; cfg.target = "aarch64-linux-gnu"; TestRunner r = TestRunner::ForTarget(cfg); Check(r.name == "cmd:qemu-aarch64", "ForTarget(aarch64-linux-gnu) routes through qemu-aarch64"); } { Configuration cfg; cfg.target = "i686-linux-gnu"; TestRunner r = TestRunner::ForTarget(cfg); Check(r.name == "cmd:qemu-i386", "ForTarget(i686-linux-gnu) rewrites arch to i386"); } { // Sysroot propagates to QEMU_LD_PREFIX so the dynamic linker is reachable. Configuration cfg; cfg.target = "aarch64-linux-gnu"; cfg.sysroot = "/opt/alarm-sysroot"; TestRunner r = TestRunner::ForTarget(cfg); Check(r.exec.find("QEMU_LD_PREFIX=/opt/alarm-sysroot") != std::string::npos, "ForTarget propagates sysroot to QEMU_LD_PREFIX"); } { // The only way to run x86_64-w64-mingw32 output is wine. Configuration cfg; cfg.target = "x86_64-w64-mingw32"; TestRunner r = TestRunner::ForTarget(cfg); Check(r.name == "wine", "ForTarget(mingw) uses wine"); } // SameTarget: the vendor field is spelling, not identity. A clang built // without a vendor reports x86_64-unknown-linux-gnu where Arch's reports // x86_64-pc-linux-gnu; both are the host, and a project that wrote the // other spelling must still run its tests locally. { Check(SameTarget("x86_64-pc-linux-gnu", "x86_64-unknown-linux-gnu"), "pc and unknown vendors compare equal"); Check(SameTarget("x86_64-pc-linux-gnu", "x86_64-linux-gnu"), "a vendorless triple compares equal"); Check(SameTarget("x86_64-alpine-linux-musl", "x86_64-linux-musl"), "a distro vendor compares equal"); Check(SameTarget("aarch64-unknown-linux-gnu", "aarch64-linux-gnu"), "the vendor is dropped on other arches too"); Check(SameTarget("wasm32-unknown-wasip1", "wasm32-wasip1"), "a three-part triple with a vendor compares equal"); Check(!SameTarget("x86_64-pc-linux-gnu", "x86_64-linux-musl"), "a different environment stays different"); Check(!SameTarget("x86_64-pc-linux-gnu", "aarch64-unknown-linux-gnu"), "a different arch stays different"); Check(!SameTarget("x86_64-w64-mingw32", "x86_64-linux-gnu"), "mingw's w64 is not a vendor to drop"); Check(NormalizeTarget("x86_64-w64-mingw32") == "x86_64-w64-mingw32", "a mingw triple is left alone"); Check(NormalizeTarget("wasm32-wasip1") == "wasm32-wasip1", "a two-part triple is left alone"); Configuration pc; pc.target = "x86_64-pc-linux-gnu"; Configuration unknown; unknown.target = "x86_64-unknown-linux-gnu"; if (SameTarget(HostTarget(), "x86_64-linux-gnu")) { Check(TestRunner::ForTarget(pc).IsLocal(), "ForTarget runs the pc spelling locally on an x86_64 glibc host"); Check(TestRunner::ForTarget(unknown).IsLocal(), "ForTarget runs the unknown spelling locally on an x86_64 glibc host"); } } // FromEnv: when the env var is set, it wins over the fallback. { // Use a unique fake triple so we don't stomp on a real env var the // CI/dev shell may have set. const std::string name = "CRAFTER_BUILD_RUNNER_fake_target_for_unit_test"; setenv(name.c_str(), "cmd:fake-tool", 1); TestRunner r = TestRunner::FromEnv("fake-target-for-unit-test", TestRunner::Local()); Check(r.name == "cmd:fake-tool", "FromEnv reads CRAFTER_BUILD_RUNNER_"); unsetenv(name.c_str()); TestRunner fallback = TestRunner::FromEnv("fake-target-for-unit-test", TestRunner::Local()); Check(fallback.IsLocal(), "FromEnv falls back when env var is unset"); } if (Failures > 0) { std::println(std::cerr, "{} assertions failed", Failures); return 1; } return 0; }