use probe_code::language::tree_cache;
use std::sync::Mutex;
use std::thread;
use std::time::Duration;
use tree_sitter::Parser;
lazy_static::lazy_static! {
static ref TEST_MUTEX: Mutex<()> = Mutex::new(());
}
#[test]
fn test_tree_cache_basic() {
let unique_file_name = "test_file_basic_unique.rs";
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tree_cache::clear_tree_cache();
tree_cache::reset_cache_hit_counter();
assert_eq!(
tree_cache::get_cache_size(),
0,
"Cache should be empty after clearing"
);
assert!(
!tree_cache::is_in_cache(unique_file_name),
"Test file should not be in cache initially"
);
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_rust::LANGUAGE.into())
.unwrap();
let content = r#"
fn test_function() {
println!("Hello, world!");
}
"#;
let tree1 = tree_cache::get_or_parse_tree(unique_file_name, content, &mut parser).unwrap();
assert_eq!(tree_cache::get_cache_hit_count(), 0);
assert!(
tree_cache::is_in_cache(unique_file_name),
"Test file should be in cache after first parse"
);
let tree2 = tree_cache::get_or_parse_tree(unique_file_name, content, &mut parser).unwrap();
assert_eq!(tree_cache::get_cache_hit_count(), 1);
assert_eq!(tree1.root_node().kind(), tree2.root_node().kind());
assert_eq!(
tree1.root_node().start_position(),
tree2.root_node().start_position()
);
assert_eq!(
tree1.root_node().end_position(),
tree2.root_node().end_position()
);
assert!(
tree_cache::is_in_cache(unique_file_name),
"Test file should still be in cache after second parse"
);
let _tree3 = tree_cache::get_or_parse_tree(unique_file_name, content, &mut parser).unwrap();
assert_eq!(tree_cache::get_cache_hit_count(), 2);
assert!(tree_cache::is_in_cache(unique_file_name));
tree_cache::invalidate_cache_entry(unique_file_name);
assert!(
!tree_cache::is_in_cache(unique_file_name),
"Test file should be removed after cleanup"
);
}
#[test]
fn test_tree_cache_invalidation() {
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tree_cache::clear_tree_cache();
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_rust::LANGUAGE.into())
.unwrap();
let content1 = r#"
fn test_function() {
println!("Hello, world!");
}
"#;
let content2 = r#"
fn test_function() {
println!("Hello, modified world!");
}
"#;
let tree1 = tree_cache::get_or_parse_tree("test_file2.rs", content1, &mut parser).unwrap();
assert!(tree_cache::get_cache_size() >= 1);
assert!(tree_cache::is_in_cache("test_file2.rs"));
let tree2 = tree_cache::get_or_parse_tree("test_file2.rs", content2, &mut parser).unwrap();
assert_eq!(tree1.root_node().kind(), tree2.root_node().kind()); assert_eq!(
tree1.root_node().start_position(),
tree2.root_node().start_position()
); assert_eq!(
tree1.root_node().end_position().row,
tree2.root_node().end_position().row
);
assert_ne!(tree1.root_node().end_byte(), tree2.root_node().end_byte());
assert_eq!(tree_cache::get_cache_size(), 1);
assert!(tree_cache::is_in_cache("test_file2.rs"));
}
#[test]
fn test_tree_cache_clear() {
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tree_cache::clear_tree_cache();
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_rust::LANGUAGE.into())
.unwrap();
let content = r#"
fn another_function() {
println!("Testing cache clear");
}
"#;
tree_cache::get_or_parse_tree("test_file3.rs", content, &mut parser).unwrap();
tree_cache::get_or_parse_tree("test_file3.rs", content, &mut parser).unwrap();
assert!(tree_cache::get_cache_size() > 0);
tree_cache::clear_tree_cache();
assert_eq!(tree_cache::get_cache_size(), 0);
}
#[test]
fn test_tree_cache_invalidate_entry() {
let unique_file_name = "test_file_invalidate_unique.rs";
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tree_cache::clear_tree_cache();
assert_eq!(
tree_cache::get_cache_size(),
0,
"Cache should be empty after clearing"
);
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_rust::LANGUAGE.into())
.unwrap();
let content = r#"
fn yet_another_function() {
println!("Testing cache invalidation");
}
"#;
tree_cache::get_or_parse_tree(unique_file_name, content, &mut parser).unwrap();
assert!(
tree_cache::is_in_cache(unique_file_name),
"Test file should be in cache after parsing"
);
let cache_size_after_parse = tree_cache::get_cache_size();
tree_cache::invalidate_cache_entry(unique_file_name);
assert!(
!tree_cache::is_in_cache(unique_file_name),
"Test file should be removed after invalidation"
);
assert_eq!(
tree_cache::get_cache_size(),
cache_size_after_parse - 1,
"Cache size should decrease by 1 after invalidation"
);
}
#[test]
fn test_tree_cache_concurrent_access() {
let _guard = TEST_MUTEX
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner());
tree_cache::clear_tree_cache();
let handles: Vec<_> = (0..5)
.map(|i| {
thread::spawn(move || {
let mut parser = Parser::new();
parser
.set_language(&tree_sitter_rust::LANGUAGE.into())
.unwrap();
let content = format!(
r#"
fn thread_function_{i}() {{
println!("Hello from thread {i}");
}}
"#
);
for j in 0..10 {
let file_name = format!("thread_{i}_iteration_{j}.rs");
tree_cache::get_or_parse_tree(&file_name, &content, &mut parser).unwrap();
thread::sleep(Duration::from_millis(1));
}
})
})
.collect();
for handle in handles {
handle.join().unwrap();
}
assert!(tree_cache::get_cache_size() > 0);
tree_cache::clear_tree_cache();
}