diff --git a/project.cpp b/project.cpp index 89ab636..a1e9fd1 100644 --- a/project.cpp +++ b/project.cpp @@ -98,15 +98,6 @@ extern "C" Configuration CrafterBuildProject(std::span a // Mirrors how downstream consumers link their own libraries into tests. if (cfg.target == "x86_64-pc-linux-gnu") { cfg.AddTest("HelloWorld").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("StaticLib").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("ModuleInterface").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("DependencyLink").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("ShaderCompile").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("StandardArgs").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("TestRunnerSpec").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("VariantId").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("WasiBrowserRuntime").Dependencies({ crafterBuildLib.get() }); - cfg.AddTest("RunSingleTestExit").Dependencies({ crafterBuildLib.get() }); } return cfg; diff --git a/tests/DependencyLink/fixture/main.cpp b/tests/DependencyLink/fixture/main.cpp deleted file mode 100644 index 93a058f..0000000 --- a/tests/DependencyLink/fixture/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -import std; -import Calc; - -int main() { - std::print("{}", Add(3, 4)); - return 0; -} diff --git a/tests/DependencyLink/fixture/mathlib/Calc.cppm b/tests/DependencyLink/fixture/mathlib/Calc.cppm deleted file mode 100644 index 994e745..0000000 --- a/tests/DependencyLink/fixture/mathlib/Calc.cppm +++ /dev/null @@ -1,4 +0,0 @@ -export module Calc; -import std; - -export int Add(int a, int b) { return a + b; } diff --git a/tests/DependencyLink/main.cpp b/tests/DependencyLink/main.cpp deleted file mode 100644 index 445f56e..0000000 --- a/tests/DependencyLink/main.cpp +++ /dev/null @@ -1,90 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -// Two-config build: an exe linked against a sibling static library via -// cfg.dependencies. Exercises cross-config module import resolution (the -// exe imports Calc, which is defined in the lib's interface set) and the -// dep-archive linking step. -int main() { - fs::path fixtureRoot = fs::current_path() / "tests" / "DependencyLink" / "fixture"; - - auto lib = std::make_unique(); - lib->path = fixtureRoot / "mathlib"; - lib->name = "calc"; - lib->outputName = "calc"; - lib->target = HostTarget(); - lib->type = ConfigurationType::LibraryStatic; - { - std::array ifaces = { "Calc" }; - std::array impls = {}; - lib->GetInterfacesAndImplementations(ifaces, impls); - } - - Configuration app; - app.path = fixtureRoot; - app.name = "calc-app"; - app.outputName = "calc-app"; - app.target = HostTarget(); - app.type = ConfigurationType::Executable; - app.dependencies = { lib.get() }; - { - std::array ifaces = {}; - std::array impls = { "main" }; - app.GetInterfacesAndImplementations(ifaces, impls); - } - - if (app.implementations.size() != 1) { - std::println(std::cerr, "expected 1 implementation, got {}", app.implementations.size()); - return 1; - } - // The import resolves to the dependency's interface, not a local one. - if (!app.implementations[0].moduleDependencies.empty()) { - std::println(std::cerr, "expected no local module deps, got {}", - app.implementations[0].moduleDependencies.size()); - return 1; - } - if (app.implementations[0].externalModuleDependencies.size() != 1) { - std::println(std::cerr, "expected 1 external module dep, got {}", - app.implementations[0].externalModuleDependencies.size()); - return 1; - } - if (app.implementations[0].externalModuleDependencies[0].first->name != "Calc") { - std::println(std::cerr, "expected external dep 'Calc', got '{}'", - app.implementations[0].externalModuleDependencies[0].first->name); - return 1; - } - - std::unordered_map> depResults; - std::mutex depMutex; - BuildResult r = Build(app, depResults, depMutex); - if (!r.result.empty()) { - std::println(std::cerr, "build failed: {}", r.result); - return 1; - } - - fs::path libArchive = lib->BinDir() / "libcalc.a"; - if (!fs::exists(libArchive)) { - std::println(std::cerr, "dep archive not produced at {}", libArchive.string()); - return 1; - } - - fs::path bin = app.BinDir() / "calc-app"; - if (!fs::exists(bin)) { - std::println(std::cerr, "binary not produced at {}", bin.string()); - return 1; - } - - auto run = RunCommandWithTimeout(bin.string(), std::chrono::seconds(10)); - if (run.exitCode != 0 || run.timedOut || run.crashed) { - std::println(std::cerr, "exe did not exit cleanly: exit={} output={}", - run.exitCode, run.output); - return 1; - } - if (run.output != "7") { - std::println(std::cerr, "expected '7', got '{}'", run.output); - return 1; - } - return 0; -} diff --git a/tests/ModuleInterface/fixture/Greeter.cppm b/tests/ModuleInterface/fixture/Greeter.cppm deleted file mode 100644 index 97ebc78..0000000 --- a/tests/ModuleInterface/fixture/Greeter.cppm +++ /dev/null @@ -1,4 +0,0 @@ -export module Greeter; -import std; - -export std::string Greet() { return "ok-from-module"; } diff --git a/tests/ModuleInterface/fixture/main.cpp b/tests/ModuleInterface/fixture/main.cpp deleted file mode 100644 index e4fc1b5..0000000 --- a/tests/ModuleInterface/fixture/main.cpp +++ /dev/null @@ -1,7 +0,0 @@ -import std; -import Greeter; - -int main() { - std::print("{}", Greet()); - return 0; -} diff --git a/tests/ModuleInterface/main.cpp b/tests/ModuleInterface/main.cpp deleted file mode 100644 index 9173dc1..0000000 --- a/tests/ModuleInterface/main.cpp +++ /dev/null @@ -1,65 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -// Build an executable that imports a project-local .cppm module, then run -// it and check the produced output. Exercises the within-project module -// dependency path (main.cpp imports Greeter, resolved through cfg.interfaces). -int main() { - Configuration cfg; - cfg.path = fs::current_path() / "tests" / "ModuleInterface" / "fixture"; - cfg.name = "greeter-app"; - cfg.outputName = "greeter-app"; - cfg.target = HostTarget(); - cfg.type = ConfigurationType::Executable; - - std::array ifaces = { "Greeter" }; - std::array impls = { "main" }; - cfg.GetInterfacesAndImplementations(ifaces, impls); - - // Implementation should have picked up the import Greeter; through the - // interface list resolution in GetInterfacesAndImplementations. - if (cfg.implementations.size() != 1) { - std::println(std::cerr, "expected 1 implementation, got {}", cfg.implementations.size()); - return 1; - } - if (cfg.implementations[0].moduleDependencies.size() != 1) { - std::println(std::cerr, - "expected main.cpp to depend on 1 module, got {}", - cfg.implementations[0].moduleDependencies.size()); - return 1; - } - if (cfg.implementations[0].moduleDependencies[0]->name != "Greeter") { - std::println(std::cerr, - "expected dep 'Greeter', got '{}'", - cfg.implementations[0].moduleDependencies[0]->name); - return 1; - } - - std::unordered_map> depResults; - std::mutex depMutex; - BuildResult r = Build(cfg, depResults, depMutex); - if (!r.result.empty()) { - std::println(std::cerr, "build failed: {}", r.result); - return 1; - } - - fs::path bin = cfg.BinDir() / "greeter-app"; - if (!fs::exists(bin)) { - std::println(std::cerr, "binary not produced at {}", bin.string()); - return 1; - } - - auto run = RunCommandWithTimeout(bin.string(), std::chrono::seconds(10)); - if (run.exitCode != 0 || run.timedOut || run.crashed) { - std::println(std::cerr, "binary did not exit cleanly: exit={} output={}", - run.exitCode, run.output); - return 1; - } - if (run.output != "ok-from-module") { - std::println(std::cerr, "unexpected output: '{}'", run.output); - return 1; - } - return 0; -} diff --git a/tests/RunSingleTestExit/main.cpp b/tests/RunSingleTestExit/main.cpp deleted file mode 100644 index a76a0ab..0000000 --- a/tests/RunSingleTestExit/main.cpp +++ /dev/null @@ -1,87 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -namespace { - int failures = 0; - - void Check(bool cond, std::string_view msg) { - if (!cond) { - std::println(std::cerr, "FAIL: {}", msg); - ++failures; - } - } - - // Write a tiny POSIX shell script that exits with `code` and return its - // path. The harness invokes ` ` through the host shell, so - // a #!/bin/sh script works as a stand-in for a real test binary. - fs::path WriteExitScript(const fs::path& dir, std::string_view stem, int code) { - fs::path script = dir / stem; - std::ofstream out(script); - out << "#!/bin/sh\nexit " << code << "\n"; - out.close(); - fs::permissions(script, - fs::perms::owner_read | fs::perms::owner_write | fs::perms::owner_exec - | fs::perms::group_read | fs::perms::group_exec - | fs::perms::others_read | fs::perms::others_exec, - fs::perm_options::replace); - return script; - } -} - -// Drives RunSingleTest against shell-script fixtures and asserts the -// exit-code → TestOutcome mapping documented in the README: -// 0 -> Pass -// 77 -> Skipped (autoconf convention) -// anything else -> Fail -int main() { - fs::path dir = fs::temp_directory_path() / "crafter-build-run-single-test"; - std::error_code ec; - fs::remove_all(dir, ec); - fs::create_directories(dir); - - Test t; - t.runner = TestRunner::Local(); - t.config.outputName = "run-single"; - - { - fs::path script = WriteExitScript(dir, "pass.sh", 0); - TestResult r = RunSingleTest(t, script, std::chrono::seconds(5)); - Check(r.outcome == TestOutcome::Pass, "exit 0 -> Pass"); - Check(r.exitCode == 0, "Pass carries exitCode 0"); - } - { - fs::path script = WriteExitScript(dir, "skip.sh", 77); - TestResult r = RunSingleTest(t, script, std::chrono::seconds(5)); - Check(r.outcome == TestOutcome::Skipped, "exit 77 -> Skipped"); - Check(r.exitCode == 77, "Skipped carries exitCode 77"); - } - { - fs::path script = WriteExitScript(dir, "fail.sh", 3); - TestResult r = RunSingleTest(t, script, std::chrono::seconds(5)); - Check(r.outcome == TestOutcome::Fail, "non-zero/non-77 exit -> Fail"); - Check(r.exitCode == 3, "Fail carries the original exitCode"); - } - - // Cmd-prefix runner: wrap the script in `sh {bin} {args}` so we exercise - // the templated-exec path through RunSingleTest. Probes don't run here - // (RunSingleTest doesn't probe — RunTests does), so any prefix that - // resolves on PATH is fine. - { - Test ct; - ct.runner = TestRunner::Cmd("sh"); - ct.config.outputName = "run-single-prefix"; - fs::path script = WriteExitScript(dir, "prefixed.sh", 0); - TestResult r = RunSingleTest(ct, script, std::chrono::seconds(5)); - Check(r.outcome == TestOutcome::Pass, "Cmd-prefixed exit 0 -> Pass"); - } - - fs::remove_all(dir, ec); - - if (failures > 0) { - std::println(std::cerr, "{} assertions failed", failures); - return 1; - } - return 0; -} diff --git a/tests/ShaderCompile/fixture/triangle.frag b/tests/ShaderCompile/fixture/triangle.frag deleted file mode 100644 index 7acc257..0000000 --- a/tests/ShaderCompile/fixture/triangle.frag +++ /dev/null @@ -1,8 +0,0 @@ -#version 450 - -layout(location = 0) in vec2 inUV; -layout(location = 0) out vec4 outColor; - -void main() { - outColor = vec4(inUV, 0.0, 1.0); -} diff --git a/tests/ShaderCompile/main.cpp b/tests/ShaderCompile/main.cpp deleted file mode 100644 index 6fac150..0000000 --- a/tests/ShaderCompile/main.cpp +++ /dev/null @@ -1,53 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -// Compile a tiny GLSL fragment shader directly via the Shader API. Verifies -// the SPIR-V output lands in the target dir and starts with the SPIR-V magic -// number, without dragging in the full Build() pipeline. -int main() { - fs::path src = fs::current_path() / "tests" / "ShaderCompile" / "fixture" / "triangle.frag"; - if (!fs::exists(src)) { - std::println(std::cerr, "fixture missing: {}", src.string()); - return 1; - } - - fs::path outDir = fs::temp_directory_path() / "crafter-build-shader-test"; - std::error_code ec; - fs::remove_all(outDir, ec); - fs::create_directories(outDir); - - Shader shader(fs::path(src), "main", ShaderType::Fragment); - - std::array includeDirs = {}; - std::string err = shader.Compile(outDir, includeDirs); - if (!err.empty()) { - std::println(std::cerr, "shader compile failed: {}", err); - return 1; - } - - fs::path spv = outDir / "triangle.spv"; - if (!fs::exists(spv)) { - std::println(std::cerr, "spv not produced at {}", spv.string()); - return 1; - } - - // SPIR-V binaries start with the magic number 0x07230203 (little-endian - // when written by glslang on x86). Read the first four bytes to confirm - // the produced file is real SPIR-V, not a fluke empty file. - std::ifstream in(spv, std::ios::binary); - std::uint32_t magic = 0; - in.read(reinterpret_cast(&magic), sizeof(magic)); - if (!in || magic != 0x07230203u) { - std::println(std::cerr, "spv has wrong magic: 0x{:08x}", magic); - return 1; - } - - // Compiling again should be a no-op since the source isn't newer. - if (!shader.Check(outDir)) { - std::println(std::cerr, "Shader::Check returned false on an up-to-date spv"); - return 1; - } - return 0; -} diff --git a/tests/StandardArgs/main.cpp b/tests/StandardArgs/main.cpp deleted file mode 100644 index 200c196..0000000 --- a/tests/StandardArgs/main.cpp +++ /dev/null @@ -1,75 +0,0 @@ -import std; -import Crafter.Build; -using namespace Crafter; - -namespace { - int failures = 0; - - void Check(bool cond, std::string_view msg) { - if (!cond) { - std::println(std::cerr, "FAIL: {}", msg); - ++failures; - } - } -} - -// Pure-data tests for ApplyStandardArgs and ArgQuery — no build/run, just -// confirms the documented arg parsing produces the documented mutations. -int main() { - // --debug + --target= + --march= + --mtune= - { - Configuration cfg; - cfg.target = "ignored"; - std::array raw = { - "--debug", "--target=aarch64-linux-gnu", "--march=armv8-a", "--mtune=cortex-a72", - }; - ApplyStandardArgs(cfg, raw); - Check(cfg.debug, "--debug should set cfg.debug"); - Check(cfg.target == "aarch64-linux-gnu", "--target= should overwrite cfg.target"); - Check(cfg.march == "armv8-a", "--march= should overwrite cfg.march"); - Check(cfg.mtune == "cortex-a72", "--mtune= should overwrite cfg.mtune"); - } - - // --lib promotes Executable -> LibraryStatic; --shared then promotes to Dynamic. - { - Configuration cfg; - cfg.type = ConfigurationType::Executable; - std::array raw = { "--lib" }; - ApplyStandardArgs(cfg, raw); - Check(cfg.type == ConfigurationType::LibraryStatic, "--lib promotes Exe -> Static"); - } - { - Configuration cfg; - cfg.type = ConfigurationType::Executable; - // Order shouldn't matter — promotions chain in priority order. - std::array raw = { "--shared", "--lib" }; - ApplyStandardArgs(cfg, raw); - Check(cfg.type == ConfigurationType::LibraryDynamic, "--lib + --shared lands on Dynamic regardless of order"); - } - { - Configuration cfg; - cfg.type = ConfigurationType::Executable; - std::array raw = { "--shared" }; - ApplyStandardArgs(cfg, raw); - Check(cfg.type == ConfigurationType::Executable, "--shared alone on Executable is a no-op"); - } - - // ArgQuery is returned over the same args span and exposes Has/Get. - { - Configuration cfg; - std::array raw = { "--timing", "--prefix=/opt/x", "extra" }; - ArgQuery q = ApplyStandardArgs(cfg, raw); - Check(q.Has("--timing"), "ArgQuery::Has true for present flag"); - Check(!q.Has("--missing"), "ArgQuery::Has false for absent flag"); - auto prefix = q.Get("--prefix="); - Check(prefix.has_value(), "ArgQuery::Get returns a value for known prefix"); - Check(prefix.value_or("") == "/opt/x", "ArgQuery::Get returns the substring after ="); - Check(!q.Get("--other=").has_value(), "ArgQuery::Get returns nullopt for absent prefix"); - } - - if (failures > 0) { - std::println(std::cerr, "{} assertions failed", failures); - return 1; - } - return 0; -} diff --git a/tests/StaticLib/fixture/Greet.cppm b/tests/StaticLib/fixture/Greet.cppm deleted file mode 100644 index b54de20..0000000 --- a/tests/StaticLib/fixture/Greet.cppm +++ /dev/null @@ -1,4 +0,0 @@ -export module Greet; -import std; - -export std::string Hello() { return "hi"; } diff --git a/tests/StaticLib/main.cpp b/tests/StaticLib/main.cpp deleted file mode 100644 index 10dc5d4..0000000 --- a/tests/StaticLib/main.cpp +++ /dev/null @@ -1,49 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -// Build a tiny module-only static library and confirm libgreet.a lands in -// BinDir(). Exercises ConfigurationType::LibraryStatic, interface module -// parsing via GetInterfacesAndImplementations, and the ar/llvm-lib archive -// step in Crafter.Build-Clang.cpp. -int main() { - Configuration cfg; - cfg.path = fs::current_path() / "tests" / "StaticLib" / "fixture"; - cfg.name = "greet"; - cfg.outputName = "greet"; - cfg.target = HostTarget(); - cfg.type = ConfigurationType::LibraryStatic; - - std::array ifaces = { "Greet" }; - std::array impls = {}; - cfg.GetInterfacesAndImplementations(ifaces, impls); - - if (cfg.interfaces.size() != 1) { - std::println(std::cerr, "expected 1 interface, got {}", cfg.interfaces.size()); - return 1; - } - if (cfg.interfaces[0]->name != "Greet") { - std::println(std::cerr, "expected interface 'Greet', got '{}'", cfg.interfaces[0]->name); - return 1; - } - - std::unordered_map> depResults; - std::mutex depMutex; - BuildResult r = Build(cfg, depResults, depMutex); - if (!r.result.empty()) { - std::println(std::cerr, "build failed: {}", r.result); - return 1; - } - - fs::path archive = cfg.BinDir() / "libgreet.a"; - if (!fs::exists(archive)) { - std::println(std::cerr, "archive not produced at {}", archive.string()); - return 1; - } - if (fs::file_size(archive) == 0) { - std::println(std::cerr, "archive {} is empty", archive.string()); - return 1; - } - return 0; -} diff --git a/tests/TestRunnerSpec/main.cpp b/tests/TestRunnerSpec/main.cpp deleted file mode 100644 index 0b94adc..0000000 --- a/tests/TestRunnerSpec/main.cpp +++ /dev/null @@ -1,115 +0,0 @@ -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. The -// test only runs on POSIX hosts (the project.cpp gates new tests by -// x86_64-pc-linux-gnu) so we don't need a Windows alternative here. -extern "C" int setenv(const char* name, const char* value, int overwrite); -extern "C" int unsetenv(const char* name); - -namespace { - int 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"); - } - { - // From a Linux host the only sensible way to run x86_64-w64-mingw32 is wine. - Configuration cfg; - cfg.target = "x86_64-w64-mingw32"; - TestRunner r = TestRunner::ForTarget(cfg); - if (HostTarget().find("linux") != std::string::npos) { - Check(r.name == "wine", "ForTarget(mingw) on Linux host uses wine"); - } - } - - // 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 char* name = "CRAFTER_BUILD_RUNNER_fake_target_for_unit_test"; - setenv(name, "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); - 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; -} diff --git a/tests/VariantId/main.cpp b/tests/VariantId/main.cpp deleted file mode 100644 index 13331ce..0000000 --- a/tests/VariantId/main.cpp +++ /dev/null @@ -1,120 +0,0 @@ -import std; -import Crafter.Build; -using namespace Crafter; - -namespace { - int failures = 0; - - void Check(bool cond, std::string_view msg) { - if (!cond) { - std::println(std::cerr, "FAIL: {}", msg); - ++failures; - } - } - - Configuration MakeBase() { - Configuration cfg; - cfg.path = "/tmp/variant-id-test"; - cfg.name = "vt"; - cfg.outputName = "vt"; - cfg.target = "x86_64-pc-linux-gnu"; - cfg.march = "native"; - cfg.mtune = "native"; - cfg.type = ConfigurationType::Executable; - return cfg; - } -} - -// VariantId is the cache key for build outputs. Two Configurations that -// differ only in something that affects codegen must produce different -// VariantId / BuildDir / BinDir, otherwise their .o files collide. -int main() { - { - // Baseline against itself: VariantId is deterministic. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - Check(a.VariantId() == b.VariantId(), "identical configs have identical VariantId"); - Check(a.BuildDir() == b.BuildDir(), "identical configs have identical BuildDir"); - } - { - // type differs -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.type = ConfigurationType::LibraryStatic; - Check(a.VariantId() != b.VariantId(), "type change perturbs VariantId"); - } - { - // debug differs -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.debug = true; - Check(a.VariantId() != b.VariantId(), "debug change perturbs VariantId"); - } - { - // sysroot differs -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.sysroot = "/opt/sysroot"; - Check(a.VariantId() != b.VariantId(), "sysroot change perturbs VariantId"); - } - { - // defines differ -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.defines.push_back({"FOO", "1"}); - Check(a.VariantId() != b.VariantId(), "define added perturbs VariantId"); - } - { - // Same define name, different value -> VariantId differs. - Configuration a = MakeBase(); - a.defines.push_back({"FOO", "1"}); - Configuration b = MakeBase(); - b.defines.push_back({"FOO", "2"}); - Check(a.VariantId() != b.VariantId(), "define value change perturbs VariantId"); - } - { - // compileFlags differ -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.compileFlags.push_back("-fno-omit-frame-pointer"); - Check(a.VariantId() != b.VariantId(), "compile flag added perturbs VariantId"); - } - { - // target differs -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.target = "aarch64-linux-gnu"; - Check(a.VariantId() != b.VariantId(), "target change perturbs VariantId"); - } - { - // march differs -> VariantId differs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.march = "x86-64-v3"; - Check(a.VariantId() != b.VariantId(), "march change perturbs VariantId"); - } - { - // PcmDir() differs between Executable (in BuildDir) and Library - // (in BinDir) — Library PCMs land in the installable bin dir so - // downstream consumers can find them. - Configuration exe = MakeBase(); - Configuration lib = MakeBase(); - lib.type = ConfigurationType::LibraryStatic; - Check(exe.PcmDir() == exe.BuildDir(), "Executable PcmDir == BuildDir"); - Check(lib.PcmDir() == lib.BinDir(), "Library PcmDir == BinDir"); - } - { - // VariantId is embedded in the path so distinct ids produce distinct dirs. - Configuration a = MakeBase(); - Configuration b = MakeBase(); - b.debug = true; - Check(a.BuildDir() != b.BuildDir(), "different VariantId yields different BuildDir"); - Check(a.BinDir() != b.BinDir(), "different VariantId yields different BinDir"); - } - - if (failures > 0) { - std::println(std::cerr, "{} assertions failed", failures); - return 1; - } - return 0; -} diff --git a/tests/WasiBrowserRuntime/main.cpp b/tests/WasiBrowserRuntime/main.cpp deleted file mode 100644 index ab61d0e..0000000 --- a/tests/WasiBrowserRuntime/main.cpp +++ /dev/null @@ -1,97 +0,0 @@ -import std; -import Crafter.Build; -namespace fs = std::filesystem; -using namespace Crafter; - -// POSIX env helper — std::setenv is non-standard. Linux-only is fine here; -// project.cpp gates this test on x86_64-pc-linux-gnu. -extern "C" int setenv(const char* name, const char* value, int overwrite); - -// EnableWasiBrowserRuntime stamps runtime.js, a generated index.html, and a -// files.json manifest onto cfg.files so the build step copies them next to -// the .wasm. We don't run the wasm build here (avoids requiring wasi-sdk + -// the wasi-libc++ PCM rebuild on top of every test); we just verify the -// helper produced the right artifacts and registered them on the config. -int main() { - Configuration cfg; - cfg.path = fs::temp_directory_path() / "crafter-build-wasi-runtime-test"; - cfg.name = "wasi-helper"; - cfg.outputName = "wasi-helper"; - cfg.target = "wasm32-wasip1"; - cfg.type = ConfigurationType::Executable; - - std::error_code ec; - fs::remove_all(cfg.path, ec); - fs::create_directories(cfg.path); - - // EnableWasiBrowserRuntime looks up runtime.js + index.html.in under - // GetCrafterBuildHome()/wasi-runtime. The test binary lives in - // bin//, so the auto-detect walks parents and won't find a - // share/crafter-build/Crafter.Build.cppm anchor — point CRAFTER_BUILD_HOME - // at the repo root (cwd when crafter-build test runs) so it picks up - // ./wasi-runtime/. Probe both repo root and the system-installed share - // dir to also work from a packaged checkout. - fs::path repoWasi = fs::current_path() / "wasi-runtime"; - fs::path systemHome = "/usr/local/share/crafter-build"; - if (fs::exists(repoWasi / "runtime.js")) { - setenv("CRAFTER_BUILD_HOME", fs::current_path().c_str(), 1); - } else if (fs::exists(systemHome / "wasi-runtime" / "runtime.js")) { - setenv("CRAFTER_BUILD_HOME", systemHome.c_str(), 1); - } else { - std::println(std::cerr, - "wasi-runtime assets not found near {} or {}", - repoWasi.string(), systemHome.string()); - return 77; // skipped — environmental, not a code defect - } - - EnableWasiBrowserRuntime(cfg); - - // Three files registered: runtime.js, generated index.html, files.json. - if (cfg.files.size() != 3) { - std::println(std::cerr, "expected 3 cfg.files entries, got {}", cfg.files.size()); - for (const auto& f : cfg.files) std::println(std::cerr, " - {}", f.string()); - return 1; - } - - auto findByName = [&](std::string_view name) -> const fs::path* { - for (const fs::path& f : cfg.files) { - if (f.filename() == name) return &f; - } - return nullptr; - }; - - const fs::path* runtimeJs = findByName("runtime.js"); - const fs::path* indexHtml = findByName("index.html"); - const fs::path* manifest = findByName("files.json"); - - if (!runtimeJs) { std::println(std::cerr, "runtime.js not registered"); return 1; } - if (!indexHtml) { std::println(std::cerr, "index.html not registered"); return 1; } - if (!manifest) { std::println(std::cerr, "files.json not registered"); return 1; } - - // The generated index.html and manifest live under the project's build/ - // wasi-runtime// directory and must actually exist on disk. - if (!fs::exists(*indexHtml)) { - std::println(std::cerr, "index.html not produced at {}", indexHtml->string()); - return 1; - } - if (!fs::exists(*manifest)) { - std::println(std::cerr, "files.json not produced at {}", manifest->string()); - return 1; - } - - // The template's {{WASM}} placeholder should have been replaced with - // the configured outputName. - std::ifstream in(*indexHtml); - std::string html{std::istreambuf_iterator(in), std::istreambuf_iterator()}; - if (html.find("wasi-helper.wasm") == std::string::npos) { - std::println(std::cerr, "index.html missing wasi-helper.wasm reference"); - return 1; - } - if (html.find("{{WASM}}") != std::string::npos) { - std::println(std::cerr, "index.html still contains unreplaced {{WASM}} placeholder"); - return 1; - } - - fs::remove_all(cfg.path, ec); - return 0; -}