#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use codehelion_core::clone_class::CloneClass;
use codehelion_core::discovery::{BuildVariant, Language, LanguageSelection};
use codehelion_core::ir::{StructuralFrontend, SyntaxIrFile};
use codehelion_core::structural::{self, StructuralConfig, StructuralReport};
use codehelion_frontend_rust::ir::RustStructuralFrontend;
const DONOR: &str = "\
fn collect_widths(items: &[String]) -> u32 {
let mut total = 0;
let mut seen = 0;
for item in items {
let text = item.trim();
let width = text.chars().count() as u32;
total += width;
seen += 1;
}
total + seen
}
";
const VERBATIM_HOST: &str = "\
fn longest_line(lines: &[String]) -> u32 {
let mut total = 0;
let mut seen = 0;
if lines.is_empty() {
return 0;
}
while total < 3 {
total += 1;
}
for item in lines {
let text = item.trim();
let width = text.chars().count() as u32;
total += width;
seen += 1;
}
total
}
";
const RENAMED_HOST: &str = "\
fn gather_sizes(entries: &[String]) -> u32 {
let mut sum = 0;
let mut count = 0;
let cap = entries.len();
match cap {
0 => return 0,
_ => {}
}
for entry in entries {
let body = entry.trim();
let span = body.chars().count() as u32;
sum += span;
count += 1;
}
sum
}
";
const LOOKALIKE_HOST: &str = "\
fn tally_scores(items: &[String]) -> u32 {
let mut total = 0;
let mut seen = 0;
loop {
total += 1;
break;
}
for item in items {
let text = item.to_uppercase();
let width = text.len() as u32;
total -= width;
seen += 2;
}
total
}
";
fn analyze(sources: &[&str]) -> StructuralReport {
let files: Vec<SyntaxIrFile> = sources
.iter()
.map(|source| RustStructuralFrontend.parse(source))
.collect();
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
structural::analyze(&files, &variant, &StructuralConfig::default())
}
fn shape(report: &StructuralReport) -> Vec<(CloneClass, u32, usize)> {
let mut shapes: Vec<(CloneClass, u32, usize)> = report
.regions
.iter()
.map(|region| {
(
region.clone_type,
region.statements,
region.occurrences.len(),
)
})
.collect();
shapes.sort_by_key(|&(class, statements, occurrences)| (statements, occurrences, class.name()));
shapes
}
#[test]
fn a_verbatim_shared_loop_body_is_one_type1_run() {
let report = analyze(&[DONOR, VERBATIM_HOST]);
assert_eq!(shape(&report), vec![(CloneClass::Type1, 4, 2)]);
let region = &report.regions[0];
let files: Vec<usize> = region
.occurrences
.iter()
.map(|occurrence| occurrence.file)
.collect();
assert_eq!(files, vec![0, 1]);
assert_eq!(
region.occurrences[0].token_end - region.occurrences[0].token_start,
region.occurrences[1].token_end - region.occurrences[1].token_start,
"a verbatim copy covers the same tokens"
);
for occurrence in ®ion.occurrences {
let unit = &report.units[occurrence.unit];
assert!(
occurrence.token_start > unit.token_start && occurrence.token_end < unit.token_end,
"the run must sit strictly inside its host unit"
);
}
}
#[test]
fn a_consistently_renamed_loop_body_is_a_type2_run() {
let report = analyze(&[DONOR, RENAMED_HOST]);
assert_eq!(shape(&report), vec![(CloneClass::Type2, 4, 2)]);
let region = &report.regions[0];
assert_ne!(region.occurrences[0].content, region.occurrences[1].content);
}
#[test]
fn a_run_that_only_summarises_alike_is_confirmed_away() {
let report = analyze(&[DONOR, LOOKALIKE_HOST]);
assert!(
report.regions.is_empty(),
"a summary-level coincidence is not a duplicated run: {:#?}",
shape(&report)
);
assert!(
report.stats.maximal.shared > 0,
"the coincidence must reach confirmation, or this proves nothing"
);
assert!(report.stats.region_singletons >= 2);
}
#[test]
fn every_copy_of_one_run_lands_in_the_same_report_entry() {
let report = analyze(&[DONOR, VERBATIM_HOST, RENAMED_HOST]);
assert_eq!(shape(&report), vec![(CloneClass::Type2, 4, 3)]);
let files: Vec<usize> = report.regions[0]
.occurrences
.iter()
.map(|occurrence| occurrence.file)
.collect();
assert_eq!(files, vec![0, 1, 2]);
}
#[test]
fn a_run_keeps_its_identity_when_an_unrelated_file_joins_the_scan() {
let alone = analyze(&[DONOR, VERBATIM_HOST]);
let joined = analyze(&[DONOR, VERBATIM_HOST, LOOKALIKE_HOST]);
assert_eq!(
alone.regions[0].fingerprint, joined.regions[0].fingerprint,
"an unrelated file must not move an existing run's identity"
);
}
#[test]
fn confirmation_is_deterministic() {
assert_eq!(
analyze(&[DONOR, VERBATIM_HOST, RENAMED_HOST]).regions,
analyze(&[DONOR, VERBATIM_HOST, RENAMED_HOST]).regions
);
}
const NESTED_DONOR: &str = "\
fn summarise_alpha(rows: &[String]) -> usize {
let mut out = String::new();
let mut cursor = 0usize;
while cursor < rows.len() {
out.push_str(&rows[cursor]);
out.push('|');
cursor += 1;
}
if out.is_empty() {
return 0;
}
let mut total = 0usize;
let mut widest = 0usize;
for row in rows {
let trimmed = row.trim_end();
let size = trimmed.chars().count();
total = total.saturating_add(size);
widest = widest.max(size);
}
let ratio = total / widest.max(1);
let spread = widest.saturating_sub(ratio);
out.push('!');
total + spread + out.len()
}
";
const NESTED_HOST: &str = "\
fn summarise_beta(rows: &[String], cap: usize) -> usize {
let mut guard = 0usize;
match rows.first() {
Some(head) if head.is_empty() => return 0,
Some(_) => guard += 1,
None => return cap,
}
while guard < cap {
guard += 1;
}
let mut total = 0usize;
let mut widest = 0usize;
for row in rows {
let trimmed = row.trim_end();
let size = trimmed.chars().count();
total = total.saturating_add(size);
widest = widest.max(size);
}
let ratio = total / widest.max(1);
let spread = widest.saturating_sub(ratio);
total + spread + guard
}
";
const RENAMED_NESTED_HOST: &str = "\
fn summarise_beta(rows: &[String], cap: usize) -> usize {
let mut guard = 0usize;
match rows.first() {
Some(head) if head.is_empty() => return 0,
Some(_) => guard += 1,
None => return cap,
}
while guard < cap {
guard += 1;
}
let mut total = 0usize;
let mut widest = 0usize;
for row in rows {
let trimmed = row.trim_end();
let size = trimmed.chars().count();
total = total.saturating_add(size);
widest = widest.max(size);
}
let quotient = total / widest.max(1);
let slack = widest.saturating_sub(quotient);
total + slack + guard
}
";
const SUMMARY_TWIN: &str = "\
fn tally_gamma(items: &[String], seed: usize) -> usize {
let mut score = seed;
let mut peak = seed;
loop {
score += 1;
break;
}
for item in items {
let label = item.to_uppercase();
let count = label.chars().rev().count();
score = score.wrapping_sub(count);
peak = peak.min(count);
}
score + peak
}
";
#[test]
fn a_nested_loop_body_is_reported_once() {
let report = analyze(&[NESTED_DONOR, NESTED_HOST, SUMMARY_TWIN]);
assert_eq!(shape(&report), vec![(CloneClass::Type1, 4, 2)]);
assert_eq!(report.stats.region_subsumed, 0);
}
#[test]
fn a_verbatim_nested_run_survives_a_renamed_surrounding_host() {
let report = analyze(&[NESTED_DONOR, RENAMED_NESTED_HOST, SUMMARY_TWIN]);
assert_eq!(shape(&report), vec![(CloneClass::Type1, 4, 2)]);
assert_eq!(report.stats.region_subsumed, 0);
}