deleted tests
All checks were successful
CI / build-test-release (push) Successful in 21m52s

This commit is contained in:
Jorijn van der Graaf 2026-05-27 18:11:06 +02:00
commit 725910eb9c
64 changed files with 0 additions and 1293 deletions

View file

@ -1,41 +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 {
fs::path src = fs::current_path() / "tests" / "BuildError" / "inner";
Configuration cfg = LoadFixture("BuildError", src);
fs::path work = fs::current_path();
auto br = BuildOnce(cfg);
if (br.result.empty()) {
std::println(std::cerr, "expected build failure, got success");
return 1;
}
if (br.result.find("undefined_symbol_xyzzy_oqv") == std::string::npos) {
std::println(std::cerr, "diagnostic missing unresolved-name reference:\n{}", br.result);
return 1;
}
fs::path artifact = work / "bin" / "broken-x86_64-pc-linux-gnu-native" / "broken";
if (fs::exists(artifact)) {
std::println(std::cerr, "artifact unexpectedly produced at {}", artifact.string());
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -1,3 +0,0 @@
int main() {
return undefined_symbol_xyzzy_oqv;
}

View file

@ -1,22 +0,0 @@
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;
}

View file

@ -1,20 +0,0 @@
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 = "tests/BuildError/";
cfg.name = "BuildError";
cfg.outputName = "BuildError";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "BuildError" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,6 +0,0 @@
import std;
int main() {
std::println("hi from {}-bit aarch64", sizeof(void*) * 8);
return 0;
}

View file

@ -1,13 +0,0 @@
# 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",
]

View file

@ -1,5 +0,0 @@
export module Foo;
export int Compute() {
return 7 * 6;
}

View file

@ -1,7 +0,0 @@
import std;
import Foo;
int main() {
if (Compute() != 42) return 1;
return 0;
}

View file

@ -1,35 +0,0 @@
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
// 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<Configuration>();
fooLib->path = "tests/CrossProjectModule/lib/";
fooLib->name = std::format("Foo-{}", target);
fooLib->outputName = "Foo";
fooLib->target = target;
fooLib->type = ConfigurationType::LibraryStatic;
{
std::array<fs::path, 1> ifaces = { "Foo" };
std::array<fs::path, 0> impls = {};
fooLib->GetInterfacesAndImplementations(ifaces, impls);
}
Configuration cfg;
cfg.path = "tests/CrossProjectModule/";
cfg.name = "CrossProjectModule";
cfg.outputName = "cross-app";
cfg.target = target;
cfg.type = ConfigurationType::Executable;
cfg.dependencies = { fooLib.get() };
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "main" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,94 +0,0 @@
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
LGPL-3.0-only.
End-to-end CUDA build through the V2 pipeline:
the inner project.cpp registers a .cu translation unit via cfg.cuda, and
crafter.build-lib drives nvcc to produce a host object that links into
the final executable. The test skips when the CUDA SDK is missing
(CI hosts have neither nvcc nor libcudart) and otherwise runs the built
binary to confirm the .cu's host-callable entry returns through the link.
No GPU is required: the kernel is staged but never launched, so cudart's
fat-binary registration runs at startup but device init does not.
*/
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 {
if (!ToolPresent("nvcc")) {
Skip("nvcc not on PATH — install the CUDA SDK to enable this test");
}
// Resolve the CUDA root from nvcc's own location so the inner project
// can point the linker at libcudart.so without a hardcoded path.
// Standard SDK layout puts nvcc at <root>/bin/nvcc and cudart at
// <root>/lib64; if either assumption is broken we skip rather than
// fail, since that means the install is non-standard.
fs::path cwd = fs::current_path();
auto probe = RunInDir(cwd, "command -v nvcc");
if (probe.exitCode != 0 || probe.output.empty()) {
Skip("nvcc not on PATH");
}
std::string nvccPath = probe.output;
while (!nvccPath.empty() && (nvccPath.back() == '\n' || nvccPath.back() == '\r')) {
nvccPath.pop_back();
}
fs::path real = fs::canonical(nvccPath);
fs::path cudaLibDir = real.parent_path().parent_path() / "lib64";
if (!fs::exists(cudaLibDir / "libcudart.so")) {
Skip(std::format("libcudart.so not found at {} — incomplete CUDA SDK install",
cudaLibDir.string()));
}
::setenv("CRAFTER_CUDA_LIBDIR", cudaLibDir.string().c_str(), 1);
fs::path src = cwd / "tests" / "Cuda" / "inner";
Configuration cfg = LoadFixture("Cuda", 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() / "cuda-test";
if (!fs::exists(artifact)) {
std::println(std::cerr, "expected artifact missing at {}", artifact.string());
return 1;
}
// libcudart.so is on the system loader path on most CUDA installs
// (Arch puts /opt/cuda/lib64 in /etc/ld.so.conf.d), but other distros
// don't, so set LD_LIBRARY_PATH explicitly for the run.
auto run = RunInDir(work, std::format(
"LD_LIBRARY_PATH='{}' '{}'", cudaLibDir.string(), artifact.string()));
if (run.exitCode != 0) {
std::println(std::cerr, "artifact exited nonzero (rc={}):\n{}", run.exitCode, run.output);
return 1;
}
if (run.output != "answer=42\n") {
std::println(std::cerr, "output mismatch:\n expected: \"answer=42\\n\"\n got: {:?}", run.output);
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -1,16 +0,0 @@
// A staged-but-unlaunched kernel forces nvcc to emit a real fatbin and
// the cudart-registration glue, which is what makes this a meaningful
// CUDA-pipeline test. The host entry the C++ side actually calls returns
// the same constant via plain CPU code so the test passes on hosts that
// have the SDK but no NVIDIA driver/GPU.
__device__ int compute_on_device() {
return 42;
}
__global__ void kernel(int* out) {
*out = compute_on_device();
}
extern "C" int kernel_compute() {
return 42;
}

View file

@ -1,8 +0,0 @@
import std;
extern "C" int kernel_compute();
int main() {
std::println("answer={}", kernel_compute());
return 0;
}

View file

@ -1,29 +0,0 @@
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 = "cuda-test";
cfg.outputName = "cuda-test";
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.cuda.push_back("kernel");
// The outer test driver discovers the SDK location and exports it; nvcc
// emits __cudaRegisterFatBinary references into every .cu object even
// when no kernel is launched, so libcudart has to be on the link line.
if (const char* libDir = std::getenv("CRAFTER_CUDA_LIBDIR")) {
cfg.linkFlags.push_back(std::format("-L{}", libDir));
cfg.linkFlags.push_back("-lcudart");
}
return cfg;
}

View file

@ -1,20 +0,0 @@
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 = "tests/Cuda/";
cfg.name = "Cuda";
cfg.outputName = "Cuda";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "Cuda" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,8 +0,0 @@
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;
}

View file

@ -1,5 +0,0 @@
# 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"

View file

@ -1,6 +0,0 @@
export module B;
import X;
export int BValue() {
return XValue() * 2;
}

View file

@ -1,6 +0,0 @@
export module C;
import X;
export int CValue() {
return XValue() * 3;
}

View file

@ -1,5 +0,0 @@
export module X;
export int XValue() {
return 7;
}

View file

@ -1,9 +0,0 @@
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;
}

View file

@ -1,50 +0,0 @@
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
namespace {
std::unique_ptr<Configuration> MakeLib(std::string_view dir, std::string_view modName,
std::string_view target,
std::span<Configuration*> deps) {
auto lib = std::make_unique<Configuration>();
lib->path = std::format("tests/Diamond/{}/", dir);
lib->name = std::format("{}-Diamond-{}", modName, target);
lib->outputName = std::string(modName);
lib->target = std::string(target);
lib->type = ConfigurationType::LibraryStatic;
lib->dependencies.assign(deps.begin(), deps.end());
std::array<fs::path, 1> ifaces = { fs::path(modName) };
std::array<fs::path, 0> impls = {};
lib->GetInterfacesAndImplementations(ifaces, impls);
return lib;
}
}
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
// 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/<Name>/test.toml.
std::string target = HostTarget();
static std::unique_ptr<Configuration> X, B, C;
X = MakeLib("X", "X", target, {});
Configuration* xDeps[] = { X.get() };
B = MakeLib("B", "B", target, xDeps);
C = MakeLib("C", "C", target, xDeps);
Configuration* mainDeps[] = { B.get(), C.get() };
Configuration cfg;
cfg.path = "tests/Diamond/";
cfg.name = "Diamond";
cfg.outputName = "diamond-app";
cfg.target = target;
cfg.type = ConfigurationType::Executable;
cfg.dependencies.assign(mainDeps, mainDeps + 2);
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "main" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,7 +0,0 @@
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;
}

View file

@ -1,72 +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 std::chrono_literals;
using namespace TestUtil;
using namespace Crafter;
int main() {
try {
fs::path src = fs::current_path() / "tests" / "Incremental" / "inner";
Configuration cfg = LoadFixture("Incremental", src);
fs::path work = fs::current_path();
auto cold = BuildOnce(cfg);
if (!cold.result.empty()) {
std::println(std::cerr, "cold build failed:\n{}", cold.result);
return 1;
}
fs::path greeterObj = cfg.BuildDir() / "Greeter.o";
fs::path mainObj = cfg.BuildDir() / "main_impl.o";
if (!fs::exists(greeterObj) || !fs::exists(mainObj)) {
std::println(std::cerr, "expected .o files missing after cold build");
return 1;
}
auto greeter_t1 = fs::last_write_time(greeterObj);
auto main_t1 = fs::last_write_time(mainObj);
auto noop = BuildOnce(cfg);
if (!noop.result.empty()) {
std::println(std::cerr, "no-op rebuild failed:\n{}", noop.result);
return 1;
}
if (fs::last_write_time(greeterObj) != greeter_t1) {
std::println(std::cerr, "no-op rebuild regenerated Greeter.o");
return 1;
}
if (fs::last_write_time(mainObj) != main_t1) {
std::println(std::cerr, "no-op rebuild regenerated main_impl.o");
return 1;
}
// Touch main.cpp: only main_impl.o should regenerate.
fs::last_write_time(work / "main.cpp", std::chrono::file_clock::now() + 2s);
auto touched = BuildOnce(cfg);
if (!touched.result.empty()) {
std::println(std::cerr, "rebuild after touch failed:\n{}", touched.result);
return 1;
}
if (fs::last_write_time(greeterObj) != greeter_t1) {
std::println(std::cerr, "touching main.cpp unnecessarily rebuilt Greeter.o");
return 1;
}
if (fs::last_write_time(mainObj) <= main_t1) {
std::println(std::cerr, "touching main.cpp did NOT rebuild main_impl.o");
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -1,6 +0,0 @@
export module Greeter;
import std;
export std::string Greet(std::string_view name) {
return std::format("hello, {}!", name);
}

View file

@ -1,7 +0,0 @@
import std;
import Greeter;
int main() {
std::println("{}", Greet("crafter"));
return 0;
}

View file

@ -1,22 +0,0 @@
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;
}

View file

@ -1,20 +0,0 @@
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 = "tests/Incremental/";
cfg.name = "Incremental";
cfg.outputName = "Incremental";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "Incremental" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,63 +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 {
fs::path src = fs::current_path() / "tests" / "Libraries" / "inner";
Configuration cfg = LoadFixture("Libraries", 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 staticArchive = cfg.dependencies[0]->BinDir() / "libMathLib.a";
fs::path dynamicSO = cfg.dependencies[1]->BinDir() / "libGreetLib.so";
fs::path artifact = cfg.BinDir() / "libs-app";
if (!fs::exists(staticArchive)) {
std::println(std::cerr, "static archive missing at {}", staticArchive.string());
return 1;
}
if (!fs::exists(dynamicSO)) {
std::println(std::cerr, "dynamic .so missing at {}", dynamicSO.string());
return 1;
}
if (!fs::exists(artifact)) {
std::println(std::cerr, "exe missing at {}", artifact.string());
return 1;
}
// Linked against the dynamic .so which lives in greetlib/bin/...; set
// LD_LIBRARY_PATH explicitly for the artifact run.
auto run = RunInDir(work, std::format(
"LD_LIBRARY_PATH='{}' '{}'",
dynamicSO.parent_path().string(), artifact.string()));
if (run.exitCode != 0) {
std::println(std::cerr, "artifact exited nonzero (rc={}):\n{}", run.exitCode, run.output);
return 1;
}
if (run.output != "hi=42\n") {
std::println(std::cerr, "output mismatch:\n expected: \"hi=42\\n\"\n got: {:?}", run.output);
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -1,6 +0,0 @@
export module GreetLib;
import std;
export std::string Greet() {
return "hi";
}

View file

@ -1,8 +0,0 @@
import std;
import MathLib;
import GreetLib;
int main() {
std::println("{}={}", Greet(), Add(40, 2));
return 0;
}

View file

@ -1,5 +0,0 @@
export module MathLib;
export int Add(int a, int b) {
return a + b;
}

View file

@ -1,47 +0,0 @@
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;
}

View file

@ -1,20 +0,0 @@
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 = "tests/Libraries/";
cfg.name = "Libraries";
cfg.outputName = "Libraries";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "Libraries" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,37 +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 {
fs::path src = fs::current_path() / "tests" / "RunnerClassification" / "inner";
Configuration cfg = LoadFixture("RunnerClassification", src);
RunTestsOptions opts;
opts.timeoutOverride = std::chrono::seconds(2); // Hang test bounded
TestSummary summary = RunTests(cfg, opts);
if (summary.passed != 1 || summary.failed != 1 ||
summary.crashed != 1 || summary.timedOut != 1 || 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;
}
}

View file

@ -1,33 +0,0 @@
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;
}

View file

@ -1,4 +0,0 @@
int main() {
*(volatile int*)0 = 0;
return 0;
}

View file

@ -1 +0,0 @@
int main() { return 1; }

View file

@ -1,3 +0,0 @@
int main() {
for (;;) {}
}

View file

@ -1 +0,0 @@
int main() { return 0; }

View file

@ -1,20 +0,0 @@
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 = "tests/RunnerClassification/";
cfg.name = "RunnerClassification";
cfg.outputName = "RunnerClassification";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "RunnerClassification" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,61 +0,0 @@
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
LGPL-3.0-only.
End-to-end shader build through the V2 pipeline:
the inner project.cpp registers a vertex shader via cfg.shaders, and
crafter.build-lib invokes glslang in-process to lower it to SPIR-V. The
test verifies the resulting .spv file lands in the per-target bin dir
and starts with the SPIR-V magic word a zero-length output or one
written under a different name would be flagged here rather than
silently passing.
*/
import std;
import Crafter.Build;
#include "../_shared/TestUtil.h"
namespace fs = std::filesystem;
using namespace TestUtil;
using namespace Crafter;
int main() {
try {
fs::path src = fs::current_path() / "tests" / "Shader" / "inner";
Configuration cfg = LoadFixture("Shader", 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 spv = cfg.BinDir() / "triangle.spv";
if (!fs::exists(spv)) {
std::println(std::cerr, "expected SPIR-V output missing at {}", spv.string());
return 1;
}
// SPIR-V module header magic is 0x07230203, written as a 32-bit word
// in the module's endianness (always little-endian on the targets we
// build). Matching the raw byte sequence catches truncated writes
// and any future regression that produces a non-SPIR-V .spv.
std::ifstream f(spv, std::ios::binary);
unsigned char magic[4] = {};
f.read(reinterpret_cast<char*>(magic), 4);
if (magic[0] != 0x03 || magic[1] != 0x02 || magic[2] != 0x23 || magic[3] != 0x07) {
std::println(std::cerr,
"SPIR-V magic mismatch: got {:#04x} {:#04x} {:#04x} {:#04x}",
magic[0], magic[1], magic[2], magic[3]);
return 1;
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -1,3 +0,0 @@
import std;
int main() { return 0; }

View file

@ -1,21 +0,0 @@
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 = "shader-test";
cfg.outputName = "shader-test";
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.shaders.emplace_back("triangle.glsl", "main", ShaderType::Vertex);
return cfg;
}

View file

@ -1,13 +0,0 @@
#version 450
layout(location = 0) out vec3 color;
void main() {
vec2 positions[3] = vec2[](
vec2( 0.0, -0.5),
vec2( 0.5, 0.5),
vec2(-0.5, 0.5)
);
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
color = vec3(1.0, 0.0, 0.0);
}

View file

@ -1,20 +0,0 @@
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 = "tests/Shader/";
cfg.name = "Shader";
cfg.outputName = "Shader";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "Shader" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,117 +0,0 @@
/*
Crafter® Build
Copyright (C) 2026 Catcrafts®
Catcrafts.net
LGPL-3.0-only.
Verifies that when an executable consumes a library that registers shaders
or asset files, those runtime artifacts ship in the executable's bin dir
alongside the binary not just in the lib's own bin dir, where the runtime
exe wouldn't find them. Covers a .spv (cfg.shaders), a flat asset
(cfg.files file), and a directory tree (cfg.files dir).
Also verifies cfg.buildFiles' include-path semantic: the consumer's own
shader compile resolves `#include "ui-shared.glsl"` against the lib's
buildFiles in-place from the dep's source tree (no copy), and the file is
NOT mirrored into any build/bin dir.
*/
import std;
import Crafter.Build;
#include "../_shared/TestUtil.h"
namespace fs = std::filesystem;
using namespace TestUtil;
using namespace Crafter;
int main() {
try {
fs::path src = fs::current_path() / "tests" / "ShaderDep" / "inner";
Configuration cfg = LoadFixture("ShaderDep", src);
auto br = BuildOnce(cfg);
if (!br.result.empty()) {
std::println(std::cerr, "build failed:\n{}", br.result);
return 1;
}
fs::path libSpv = cfg.dependencies[0]->BinDir() / "triangle.spv";
fs::path appSpv = cfg.BinDir() / "triangle.spv";
if (!fs::exists(libSpv)) {
std::println(std::cerr, "lib .spv missing at {}", libSpv.string());
return 1;
}
if (!fs::exists(appSpv)) {
std::println(std::cerr, "exe .spv missing at {}", appSpv.string());
return 1;
}
std::ifstream f(appSpv, std::ios::binary);
unsigned char magic[4] = {};
f.read(reinterpret_cast<char*>(magic), 4);
if (magic[0] != 0x03 || magic[1] != 0x02 || magic[2] != 0x23 || magic[3] != 0x07) {
std::println(std::cerr,
"SPIR-V magic mismatch in exe-side copy: got {:#04x} {:#04x} {:#04x} {:#04x}",
magic[0], magic[1], magic[2], magic[3]);
return 1;
}
fs::path appAsset = cfg.BinDir() / "asset.txt";
if (!fs::exists(appAsset)) {
std::println(std::cerr, "exe-side asset.txt missing at {}", appAsset.string());
return 1;
}
if (ReadFile(appAsset) != "crafter-build asset\n") {
std::println(std::cerr, "asset.txt content mismatch at {}", appAsset.string());
return 1;
}
fs::path appNested = cfg.BinDir() / "data" / "nested.txt";
if (!fs::exists(appNested)) {
std::println(std::cerr, "exe-side data/nested.txt missing at {}", appNested.string());
return 1;
}
if (ReadFile(appNested) != "nested asset\n") {
std::println(std::cerr, "data/nested.txt content mismatch at {}", appNested.string());
return 1;
}
// The consumer shader includes a header from the lib's buildFiles.
// Successful build (above) means glslang resolved the include
// in-place from the dep's source tree — verify the resulting .spv
// and the SPIR-V magic, same as for the lib's shader.
fs::path consumerSpv = cfg.BinDir() / "consumer.spv";
if (!fs::exists(consumerSpv)) {
std::println(std::cerr, "consumer .spv missing at {}", consumerSpv.string());
return 1;
}
std::ifstream cf(consumerSpv, std::ios::binary);
unsigned char cmagic[4] = {};
cf.read(reinterpret_cast<char*>(cmagic), 4);
if (cmagic[0] != 0x03 || cmagic[1] != 0x02 || cmagic[2] != 0x23 || cmagic[3] != 0x07) {
std::println(std::cerr,
"SPIR-V magic mismatch in consumer.spv: got {:#04x} {:#04x} {:#04x} {:#04x}",
cmagic[0], cmagic[1], cmagic[2], cmagic[3]);
return 1;
}
// buildFiles must NOT be copied anywhere — they're read in place.
for (fs::path probe : {
cfg.BinDir() / "ui-shared.glsl",
cfg.BuildDir() / "ui-shared.glsl",
cfg.dependencies[0]->BinDir() / "ui-shared.glsl",
cfg.dependencies[0]->BuildDir() / "ui-shared.glsl",
}) {
if (fs::exists(probe)) {
std::println(std::cerr, "ui-shared.glsl unexpectedly copied to {}", probe.string());
return 1;
}
}
return 0;
} catch (const std::exception& e) {
std::println(std::cerr, "test exception: {}", e.what());
return 1;
}
}

View file

@ -1,16 +0,0 @@
#version 450
#extension GL_GOOGLE_include_directive : require
#include "ui-shared.glsl"
layout(location = 0) out vec3 color;
void main() {
vec2 positions[3] = vec2[](
vec2( 0.0, -0.5),
vec2( 0.5, 0.5),
vec2(-0.5, 0.5)
);
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
color = ui_color();
}

View file

@ -1,3 +0,0 @@
export module ShaderLib;
export int Identify() { return 0; }

View file

@ -1 +0,0 @@
crafter-build asset

View file

@ -1 +0,0 @@
nested asset

View file

@ -1,13 +0,0 @@
#version 450
layout(location = 0) out vec3 color;
void main() {
vec2 positions[3] = vec2[](
vec2( 0.0, -0.5),
vec2( 0.5, 0.5),
vec2(-0.5, 0.5)
);
gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0);
color = vec3(1.0, 0.0, 0.0);
}

View file

@ -1,8 +0,0 @@
#ifndef UI_SHARED_GLSL
#define UI_SHARED_GLSL
vec3 ui_color() {
return vec3(0.25, 0.5, 1.0);
}
#endif

View file

@ -1,4 +0,0 @@
import std;
import ShaderLib;
int main() { return Identify(); }

View file

@ -1,37 +0,0 @@
import std;
import Crafter.Build;
namespace fs = std::filesystem;
using namespace Crafter;
extern "C" Configuration CrafterBuildProject(std::span<const std::string_view>) {
static auto ShaderLib = std::make_unique<Configuration>();
ShaderLib->path = "./lib/";
ShaderLib->name = "ShaderLib";
ShaderLib->outputName = "ShaderLib";
ShaderLib->target = "x86_64-pc-linux-gnu";
ShaderLib->type = ConfigurationType::LibraryStatic;
{
std::array<fs::path, 1> ifaces = { "ShaderLib" };
std::array<fs::path, 0> impls = {};
ShaderLib->GetInterfacesAndImplementations(ifaces, impls);
}
ShaderLib->shaders.emplace_back("./lib/triangle.glsl", "main", ShaderType::Vertex);
ShaderLib->files.emplace_back("./lib/asset.txt");
ShaderLib->files.emplace_back("./lib/data");
ShaderLib->buildFiles.emplace_back("./lib/ui-shared.glsl");
Configuration app;
app.path = "./";
app.name = "shader-dep-app";
app.outputName = "shader-dep-app";
app.target = "x86_64-pc-linux-gnu";
app.type = ConfigurationType::Executable;
app.dependencies = { ShaderLib.get() };
{
std::array<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "main" };
app.GetInterfacesAndImplementations(ifaces, impls);
}
app.shaders.emplace_back("./consumer.glsl", "main", ShaderType::Vertex);
return app;
}

View file

@ -1,20 +0,0 @@
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 = "tests/ShaderDep/";
cfg.name = "ShaderDep";
cfg.outputName = "ShaderDep";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "ShaderDep" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,27 +0,0 @@
import std;
import Crafter.Build;
using namespace Crafter;
int main() {
// Local() is the no-op runner — empty exec template, name "local".
if (TestRunner::Local().name != "local") return 1;
// FromSpec parses each known kind and labels it consistently.
auto cmd = TestRunner::FromSpec("cmd:wine");
if (!cmd || cmd->name != "cmd:wine") return 1;
auto local = TestRunner::FromSpec("local");
if (!local || local->name != "local") return 1;
// 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;
for (auto bogus : { "nonsense:thing", "ssh:somehost", "sshwin:winhost", "wsl" }) {
try {
TestRunner::FromSpec(bogus);
return 1;
} catch (const std::exception&) {}
}
return 0;
}

View file

@ -1,21 +0,0 @@
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 = "tests/UnitLib/";
cfg.name = "UnitLib";
cfg.outputName = "UnitLib";
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<fs::path, 0> ifaces = {};
std::array<fs::path, 1> impls = { "main" };
cfg.GetInterfacesAndImplementations(ifaces, impls);
return cfg;
}

View file

@ -1,6 +0,0 @@
import std;
int main() {
std::println("Hello, WASI!");
return 0;
}

View file

@ -1,7 +0,0 @@
# 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",
]

View file

@ -1,6 +0,0 @@
import std;
int main() {
std::println("hi from win{}", sizeof(void*) * 8);
return 0;
}

View file

@ -1,9 +0,0 @@
# 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++",
]

View file

@ -1,6 +0,0 @@
export module Greeter;
import std;
export std::string Greet(std::string_view name) {
return std::format("hello, {}!", name);
}

View file

@ -1,7 +0,0 @@
import std;
import Greeter;
int main() {
if (Greet("crafter") != "hello, crafter!") return 1;
return 0;
}

View file

@ -1,76 +0,0 @@
#pragma once
#include <stdlib.h> // setenv (POSIX, not in `import std`)
namespace TestUtil {
// Exit code 77 follows the autoconf convention. crafter-build's test
// runner maps it to TestOutcome::Skipped and renders the test's stdout
// as the reason. Use this when the test discovers at runtime that its
// preconditions aren't met (tool missing, env unset, etc).
[[noreturn]] inline void Skip(std::string_view reason) {
std::print("{}", reason);
std::exit(77);
}
inline std::string ReadFile(const std::filesystem::path& p) {
std::ifstream f(p);
std::stringstream ss;
ss << f.rdbuf();
return ss.str();
}
inline std::filesystem::path CopyFixtureToTemp(std::string_view testName, const std::filesystem::path& source) {
namespace fs = std::filesystem;
fs::path tmp = fs::temp_directory_path() / std::format("crafter-test-{}", testName);
fs::remove_all(tmp);
fs::copy(source, tmp, fs::copy_options::recursive);
return tmp;
}
struct CmdResult {
int exitCode;
std::string output;
};
inline CmdResult RunInDir(const std::filesystem::path& cwd, std::string_view command) {
namespace fs = std::filesystem;
// Log inside cwd so parallel test drivers don't trample each other.
fs::path log = cwd / ".crafter-cmd-output.log";
std::string cmd = std::format("cd '{}' && {} > '{}' 2>&1",
cwd.string(), command, log.string());
int rc = std::system(cmd.c_str());
std::string out = ReadFile(log);
std::error_code ec;
fs::remove(log, ec);
return {rc, std::move(out)};
}
// Build cfg with a fresh dep cache. Convenient for outer-driver tests that
// exercise the build API in-process and don't need to share the dep cache
// across multiple Build() calls.
inline Crafter::BuildResult BuildOnce(Crafter::Configuration& cfg) {
std::unordered_map<std::filesystem::path, std::shared_future<Crafter::BuildResult>> depResults;
std::mutex depMutex;
return Crafter::Build(cfg, depResults, depMutex);
}
// Copy a fixture into a fresh temp dir, chdir there (so cfg.path = "./"
// inside the inner project.cpp resolves to the temp dir), and load its
// project.cpp. Common prep for in-process build/test API tests.
//
// Also sets CRAFTER_BUILD_HOME to <projectRoot>/share/crafter-build before
// loading. The lib's default sourceDir is derived from /proc/self/exe, but
// test exes live in tests/<Name>/bin/... rather than next to the project's
// share/, so the default lookup misses. The test runner launches tests
// with cwd = project root, so we capture that here before the chdir below.
inline Crafter::Configuration LoadFixture(std::string_view testName,
const std::filesystem::path& source,
std::span<const std::string_view> args = {}) {
auto projectRoot = std::filesystem::current_path();
auto sharePath = projectRoot / "share" / "crafter-build";
::setenv("CRAFTER_BUILD_HOME", sharePath.string().c_str(), 1);
auto work = CopyFixtureToTemp(testName, source);
std::filesystem::current_path(work);
return Crafter::LoadProject("project.cpp", args);
}
}