test runner, cross-target runners, lib/exe split
- subprocess-isolated test runner (replaces V1 dlopen-RunTest); Pass/Fail/Crash/Timeout/Skipped outcomes via :Test partition - TestRunner abstraction with command templates: Local, Ssh, SshWin (cmd.exe-shell), QemuUser, FromEnv; probe-based skip when runner unreachable - transitive PCM-path propagation in Build(); resolveImport walks deps recursively; depResults cache keyed by PcmDir() so per-target builds don't collide - cfg.sysroot threaded through BuildStdPcm + base compile/link command (enables aarch64 cross via Arch Linux ARM rootfs) - lib + exe split: project.cpp defines crafterBuildLib (LibraryStatic) + crafterBuildExe (Executable depending on it); build.sh produces lib/libcrafter-build.a alongside bin/crafter-build for downstream static-link consumers - Windows DLL+launcher: CRAFTER_API macro, /EXPORT flag for project.dll's CrafterBuildProject; Crafter::Run as the real entry point with main.cpp as a thin wrapper - 18 tests: HelloWorld/WithModule/Defines/CrossProjectModule/ Diamond × (Linux + sshwin:winvm), plus Incremental, BuildError, Libraries, RunnerClassification, QemuUser, SshRunner, WindowsViaSsh, CrossArchAarch64 - single ./bin/crafter-build test runs everything; Windows variants skip gracefully if winvm SSH alias unreachable Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
parent
f13671b2be
commit
cdfdb976c8
60 changed files with 2029 additions and 104 deletions
3
tests/fixtures/build-error/main.cpp
vendored
Normal file
3
tests/fixtures/build-error/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
int main() {
|
||||
return undefined_symbol_xyzzy_oqv;
|
||||
}
|
||||
22
tests/fixtures/build-error/project.cpp
vendored
Normal file
22
tests/fixtures/build-error/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
Configuration cfg;
|
||||
cfg.path = "./";
|
||||
cfg.name = "broken";
|
||||
cfg.outputName = "broken";
|
||||
cfg.target = "x86_64-pc-linux-gnu";
|
||||
cfg.type = ConfigurationType::Executable;
|
||||
|
||||
std::array<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||
|
||||
cfg.linkFlags.push_back("-Wl,--export-dynamic");
|
||||
cfg.linkFlags.push_back("-ldl");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
6
tests/fixtures/cross-arch-aarch64/main.cpp
vendored
Normal file
6
tests/fixtures/cross-arch-aarch64/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import std;
|
||||
|
||||
int main() {
|
||||
std::println("hi from {}-bit aarch64", sizeof(void*) * 8);
|
||||
return 0;
|
||||
}
|
||||
24
tests/fixtures/cross-arch-aarch64/project.cpp
vendored
Normal file
24
tests/fixtures/cross-arch-aarch64/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
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<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||
|
||||
cfg.linkFlags.push_back("-fuse-ld=lld");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
5
tests/fixtures/cross-project/lib/Foo.cppm
vendored
Normal file
5
tests/fixtures/cross-project/lib/Foo.cppm
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export module Foo;
|
||||
|
||||
export int Compute() {
|
||||
return 7 * 6;
|
||||
}
|
||||
7
tests/fixtures/cross-project/main.cpp
vendored
Normal file
7
tests/fixtures/cross-project/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import std;
|
||||
import Foo;
|
||||
|
||||
int main() {
|
||||
if (Compute() != 42) return 1;
|
||||
return 0;
|
||||
}
|
||||
8
tests/fixtures/defines/main.cpp
vendored
Normal file
8
tests/fixtures/defines/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import std;
|
||||
|
||||
static_assert(CRAFTER_TEST_FOO == 42, "CRAFTER_TEST_FOO should be 42");
|
||||
|
||||
int main() {
|
||||
if (CRAFTER_TEST_FOO != 42) return 1;
|
||||
return 0;
|
||||
}
|
||||
6
tests/fixtures/diamond/B/B.cppm
vendored
Normal file
6
tests/fixtures/diamond/B/B.cppm
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export module B;
|
||||
import X;
|
||||
|
||||
export int BValue() {
|
||||
return XValue() * 2;
|
||||
}
|
||||
6
tests/fixtures/diamond/C/C.cppm
vendored
Normal file
6
tests/fixtures/diamond/C/C.cppm
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export module C;
|
||||
import X;
|
||||
|
||||
export int CValue() {
|
||||
return XValue() * 3;
|
||||
}
|
||||
5
tests/fixtures/diamond/X/X.cppm
vendored
Normal file
5
tests/fixtures/diamond/X/X.cppm
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export module X;
|
||||
|
||||
export int XValue() {
|
||||
return 7;
|
||||
}
|
||||
9
tests/fixtures/diamond/main.cpp
vendored
Normal file
9
tests/fixtures/diamond/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
import std;
|
||||
import B;
|
||||
import C;
|
||||
|
||||
int main() {
|
||||
if (BValue() != 14) return 1; // X(7) * 2
|
||||
if (CValue() != 21) return 1; // X(7) * 3
|
||||
return 0;
|
||||
}
|
||||
7
tests/fixtures/hello-world/main.cpp
vendored
Normal file
7
tests/fixtures/hello-world/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import std;
|
||||
|
||||
int main() {
|
||||
// hello-world is degenerate: the runner reports ✅ on exit 0, which is the
|
||||
// signal that the build produced a runnable binary.
|
||||
return 0;
|
||||
}
|
||||
6
tests/fixtures/incremental/interfaces/Greeter.cppm
vendored
Normal file
6
tests/fixtures/incremental/interfaces/Greeter.cppm
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export module Greeter;
|
||||
import std;
|
||||
|
||||
export std::string Greet(std::string_view name) {
|
||||
return std::format("hello, {}!", name);
|
||||
}
|
||||
7
tests/fixtures/incremental/main.cpp
vendored
Normal file
7
tests/fixtures/incremental/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import std;
|
||||
import Greeter;
|
||||
|
||||
int main() {
|
||||
std::println("{}", Greet("crafter"));
|
||||
return 0;
|
||||
}
|
||||
22
tests/fixtures/incremental/project.cpp
vendored
Normal file
22
tests/fixtures/incremental/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
Configuration cfg;
|
||||
cfg.path = "./";
|
||||
cfg.name = "hello-mod";
|
||||
cfg.outputName = "hello-mod";
|
||||
cfg.target = "x86_64-pc-linux-gnu";
|
||||
cfg.type = ConfigurationType::Executable;
|
||||
|
||||
std::array<fs::path, 1> ifaces = { "interfaces/Greeter" };
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
cfg.GetInterfacesAndImplementations(ifaces, impls);
|
||||
|
||||
cfg.linkFlags.push_back("-Wl,--export-dynamic");
|
||||
cfg.linkFlags.push_back("-ldl");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
6
tests/fixtures/libraries/greetlib/GreetLib.cppm
vendored
Normal file
6
tests/fixtures/libraries/greetlib/GreetLib.cppm
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export module GreetLib;
|
||||
import std;
|
||||
|
||||
export std::string Greet() {
|
||||
return "hi";
|
||||
}
|
||||
8
tests/fixtures/libraries/main.cpp
vendored
Normal file
8
tests/fixtures/libraries/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
import std;
|
||||
import MathLib;
|
||||
import GreetLib;
|
||||
|
||||
int main() {
|
||||
std::println("{}={}", Greet(), Add(40, 2));
|
||||
return 0;
|
||||
}
|
||||
5
tests/fixtures/libraries/mathlib/MathLib.cppm
vendored
Normal file
5
tests/fixtures/libraries/mathlib/MathLib.cppm
vendored
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export module MathLib;
|
||||
|
||||
export int Add(int a, int b) {
|
||||
return a + b;
|
||||
}
|
||||
47
tests/fixtures/libraries/project.cpp
vendored
Normal file
47
tests/fixtures/libraries/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
static auto MathStatic = std::make_unique<Configuration>();
|
||||
MathStatic->path = "./mathlib/";
|
||||
MathStatic->name = "MathLib";
|
||||
MathStatic->outputName = "MathLib";
|
||||
MathStatic->target = "x86_64-pc-linux-gnu";
|
||||
MathStatic->type = ConfigurationType::LibraryStatic;
|
||||
{
|
||||
std::array<fs::path, 1> ifaces = { "MathLib" };
|
||||
std::array<fs::path, 0> impls = {};
|
||||
MathStatic->GetInterfacesAndImplementations(ifaces, impls);
|
||||
}
|
||||
|
||||
static auto GreetDynamic = std::make_unique<Configuration>();
|
||||
GreetDynamic->path = "./greetlib/";
|
||||
GreetDynamic->name = "GreetLib";
|
||||
GreetDynamic->outputName = "GreetLib";
|
||||
GreetDynamic->target = "x86_64-pc-linux-gnu";
|
||||
GreetDynamic->type = ConfigurationType::LibraryDynamic;
|
||||
{
|
||||
std::array<fs::path, 1> ifaces = { "GreetLib" };
|
||||
std::array<fs::path, 0> impls = {};
|
||||
GreetDynamic->GetInterfacesAndImplementations(ifaces, impls);
|
||||
}
|
||||
|
||||
Configuration app;
|
||||
app.path = "./";
|
||||
app.name = "libs-app";
|
||||
app.outputName = "libs-app";
|
||||
app.target = "x86_64-pc-linux-gnu";
|
||||
app.type = ConfigurationType::Executable;
|
||||
app.dependencies = { MathStatic.get(), GreetDynamic.get() };
|
||||
{
|
||||
std::array<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
app.GetInterfacesAndImplementations(ifaces, impls);
|
||||
}
|
||||
app.linkFlags.push_back("-Wl,--export-dynamic");
|
||||
app.linkFlags.push_back("-ldl");
|
||||
|
||||
return app;
|
||||
}
|
||||
27
tests/fixtures/qemu-runner/project.cpp
vendored
Normal file
27
tests/fixtures/qemu-runner/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
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<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "tests/Hello" };
|
||||
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
||||
t.runner = TestRunner::FromEnv(t.config.target);
|
||||
cfg.tests.push_back(std::move(t));
|
||||
|
||||
return cfg;
|
||||
}
|
||||
6
tests/fixtures/qemu-runner/tests/Hello.cpp
vendored
Normal file
6
tests/fixtures/qemu-runner/tests/Hello.cpp
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import std;
|
||||
|
||||
int main() {
|
||||
std::println("hello-from-qemu");
|
||||
return 0;
|
||||
}
|
||||
33
tests/fixtures/runner-classification/project.cpp
vendored
Normal file
33
tests/fixtures/runner-classification/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
Configuration cfg;
|
||||
cfg.path = "./";
|
||||
cfg.name = "rc-meta";
|
||||
cfg.outputName = "rc-meta";
|
||||
cfg.target = "x86_64-pc-linux-gnu";
|
||||
cfg.type = ConfigurationType::Executable;
|
||||
|
||||
auto addTest = [&](std::string name) {
|
||||
Test t;
|
||||
t.config.path = "./";
|
||||
t.config.name = name;
|
||||
t.config.outputName = name;
|
||||
t.config.target = "x86_64-pc-linux-gnu";
|
||||
t.config.type = ConfigurationType::Executable;
|
||||
std::array<fs::path, 0> empty = {};
|
||||
std::array<fs::path, 1> impls = { fs::path("tests") / name };
|
||||
t.config.GetInterfacesAndImplementations(empty, impls);
|
||||
cfg.tests.push_back(std::move(t));
|
||||
};
|
||||
|
||||
addTest("Pass");
|
||||
addTest("Fail");
|
||||
addTest("Crash");
|
||||
addTest("Hang");
|
||||
|
||||
return cfg;
|
||||
}
|
||||
4
tests/fixtures/runner-classification/tests/Crash.cpp
vendored
Normal file
4
tests/fixtures/runner-classification/tests/Crash.cpp
vendored
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
int main() {
|
||||
*(volatile int*)0 = 0;
|
||||
return 0;
|
||||
}
|
||||
1
tests/fixtures/runner-classification/tests/Fail.cpp
vendored
Normal file
1
tests/fixtures/runner-classification/tests/Fail.cpp
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
int main() { return 1; }
|
||||
3
tests/fixtures/runner-classification/tests/Hang.cpp
vendored
Normal file
3
tests/fixtures/runner-classification/tests/Hang.cpp
vendored
Normal file
|
|
@ -0,0 +1,3 @@
|
|||
int main() {
|
||||
for (;;) {}
|
||||
}
|
||||
1
tests/fixtures/runner-classification/tests/Pass.cpp
vendored
Normal file
1
tests/fixtures/runner-classification/tests/Pass.cpp
vendored
Normal file
|
|
@ -0,0 +1 @@
|
|||
int main() { return 0; }
|
||||
27
tests/fixtures/ssh-runner/project.cpp
vendored
Normal file
27
tests/fixtures/ssh-runner/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
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<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "tests/Hello" };
|
||||
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
||||
t.runner = TestRunner::FromEnv(t.config.target);
|
||||
cfg.tests.push_back(std::move(t));
|
||||
|
||||
return cfg;
|
||||
}
|
||||
6
tests/fixtures/ssh-runner/tests/Hello.cpp
vendored
Normal file
6
tests/fixtures/ssh-runner/tests/Hello.cpp
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import std;
|
||||
|
||||
int main() {
|
||||
std::println("hello-from-ssh");
|
||||
return 0;
|
||||
}
|
||||
6
tests/fixtures/windows-via-ssh/main.cpp
vendored
Normal file
6
tests/fixtures/windows-via-ssh/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
import std;
|
||||
|
||||
int main() {
|
||||
std::println("hi from windows");
|
||||
return 0;
|
||||
}
|
||||
30
tests/fixtures/windows-via-ssh/project.cpp
vendored
Normal file
30
tests/fixtures/windows-via-ssh/project.cpp
vendored
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import std;
|
||||
import Crafter.Build;
|
||||
namespace fs = std::filesystem;
|
||||
using namespace Crafter;
|
||||
|
||||
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
|
||||
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<fs::path, 0> ifaces = {};
|
||||
std::array<fs::path, 1> impls = { "main" };
|
||||
t.config.GetInterfacesAndImplementations(ifaces, impls);
|
||||
t.config.linkFlags.push_back("-fuse-ld=lld");
|
||||
t.config.linkFlags.push_back("-lstdc++exp");
|
||||
|
||||
t.runner = TestRunner::FromEnv(t.config.target);
|
||||
|
||||
cfg.tests.push_back(std::move(t));
|
||||
return cfg;
|
||||
}
|
||||
6
tests/fixtures/with-module/interfaces/Greeter.cppm
vendored
Normal file
6
tests/fixtures/with-module/interfaces/Greeter.cppm
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
export module Greeter;
|
||||
import std;
|
||||
|
||||
export std::string Greet(std::string_view name) {
|
||||
return std::format("hello, {}!", name);
|
||||
}
|
||||
7
tests/fixtures/with-module/main.cpp
vendored
Normal file
7
tests/fixtures/with-module/main.cpp
vendored
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
import std;
|
||||
import Greeter;
|
||||
|
||||
int main() {
|
||||
if (Greet("crafter") != "hello, crafter!") return 1;
|
||||
return 0;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue