fold/tests/unit/test_production_roots.cpp
trvon 86b2f14a43
feat(analysis): harden project-wide scans
- persist TU-owned graphs and guarded configuration evidence
- add closed-world production reachability classification
- calibrate detector precision and reviewed self-scan policy
- terminate poisoned workers instead of longjmp recovery
2026-07-30 18:18:04 -04:00

102 lines
4.1 KiB
C++

#include <clang/Tooling/Tooling.h>
#include <algorithm>
#include <catch2/catch_test_macros.hpp>
#include <string>
#include <vector>
#include "fold/analysis/production_roots.hpp"
#include "fold/config/configuration_profile.hpp"
namespace {
using fold::analysis::ProductionRootKind;
bool hasRoot(const std::vector<fold::analysis::ProductionRootRecord>& roots,
const std::string& name, ProductionRootKind kind) {
return std::ranges::any_of(
roots, [&](const auto& root) { return root.qualified_name == name && root.kind == kind; });
}
} // namespace
TEST_CASE("production root discovery records explicit conventional and escape roots",
"[production-roots]") {
constexpr const char* source = R"cpp(
extern "C" void exported_api() {}
void register_callback(void (*)()) {}
void callback() {}
void escaped() {}
void direct_only() {}
void static_entry() {}
int global_value = (static_entry(), 1);
struct Concrete { virtual void run() {} };
void manual() {}
int main() {
register_callback(callback);
auto pointer = &escaped;
(void)pointer;
direct_only();
Concrete concrete;
concrete.run();
return 0;
}
)cpp";
auto ast =
clang::tooling::buildASTFromCodeWithArgs(source, {"-std=c++20"}, "/project/src/root.cpp");
REQUIRE(ast);
const auto profile = fold::config::parseConfigurationProfileText(
R"({"version":1,"entrypoints":["manual"],"assumptions":[]})");
REQUIRE(profile);
std::vector<fold::analysis::ProductionRootRecord> roots;
fold::analysis::collectProductionRootRecords(ast->getASTContext(), &*profile.profile, roots);
CHECK(hasRoot(roots, "main", ProductionRootKind::Main));
CHECK(hasRoot(roots, "manual", ProductionRootKind::Explicit));
CHECK(hasRoot(roots, "exported_api", ProductionRootKind::ExportedAbi));
CHECK(hasRoot(roots, "static_entry", ProductionRootKind::StaticInitializer));
CHECK(hasRoot(roots, "callback", ProductionRootKind::RegisteredCallback));
CHECK(hasRoot(roots, "escaped", ProductionRootKind::EscapedAddress));
CHECK(hasRoot(roots, "Concrete::run", ProductionRootKind::VirtualCandidate));
CHECK_FALSE(std::ranges::any_of(roots, [](const auto& root) {
return root.qualified_name == "direct_only" &&
root.kind == ProductionRootKind::EscapedAddress;
}));
const auto inventory =
fold::analysis::buildProductionRootInventory(roots, true, profile.profile->entrypoints());
CHECK_FALSE(inventory.closed_world);
CHECK(inventory.has_unresolved_dynamic_calls);
CHECK(inventory.unresolved_explicit_roots.empty());
CHECK(inventory.production_roots.size() >= 6);
REQUIRE(inventory.conditional_roots.size() == 1);
CHECK(inventory.conditional_roots.front().qualified_name == "Concrete::run");
const auto missing =
fold::analysis::buildProductionRootInventory(roots, false, {"missing-entrypoint"});
CHECK_FALSE(missing.closed_world);
REQUIRE(missing.unresolved_explicit_roots.size() == 1);
CHECK(missing.unresolved_explicit_roots.front() == "missing-entrypoint");
}
TEST_CASE("test and benchmark entrypoints stay outside production roots",
"[production-roots][test-domain]") {
auto ast = clang::tooling::buildASTFromCodeWithArgs("int main(){ return 0; }", {"-std=c++20"},
"/project/tests/root_test.cpp");
REQUIRE(ast);
std::vector<fold::analysis::ProductionRootRecord> roots;
fold::analysis::collectProductionRootRecords(ast->getASTContext(), nullptr, roots);
const auto inventory = fold::analysis::buildProductionRootInventory(roots, false);
CHECK(inventory.closed_world);
CHECK(inventory.production_roots.empty());
REQUIRE(inventory.test_roots.size() == 1);
CHECK(inventory.test_roots.front().kind == ProductionRootKind::TestEntry);
auto third_party = clang::tooling::buildASTFromCodeWithArgs(
"int main(){ return 0; }", {"-std=c++20"}, "/project/third_party/vendor/main.cpp");
REQUIRE(third_party);
roots.clear();
fold::analysis::collectProductionRootRecords(third_party->getASTContext(), nullptr, roots);
CHECK(roots.empty());
}