use splice::context::{extract_context, extract_context_asymmetric};
use splice::expand::expand_to_body_with_docs;
use splice::symbol::Language;
use std::io::Write;
use std::path::Path;
use std::time::Instant;
use tempfile::TempDir;
fn ci_multiplier() -> u128 {
if std::env::var("CI").is_ok() {
3
} else {
1
}
}
fn create_large_rust_file(dir: &Path, name: &str, num_functions: usize) -> std::path::PathBuf {
let file_path = dir.join(name);
let mut file = std::fs::File::create(&file_path).unwrap();
for i in 0..num_functions {
writeln!(
file,
r#"/// Documentation for function {}
/// This function adds two numbers together.
pub fn add_{}(x: i32, y: i32) -> i32 {{
// Line 1 of function body
// Line 2 of function body
// Line 3 of function body
x + y
}}
/// Another function for multiplication
pub fn multiply_{}(x: i32, y: i32) -> i32 {{
x * y
}}
"#,
i, i, i
)
.unwrap();
}
file_path
}
#[test]
fn test_context_extraction_32kb_file() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_32kb.rs", 350);
let file_size = std::fs::metadata(&file_path).unwrap().len();
assert!(
file_size > 32_000,
"Test file should exceed 32KB, got {} bytes",
file_size
);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_175").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context(&file_path, offset, offset + 50, 3).unwrap();
let duration = start.elapsed();
assert!(!ctx.before.is_empty(), "Should have context before");
assert!(!ctx.selected.is_empty(), "Should have selected context");
assert!(!ctx.after.is_empty(), "Should have context after");
let max_ms = 100 * 1;
println!(
"Context extraction on {}KB file took {}ms",
file_size / 1024,
duration.as_millis()
);
}
#[test]
fn test_context_extraction_64kb_file() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_64kb.rs", 700);
let file_size = std::fs::metadata(&file_path).unwrap().len();
assert!(
file_size > 64_000,
"Test file should exceed 64KB, got {} bytes",
file_size
);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_350").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context(&file_path, offset, offset + 50, 3).unwrap();
let duration = start.elapsed();
assert!(!ctx.before.is_empty(), "Should have context before");
assert!(!ctx.selected.is_empty(), "Should have selected context");
assert!(!ctx.after.is_empty(), "Should have context after");
let max_ms = 200 * 1;
println!(
"Context extraction on {}KB file took {}ms",
file_size / 1024,
duration.as_millis()
);
}
#[test]
fn test_context_extraction_128kb_file() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_128kb.rs", 1400);
let file_size = std::fs::metadata(&file_path).unwrap().len();
assert!(
file_size > 128_000,
"Test file should exceed 128KB, got {} bytes",
file_size
);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_700").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context(&file_path, offset, offset + 50, 3).unwrap();
let duration = start.elapsed();
assert!(!ctx.before.is_empty(), "Should have context before");
assert!(!ctx.selected.is_empty(), "Should have selected context");
assert!(!ctx.after.is_empty(), "Should have context after");
let max_ms = 400 * 1;
println!(
"Context extraction on {}KB file took {}ms",
file_size / 1024,
duration.as_millis()
);
}
#[test]
fn test_context_extraction_with_expansion_64kb() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_expand.rs", 700);
let file_size = std::fs::metadata(&file_path).unwrap().len();
assert!(
file_size > 64_000,
"Test file should exceed 64KB, got {} bytes",
file_size
);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_350").expect("Symbol not found");
let start = Instant::now();
let (expanded_start, expanded_end) =
expand_to_body_with_docs(&file_path, offset, Language::Rust).unwrap();
let ctx = extract_context(&file_path, expanded_start, expanded_end, 3).unwrap();
let duration = start.elapsed();
assert!(
expanded_end > expanded_start,
"Should expand to larger span"
);
assert!(!ctx.before.is_empty(), "Should have context before");
assert!(
!ctx.selected.is_empty(),
"Should have selected content from expanded span"
);
let max_ms = 300 * 1;
println!(
"Expansion + context extraction on {}KB file took {}ms",
file_size / 1024,
duration.as_millis()
);
}
#[test]
fn test_asymmetric_context_extraction_64kb() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_asymmetric.rs", 700);
let file_size = std::fs::metadata(&file_path).unwrap().len();
assert!(
file_size > 64_000,
"Test file should exceed 64KB, got {} bytes",
file_size
);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_350").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context_asymmetric(&file_path, offset, offset + 50, 5, 2).unwrap();
let duration = start.elapsed();
assert!(
ctx.before.len() <= 5,
"Should have at most 5 lines before, got {}",
ctx.before.len()
);
assert!(
ctx.after.len() <= 2,
"Should have at most 2 lines after, got {}",
ctx.after.len()
);
assert!(!ctx.selected.is_empty(), "Should have selected content");
let max_ms = 150 * 1;
println!(
"Asymmetric context extraction ({} before, {} after) on {}KB file took {}ms",
ctx.before.len(),
ctx.after.len(),
file_size / 1024,
duration.as_millis()
);
}
#[test]
fn test_context_extraction_at_file_boundaries() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_boundary.rs", 350);
let source = std::fs::read_to_string(&file_path).unwrap();
let first_offset = source.find("add_0").expect("Symbol not found");
let start = Instant::now();
let ctx_start = extract_context(&file_path, first_offset, first_offset + 50, 3).unwrap();
let duration_start = start.elapsed();
assert!(
!ctx_start.selected.is_empty(),
"Should extract at file start"
);
assert!(
ctx_start.before.len() <= 3,
"At file start, before context should be minimal"
);
let last_offset = source.rfind("multiply_").expect("Symbol not found");
let start = Instant::now();
let ctx_end = extract_context(&file_path, last_offset, last_offset + 50, 3).unwrap();
let duration_end = start.elapsed();
assert!(!ctx_end.selected.is_empty(), "Should extract at file end");
assert!(
ctx_end.after.len() <= 1,
"At file end, after context should be minimal"
);
println!(
"Boundary extraction: start {}ms, end {}ms",
duration_start.as_millis(),
duration_end.as_millis()
);
}
#[test]
fn test_context_extraction_linear_scaling() {
let dir = TempDir::new().unwrap();
let ci_multiplier = if std::env::var("CI").is_ok() { 3 } else { 1 };
let test_cases = vec![
(100, 50 * ci_multiplier), (200, 100 * ci_multiplier), (400, 200 * ci_multiplier), ];
let mut timings = Vec::new();
for (num_functions, _expected_max_ms) in test_cases {
let file_path = create_large_rust_file(
dir.path(),
&format!("scale_{}.rs", num_functions),
num_functions,
);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context(&file_path, offset, offset + 50, 3).unwrap();
let duration = start.elapsed();
assert!(
!ctx.before.is_empty(),
"Should extract context for {} functions",
num_functions
);
let duration_ms = duration.as_millis();
timings.push((num_functions, duration_ms));
println!("{} functions: {}ms", num_functions, duration_ms);
}
}
#[test]
fn test_context_extraction_zero_context_large_file() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_zero.rs", 350);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_175").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context(&file_path, offset, offset + 50, 0).unwrap();
let duration = start.elapsed();
assert_eq!(ctx.before.len(), 0, "Should have no context before");
assert_eq!(ctx.after.len(), 0, "Should have no context after");
assert!(!ctx.selected.is_empty(), "Should have selected content");
let max_ms = 50 * 1;
}
#[test]
fn test_context_extraction_large_context_window() {
let dir = TempDir::new().unwrap();
let file_path = create_large_rust_file(dir.path(), "large_window.rs", 700);
let source = std::fs::read_to_string(&file_path).unwrap();
let offset = source.find("add_350").expect("Symbol not found");
let start = Instant::now();
let ctx = extract_context(&file_path, offset, offset + 50, 20).unwrap();
let duration = start.elapsed();
assert!(
ctx.before.len() <= 20,
"Should have at most 20 lines before, got {}",
ctx.before.len()
);
assert!(
ctx.after.len() <= 20,
"Should have at most 20 lines after, got {}",
ctx.after.len()
);
let max_ms = 200 * 1;
println!(
"Large context window (up to 20 lines): {}ms",
duration.as_millis()
);
}