#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use codehelion_core::discovery::{BuildVariant, Language, LanguageSelection};
use codehelion_core::frontend::UnitKind;
use codehelion_core::ir::{StructuralFrontend, SyntaxIrFile};
use codehelion_core::structural::{self, StructuralConfig};
use codehelion_frontend_rust::ir::RustStructuralFrontend;
const ALPHA: &str = "\
fn alpha(data: &[u32]) -> u32 {
let mut acc = 0u32;
let mut count = 0u32;
for value in data {
if *value > 10 {
acc = acc.wrapping_add(*value);
} else {
acc = acc.wrapping_sub(1);
}
count += 1;
}
acc = acc.wrapping_mul(3);
return acc + count;
}
";
const ALPHA_COPY: &str = "\
fn alpha_copy(data: &[u32]) -> u32 {
let mut acc = 0u32;
let mut count = 0u32;
for value in data {
if *value > 10 {
acc = acc.wrapping_add(*value);
} else {
acc = acc.wrapping_sub(1);
}
count += 1;
}
acc = acc.wrapping_mul(3);
return acc + count;
}
";
const ALPHA_RENAMED: &str = "\
fn beta(feed: &[u32]) -> u32 {
let mut state = 3u32;
let mut seen = 7u32;
for item in feed {
if *item > 99 {
state = state.wrapping_add(*item);
} else {
state = state.wrapping_sub(2);
}
seen += 4;
}
state = state.wrapping_mul(8);
return state + seen;
}
";
const ALPHA_TYPE3: &str = "\
fn gamma(data: &[u32]) -> u32 {
let mut acc = 0u32;
let mut count = 0u32;
for value in data {
if *value > 10 {
acc = acc.wrapping_add(*value);
} else {
acc = acc.wrapping_sub(1);
}
count += 1;
}
acc = acc.wrapping_mul(3);
let extra = acc ^ count;
return acc + count + extra;
}
";
const UNRELATED: &str = "\
fn label(name: &str) -> usize {
let trimmed = name.trim();
let width = trimmed.chars().count();
for _ in 0..width {
if width > 3 {
return width;
}
}
return width.saturating_mul(2);
}
";
fn parse_all(sources: &[&str]) -> Vec<SyntaxIrFile> {
sources
.iter()
.map(|source| RustStructuralFrontend.parse(source))
.collect()
}
fn variant() -> BuildVariant {
BuildVariant::structural(LanguageSelection::default(), Language::C)
}
#[test]
fn verbatim_and_renamed_copies_group_together() {
let files = parse_all(&[ALPHA, ALPHA_COPY, ALPHA_RENAMED, UNRELATED]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
assert_eq!(report.units.len(), 4, "one unit per file");
let group = report
.groups
.groups
.iter()
.find(|g| g.members.contains(&0))
.expect("alpha is grouped with its copies");
assert!(group.members.contains(&1), "verbatim copy joins");
assert!(group.members.contains(&2), "renamed copy joins");
assert!(
!group.members.contains(&3),
"the unrelated function stays out"
);
assert_eq!(report.stats.units, 4);
assert!(report.stats.verified_pairs >= 2);
assert_eq!(report.details.len(), report.groups.groups.len());
let group_index = report
.groups
.groups
.iter()
.position(|g| g.members.contains(&0))
.unwrap();
let detail = &report.details[group_index];
assert_eq!(
detail.member_breakdowns.len(),
report.groups.groups[group_index].members.len()
);
assert!((detail.member_breakdowns[0].composite - 1.0).abs() < 1e-9);
assert!(
detail
.member_breakdowns
.iter()
.all(|b| b.type_similarity.is_none())
);
}
#[test]
fn a_type3_edit_is_grouped_as_a_near_clone() {
let files = parse_all(&[ALPHA, ALPHA_TYPE3, UNRELATED]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
let group = report
.groups
.groups
.iter()
.find(|g| g.members.contains(&0))
.expect("alpha is grouped with its gapped edit");
assert!(
group.members.contains(&1),
"the Type-3 edit joins alpha's group"
);
assert!(!group.members.contains(&2));
}
#[test]
fn units_carry_the_anchors_a_report_needs() {
let files = parse_all(&[ALPHA, UNRELATED]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
let alpha = &report.units[0];
assert_eq!(alpha.file, 0);
assert_eq!(alpha.kind, UnitKind::Function);
assert_eq!(alpha.name.as_deref(), Some("alpha"));
assert_eq!(alpha.start_line, 1);
assert_eq!(
alpha.end_line,
u32::try_from(ALPHA.lines().count()).unwrap(),
"the unit ends on the closing brace's line"
);
assert!(
alpha.token_end - alpha.token_start > 40,
"a whole function's tokens"
);
assert!(alpha.range.start < alpha.range.end);
let other = &report.units[1];
assert_ne!(alpha.fingerprint, other.fingerprint);
assert_eq!(other.file, 1);
assert_eq!(other.start_line, 1);
}
#[test]
fn analysis_is_deterministic() {
let files = parse_all(&[ALPHA, ALPHA_COPY, ALPHA_RENAMED, UNRELATED]);
let first = structural::analyze(&files, &variant(), &StructuralConfig::default());
let second = structural::analyze(&files, &variant(), &StructuralConfig::default());
assert_eq!(first, second);
}
const BOILERPLATE: &str = "\
struct Config {
retries: u32,
inner: Inner,
}
impl Config {
fn retries(&self) -> u32 {
self.retries
}
fn set_retries(&mut self, value: u32) {
self.retries = value;
}
fn resolve(&self, name: &str) -> u32 {
self.inner.resolve(name)
}
fn dump(&self) {
println!(\"retries\");
println!(\"inner\");
println!(\"done\");
}
fn clamp(&self, value: u32) -> u32 {
if value > self.retries {
return self.retries;
}
value
}
fn reload(&self, name: &str) -> Result<u32, Error> {
Ok(self.inner.reload(name)?)
}
}
";
#[test]
fn boilerplate_shapes_are_classified_on_real_parsed_code() {
use codehelion_core::boilerplate::Boilerplate;
let files = parse_all(&[BOILERPLATE]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
let category = |name: &str| {
report
.units
.iter()
.find(|unit| unit.name.as_deref() == Some(name))
.unwrap_or_else(|| panic!("{name} is an analysed unit"))
.boilerplate
};
assert_eq!(category("retries"), Some(Boilerplate::TrivialBody));
assert_eq!(category("set_retries"), Some(Boilerplate::TrivialBody));
assert_eq!(category("resolve"), Some(Boilerplate::Forwarding));
assert_eq!(category("dump"), Some(Boilerplate::MacroRepetition));
assert_eq!(category("clamp"), None);
assert_eq!(category("reload"), Some(Boilerplate::Forwarding));
}
const DUMP_A: &str = "\
fn dump_config(config: &Config) {
println!(\"retries: {}\", config.retries);
println!(\"timeout: {}\", config.timeout);
println!(\"verbose: {}\", config.verbose);
println!(\"backend: {}\", config.backend);
println!(\"workers: {}\", config.workers);
println!(\"root: {}\", config.root);
}
";
const DUMP_B: &str = "\
fn dump_limits(limits: &Limits) {
println!(\"files: {}\", limits.files);
println!(\"bytes: {}\", limits.bytes);
println!(\"depth: {}\", limits.depth);
println!(\"jobs: {}\", limits.jobs);
println!(\"budget: {}\", limits.budget);
println!(\"cap: {}\", limits.cap);
}
";
#[test]
fn a_group_of_macro_runs_is_classified_as_boilerplate() {
use codehelion_core::boilerplate::Boilerplate;
let files = parse_all(&[DUMP_A, DUMP_B, ALPHA]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
let index = report
.groups
.groups
.iter()
.position(|group| group.members.contains(&0))
.expect("the two dump routines are clones of each other");
assert!(report.groups.groups[index].members.contains(&1));
assert_eq!(
report.details[index].boilerplate,
Some(Boilerplate::MacroRepetition),
"every member is a run of macro invocations, so the group is"
);
for (group, detail) in report.groups.groups.iter().zip(&report.details) {
if group.members.contains(&2) {
assert_eq!(detail.boilerplate, None);
}
}
}
const SHORT_HEAD: &str = "\
fn short_head(seed: u32) -> u32 {
let mut acc = seed;
let mut count = 0u32;
acc = acc.wrapping_add(7);
count = count.wrapping_add(3);
acc = acc.wrapping_mul(5);
count = count.wrapping_mul(2);
return acc + count;
}
";
const LONG_BODY: &str = "\
fn long_body(seed: u32, data: &[u32]) -> u32 {
let mut acc = seed;
let mut count = 0u32;
acc = acc.wrapping_add(7);
count = count.wrapping_add(3);
acc = acc.wrapping_mul(5);
count = count.wrapping_mul(2);
for value in data {
if *value > 10 {
acc = acc.wrapping_add(*value);
} else if *value > 5 {
acc = acc.wrapping_sub(1);
} else {
acc = acc ^ *value;
}
count += 1;
}
while count > 0 {
count -= 1;
acc = acc.rotate_left(1);
}
let mut index = 0u32;
loop {
index += 1;
if index > 4 {
break;
}
acc = acc.wrapping_add(index);
}
for step in 0..8u32 {
if step % 2 == 0 {
acc = acc.wrapping_sub(step);
} else {
count = count.wrapping_add(step);
}
}
return acc + count + index;
}
";
#[test]
fn a_pair_too_far_apart_in_shape_never_reaches_verification() {
let files = parse_all(&[SHORT_HEAD, LONG_BODY]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
assert!(
report.stats.divergent_shape_pairs >= 1,
"the shared statements propose the pair, and the shape mixes drop it"
);
assert!(
!report
.groups
.groups
.iter()
.any(|group| group.members.contains(&0) && group.members.contains(&1)),
"a unit and one five times its size are not copies of each other"
);
}
#[test]
fn dropping_a_divergent_pair_leaves_the_shared_run_reported() {
let files = parse_all(&[SHORT_HEAD, LONG_BODY]);
let report = structural::analyze(&files, &variant(), &StructuralConfig::default());
assert!(
report.regions.iter().any(|region| {
region.occurrences.iter().any(|o| o.file == 0)
&& region.occurrences.iter().any(|o| o.file == 1)
}),
"the statements the two share are still reported as a duplicated run"
);
}