#include #include #include #include #include #include #include "fold/detect/class_dup.hpp" #include "fold/report/finding.hpp" #include "fold/symbolic/budget.hpp" namespace { std::unique_ptr parse(const std::string& code) { return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp"); } std::vector runClassDup(const std::string& code, double threshold = 0.7) { auto unit = parse(code); REQUIRE(unit); fold::SolverBudget b; fold::SolverStats s; fold::ClassDupOptions opts; opts.similarity_threshold = threshold; opts.min_methods = 2; return fold::detectDuplicateClassesInContext(unit->getASTContext(), b, s, opts); } } // namespace TEST_CASE("classes with equivalent methods are flagged", "[class-dup]") { auto findings = runClassDup("class CalcA { public:" " int twice(int x){ return x + x; }" " int square(int x){ return x * x; }" "};" "class CalcB { public:" " int twice(int y){ return y * 2; }" " int square(int y){ return y * y; }" "};"); REQUIRE(findings.size() == 1); REQUIRE(findings[0].kind == fold::FindingKind::ClassDup); } TEST_CASE("classes with distinct semantics are not flagged", "[class-dup][negative]") { auto findings = runClassDup("class A { public:" " int adjust(int x){ return x + 10; }" " int weight(int x){ return x * 3; }" "};" "class B { public:" " int adjust(int x){ return x - 10; }" " int weight(int x){ return x * 5; }" "};"); REQUIRE(findings.empty()); } TEST_CASE("threshold gates partial overlap", "[class-dup][threshold]") { // One of two methods is equivalent; with threshold 0.7 we expect NO flag, // with threshold 0.4 we expect ONE flag. const char* code = "class A { public:" " int twice(int x){ return x + x; }" " int weight(int x){ return x * 3; }" "};" "class B { public:" " int twice(int y){ return y * 2; }" " int weight(int y){ return y * 7; }" "};"; REQUIRE(runClassDup(code, 0.7).empty()); REQUIRE(runClassDup(code, 0.4).size() == 1); }