use std::path::Path;
use super::*;
use crate::graph::pdg::{Node, ProgramDependenceGraph};
use crate::parse::grammar::LanguageId;
fn ts_language(ext: &str) -> tree_sitter::Language {
LanguageId::from_extension(ext)
.expect("language must exist")
.from_cache()
.expect("grammar loads")
}
#[test]
fn test_basic_rust_chunking() {
let source_code = r#"
#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}
impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}
fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};
println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}
"#;
let max_chunk_size = 128;
let chunks = chunk_semantic(
source_code,
Path::new("test.rs"),
max_chunk_size,
&ts_language("rs"),
)
.unwrap();
assert_eq!(chunks.len(), 4);
for chunk in &chunks {
assert!(
chunk.content.len() <= max_chunk_size,
"Chunk should not exceed max size of {max_chunk_size} but was: {}",
chunk.content.len()
);
}
assert_eq!(
chunks[0].content.trim(),
r#"#[derive(Debug)]
struct Rectangle {
width: u32,
height: u32,
}"#
);
assert_eq!(
chunks[1].content.trim(),
r#"impl Rectangle {
fn area(&self) -> u32 {
self.width * self.height
}
}"#
);
assert_eq!(
chunks[2].content.trim(),
r#"fn main() {
let rect1 = Rectangle {
width: 30,
height: 50,
};"#
);
assert_eq!(
chunks[3].content.trim(),
r#"println!(
"The area of the rectangle is {} square pixels.",
rect1.area()
);
}"#
);
}
#[test]
fn test_no_fragment_crosses_owner_range() {
let source_code = r#"
fn long_function() {
let a = 1;
let b = 2;
let c = 3;
let d = 4;
let e = 5;
let f = 6;
let g = 7;
let h = 8;
let i = 9;
let j = 10;
let k = 11;
let l = 12;
let m = 13;
let n = 14;
let o = 15;
}
"#;
let chunks = chunk_semantic(source_code, Path::new("test.rs"), 64, &ts_language("rs"))
.expect("semantic chunking succeeds for rust");
let mut last_end = 0usize;
for chunk in &chunks {
assert_eq!(
chunk.content,
&source_code[chunk.start_byte_index..chunk.end_byte_index]
);
assert!(chunk.start_byte_index >= last_end, "fragments overlap");
last_end = chunk.end_byte_index;
}
}
#[test]
fn test_chunker() {
let code = "This is some text content\nthat should be chunked\nusing the naive chunker\nbecause the language isn't recognized.";
let path = Path::new("test_file.xyz");
let max_lines = 1;
let fragments = chunk_naive(code, path, 10000, max_lines);
assert!(!fragments.is_empty(), "Expected at least one fragment");
assert_eq!(fragments.len(), code.lines().count());
for (idx, line) in code.lines().enumerate() {
assert_eq!(fragments[idx].content, line);
assert_eq!(fragments[idx].start_line, idx);
assert_eq!(fragments[idx].end_line, idx);
}
}
#[test]
fn test_chunker_large_chunk() {
let code = "This is some text content\nthat should be chunked\nusing the naive chunker\nbecause the language isn't recognized.";
let path = Path::new("test_file.xyz");
let fragments = chunk_naive(code, path, 10000, 100);
assert_eq!(fragments.len(), 1);
assert_eq!(fragments[0].content, code);
assert_eq!(fragments[0].start_line, 0);
assert_eq!(fragments[0].end_line, code.lines().count() - 1);
}
#[test]
fn test_chunker_max_bytes() {
let code = "line1\nline2\nline3\nline4abcdefghijklmnopqrstuvwxyz";
let path = Path::new("test_file.xyz");
let max_bytes_per_chunk = 25;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert!(
fragments.len() > 1,
"Expected multiple chunks due to size limit"
);
for (i, fragment) in fragments.iter().enumerate() {
assert!(
fragment.content.trim().len() <= max_bytes_per_chunk,
"Fragment {} has size {} bytes, which exceeds limit of {} bytes",
i,
fragment.content.len(),
max_bytes_per_chunk
);
}
assert_eq!(fragments[0].content, "line1\nline2\nline3");
assert_eq!(fragments[1].content, "line4abcdefghijklmnopqrst");
assert_eq!(fragments[2].content, "uvwxyz");
let reassembled_content: String = fragments
.iter()
.map(|f| f.content)
.collect::<Vec<_>>()
.join("");
assert_eq!(
reassembled_content.replace('\n', ""),
code.replace('\n', ""),
"Reassembled content does not match original"
);
}
#[test]
fn test_utf8_emoji_chunking() {
let code = "Hello 🦀 Rust\nWorld 🌍 Test\n🚀 Rocket 🎯 Target";
let path = Path::new("test_emoji.txt");
let max_bytes_per_chunk = 15; let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert!(
fragments.len() > 1,
"Expected multiple chunks due to size limit"
);
for (i, fragment) in fragments.iter().enumerate() {
assert!(
fragment.content.len() <= max_bytes_per_chunk,
"Fragment {} has size {} bytes, which exceeds limit of {} bytes. Content: '{}'",
i,
fragment.content.len(),
max_bytes_per_chunk,
fragment.content
);
}
for (i, fragment) in fragments.iter().enumerate() {
assert!(
fragment.content.is_ascii() || std::str::from_utf8(fragment.content.as_bytes()).is_ok(),
"Fragment {} contains invalid UTF-8: {:?}",
i,
fragment.content
);
}
let reassembled_content: String = fragments
.iter()
.map(|f| f.content)
.collect::<Vec<_>>()
.join("");
assert_eq!(
reassembled_content.replace('\n', ""),
code.replace('\n', ""),
"Reassembled content does not match original"
);
}
#[test]
fn test_utf8_accented_characters() {
let code = "Café résumé naïve\nÉlève découvrir\nMañana piñata";
let path = Path::new("test_accents.txt");
let max_bytes_per_chunk = 10;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert!(
fragments.len() > 1,
"Expected multiple chunks due to size limit"
);
for (i, fragment) in fragments.iter().enumerate() {
assert!(
std::str::from_utf8(fragment.content.as_bytes()).is_ok(),
"Fragment {} contains invalid UTF-8: {:?}",
i,
fragment.content.as_bytes()
);
}
let reassembled_content: String = fragments
.iter()
.map(|f| f.content)
.collect::<Vec<_>>()
.join("");
assert_eq!(
reassembled_content.replace('\n', ""),
code.replace('\n', ""),
"Reassembled content does not match original"
);
}
#[test]
fn test_utf8_mixed_characters() {
let code = "ASCII text 中文 🦀 résumé ℘ math symbols";
let path = Path::new("test_mixed.txt");
let max_bytes_per_chunk = 8;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert!(
fragments.len() > 1,
"Expected multiple chunks due to size limit"
);
for (i, fragment) in fragments.iter().enumerate() {
assert!(
fragment.content.len() <= max_bytes_per_chunk,
"Fragment {} has size {} bytes, which exceeds limit of {} bytes",
i,
fragment.content.len(),
max_bytes_per_chunk
);
assert!(
std::str::from_utf8(fragment.content.as_bytes()).is_ok(),
"Fragment {} contains invalid UTF-8: {:?}",
i,
fragment.content.as_bytes()
);
}
let reassembled_content: String = fragments
.iter()
.map(|f| f.content)
.collect::<Vec<_>>()
.join("");
assert_eq!(
reassembled_content, code,
"Reassembled content does not match original"
);
}
#[test]
fn test_utf8_boundary_edge_cases() {
let code = "ab🦀cd"; let path = Path::new("test_edge.txt");
let max_bytes_per_chunk = 3;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert!(fragments.len() >= 2, "Expected at least 2 fragments");
for (i, fragment) in fragments.iter().enumerate() {
assert!(
std::str::from_utf8(fragment.content.as_bytes()).is_ok(),
"Fragment {} contains invalid UTF-8: {:?}",
i,
fragment.content.as_bytes()
);
}
let reassembled_content: String = fragments
.iter()
.map(|f| f.content)
.collect::<Vec<_>>()
.join("");
assert_eq!(
reassembled_content, code,
"Reassembled content does not match original"
);
}
#[test]
fn test_utf8_single_multibyte_character() {
let code = "🦀"; let path = Path::new("test_single.txt");
let max_bytes_per_chunk = 2;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert_eq!(fragments.len(), 1, "Should have exactly one fragment");
assert_eq!(fragments[0].content, code);
assert!(
std::str::from_utf8(fragments[0].content.as_bytes()).is_ok(),
"Fragment contains invalid UTF-8"
);
}
#[test]
fn test_utf8_line_endings_with_multibyte() {
let code = "Hello🌍\nWorld🦀\nTest🎯";
let path = Path::new("test_lines.txt");
let max_bytes_per_chunk = 10;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1);
assert_eq!(fragments.len(), 3, "Should have 3 fragments for 3 lines");
for (i, fragment) in fragments.iter().enumerate() {
assert!(
std::str::from_utf8(fragment.content.as_bytes()).is_ok(),
"Fragment {} contains invalid UTF-8: {:?}",
i,
fragment.content.as_bytes()
);
}
assert_eq!(fragments[0].start_line, 0);
assert_eq!(fragments[0].end_line, 0);
assert_eq!(fragments[1].start_line, 1);
assert_eq!(fragments[1].end_line, 1);
assert_eq!(fragments[2].start_line, 2);
assert_eq!(fragments[2].end_line, 2);
}
#[test]
fn test_panic_regression_byte_boundary() {
let code = "Hi🦀Test";
let path = Path::new("test_panic.txt");
let max_bytes_per_chunk = 3;
let fragments = chunk_naive(code, path, max_bytes_per_chunk, 1000);
assert!(!fragments.is_empty(), "Should have at least one fragment");
for (i, fragment) in fragments.iter().enumerate() {
assert!(
std::str::from_utf8(fragment.content.as_bytes()).is_ok(),
"Fragment {} contains invalid UTF-8: {:?}",
i,
fragment.content.as_bytes()
);
}
let reassembled_content: String = fragments
.iter()
.map(|f| f.content)
.collect::<Vec<_>>()
.join("");
assert_eq!(
reassembled_content, code,
"Reassembled content does not match original"
);
}
fn orphan_input<'a>(
code: &'a str,
node_ranges: &'a [(usize, usize)],
file_doc_end: usize,
max_bytes: usize,
) -> OrphanInput<'a> {
OrphanInput {
file_bytes: code.as_bytes(),
path: Path::new("orphan_test.rs"),
node_ranges,
file_doc_end,
max_bytes,
}
}
#[test]
fn test_orphan_module_level_statements_retrievable() {
let code = "// file doc line\n\nconst MAX: u32 = 10;\n\nfn used() {}\n\n// standalone helper constant\nconst MIN: u32 = 1;\n";
let fn_start = code.find("fn used").unwrap();
let fn_end = fn_start + "fn used() {}".len();
let node_ranges = [(fn_start, fn_end)];
let orphans = orphan_fragments(orphan_input(code, &node_ranges, 18, 4096));
assert!(!orphans.is_empty(), "expected orphan fragments");
let combined: String = orphans.iter().map(|f| f.content).collect();
assert!(
combined.contains("const MAX"),
"module const must be retrievable"
);
assert!(
combined.contains("const MIN"),
"module const must be retrievable"
);
assert!(
combined.contains("standalone helper"),
"orphan comment must be retrievable"
);
assert!(
!combined.contains("file doc line"),
"file-doc region must be excluded from orphans"
);
for f in &orphans {
assert_eq!(f.content, &code[f.start_byte_index..f.end_byte_index]);
}
}
#[test]
fn test_orphan_file_doc_region_excluded() {
let code = "//! Module docs\n//! more docs\n\nfn f() {}\n";
let fn_start = code.find("fn f").unwrap();
let fn_end = fn_start + "fn f() {}".len();
let doc_end = code.find("\n\nfn f").unwrap() + 1;
let node_ranges = [(fn_start, fn_end)];
let orphans = orphan_fragments(orphan_input(code, &node_ranges, doc_end, 4096));
let combined: String = orphans.iter().map(|f| f.content).collect();
assert!(
!combined.contains("Module docs"),
"file-doc region must not be double-indexed"
);
assert!(!combined.contains("fn f"));
}
#[test]
fn test_orphan_empty_complement_yields_zero_rows() {
let code = "// doc\nfn f() {}\n";
let doc_end = 6;
let whole = (0usize, code.len());
let node_ranges = [whole];
let orphans = orphan_fragments(orphan_input(code, &node_ranges, doc_end, 4096));
assert!(
orphans.is_empty(),
"fully covered file must yield zero orphans"
);
}
#[test]
fn test_orphan_invalid_utf8_yields_zero_rows() {
let input = OrphanInput {
file_bytes: &[0xff, 0xfe, 0x00, 0x01],
path: Path::new("binary.bin"),
node_ranges: &[],
file_doc_end: 0,
max_bytes: 4096,
};
assert!(orphan_fragments(input).is_empty());
}
#[test]
fn test_owner_header_format() {
let header = owner_header("function", "rust", 3, 2, 7);
assert_eq!(
header,
"// type:function lang:rust callers:3 callees:2 complexity:7"
);
let capped = owner_header("function", "rust", 500, 500, 7);
assert!(
capped.contains("callers:50 callees:50"),
"callers/callees capped at 50"
);
}
#[test]
fn test_enrich_fragment_prepends_header_doc_and_symbol() {
let code = "// Computes area.\nfn area() {\n 1\n}\n";
let frag_start = code.find("fn area").unwrap();
let fragment = Fragment {
content: &code[frag_start..],
start_line: 0,
end_line: 2,
start_byte_index: frag_start,
end_byte_index: code.len(),
file_path: Path::new("rect.rs"),
};
let header = owner_header("function", "rust", 0, 0, 1);
let enriched = enrich_fragment(&fragment, code.as_bytes(), &header, "area");
assert!(enriched.starts_with(&header), "header first");
assert!(enriched.contains("// area in rect.rs"), "symbol line");
assert!(enriched.contains("Computes area."), "stripped doc context");
assert!(
!enriched.contains("// Computes area."),
"doc markers stripped"
);
assert!(
enriched.ends_with(fragment.content),
"fragment content last"
);
}
#[test]
fn test_enrich_orphan_module_header() {
let fragment = Fragment {
content: "const MIN: u32 = 1;",
start_line: 3,
end_line: 3,
start_byte_index: 0,
end_byte_index: 18,
file_path: Path::new("orphan_test.rs"),
};
let header = orphan_header("rust", Path::new("orphan_test.rs"));
let enriched = enrich_orphan(&fragment, &header);
assert_eq!(
enriched,
"// type:module lang:rust file:orphan_test.rs\nconst MIN: u32 = 1;"
);
}
fn sample_metadata(content_hash: &str, owner: Option<&str>, offset: u64) -> FragmentMetadata {
FragmentMetadata {
content_hash: content_hash.to_string(),
owner: owner.map(str::to_string),
file_path: "rect.rs".to_string(),
byte_range: (0, 10),
line_range: (0, 2),
embedding_offset: offset,
}
}
#[test]
fn test_store_dedup_one_row_many_refs() {
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", Some("node1"), 0));
store.insert(sample_metadata("abc123", Some("node2"), 0));
store.insert(sample_metadata("def456", None, 1));
assert_eq!(
store.len(),
2,
"two unique content hashes → two embedding rows"
);
let total_refs: usize = store
.content_hashes()
.map(|h| store.get(h).map(|metas| metas.len()).unwrap_or(0))
.sum();
assert_eq!(
total_refs, 3,
"three metadata refs total across the two rows"
);
assert_eq!(store.get("abc123").unwrap().len(), 2);
assert!(store.get("missing").is_none());
}
#[test]
fn test_store_owner_mapping() {
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", Some("node1"), 0));
store.insert(sample_metadata("def456", Some("node1"), 1));
store.insert(sample_metadata("ghi789", Some("node2"), 2));
store.insert(sample_metadata("orphan1", None, 3));
let owners_of = |hash: &str| -> Vec<Option<String>> {
store
.get(hash)
.map(|metas| metas.iter().map(|m| m.owner.clone()).collect())
.unwrap_or_default()
};
let node1 = owners_of("abc123");
assert!(node1.iter().any(|o| o.as_deref() == Some("node1")));
let node1_b = owners_of("def456");
assert!(node1_b.iter().any(|o| o.as_deref() == Some("node1")));
let node2 = owners_of("ghi789");
assert!(node2.iter().any(|o| o.as_deref() == Some("node2")));
assert!(owners_of("orphan1").iter().all(|o| o.is_none()));
}
#[test]
fn test_store_persist_load_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", Some("node1"), 0));
store.insert(sample_metadata("abc123", Some("node2"), 0));
store.insert(sample_metadata("def456", None, 1));
store.persist_to_storage(dir.path()).unwrap();
let loaded = FragmentStore::load_from_storage(dir.path())
.unwrap()
.expect("store loads");
assert_eq!(loaded.len(), 2);
let loaded_refs: usize = loaded
.content_hashes()
.map(|h| loaded.get(h).map(|metas| metas.len()).unwrap_or(0))
.sum();
assert_eq!(loaded_refs, 3);
assert_eq!(loaded.get("abc123").unwrap().len(), 2);
assert_eq!(
loaded.get("abc123").unwrap()[0].owner.as_deref(),
Some("node1")
);
assert_eq!(loaded.get("def456").unwrap()[0].owner, None);
}
#[test]
fn test_store_load_missing_is_none() {
let dir = tempfile::tempdir().unwrap();
assert!(FragmentStore::default().is_empty());
assert!(
FragmentStore::load_from_storage(dir.path())
.unwrap()
.is_none()
);
}
#[test]
fn test_store_schema_mismatch_rejected() {
let dir = tempfile::tempdir().unwrap();
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", None, 0));
store.persist_to_storage(dir.path()).unwrap();
let path = dir.path().join(".leindex").join("fragment_store.bin");
let mut bytes = std::fs::read(&path).unwrap();
bytes[0] = 0xff; std::fs::write(&path, &bytes).unwrap();
assert!(
FragmentStore::load_from_storage(dir.path())
.unwrap()
.is_none(),
"schema-mismatched store must be discarded"
);
}
#[test]
fn test_root_hash_deterministic_and_content_sensitive() {
let mut a = FragmentStore::default();
a.insert(sample_metadata("abc123", Some("node1"), 0));
a.insert(sample_metadata("def456", None, 1));
let mut b = FragmentStore::default();
b.insert(sample_metadata("def456", None, 0));
b.insert(sample_metadata("abc123", Some("node1"), 1));
let root_a = compute_fragment_root_hash(&a);
let root_b = compute_fragment_root_hash(&b);
assert_eq!(root_a, root_b, "root is order-independent (sorted pairs)");
let mut c = FragmentStore::default();
c.insert(sample_metadata("abc123", Some("node1"), 0));
c.insert(sample_metadata("changed", None, 1));
assert_ne!(
root_a,
compute_fragment_root_hash(&c),
"content change must change the root"
);
}
#[test]
fn test_root_persist_load_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", None, 0));
persist_fragment_root(dir.path(), &store, 7).unwrap();
let state = load_fragment_root(&dir.path().join(".leindex"))
.unwrap()
.expect("root loads");
assert_eq!(state.root_hash, compute_fragment_root_hash(&store));
assert_eq!(state.generation, 7);
assert_eq!(state.fragment_rows, 1);
}
#[test]
fn test_root_load_missing_is_none() {
let dir = tempfile::tempdir().unwrap();
assert!(
load_fragment_root(&dir.path().join(".leindex"))
.unwrap()
.is_none()
);
}
#[test]
fn test_root_schema_mismatch_rejected() {
let dir = tempfile::tempdir().unwrap();
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", None, 0));
persist_fragment_root(dir.path(), &store, 1).unwrap();
let path = dir.path().join(".leindex").join("fragment_root.bin");
let mut bytes = std::fs::read(&path).unwrap();
bytes[0] = 0xff;
std::fs::write(&path, &bytes).unwrap();
assert!(
load_fragment_root(&dir.path().join(".leindex"))
.unwrap()
.is_none(),
"schema-mismatched root must be discarded"
);
}
fn one_fragment_per_file(
path: &std::path::Path,
bytes: &[u8],
owner: Option<&str>,
) -> Vec<FragmentCandidate> {
let text = String::from_utf8_lossy(bytes);
let enriched = format!("// sync-test\n{}", text);
let content_hash = blake3::hash(enriched.as_bytes()).to_hex().to_string();
vec![FragmentCandidate {
content_hash: content_hash.clone(),
enriched_text: enriched,
meta: FragmentMetadata {
content_hash,
owner: owner.map(str::to_string),
file_path: path.display().to_string(),
byte_range: (0, bytes.len()),
line_range: (0, bytes.len().max(1) - 1),
embedding_offset: 0,
},
}]
}
#[test]
fn test_sync_unchanged_file_zero_reembeds() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
std::fs::write(&file, b"fn main() { println!(\"hi\"); }\n").unwrap();
let file_hash = blake3::hash(b"fn main() { println!(\"hi\"); }\n")
.to_hex()
.to_string();
let files = vec![(file.clone(), file_hash)];
let mut store = FragmentStore::default();
let mut embed_calls: usize = 0;
let (summary, _rows) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| {
embed_calls += texts.len();
texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect()
},
false,
&FragmentExtractionIdentity::default(),
)
.unwrap();
assert_eq!(summary.files_changed, 1, "first sync sees the new file");
assert_eq!(summary.embedded, 1);
assert_eq!(summary.generation, 1);
embed_calls = 0;
let (summary2, _rows2) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| {
embed_calls += texts.len();
texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect()
},
false,
&FragmentExtractionIdentity::default(),
)
.unwrap();
assert_eq!(summary2.files_changed, 0, "unchanged file is skipped");
assert_eq!(embed_calls, 0, "no re-embeds for an unchanged file");
assert_eq!(summary2.embedded, 0);
assert_eq!(summary2.generation, 1, "generation does not bump on no-op");
}
#[test]
fn test_sync_single_edit_only_affected_reembedded() {
let dir = tempfile::tempdir().unwrap();
let a = dir.path().join("a.rs");
let b = dir.path().join("b.rs");
std::fs::write(&a, b"pub fn alpha() -> i32 { 1 }\n").unwrap();
std::fs::write(&b, b"pub fn beta() -> i32 { 2 }\n").unwrap();
let files_a = vec![
(
a.clone(),
blake3::hash(b"pub fn alpha() -> i32 { 1 }\n")
.to_hex()
.to_string(),
),
(
b.clone(),
blake3::hash(b"pub fn beta() -> i32 { 2 }\n")
.to_hex()
.to_string(),
),
];
let mut store = FragmentStore::default();
let (summary, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files_a,
&mut |path: &std::path::Path, bytes: &[u8]| one_fragment_per_file(path, bytes, Some("x")),
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&FragmentExtractionIdentity::default(),
)
.unwrap();
assert_eq!(summary.embedded, 2, "both files embedded on first pass");
assert_eq!(store.len(), 2);
let gen_after_first = summary.generation;
std::fs::write(&b, b"pub fn beta() -> i32 { 42 }\n").unwrap();
let files_b = vec![
(
a.clone(),
blake3::hash(b"pub fn alpha() -> i32 { 1 }\n")
.to_hex()
.to_string(),
),
(
b.clone(),
blake3::hash(b"pub fn beta() -> i32 { 42 }\n")
.to_hex()
.to_string(),
),
];
let mut embed_calls: usize = 0;
let (summary2, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files_b,
&mut |path: &std::path::Path, bytes: &[u8]| one_fragment_per_file(path, bytes, Some("x")),
&mut |texts: &[String]| {
embed_calls += texts.len();
texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect()
},
false,
&FragmentExtractionIdentity::default(),
)
.unwrap();
assert_eq!(summary2.files_changed, 1, "only b changed");
assert_eq!(embed_calls, 1, "only the affected fragment is re-embedded");
assert_eq!(summary2.embedded, 1);
assert_eq!(store.len(), 2, "a's row survives; b's row replaced");
assert!(
summary2.generation > gen_after_first,
"generation bumps on edit"
);
}
#[test]
fn test_sync_manifest_roundtrip() {
let dir = tempfile::tempdir().unwrap();
let storage = dir.path().join(".leindex");
let mut manifest = FragmentFileManifest::new();
manifest.generation = 3;
manifest
.file_hashes
.insert("a.rs".to_string(), "abc".to_string());
manifest
.file_content_hashes
.insert("a.rs".to_string(), vec!["h1".to_string()]);
persist_fragment_sync_manifest(&storage, &manifest).unwrap();
let loaded = load_fragment_sync_manifest(&storage)
.unwrap()
.expect("manifest loads");
assert_eq!(loaded.generation, 3);
assert_eq!(
loaded.file_hashes.get("a.rs").map(String::as_str),
Some("abc")
);
assert_eq!(
loaded.file_content_hashes.get("a.rs").map(Vec::as_slice),
Some(&["h1".to_string()][..])
);
}
#[test]
fn test_sync_mid_build_generation_serves_last_complete_root() {
let dir = tempfile::tempdir().unwrap();
let storage = dir.path().join(".leindex");
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", None, 0));
persist_fragment_root(dir.path(), &store, 1).unwrap();
let mut manifest = FragmentFileManifest::new();
manifest.generation = 1;
persist_fragment_sync_manifest(&storage, &manifest).unwrap();
let root = load_fragment_root(&storage).unwrap().unwrap();
assert!(
fragment_layer_generation_is_consistent(&storage, &root),
"matching generations are consistent"
);
let mut mid_build = FragmentFileManifest::new();
mid_build.generation = 2;
persist_fragment_sync_manifest(&storage, &mid_build).unwrap();
let root_after = load_fragment_root(&storage).unwrap().unwrap();
assert!(
!fragment_layer_generation_is_consistent(&storage, &root_after),
"manifest ahead of root ⇒ half-synced tree must not serve"
);
}
#[test]
fn test_sync_root_mismatch_forces_rebuild() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
let v1 = b"fn main() { println!(\"v1\"); }\n";
let v2 = b"fn main() { println!(\"v2\"); }\n";
std::fs::write(&file, v1).unwrap();
let mut store = FragmentStore::default();
let files = |bytes: &[u8]| vec![(file.clone(), blake3::hash(bytes).to_hex().to_string())];
let (summary, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files(v1),
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&FragmentExtractionIdentity::default(),
)
.unwrap();
assert_eq!(summary.generation, 1);
let root_v1 = load_fragment_root(&dir.path().join(".leindex"))
.unwrap()
.expect("root after first sync");
std::fs::write(&file, v2).unwrap();
let (summary2, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files(v2),
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&FragmentExtractionIdentity::default(),
)
.unwrap();
assert_eq!(summary2.embedded, 1, "edited content is re-embedded");
assert_eq!(summary2.generation, 2, "generation bumps on the edit");
let root_v2 = load_fragment_root(&dir.path().join(".leindex"))
.unwrap()
.expect("root after second sync");
assert_ne!(
root_v2.root_hash, root_v1.root_hash,
"content edit must change the persisted root"
);
assert_eq!(
root_v2.root_hash,
compute_fragment_root_hash(&store),
"persisted root always matches the live store"
);
assert_eq!(root_v2.generation, 2);
}
#[test]
fn test_sync_extraction_identity_change_forces_resync() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
let contents = b"fn main() { println!(\"hi\"); }\n";
std::fs::write(&file, contents).unwrap();
let file_hash = blake3::hash(contents).to_hex().to_string();
let files = vec![(file.clone(), file_hash)];
let mut store = FragmentStore::default();
let identity_a = FragmentExtractionIdentity::new("model-a", 12_000, true, true);
let identity_b = FragmentExtractionIdentity::new("model-b", 12_000, true, true);
let (summary, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&identity_a,
)
.unwrap();
assert_eq!(summary.files_changed, 1, "first sync sees the new file");
assert_eq!(summary.embedded, 1);
assert_eq!(summary.generation, 1);
let mut embed_calls: usize = 0;
let (summary_same, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| {
embed_calls += texts.len();
texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect()
},
false,
&identity_a,
)
.unwrap();
assert_eq!(summary_same.files_changed, 0, "unchanged file is skipped");
assert_eq!(embed_calls, 0, "no re-embeds for unchanged file + identity");
let (summary_b, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&identity_b,
)
.unwrap();
assert_eq!(
summary_b.files_changed, 1,
"identity change re-chunks a byte-identical file"
);
assert_eq!(
summary_b.embedded, 1,
"model change re-embeds the stale row under the new model"
);
assert_eq!(summary_b.generation, 2, "generation bumps on the re-sync");
let (summary_b2, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&identity_b,
)
.unwrap();
assert_eq!(
summary_b2.files_changed, 0,
"persisted identity makes the next run incremental again"
);
assert_eq!(summary_b2.embedded, 0);
let identity_c = FragmentExtractionIdentity::new("model-b", 24_000, true, true);
let (summary_c, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&identity_c,
)
.unwrap();
assert_eq!(
summary_c.files_changed, 1,
"knob change invalidates the source-hash skip too"
);
}
#[test]
fn test_sync_partial_failure_does_not_commit_identity() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
let contents = b"fn main() { println!(\"hi\"); }\n";
std::fs::write(&file, contents).unwrap();
let file_hash = blake3::hash(contents).to_hex().to_string();
let files = vec![(file.clone(), file_hash)];
let mut store = FragmentStore::default();
let identity = FragmentExtractionIdentity::new("model-a", 12_000, true, true);
let (summary_fail, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |_texts: &[String]| vec![None],
false,
&identity,
)
.unwrap();
assert_eq!(summary_fail.files_changed, 1, "first pass sees the file");
assert_eq!(summary_fail.embedded, 0, "embeds failed, nothing stored");
assert_eq!(store.len(), 0, "failed embeds insert no rows");
let storage = dir.path().join(".leindex");
let manifest_after_fail = load_fragment_sync_manifest(&storage)
.unwrap()
.expect("manifest persisted");
assert_ne!(
manifest_after_fail.extraction_identity, identity,
"failed pass must not commit the identity"
);
assert!(
manifest_after_fail.file_hashes.is_empty(),
"failed file's hash must stay uncommitted"
);
let (summary_ok, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&identity,
)
.unwrap();
assert_eq!(summary_ok.embedded, 1, "retry embeds the recovered file");
assert_eq!(store.len(), 1);
let manifest_after_ok = load_fragment_sync_manifest(&storage)
.unwrap()
.expect("manifest persisted");
assert_eq!(
manifest_after_ok.extraction_identity, identity,
"fully-successful pass commits the identity"
);
let mut embed_calls: usize = 0;
let (summary_inc, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut |path: &std::path::Path, bytes: &[u8]| {
one_fragment_per_file(path, bytes, Some("main"))
},
&mut |texts: &[String]| {
embed_calls += texts.len();
texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect()
},
false,
&identity,
)
.unwrap();
assert_eq!(summary_inc.files_changed, 0, "committed identity ⇒ skip");
assert_eq!(embed_calls, 0, "no re-embeds after recovery");
}
#[test]
fn test_chunk_code_never_emits_empty_fragments() {
for path in [
Path::new("empty.rs"),
Path::new("empty.py"),
Path::new("empty.js"),
] {
let chunks = chunk_code("", path, 12_000, true);
assert!(
chunks.iter().all(|f| !f.content.is_empty()),
"chunk_code({path:?}) emitted an empty fragment: {chunks:?}"
);
assert!(
chunks.is_empty(),
"chunk_code({path:?}) on empty input must return [], got {chunks:?}"
);
}
}
#[test]
fn test_chunk_naive_zero_max_bytes_no_infinite_loop() {
let code = "line1\nline2\nline3\nline4abcdefghijklmnopqrstuvwxyz";
let path = Path::new("test_file.xyz");
let fragments = chunk_naive(code, path, 0, 1000);
assert_eq!(fragments.len(), 1, "zero byte cap → one fragment, no loop");
assert_eq!(fragments[0].content, code);
assert!(!fragments[0].content.is_empty());
}
#[test]
fn test_chunk_code_zero_max_bytes_returns_whole_source() {
let source = "fn foo() {}\nfn bar() {}";
let fragments = chunk_code(source, Path::new("test.rs"), 0, true);
assert_eq!(
fragments.len(),
1,
"zero byte cap → whole source as one fragment"
);
assert_eq!(fragments[0].content, source);
}
#[test]
fn test_extract_orphans_for_top_level_only_file() {
let mut pdg = ProgramDependenceGraph::new();
let _fs = pdg.add_node(Node::new_file_summary("script.py", "python"));
let code = b"import os\n\nprint(\"hello world\")";
let candidates = extract_file_fragments(&pdg, Path::new("script.py"), code, 12_000, true, true);
assert!(
!candidates.is_empty(),
"top-level-only file must produce Tier-3 orphan fragments"
);
let mut covered = 0usize;
for candidate in &candidates {
assert_eq!(
candidate.meta.owner.as_deref(),
Some("script.py::file_summary"),
"orphans map back to the FileSummary owner (invariant 6)"
);
assert!(!candidate.enriched_text.is_empty());
covered += candidate.meta.byte_range.1 - candidate.meta.byte_range.0;
}
assert_eq!(covered, code.len(), "orphan coverage spans the whole file");
}
#[test]
fn test_store_persist_atomic_no_temp_leftover() {
let dir = tempfile::tempdir().unwrap();
let mut store = FragmentStore::default();
store.insert(sample_metadata("abc123", Some("node1"), 0));
store.persist_to_storage(dir.path()).unwrap();
let leindex = dir.path().join(".leindex");
let store_path = leindex.join("fragment_store.bin");
assert!(store_path.exists(), "store artifact written");
assert!(
!leindex.join("fragment_store.bin.next").exists(),
"atomic write must leave no .next temp sibling"
);
std::fs::write(&store_path, b"\x01\x02").unwrap();
store.persist_to_storage(dir.path()).unwrap();
let loaded = FragmentStore::load_from_storage(dir.path())
.unwrap()
.expect("store loads after heal");
assert_eq!(loaded.len(), 1);
assert!(
!leindex.join("fragment_store.bin.next").exists(),
"healing persist also leaves no temp sibling"
);
}
#[test]
fn test_chunk_semantic_oversized_leaf_bytes_not_dropped() {
let comment = format!("// {}", "x".repeat(500));
let source = format!("fn main() {{\n {comment}\n}}\n");
let chunks = chunk_semantic(&source, Path::new("test.rs"), 64, &ts_language("rs"))
.expect("semantic chunking succeeds");
assert!(!chunks.is_empty(), "chunks produced");
let comment_start = source.find("// x").expect("comment present");
let comment_end = source[comment_start..]
.find('\n')
.map(|off| comment_start + off)
.unwrap_or(source.len());
let mut covered = vec![false; source.len()];
for chunk in &chunks {
covered[chunk.start_byte_index..chunk.end_byte_index].fill(true);
}
for (offset, &covered_byte) in covered[comment_start..comment_end].iter().enumerate() {
assert!(
covered_byte,
"leaf byte {} dropped by the semantic chunker",
comment_start + offset
);
}
}
#[test]
fn test_sync_removed_partial_file_clears_rows() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
std::fs::write(&file, b"fn main() { println!(\"hi\"); }\n").unwrap();
let file_hash = blake3::hash(b"fn main() { println!(\"hi\"); }\n")
.to_hex()
.to_string();
let files = vec![(file.clone(), file_hash)];
let mut store = FragmentStore::default();
let identity = FragmentExtractionIdentity::default();
let mut two_fragments = |path: &std::path::Path, bytes: &[u8]| {
let text = String::from_utf8_lossy(bytes);
(0..2)
.map(|i| {
let enriched = format!("// part{i}\n{text}");
let content_hash = blake3::hash(enriched.as_bytes()).to_hex().to_string();
FragmentCandidate {
content_hash: content_hash.clone(),
enriched_text: enriched,
meta: FragmentMetadata {
content_hash,
owner: Some("main".to_string()),
file_path: path.display().to_string(),
byte_range: (0, bytes.len()),
line_range: (0, bytes.len().max(1) - 1),
embedding_offset: 0,
},
}
})
.collect()
};
let (summary, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut two_fragments,
&mut |texts: &[String]| {
texts
.iter()
.enumerate()
.map(|(i, _)| {
if i == 0 {
Some(vec![1.0, 0.0, 0.0])
} else {
None
}
})
.collect()
},
false,
&identity,
)
.unwrap();
assert_eq!(summary.embedded, 1, "one of two fragments embedded");
assert_eq!(store.len(), 1, "one row inserted");
let storage = dir.path().join(".leindex");
let m1 = load_fragment_sync_manifest(&storage)
.unwrap()
.expect("manifest persisted");
assert!(
m1.file_content_hashes
.contains_key(&file.display().to_string()),
"partial file recorded in file_content_hashes"
);
assert!(
!m1.file_hashes.contains_key(&file.display().to_string()),
"partial file NOT committed to file_hashes"
);
let (_, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&[],
&mut two_fragments,
&mut |_texts: &[String]| vec![None],
false,
&identity,
)
.unwrap();
assert_eq!(
store.len(),
0,
"deleted partial file's rows must be cleared (wave-5 item 3)"
);
let m2 = load_fragment_sync_manifest(&storage)
.unwrap()
.expect("manifest persisted");
assert!(
!m2.file_content_hashes
.contains_key(&file.display().to_string()),
"stale file_content_hashes entry removed"
);
}
#[test]
fn test_sync_read_failure_drops_stale_rows() {
let dir = tempfile::tempdir().unwrap();
let file = dir.path().join("main.rs");
let content_a = b"fn main() { println!(\"hi\"); }\n";
std::fs::write(&file, content_a).unwrap();
let file_hash_a = blake3::hash(content_a).to_hex().to_string();
let files = vec![(file.clone(), file_hash_a.clone())];
let mut store = FragmentStore::default();
let identity = FragmentExtractionIdentity::default();
let mut one_fragment = |path: &std::path::Path, bytes: &[u8]| {
let text = String::from_utf8_lossy(bytes);
let enriched = format!("// frag\n{text}");
let content_hash = blake3::hash(enriched.as_bytes()).to_hex().to_string();
vec![FragmentCandidate {
content_hash: content_hash.clone(),
enriched_text: enriched,
meta: FragmentMetadata {
content_hash,
owner: Some("main".to_string()),
file_path: path.display().to_string(),
byte_range: (0, bytes.len()),
line_range: (0, bytes.len().max(1) - 1),
embedding_offset: 0,
},
}]
};
let (summary, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files,
&mut one_fragment,
&mut |texts: &[String]| texts.iter().map(|_| Some(vec![1.0, 0.0, 0.0])).collect(),
false,
&identity,
)
.unwrap();
assert_eq!(summary.embedded, 1, "pass 1 embeds one fragment");
assert_eq!(store.len(), 1, "pass 1 commits one row");
let content_b = b"fn main() { println!(\"changed\"); }\n";
let file_hash_b = blake3::hash(content_b).to_hex().to_string();
assert_ne!(file_hash_b, file_hash_a, "hash changed on disk");
std::fs::remove_file(&file).unwrap();
let files_b = vec![(file.clone(), file_hash_b)];
let (_, _) = incremental_sync_fragments(
dir.path(),
&mut store,
&files_b,
&mut one_fragment,
&mut |_texts: &[String]| vec![None],
false,
&identity,
)
.unwrap();
assert_eq!(
store.len(),
0,
"unreadable changed file's stale rows must be dropped (wave-6 item 2)"
);
let storage = dir.path().join(".leindex");
let m2 = load_fragment_sync_manifest(&storage)
.unwrap()
.expect("manifest persisted");
assert!(
!m2.file_hashes.contains_key(&file.display().to_string()),
"stale file_hashes entry removed on read failure"
);
assert!(
!m2.file_content_hashes
.contains_key(&file.display().to_string()),
"stale file_content_hashes entry removed on read failure"
);
}