linting
Some checks failed
CI / build-test-release (push) Failing after 7m15s

This commit is contained in:
Jorijn van der Graaf 2026-07-23 01:24:42 +02:00
commit 8892154b28
70 changed files with 2780 additions and 596 deletions

View file

@ -1,5 +1,5 @@
//SPDX-License-Identifier: LGPL-3.0-only
//SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
// SPDX-License-Identifier: LGPL-3.0-only
// SPDX-FileCopyrightText: Copyright (C) 2026 Catcrafts®
module;
export module Crafter.Build:Test_impl;
@ -13,41 +13,15 @@ using namespace Crafter;
namespace {
bool TargetIsWindows(std::string_view target) {
return target.find("windows") != std::string_view::npos
|| target.find("mingw") != std::string_view::npos;
return target.find("windows") != std::string_view::npos || target.find("mingw") != std::string_view::npos;
}
fs::path TestBinaryPath(const Configuration& cfg) {
fs::path outputDir = cfg.BinDir();
return outputDir / (TargetIsWindows(cfg.target) ? cfg.outputName + ".exe" : cfg.outputName);
return outputDir / (TargetIsWindows(cfg.target) ? std::format("{}.exe", cfg.outputName) : cfg.outputName);
}
bool MatchGlob(std::string_view glob, std::string_view name) {
std::size_t gi = 0, ni = 0, star = std::string_view::npos, mark = 0;
while (ni < name.size()) {
if (gi < glob.size() && (glob[gi] == '?' || glob[gi] == name[ni])) {
++gi; ++ni;
} else if (gi < glob.size() && glob[gi] == '*') {
star = gi++;
mark = ni;
} else if (star != std::string_view::npos) {
gi = star + 1;
ni = ++mark;
} else {
return false;
}
}
while (gi < glob.size() && glob[gi] == '*') ++gi;
return gi == glob.size();
}
bool MatchAny(std::span<const std::string> globs, std::string_view name) {
if (globs.empty()) return true;
for (const auto& g : globs) {
if (MatchGlob(g, name)) return true;
}
return false;
}
// MatchGlob/MatchAny live in :Platform — shared with the lint verb.
std::string ShellQuoteSh(std::string_view s) {
std::string out;
@ -118,7 +92,7 @@ namespace {
return out;
}
std::string SignalName(int sig) {
std::string SignalName(std::int32_t sig) {
switch (sig) {
case 1: return "SIGHUP";
case 2: return "SIGINT";
@ -139,7 +113,7 @@ namespace {
std::error_code ec;
fs::create_directories(logDir, ec);
if (ec) return;
std::ofstream(logDir / (name + ".log")) << output;
std::ofstream(logDir / (std::format("{}.log", name))) << output;
}
void PrintResult(const TestResult& r, std::string_view runnerName) {
@ -174,19 +148,16 @@ namespace {
std::println("⏱ {}{} ({}ms) timeout", r.name, runnerSuffix, ms);
break;
case TestOutcome::Skipped:
std::println("⏭ {}{} skipped: {}", r.name, runnerSuffix,
r.output.empty() ? std::string("(no reason)") : r.output);
std::println("⏭ {}{} skipped: {}", r.name, runnerSuffix, r.output.empty() ? std::string("(no reason)") : r.output);
break;
}
}
}
namespace {
std::atomic<Configuration*> g_parentProject{nullptr};
std::atomic<Configuration*> ParentProject{nullptr};
Configuration* FindLibInTree(Configuration* root,
std::string_view name,
std::unordered_set<Configuration*>& seen) {
Configuration* FindLibInTree(Configuration* root, std::string_view name, std::unordered_set<Configuration*>& seen) {
if (!seen.insert(root).second) return nullptr;
if (root->name == name) return root;
for (Configuration* dep : root->dependencies) {
@ -197,20 +168,17 @@ namespace {
}
void Crafter::SetParentProject(Configuration* parent) {
g_parentProject.store(parent);
ParentProject.store(parent);
}
Configuration* Crafter::ParentLib(std::string_view name) {
Configuration* root = g_parentProject.load();
Configuration* root = ParentProject.load();
if (!root) {
throw std::runtime_error(std::format(
"Crafter::ParentLib('{}'): no parent project set", name));
throw std::runtime_error(std::format("Crafter::ParentLib('{}'): no parent project set", name));
}
std::unordered_set<Configuration*> seen;
if (auto found = FindLibInTree(root, name, seen)) return found;
throw std::runtime_error(std::format(
"Crafter::ParentLib('{}'): not found in parent project '{}'",
name, root->name));
throw std::runtime_error(std::format("Crafter::ParentLib('{}'): not found in parent project '{}'", name, root->name));
}
TestRunner TestRunner::Local() {
@ -286,9 +254,7 @@ TestRunner TestRunner::ForTarget(const Configuration& cfg) {
// emulated loader then searches ITS default paths against the
// host filesystem, picking up host-arch libraries from /lib —
// LD_LIBRARY_PATH steers it back into the sysroot.
r.exec = std::format(
"env QEMU_LD_PREFIX={0} LD_LIBRARY_PATH={0}/lib:{0}/usr/lib {1}",
cfg.sysroot, r.exec);
r.exec = std::format("env QEMU_LD_PREFIX={0} LD_LIBRARY_PATH={0}/lib:{0}/usr/lib {1}", cfg.sysroot, r.exec);
}
return r;
}
@ -317,9 +283,7 @@ namespace {
if (spec.starts_with("cmd:") && spec.size() > 4) {
return TestRunner::Cmd(std::string(spec.substr(4)));
}
throw std::runtime_error(std::format(
"TestRunner::FromSpec: unrecognized runner spec '{}' "
"(expected 'local' or 'cmd:<binary>')", spec));
throw std::runtime_error(std::format("TestRunner::FromSpec: unrecognized runner spec '{}' " "(expected 'local' or 'cmd:<binary>')", spec));
}
}
@ -411,8 +375,7 @@ namespace {
return {false, std::format("env '{}' unset", arg)};
}
} else {
return {false, std::format(
"unknown require kind '{}' (expected tool/file/env)", kind)};
return {false, std::format("unknown require kind '{}' (expected tool/file/env)", kind)};
}
}
return {true, ""};
@ -479,9 +442,7 @@ TestBuilder Configuration::AddTest(std::string_view name, std::span<fs::path> in
return TestBuilder{this, tests.size() - 1};
}
void Configuration::AddMarchVariants(std::string_view name,
std::span<fs::path> interfaces,
std::span<const MarchTier> tiers) {
void Configuration::AddMarchVariants(std::string_view name, std::span<fs::path> interfaces, std::span<const MarchTier> tiers) {
for (const auto& tier : tiers) {
Test t;
t.config.path = "./";
@ -507,25 +468,25 @@ void Configuration::AddMarchVariants(std::string_view name,
}
}
TestBuilder& TestBuilder::Path(fs::path p) { test().config.path = std::move(p); return *this; }
TestBuilder& TestBuilder::Target(std::string t) { test().config.target = std::move(t); return *this; }
TestBuilder& TestBuilder::March(std::string m) { test().config.march = std::move(m); return *this; }
TestBuilder& TestBuilder::Mtune(std::string m) { test().config.mtune = std::move(m); return *this; }
TestBuilder& TestBuilder::Sysroot(fs::path s) { test().config.sysroot = s.string(); return *this; }
TestBuilder& TestBuilder::Debug(bool d) { test().config.debug = d; return *this; }
TestBuilder& TestBuilder::Path(fs::path p) { Ref().config.path = std::move(p); return *this; }
TestBuilder& TestBuilder::Target(std::string t) { Ref().config.target = std::move(t); return *this; }
TestBuilder& TestBuilder::March(std::string m) { Ref().config.march = std::move(m); return *this; }
TestBuilder& TestBuilder::Mtune(std::string m) { Ref().config.mtune = std::move(m); return *this; }
TestBuilder& TestBuilder::Sysroot(fs::path s) { Ref().config.sysroot = s.string(); return *this; }
TestBuilder& TestBuilder::Debug(bool d) { Ref().config.debug = d; return *this; }
TestBuilder& TestBuilder::Define(std::string n, std::string v) {
test().config.defines.push_back({std::move(n), std::move(v)});
Ref().config.defines.push_back({std::move(n), std::move(v)});
return *this;
}
TestBuilder& TestBuilder::Timeout(std::chrono::seconds s) { test().timeout = s; return *this; }
TestBuilder& TestBuilder::Args(std::vector<std::string> a) { test().args = std::move(a); return *this; }
TestBuilder& TestBuilder::Requires(std::string r) { test().requires_.push_back(std::move(r)); return *this; }
TestBuilder& TestBuilder::Timeout(std::chrono::seconds s) { Ref().timeout = s; return *this; }
TestBuilder& TestBuilder::Args(std::vector<std::string> a) { Ref().args = std::move(a); return *this; }
TestBuilder& TestBuilder::Requires(std::string r) { Ref().requires_.push_back(std::move(r)); return *this; }
TestBuilder& TestBuilder::Dependencies(std::vector<Configuration*> d) {
test().config.dependencies = std::move(d);
Ref().config.dependencies = std::move(d);
return *this;
}
TestBuilder& TestBuilder::LinkFlag(std::string f) { test().config.linkFlags.push_back(std::move(f)); return *this; }
TestBuilder& TestBuilder::CompileFlag(std::string f) { test().config.compileFlags.push_back(std::move(f)); return *this; }
TestBuilder& TestBuilder::LinkFlag(std::string f) { Ref().config.linkFlags.push_back(std::move(f)); return *this; }
TestBuilder& TestBuilder::CompileFlag(std::string f) { Ref().config.compileFlags.push_back(std::move(f)); return *this; }
TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions& opts, std::span<const std::string_view> projectArgs) {
// Multi-target sweep: when no --target= was given, the run covers every
@ -590,10 +551,10 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions&
return summary;
}
int jobs = opts.jobs > 0
std::int32_t jobs = opts.jobs > 0
? opts.jobs
: std::max(1u, std::thread::hardware_concurrency());
jobs = std::min(jobs, static_cast<int>(filtered.size()));
jobs = std::min(jobs, static_cast<std::int32_t>(filtered.size()));
std::unordered_map<fs::path, std::shared_future<BuildResult>> depResults;
std::mutex depMutex;
@ -651,10 +612,7 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions&
if (!tool.empty() && !RequiresMentionsTool(t.requires_, tool)) {
r.outcome = TestOutcome::Fail;
r.exitCode = -1;
r.output = std::format(
"runner '{}' unavailable and not declared in requires "
"(add .Requires(\"tool:{}\") to permit skipping)",
t.runner.name, tool);
r.output = std::format("runner '{}' unavailable and not declared in requires " "(add .Requires(\"tool:{}\") to permit skipping)", t.runner.name, tool);
} else {
r.outcome = TestOutcome::Skipped;
r.output = std::format("runner '{}' not available", t.runner.name);
@ -718,7 +676,7 @@ TestSummary Crafter::RunTests(Configuration& projectCfg, const RunTestsOptions&
std::vector<std::jthread> threads;
threads.reserve(jobs);
for (int j = 0; j < jobs; ++j) {
for (std::int32_t j = 0; j < jobs; ++j) {
threads.emplace_back(worker);
}
threads.clear(); // joins all jthreads