#![allow(clippy::unwrap_used, clippy::expect_used, clippy::panic)]
use std::path::PathBuf;
use codehelion_core::clone_class::CloneClass;
use codehelion_core::discovery::{BuildVariant, Language, LanguageSelection};
use codehelion_core::ir::{StructuralFrontend, SyntaxIrFile};
use codehelion_core::structural::{
self, RegionOccurrence, StructuralConfig, StructuralRegion, StructuralReport,
};
use codehelion_frontend_rust::ir::RustStructuralFrontend;
const CORPUS: &str = "../../corpus/synthetic/rust-partial";
const FILES: [&str; 3] = ["seed.rs", "partial1.rs", "partial2.rs"];
fn read(name: &str) -> String {
let path = PathBuf::from(CORPUS).join(name);
std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("reading {}: {e}", path.display()))
}
fn analyze() -> StructuralReport {
let files: Vec<SyntaxIrFile> = FILES
.iter()
.map(|name| RustStructuralFrontend.parse(&read(name)))
.collect();
let variant = BuildVariant::structural(LanguageSelection::default(), Language::C);
structural::analyze(&files, &variant, &StructuralConfig::default())
}
fn host(report: &StructuralReport, occurrence: &RegionOccurrence) -> String {
report.units[occurrence.unit]
.name
.as_ref()
.map_or_else(|| "?".to_string(), |name| name.as_str().to_string())
}
fn describe(report: &StructuralReport, region: &StructuralRegion) -> Vec<String> {
region
.occurrences
.iter()
.map(|occurrence| {
format!(
"{}:({}, {}) {}",
FILES[occurrence.file],
occurrence.start_line,
occurrence.end_line,
host(report, occurrence)
)
})
.collect()
}
fn described(report: &StructuralReport) -> Vec<Vec<String>> {
report
.regions
.iter()
.map(|region| describe(report, region))
.collect()
}
#[test]
fn the_transplanted_run_is_reported_once_at_its_labelled_extent() {
let report = analyze();
assert_eq!(
report.regions.len(),
1,
"one labelled run is detectable here: {:#?}",
described(&report)
);
let region = &report.regions[0];
assert_eq!(region.occurrences.len(), 2);
assert_eq!(region.clone_type, CloneClass::Type1);
assert_eq!(
describe(&report, region),
vec![
"seed.rs:(9, 22) measure_lines".to_string(),
"partial1.rs:(10, 23) scan_report".to_string(),
]
);
let text: Vec<String> = region
.occurrences
.iter()
.map(|occurrence| {
read(FILES[occurrence.file])[occurrence.range.start..occurrence.range.end]
.trim()
.to_string()
})
.collect();
assert_eq!(text[0], text[1]);
}
#[test]
fn the_sliding_windows_that_found_it_collapse_into_one_region() {
let report = analyze();
assert_eq!(report.regions[0].statements, 5);
assert!(report.stats.maximal.seeds > report.stats.maximal.regions);
assert_eq!(report.stats.regions, report.regions.len());
}
#[test]
fn a_run_that_only_matches_on_summaries_is_not_reported() {
let report = analyze();
assert!(
report.stats.maximal.divergent_extent > 0,
"at least the summary-only coincidence must be rejected"
);
assert!(
!report.regions.iter().any(|region| {
region
.occurrences
.iter()
.all(|occurrence| host(&report, occurrence) == "scan_report")
}),
"the summary-level coincidence must not be reported: {:#?}",
described(&report)
);
}
#[test]
fn the_renamed_transplant_is_below_the_window_minimum() {
let report = analyze();
assert!(report.regions.iter().all(|region| {
region
.occurrences
.iter()
.all(|occurrence| host(&report, occurrence) != "merge_batches")
}));
}
#[test]
fn the_corpus_consolidates_the_same_twice() {
assert_eq!(analyze().regions, analyze().regions);
}