#include #include #include #include #include #include #include "fold/detect/complexity.hpp" #include "fold/report/finding.hpp" namespace { std::unique_ptr parse(const std::string& code) { return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp"); } std::vector run(const std::string& code, int threshold) { auto unit = parse(code); REQUIRE(unit); return fold::detectComplexityInContext(unit->getASTContext(), threshold); } } // namespace TEST_CASE("complexity: a branchy function over threshold is flagged", "[complexity]") { // Six independent decision points -> cyclomatic complexity 7. auto findings = run(R"( int f(int x) { int r = 0; if (x > 0) r++; if (x > 1) r++; if (x > 2) r++; if (x > 3) r++; if (x > 4) r++; if (x > 5) r++; return r; } )", 5); REQUIRE_FALSE(findings.empty()); REQUIRE(findings[0].kind == fold::FindingKind::HighComplexity); REQUIRE(findings[0].confidence == fold::Confidence::Syntactic); // McCabe remains the compatibility gate and message score, while // independent dimensions provide explainable ranking evidence. REQUIRE(findings[0].message.find("complexity 7") != std::string::npos); REQUIRE(findings[0].evidence.quality.has_value()); const auto& quality = *findings[0].evidence.quality; REQUIRE(quality.classification == "complexity-hotspot"); REQUIRE(quality.metrics.size() == 6); CHECK(quality.metrics[0].name == "mccabe"); CHECK(quality.metrics[0].value == 7.0); CHECK(quality.rank_reasons.front() == "mccabe"); } TEST_CASE("complexity: dimensions separate nesting, state, boolean, loops, and switches", "[complexity][dimensions]") { auto findings = run(R"( int dimensions(int x) { int result = 0; for (int i = 0; i < x; ++i) { if (i > 1 && i < 4) { switch (i) { case 2: result = 1; break; default: result = 2; break; } } } return result; } )", 1); REQUIRE(findings.size() == 1); REQUIRE(findings[0].evidence.quality.has_value()); const auto& metrics = findings[0].evidence.quality->metrics; const auto metric = [&metrics](const std::string& name) { for (const auto& item : metrics) { if (item.name == name) return item.value; } return -1.0; }; CHECK(metric("max_control_nesting") == 3.0); CHECK(metric("state_mutations") == 3.0); CHECK(metric("boolean_operators") == 1.0); CHECK(metric("loops") == 1.0); CHECK(metric("switch_cases") == 2.0); } TEST_CASE("complexity: a straight-line function is not flagged", "[complexity]") { auto findings = run(R"( int g(int x) { int y = x + 1; return y; } )", 5); REQUIRE(findings.empty()); } TEST_CASE("complexity: threshold boundary is inclusive", "[complexity]") { // Three ifs -> complexity 4. const std::string code = R"( int h(int x) { int r = 0; if (x > 0) r++; if (x > 1) r++; if (x > 2) r++; return r; } )"; // At threshold 4 the function fires (complexity == threshold). REQUIRE_FALSE(run(code, 4).empty()); // At threshold 5 it does not (complexity < threshold). REQUIRE(run(code, 5).empty()); }