test: introduce test.toml + target-derived runners alongside existing machinery

Vendors toml++ v3.4.0 as lib/toml.hpp and wires it into Crafter.Build-Test
to parse a declarative test.toml manifest (target/march/mtune/sysroot/
requires/timeout/args/defines). Test discovery now treats project.cpp and
test.toml as mutually exclusive: project.cpp stays the escape hatch for
outer-driver tests, test.toml gives downstream test authors a no-boilerplate
path.

Adds:
- TestRunner::Wine() and TestRunner::ForTarget(cfg) — runner is now derived
  from cfg.target (Local for host, Wine for Windows-on-Linux, wasmtime for
  WASI, qemu-<arch> with QEMU_LD_PREFIX for non-host Linux). The env-var
  override CRAFTER_BUILD_RUNNER_<target> still wins as a power-user escape
  hatch via FromEnv.
- Declarative preconditions: tool:<name>, file:<path>, env:<VAR> are
  evaluated before the build; missing preconditions Skip without paying
  the compile cost.
- Hard-fail-unless-declared: when a derived runner's tool is missing AND
  the test didn't declare 'tool:<that>' in requires, the missing runner
  is a Fail instead of a silent Skip. Surfaces broken cross-arch CI
  config that previously hid as "skipped".
- Multi-target sweep: bare `crafter-build test` (no --target=) now
  iterates every distinct test.toml-declared target plus the host, so
  cross-arch tests run by default without the user needing to know which
  targets exist. `--target=X` bypasses the sweep.

Test struct gains a `requires_` vector so project.cpp users can declare
preconditions too (matching what test.toml writes there).

Existing tests, factories (Ssh/SshWin/Wsl/Cmd), and CRAFTER_BUILD_RUNNER_*
machinery remain intact — this commit only adds; migration and deletion
follow in subsequent commits.

Refs issue #8.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jorijn van der Graaf 2026-05-27 17:47:39 +02:00
commit dc27c5c204
4 changed files with 18286 additions and 22 deletions

View file

@ -29,15 +29,13 @@ export namespace Crafter {
int jobs = 0;
std::optional<std::chrono::seconds> timeoutOverride;
bool listOnly = false;
// Only tests whose Configuration::target equals targetFilter are run.
// Set from --target=... (host triple if unspecified). Tests for other
// targets are silently excluded so e.g. `--target=mingw` doesn't drag
// in host-only outer-driver tests.
#ifdef _WIN32
std::string targetFilter = "x86_64-pc-windows-msvc";
#else
std::string targetFilter = "x86_64-pc-linux-gnu";
#endif
// Single-target run: only tests whose Configuration::target matches
// are included. Empty (default) = run every distinct target declared
// across discovered tests, plus the host target. Set from --target=...
// (when omitted, the harness sweeps all declared targets so cross-arch
// tests run by default without the user having to know which targets
// exist).
std::string targetFilter;
// CLI override for --runner=<spec>: applies to every test in the run.
// Target scoping is unnecessary because targetFilter ensures the run
// contains only one target's tests.