From 406a40642078ab090bd68078796bbf095b376711 Mon Sep 17 00:00:00 2001 From: Jorijn van der Graaf Date: Wed, 27 May 2026 18:01:49 +0200 Subject: [PATCH] test: migrate cross-arch tests to declarative test.toml; add WindowsViaWine MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Collapses three outer-driver tests into declarative top-level fixtures: - CrossArchAarch64: outer + inner pair becomes main.cpp + test.toml. The 'is this really an ARM aarch64 ELF?' artifact-introspection check is dropped — qemu-aarch64 refuses to run wrong-arch ELFs anyway, so a silent host-arch fallback would still fail the run. - Wasi: outer + inner pair becomes main.cpp + test.toml. The WASM magic-byte check is dropped on the same logic (wasmtime refuses non-WASM input). - Defines: simple defines-propagation smoke test becomes test.toml with [defines] CRAFTER_TEST_FOO = "42". Adds WindowsViaWine to replace the (forthcoming-deletion) WindowsViaSsh: declarative target=x86_64-w64-mingw32 + requires=[tool:wine, tool:x86_64-w64-mingw32-g++]. Exercises the new Wine runner and the ForTarget derivation end-to-end. Diamond and CrossProjectModule had speculative --target= reading that made them attempt cross-compilation under the multi-target sweep. They have no sysroot/toolchain plumbing, so those cross-builds always fail. Hardcoded them to HostTarget(); cross-arch tests live in their own test.toml from now on. Refs issue #8. Co-Authored-By: Claude Opus 4.7 (1M context) --- tests/CrossArchAarch64/CrossArchAarch64.cpp | 78 --------------------- tests/CrossArchAarch64/inner/project.cpp | 23 ------ tests/CrossArchAarch64/{inner => }/main.cpp | 0 tests/CrossArchAarch64/project.cpp | 20 ------ tests/CrossArchAarch64/test.toml | 13 ++++ tests/CrossProjectModule/project.cpp | 9 ++- tests/Defines/project.cpp | 24 ------- tests/Defines/test.toml | 5 ++ tests/Diamond/project.cpp | 11 +-- tests/Wasi/Wasi.cpp | 44 ------------ tests/Wasi/inner/project.cpp | 19 ----- tests/Wasi/{inner => }/main.cpp | 0 tests/Wasi/project.cpp | 20 ------ tests/Wasi/test.toml | 7 ++ tests/WindowsViaWine/main.cpp | 6 ++ tests/WindowsViaWine/test.toml | 9 +++ 16 files changed, 50 insertions(+), 238 deletions(-) delete mode 100644 tests/CrossArchAarch64/CrossArchAarch64.cpp delete mode 100644 tests/CrossArchAarch64/inner/project.cpp rename tests/CrossArchAarch64/{inner => }/main.cpp (100%) delete mode 100644 tests/CrossArchAarch64/project.cpp create mode 100644 tests/CrossArchAarch64/test.toml delete mode 100644 tests/Defines/project.cpp create mode 100644 tests/Defines/test.toml delete mode 100644 tests/Wasi/Wasi.cpp delete mode 100644 tests/Wasi/inner/project.cpp rename tests/Wasi/{inner => }/main.cpp (100%) delete mode 100644 tests/Wasi/project.cpp create mode 100644 tests/Wasi/test.toml create mode 100644 tests/WindowsViaWine/main.cpp create mode 100644 tests/WindowsViaWine/test.toml diff --git a/tests/CrossArchAarch64/CrossArchAarch64.cpp b/tests/CrossArchAarch64/CrossArchAarch64.cpp deleted file mode 100644 index 4e6f285..0000000 --- a/tests/CrossArchAarch64/CrossArchAarch64.cpp +++ /dev/null @@ -1,78 +0,0 @@ -/* -Crafter® Build -Copyright (C) 2026 Catcrafts® -Catcrafts.net - -LGPL-3.0-only. - -End-to-end cross-arch build through the V2 pipeline: -the inner project.cpp targets aarch64-linux-gnu with cfg.sysroot pointing at -the Arch Linux ARM rootfs at /opt/aarch64-rootfs. crafter.build-lib -cross-compiles the C++ source (with the libc++ std module from the sysroot), -produces a real aarch64 ELF, and qemu-aarch64 (with QEMU_LD_PREFIX pointing at -the sysroot so it can find ld-linux-aarch64.so.1) executes it. -*/ - -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 fs::path sysroot = "/opt/aarch64-rootfs"; - if (!fs::exists(sysroot / "usr/share/libc++/v1/std.cppm")) { - Skip(std::format("aarch64 sysroot missing at {} — see README", sysroot.string())); - } - if (!ToolPresent("qemu-aarch64")) Skip("qemu-aarch64 not on PATH"); - - fs::path src = fs::current_path() / "tests" / "CrossArchAarch64" / "inner"; - Configuration cfg = LoadFixture("CrossArchAarch64", src); - fs::path work = fs::current_path(); - - auto br = BuildOnce(cfg); - if (!br.result.empty()) { - std::println(std::cerr, "build failed:\n{}", br.result); - return 1; - } - - fs::path artifact = cfg.BinDir() / "aarch-hello"; - if (!fs::exists(artifact)) { - std::println(std::cerr, "expected artifact missing at {}", artifact.string()); - return 1; - } - - // Defend against silent host-arch fallback. - auto probe = RunInDir(work, std::format("file '{}'", artifact.string())); - if (probe.output.find("ARM aarch64") == std::string::npos) { - std::println(std::cerr, "artifact is not ARM aarch64 ELF:\n{}", probe.output); - return 1; - } - - // Run via qemu-aarch64. QEMU_LD_PREFIX tells qemu where the target's - // dynamic linker (ld-linux-aarch64.so.1) and shared libs live. - auto run = RunInDir(work, std::format( - "QEMU_LD_PREFIX={} qemu-aarch64 '{}'", - sysroot.string(), artifact.string())); - if (run.exitCode != 0) { - std::println(std::cerr, "qemu-aarch64 run failed (rc={}):\n{}", run.exitCode, run.output); - return 1; - } - if (run.output != "hi from 64-bit aarch64\n") { - std::println(std::cerr, "output mismatch:\n expected: \"hi from 64-bit aarch64\\n\"\n got: {:?}", run.output); - return 1; - } - return 0; - } catch (const std::exception& e) { - std::println(std::cerr, "test exception: {}", e.what()); - return 1; - } -} diff --git a/tests/CrossArchAarch64/inner/project.cpp b/tests/CrossArchAarch64/inner/project.cpp deleted file mode 100644 index 5300832..0000000 --- a/tests/CrossArchAarch64/inner/project.cpp +++ /dev/null @@ -1,23 +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 = "aarch-hello"; - cfg.outputName = "aarch-hello"; - cfg.target = "aarch64-linux-gnu"; - cfg.march = "armv8-a"; - cfg.mtune = "generic"; - cfg.sysroot = "/opt/aarch64-rootfs"; - cfg.type = ConfigurationType::Executable; - - std::array ifaces = {}; - std::array impls = { "main" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - - - return cfg; -} diff --git a/tests/CrossArchAarch64/inner/main.cpp b/tests/CrossArchAarch64/main.cpp similarity index 100% rename from tests/CrossArchAarch64/inner/main.cpp rename to tests/CrossArchAarch64/main.cpp diff --git a/tests/CrossArchAarch64/project.cpp b/tests/CrossArchAarch64/project.cpp deleted file mode 100644 index f073afb..0000000 --- a/tests/CrossArchAarch64/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/CrossArchAarch64/"; - cfg.name = "CrossArchAarch64"; - cfg.outputName = "CrossArchAarch64"; - 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 = { "CrossArchAarch64" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - return cfg; -} diff --git a/tests/CrossArchAarch64/test.toml b/tests/CrossArchAarch64/test.toml new file mode 100644 index 0000000..8bb84a6 --- /dev/null +++ b/tests/CrossArchAarch64/test.toml @@ -0,0 +1,13 @@ +# End-to-end cross-arch build: +# crafter-build cross-compiles main.cpp for aarch64-linux-gnu using the +# Arch Linux ARM sysroot, and the runner derivation (TestRunner::ForTarget) +# wraps the produced ELF in `qemu-aarch64`. cfg.sysroot is forwarded to +# QEMU_LD_PREFIX automatically so the target's dynamic linker is reachable. +target = "aarch64-linux-gnu" +march = "armv8-a" +mtune = "generic" +sysroot = "/opt/aarch64-rootfs" +requires = [ + "tool:qemu-aarch64", + "file:/opt/aarch64-rootfs/usr/share/libc++/v1/std.cppm", +] diff --git a/tests/CrossProjectModule/project.cpp b/tests/CrossProjectModule/project.cpp index 916b887..e282339 100644 --- a/tests/CrossProjectModule/project.cpp +++ b/tests/CrossProjectModule/project.cpp @@ -3,11 +3,10 @@ import Crafter.Build; namespace fs = std::filesystem; using namespace Crafter; -extern "C" Configuration CrafterBuildProject(std::span args) { - std::string target = "x86_64-pc-linux-gnu"; - for (auto a : args) { - if (a.starts_with("--target=")) target = std::string(a.substr(9)); - } +extern "C" Configuration CrafterBuildProject(std::span) { + // Host-only: verifies cross-project module imports work, doesn't exercise + // any cross-compile path. See tests/Diamond for the same rationale. + std::string target = HostTarget(); static auto fooLib = std::make_unique(); fooLib->path = "tests/CrossProjectModule/lib/"; diff --git a/tests/Defines/project.cpp b/tests/Defines/project.cpp deleted file mode 100644 index 03087fd..0000000 --- a/tests/Defines/project.cpp +++ /dev/null @@ -1,24 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -extern "C" Configuration CrafterBuildProject(std::span args) { - std::string target = "x86_64-pc-linux-gnu"; - for (auto a : args) { - if (a.starts_with("--target=")) target = std::string(a.substr(9)); - } - - Configuration cfg; - cfg.path = "tests/Defines/"; - cfg.name = "Defines"; - cfg.outputName = "defines-app"; - cfg.target = target; - cfg.type = ConfigurationType::Executable; - cfg.defines.push_back({"CRAFTER_TEST_FOO", "42"}); - - std::array ifaces = {}; - std::array impls = { "main" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - return cfg; -} diff --git a/tests/Defines/test.toml b/tests/Defines/test.toml new file mode 100644 index 0000000..78d6451 --- /dev/null +++ b/tests/Defines/test.toml @@ -0,0 +1,5 @@ +# Smoke test for cfg.defines propagation. main.cpp consumes CRAFTER_TEST_FOO +# both at compile time (static_assert) and at runtime; if the define doesn't +# reach the compile, the test won't even build. +[defines] +CRAFTER_TEST_FOO = "42" diff --git a/tests/Diamond/project.cpp b/tests/Diamond/project.cpp index 54c2d4e..1a4e17c 100644 --- a/tests/Diamond/project.cpp +++ b/tests/Diamond/project.cpp @@ -21,11 +21,12 @@ namespace { } } -extern "C" Configuration CrafterBuildProject(std::span args) { - std::string target = "x86_64-pc-linux-gnu"; - for (auto a : args) { - if (a.starts_with("--target=")) target = std::string(a.substr(9)); - } +extern "C" Configuration CrafterBuildProject(std::span) { + // Diamond exercises the build engine's dependency wiring at the host + // target. There's no cross-compile plumbing (no sysroot, no per-target + // toolchain), so we don't honor --target=. A real cross-arch test would + // live in its own tests//test.toml. + std::string target = HostTarget(); static std::unique_ptr X, B, C; X = MakeLib("X", "X", target, {}); diff --git a/tests/Wasi/Wasi.cpp b/tests/Wasi/Wasi.cpp deleted file mode 100644 index 3e2ce8a..0000000 --- a/tests/Wasi/Wasi.cpp +++ /dev/null @@ -1,44 +0,0 @@ -import std; -import Crafter.Build; -#include "../_shared/TestUtil.h" -namespace fs = std::filesystem; -using namespace TestUtil; -using namespace Crafter; - -int main() { - try { - if (!fs::exists("/usr/share/wasi-sysroot/share/libc++/v1/std.cppm")) { - Skip("WASI sysroot/libc++ missing — install wasi-libc, wasi-libc++, wasi-libc++abi"); - } - - fs::path src = fs::current_path() / "tests" / "Wasi" / "inner"; - Configuration cfg = LoadFixture("Wasi", src); - fs::path work = fs::current_path(); - - auto br = BuildOnce(cfg); - if (!br.result.empty()) { - std::println(std::cerr, "build failed:\n{}", br.result); - return 1; - } - - fs::path artifact = cfg.BinDir() / "wasi-hello.wasm"; - if (!fs::exists(artifact)) { - std::println(std::cerr, "expected artifact missing at {}", artifact.string()); - return 1; - } - - // Verify WASM magic bytes: \0asm - std::ifstream f(artifact, std::ios::binary); - char magic[4] = {}; - f.read(magic, 4); - if (magic[0] != '\0' || magic[1] != 'a' || magic[2] != 's' || magic[3] != 'm') { - std::println(std::cerr, "artifact is not a valid WASM file (bad magic bytes)"); - return 1; - } - - return 0; - } catch (const std::exception& e) { - std::println(std::cerr, "test exception: {}", e.what()); - return 1; - } -} diff --git a/tests/Wasi/inner/project.cpp b/tests/Wasi/inner/project.cpp deleted file mode 100644 index aeee1f0..0000000 --- a/tests/Wasi/inner/project.cpp +++ /dev/null @@ -1,19 +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 = "wasi-hello"; - cfg.outputName = "wasi-hello"; - cfg.target = "wasm32-wasip1"; - cfg.type = ConfigurationType::Executable; - - std::array ifaces = {}; - std::array impls = { "main" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - - return cfg; -} diff --git a/tests/Wasi/inner/main.cpp b/tests/Wasi/main.cpp similarity index 100% rename from tests/Wasi/inner/main.cpp rename to tests/Wasi/main.cpp diff --git a/tests/Wasi/project.cpp b/tests/Wasi/project.cpp deleted file mode 100644 index 54900c7..0000000 --- a/tests/Wasi/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/Wasi/"; - cfg.name = "Wasi"; - cfg.outputName = "Wasi"; - 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 = { "Wasi" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - return cfg; -} diff --git a/tests/Wasi/test.toml b/tests/Wasi/test.toml new file mode 100644 index 0000000..7f26732 --- /dev/null +++ b/tests/Wasi/test.toml @@ -0,0 +1,7 @@ +# WASI build via the WASI SDK's libc++ + the wasm32-wasip1 target. The +# runner derivation routes the .wasm artifact through `wasmtime`. +target = "wasm32-wasip1" +requires = [ + "tool:wasmtime", + "file:/usr/share/wasi-sysroot/share/libc++/v1/std.cppm", +] diff --git a/tests/WindowsViaWine/main.cpp b/tests/WindowsViaWine/main.cpp new file mode 100644 index 0000000..69c75fd --- /dev/null +++ b/tests/WindowsViaWine/main.cpp @@ -0,0 +1,6 @@ +import std; + +int main() { + std::println("hi from win{}", sizeof(void*) * 8); + return 0; +} diff --git a/tests/WindowsViaWine/test.toml b/tests/WindowsViaWine/test.toml new file mode 100644 index 0000000..e5c8ef8 --- /dev/null +++ b/tests/WindowsViaWine/test.toml @@ -0,0 +1,9 @@ +# Linux→Windows end-to-end: +# crafter-build cross-compiles main.cpp to x86_64-w64-mingw32 (.exe) and the +# runner derivation wraps it in `wine`. Replaces the old WindowsViaSsh test, +# which required a reachable Windows VM and an ssh + cmd.exe shell chain. +target = "x86_64-w64-mingw32" +requires = [ + "tool:wine", + "tool:x86_64-w64-mingw32-g++", +]