fold/tests/unit/test_class_dup.cpp

70 lines
2.7 KiB
C++

#include <clang/Frontend/ASTUnit.h>
#include <clang/Tooling/Tooling.h>
#include <catch2/catch_test_macros.hpp>
#include <memory>
#include <string>
#include <vector>
#include "fold/detect/class_dup.hpp"
#include "fold/report/finding.hpp"
#include "fold/symbolic/budget.hpp"
namespace {
std::unique_ptr<clang::ASTUnit> parse(const std::string& code) {
return clang::tooling::buildASTFromCodeWithArgs(code, {"-std=c++20"}, "input.cpp");
}
std::vector<fold::Finding> 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);
}