#pragma once #include // 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> 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 /share/crafter-build before // loading. The lib's default sourceDir is derived from /proc/self/exe, but // test exes live in tests//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 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); } }