#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use codehelion_core::discovery::{BuildVariant, Language, LanguageSelection};
use codehelion_core::grouping::GroupingConfig;
use codehelion_core::ir::{StructuralFrontend, SyntaxIrFile};
use codehelion_core::structural::{self, StructuralConfig, StructuralReport};
use codehelion_frontend_c::ir::CStructuralFrontend;
const CORPUS: &str = "../../corpus/synthetic/c";
const FILES: [&str; 4] = ["seed.c", "type1.c", "type2.c", "type3.c"];
type Place = (&'static str, u32);
const SUM_EVEN: [Place; 3] = [("seed.c", 4), ("type1.c", 5), ("type2.c", 4)];
const MAX_RUN: [Place; 4] = [
("seed.c", 14),
("type1.c", 17),
("type2.c", 14),
("type3.c", 17),
];
const GETTER: [Place; 2] = [("seed.c", 34), ("type2.c", 34)];
fn analyze() -> StructuralReport {
let files: Vec<SyntaxIrFile> = FILES
.iter()
.map(|name| {
let path = PathBuf::from(CORPUS).join(name);
let text = std::fs::read_to_string(&path)
.unwrap_or_else(|e| panic!("reading {}: {e}", path.display()));
CStructuralFrontend.parse(&text)
})
.collect();
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
structural::analyze(&files, &variant, &StructuralConfig::default())
}
fn unit_at(report: &StructuralReport, (file, line): Place) -> usize {
report
.units
.iter()
.position(|unit| FILES[unit.file] == file && unit.start_line == line)
.unwrap_or_else(|| panic!("no unit starts at {file}:{line}"))
}
fn group_of(report: &StructuralReport, unit: usize) -> Option<usize> {
report
.groups
.groups
.iter()
.position(|group| group.members.contains(&unit))
}
#[test]
fn the_copies_of_a_labelled_function_are_recovered_as_one_group() {
let report = analyze();
for places in [&SUM_EVEN[..], &MAX_RUN[..]] {
let units: Vec<usize> = places.iter().map(|&p| unit_at(&report, p)).collect();
let groups: Vec<Option<usize>> = units.iter().map(|&u| group_of(&report, u)).collect();
assert!(
groups[0].is_some(),
"{:?} is reported as a clone of its copies",
places[0]
);
assert!(
groups.iter().all(|found| *found == groups[0]),
"{places:?} landed in {groups:?} instead of one group"
);
}
}
#[test]
fn the_getter_the_labels_call_a_non_clone_is_not_reported() {
let report = analyze();
for place in GETTER {
let unit = unit_at(&report, place);
assert_eq!(
group_of(&report, unit),
None,
"{place:?} is a deliberate non-clone"
);
}
}
#[test]
fn every_reported_group_clears_the_cohesion_floor() {
let report = analyze();
let floor = GroupingConfig::default().min_pairwise_similarity;
assert!(!report.groups.groups.is_empty(), "the corpus holds clones");
for group in &report.groups.groups {
assert!(
group.min_pairwise >= floor,
"group around {} has cohesion {:.3}",
group.canonical,
group.min_pairwise
);
assert!(group.members.len() >= 2, "a singleton is not a group");
}
}
#[test]
fn two_runs_over_the_same_corpus_agree() {
assert_eq!(analyze().groups.groups, analyze().groups.groups);
}
const SUITE: &str = "\
int normalise(int value) {
int scaled = value * 2;
int shifted = scaled + 1;
return shifted;
}
TEST(NormaliseSuite, DoublesAndShifts) {
int input = 3;
int result = normalise(input);
ASSERT_EQ(result, 7);
ASSERT_NE(result, 0);
}
TEST(NormaliseSuite, HandlesZero) {
int input = 0;
int result = normalise(input);
ASSERT_EQ(result, 1);
ASSERT_NE(result, 0);
}
";
#[test]
fn a_case_written_as_a_framework_macro_is_no_unit_in_c() {
let files = vec![CStructuralFrontend.parse(SUITE)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
let report = structural::analyze(&files, &variant, &StructuralConfig::default());
let names: Vec<Option<&str>> = report
.units
.iter()
.map(|unit| unit.name.as_deref())
.collect();
assert_eq!(
names,
vec![Some("normalise")],
"only the function is a unit"
);
assert!(
report.groups.groups.is_empty(),
"nothing in the suite reaches a group"
);
}
const UNROLLED: &str = "\
static void write_le32(void *dst, unsigned int value32)
{
unsigned char *const p = (unsigned char *)dst;
p[0] = (unsigned char)value32;
p[1] = (unsigned char)(value32 >> 8);
p[2] = (unsigned char)(value32 >> 16);
p[3] = (unsigned char)(value32 >> 24);
}
static void write_le64(void *dst, unsigned long long value64)
{
unsigned char *const p = (unsigned char *)dst;
p[0] = (unsigned char)value64;
p[1] = (unsigned char)(value64 >> 8);
p[2] = (unsigned char)(value64 >> 16);
p[3] = (unsigned char)(value64 >> 24);
p[4] = (unsigned char)(value64 >> 32);
p[5] = (unsigned char)(value64 >> 40);
p[6] = (unsigned char)(value64 >> 48);
p[7] = (unsigned char)(value64 >> 56);
}
";
#[test]
fn an_unrolled_run_is_not_a_clone_of_itself() {
let files = vec![CStructuralFrontend.parse(UNROLLED)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
let report = structural::analyze(&files, &variant, &StructuralConfig::default());
assert!(
report.stats.region_overlapping > 0,
"the shifted windows have to be recognised, not merely absent"
);
for region in &report.regions {
let units: Vec<usize> = region
.occurrences
.iter()
.map(|occurrence| occurrence.unit)
.collect();
assert!(
units.iter().any(|unit| *unit != units[0]),
"a run reported inside one function only: {:?}",
region
.occurrences
.iter()
.map(|occurrence| (occurrence.start_line, occurrence.end_line))
.collect::<Vec<_>>()
);
}
assert!(
report.groups.groups.is_empty(),
"the partial duplication must not become a whole-unit group"
);
}
const PORTABLE: &str = "\
#ifdef _WIN32
int wait_ticks(int ms) {
int ticks = ms * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
#else
int wait_ticks(int ms) {
int ticks = ms * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
#endif
int scale_a(int v) {
int ticks = v * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
int scale_b(int v) {
int ticks = v * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
";
#[test]
fn the_two_arms_of_one_conditional_are_not_a_clone_pair() {
let files = vec![CStructuralFrontend.parse(PORTABLE)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
let report = structural::analyze(&files, &variant, &StructuralConfig::default());
let unit_at_line = |line: u32| {
report
.units
.iter()
.position(|unit| unit.start_line == line)
.unwrap_or_else(|| panic!("no unit starts at line {line}"))
};
let (guarded, otherwise) = (unit_at_line(2), unit_at_line(9));
let (open_a, open_b) = (unit_at_line(17), unit_at_line(24));
assert_eq!(
report.stats.alternative_pairs, 3,
"the guarded pair is dropped, and the funnel says so"
);
for group in &report.groups.groups {
assert!(
!(group.members.contains(&guarded) && group.members.contains(&otherwise)),
"no group holds both arms of one conditional"
);
}
assert!(
report
.groups
.groups
.iter()
.any(|group| group.members.contains(&open_a) && group.members.contains(&open_b)),
"the same code outside any conditional is still a clone"
);
}
const BROKEN_AFTERWARDS: &str = "\
#ifdef _WIN32
int wait_ticks(int ms) {
int ticks = ms * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
#else
int wait_ticks(int ms) {
int ticks = ms * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
#endif
int broken(int v) { return v +
";
const BROKEN_INSIDE: &str = "\
#ifdef _WIN32
int wait_ticks(int ms) {
int ticks = ms * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
int broken(int v) { return v + }
#else
int wait_ticks(int ms) {
int ticks = ms * 10;
int capped = ticks > 1000 ? 1000 : ticks;
int slept = capped;
return slept;
}
#endif
";
#[test]
fn a_stumble_elsewhere_in_the_file_leaves_the_conditional_readable() {
let files = vec![CStructuralFrontend.parse(BROKEN_AFTERWARDS)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
let report = structural::analyze(&files, &variant, &StructuralConfig::default());
assert!(
!files[0].error_ranges.is_empty(),
"the fixture is meant to be a file the parser struggled with"
);
assert!(
report.stats.alternative_pairs > 0,
"the conditional itself parsed, so its arms still rule each other out"
);
}
#[test]
fn a_stumble_inside_the_conditional_excludes_nothing() {
let files = vec![CStructuralFrontend.parse(BROKEN_INSIDE)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
let report = structural::analyze(&files, &variant, &StructuralConfig::default());
assert!(
!files[0].error_ranges.is_empty(),
"the fixture is meant to be a file the parser struggled with"
);
assert_eq!(
report.stats.alternative_pairs, 0,
"no exclusion is claimed from an arm the parser guessed at"
);
let unit_at_line = |line: u32| {
report
.units
.iter()
.position(|unit| unit.start_line == line)
.unwrap_or_else(|| panic!("no unit starts at line {line}"))
};
let (guarded, otherwise) = (unit_at_line(2), unit_at_line(10));
assert!(
report
.groups
.groups
.iter()
.any(|group| group.members.contains(&guarded) && group.members.contains(&otherwise)),
"the two arms are reported as the clone they measure as"
);
}
const OUT_PARAMETER: &str = "\
static unsigned read32(const void *src)
{
unsigned value;
copy_bytes(&value, src, sizeof(value));
return value;
}
static unsigned long long read64(const void *src)
{
unsigned long long value;
copy_bytes(&value, src, sizeof(value));
return value;
}
static unsigned mix32(unsigned h)
{
unsigned value = h * 31u + 7u;
return value;
}
";
#[test]
fn a_local_the_callee_answers_through_does_not_make_a_wrapper_into_work() {
use codehelion_core::boilerplate::Boilerplate;
let files = vec![CStructuralFrontend.parse(OUT_PARAMETER)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
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("read32"), Some(Boilerplate::Forwarding));
assert_eq!(category("read64"), Some(Boilerplate::Forwarding));
assert_eq!(category("mix32"), None);
}
const FOUR_LANE: &str = "\
static void accumulate(unsigned *v, const unsigned char *p)
{
v[0] = mix(v[0], read32(p)); p += 4;
v[1] = mix(v[1], read32(p)); p += 4;
v[2] = mix(v[2], read32(p)); p += 4;
v[3] = mix(v[3], read32(p)); p += 4;
}
";
#[test]
fn the_halves_of_an_unrolled_run_are_its_period_not_two_copies() {
let files = vec![CStructuralFrontend.parse(FOUR_LANE)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
let report = structural::analyze(&files, &variant, &StructuralConfig::default());
assert!(
report.stats.region_adjoining > 0,
"the tiling halves have to be recognised, not merely absent"
);
assert_eq!(
report.regions,
vec![],
"one stretch of code repeating is not two instances of anything"
);
}
const GUARDED: &str = "\
static int is_false(const item_t *item)
{
if (item == NULL) { return 0; }
return (item->type & 0xFF) == TYPE_FALSE;
}
static int is_true(const item_t *item)
{
if (item == NULL) { return 0; }
return (item->type & 0xFF) == TYPE_TRUE;
}
static int release(state_t *state)
{
if (!state) { return 0; }
free_state(state);
return 0;
}
static int rank(int a, int b, int c)
{
if (a) { return 1; }
if (b) { return 2; }
if (c) { return 3; }
return 4;
}
";
#[test]
fn a_body_that_chooses_an_answer_is_not_a_body_that_works_one_out() {
use codehelion_core::boilerplate::Boilerplate;
let files = vec![CStructuralFrontend.parse(GUARDED)];
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
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("is_false"), Some(Boilerplate::GuardedDispatch));
assert_eq!(category("is_true"), Some(Boilerplate::GuardedDispatch));
assert_eq!(category("release"), Some(Boilerplate::GuardedDispatch));
assert_eq!(category("rank"), None);
}