#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::collections::BTreeSet;
use std::path::PathBuf;
use codehelion_core::clone_class::CloneClass;
use codehelion_core::discovery::{BuildVariant, Language, LanguageSelection};
use codehelion_core::features;
use codehelion_core::ir::{Shape, StructuralFrontend, SyntaxIrFile};
use codehelion_core::stable_id::FragmentFingerprint;
use codehelion_core::structural::{self, StructuralConfig, StructuralReport};
use codehelion_core::verify::{self, UnitView, Verdict, VerifyConfig};
use codehelion_frontend_rust::ir::RustStructuralFrontend;
const CORPUS: &str = "../../corpus/synthetic/rust-negative";
const FILES: [&str; 2] = ["seed.rs", "copies.rs"];
const FUNCTIONS: [&str; 4] = [
"sum_positive",
"longest_run",
"count_transitions",
"narrowest_gap",
];
fn parse(name: &str) -> SyntaxIrFile {
let path = PathBuf::from(CORPUS).join(name);
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("reading {}: {e}", path.display()));
RustStructuralFrontend.parse(&text)
}
fn analyze() -> StructuralReport {
let files: Vec<SyntaxIrFile> = FILES.iter().map(|name| parse(name)).collect();
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
structural::analyze(&files, &variant, &StructuralConfig::default())
}
fn negative_verdicts() -> Vec<(String, Verdict)> {
let file = parse("seed.rs");
let extracted = features::extract(&file);
let mut units = Vec::new();
file.walk(&mut |node| {
if matches!(node.shape, Shape::Function) {
units.push((
node.name
.as_ref()
.map_or("?", |name| name.as_str())
.to_string(),
verify::statement_sequence(node, &file.tokens),
));
}
});
assert_eq!(units.len(), FUNCTIONS.len());
let mut scored = Vec::new();
for (i, first) in units.iter().enumerate() {
for (j, second) in units.iter().enumerate().skip(i + 1) {
let verdict = verify::verify(
&UnitView {
statements: &first.1,
tokens: &file.tokens,
content: FragmentFingerprint::from_bytes(
[u8::try_from(i).unwrap_or(u8::MAX); 16],
),
features: &extracted.units[i],
types: None,
apis: None,
},
&UnitView {
statements: &second.1,
tokens: &file.tokens,
content: FragmentFingerprint::from_bytes(
[u8::try_from(j).unwrap_or(u8::MAX); 16],
),
features: &extracted.units[j],
types: None,
apis: None,
},
&VerifyConfig::default(),
);
scored.push((format!("{} x {}", first.0, second.0), verdict));
}
}
scored
}
#[test]
fn only_the_verbatim_copies_are_reported() {
let report = analyze();
assert_eq!(report.units.len(), FUNCTIONS.len() * FILES.len());
assert_eq!(report.groups.groups.len(), FUNCTIONS.len());
let mut grouped = BTreeSet::new();
for group in &report.groups.groups {
assert_eq!(group.clone_type, CloneClass::Type1);
let names: BTreeSet<&str> = group
.members
.iter()
.map(|&member| {
report.units[member]
.name
.as_ref()
.map_or("?", |name| name.as_str())
})
.collect();
assert_eq!(
names.len(),
1,
"a group mixes two different functions: {names:?}"
);
assert_eq!(group.members.len(), FILES.len());
grouped.extend(names);
}
assert_eq!(grouped, FUNCTIONS.iter().copied().collect::<BTreeSet<_>>());
}
#[test]
fn the_negative_pairs_are_proposed_and_then_rejected() {
let report = analyze();
assert!(report.stats.unit_pairs > FUNCTIONS.len());
assert_eq!(report.stats.unit_pairs, 16);
assert_eq!(report.stats.verified_pairs, FUNCTIONS.len());
}
#[test]
fn the_judge_rejects_every_negative_pair() {
let config = VerifyConfig::default();
for (pair, verdict) in negative_verdicts() {
assert_eq!(verdict.class, None, "{pair} is not a clone");
assert!(
verdict.breakdown.composite < config.type3_min_composite,
"{pair} scored {:.4}, at or above the acceptance threshold {:.2}",
verdict.breakdown.composite,
config.type3_min_composite
);
assert!(
verdict.breakdown.structural < 1.0,
"{pair} does not have identical structure"
);
}
}
#[test]
fn lexical_agreement_is_what_separates_these_from_real_copies() {
for (pair, verdict) in negative_verdicts() {
assert!(
verdict.breakdown.lexical < 0.60,
"{pair} agrees lexically to {:.4}",
verdict.breakdown.lexical
);
}
let report = analyze();
for detail in &report.details {
for breakdown in &detail.member_breakdowns {
assert!(
breakdown.lexical > 0.99,
"a verbatim copy agrees lexically to only {:.4}",
breakdown.lexical
);
}
}
}
#[test]
fn the_call_free_negatives_carry_no_api_evidence() {
for (pair, verdict) in negative_verdicts() {
assert_eq!(
verdict.breakdown.api, None,
"{pair} has no call surface to compare"
);
}
}
#[test]
fn the_corpus_measures_the_same_twice() {
let composites = |scored: Vec<(String, Verdict)>| -> Vec<(String, f64)> {
scored
.into_iter()
.map(|(pair, verdict)| (pair, verdict.breakdown.composite))
.collect()
};
assert_eq!(
composites(negative_verdicts()),
composites(negative_verdicts())
);
}