use lex_ast::{CExpr, FnDecl, Param, TypeExpr};
use lex_vcs::compute_diff;
use std::collections::BTreeMap;
use std::time::Instant;
fn int_ty() -> TypeExpr {
TypeExpr::Named { name: "Int".into(), args: Vec::new() }
}
fn fn_decl(name: &str, lit: i64) -> FnDecl {
FnDecl {
name: name.into(),
type_params: Vec::new(),
params: vec![Param { name: "n".into(), ty: int_ty() }],
effects: Vec::new(),
effect_row_var: None,
return_type: int_ty(),
body: CExpr::BinOp {
op: "+".into(),
lhs: Box::new(CExpr::Var { name: "n".into() }),
rhs: Box::new(CExpr::Literal { value: lex_ast::CLit::Int { value: lit } }),
},
examples: Vec::new(),
}
}
const OLD_HISTORY_SIZE: usize = 15000;
const NEW_FILE_SIZE: usize = 40;
#[test]
fn rename_detection_scales_linearly_not_quadratically() {
const OLD_LIT_BASE: i64 = 0;
const NEW_LIT_BASE: i64 = 1_000_000_000;
let a: BTreeMap<String, FnDecl> = (0..OLD_HISTORY_SIZE)
.map(|i| (format!("old_fn_{i}"), fn_decl(&format!("old_fn_{i}"), OLD_LIT_BASE + i as i64)))
.collect();
let mut b: BTreeMap<String, FnDecl> = (0..NEW_FILE_SIZE)
.map(|i| (format!("new_fn_{i}"), fn_decl(&format!("new_fn_{i}"), NEW_LIT_BASE + i as i64)))
.collect();
b.insert("renamed_old_fn_7".into(), fn_decl("renamed_old_fn_7", OLD_LIT_BASE + 7));
let start = Instant::now();
let report = compute_diff(&a, &b, false);
let elapsed = start.elapsed();
assert_eq!(report.renamed.len(), 1, "expected exactly one detected rename");
assert_eq!(report.renamed[0].from, "old_fn_7");
assert_eq!(report.renamed[0].to, "renamed_old_fn_7");
assert!(!report.removed.iter().any(|r| r.name == "old_fn_7"));
assert!(!report.added.iter().any(|a| a.name == "renamed_old_fn_7"));
assert_eq!(report.removed.len(), OLD_HISTORY_SIZE - 1);
assert_eq!(report.added.len(), NEW_FILE_SIZE);
assert!(
elapsed.as_secs_f64() < 5.0,
"compute_diff({OLD_HISTORY_SIZE} old, {NEW_FILE_SIZE} new) took {elapsed:?}; \
a quadratic regression in rename detection would blow this budget (see #813)"
);
}