use std::collections::{HashMap, HashSet};
use std::fs::File;
use std::io::Write;
use tempfile::TempDir;
use probe_code::search::elastic_query;
use probe_code::search::file_processing::process_file_with_results;
use probe_code::search::query::QueryPlan;
pub fn create_test_file(dir: &TempDir, filename: &str, content: &str) -> std::path::PathBuf {
let file_path = dir.path().join(filename);
let mut file = File::create(&file_path).expect("Failed to create test file");
file.write_all(content.as_bytes())
.expect("Failed to write test content");
file_path
}
pub fn create_test_query_plan(terms: &[&str]) -> QueryPlan {
let mut term_indices = HashMap::new();
for (i, &term) in terms.iter().enumerate() {
term_indices.insert(term.to_string(), i);
}
let ast = elastic_query::Expr::Term {
keywords: terms.iter().map(|&s| s.to_string()).collect(),
field: None,
required: false,
excluded: false,
exact: false,
};
QueryPlan {
ast,
term_indices,
excluded_terms: HashSet::new(),
exact: false,
}
}
pub fn preprocess_query_for_tests(query: &str, _exact: bool) -> Vec<(String, String)> {
query
.split_whitespace()
.map(|term| (term.to_string(), term.to_string()))
.collect()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_process_file_with_results_single_line() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let content = "line 1\nline 2\nline 3\nline 4\nline 5\n";
let file_path = create_test_file(&temp_dir, "test.txt", content);
let mut line_numbers = HashSet::new();
line_numbers.insert(3);
let mut term_matches = HashMap::new();
let mut matches_for_term = HashSet::new();
matches_for_term.insert(3); term_matches.insert(0, matches_for_term);
let query_plan = create_test_query_plan(&["line"]);
let params = crate::search::file_processing::FileProcessingParams {
path: &file_path,
line_numbers: &line_numbers,
allow_tests: false,
term_matches: &term_matches,
num_queries: 1,
filename_matched_queries: HashSet::new(),
queries_terms: &[vec![("line".to_string(), "line".to_string())]],
preprocessed_queries: None,
query_plan: &query_plan,
no_merge: false,
};
let (results, _) =
process_file_with_results(¶ms).expect("Failed to process file with results");
assert!(!results.is_empty());
let result = &results[0];
assert_eq!(result.file, file_path.to_string_lossy());
assert!(result.lines.0 <= 3); assert!(result.lines.1 >= 3); }
#[test]
fn test_process_file_with_results_multiple_lines() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let content = "line 1\nline 2\nline 3\nline 4\nline 5\n";
let file_path = create_test_file(&temp_dir, "test.txt", content);
let mut line_numbers = HashSet::new();
line_numbers.insert(1);
line_numbers.insert(2);
line_numbers.insert(3);
line_numbers.insert(4);
let mut term_matches = HashMap::new();
let mut matches_for_term = HashSet::new();
matches_for_term.insert(1);
matches_for_term.insert(2);
matches_for_term.insert(3);
matches_for_term.insert(4);
term_matches.insert(0, matches_for_term);
let query_plan = create_test_query_plan(&["line"]);
let params = crate::search::file_processing::FileProcessingParams {
path: &file_path,
line_numbers: &line_numbers,
allow_tests: false,
term_matches: &term_matches,
num_queries: 1,
filename_matched_queries: HashSet::new(),
queries_terms: &[vec![("line".to_string(), "line".to_string())]],
preprocessed_queries: None,
query_plan: &query_plan,
no_merge: false,
};
let (results, _) =
process_file_with_results(¶ms).expect("Failed to process file with results");
assert!(!results.is_empty());
}
#[test]
fn test_process_file_with_results_high_coverage() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let content = "line 1\nline 2\nline 3\nline 4\nline 5\n";
let file_path = create_test_file(&temp_dir, "test.txt", content);
let mut line_numbers = HashSet::new();
line_numbers.insert(1);
line_numbers.insert(2);
line_numbers.insert(3);
line_numbers.insert(4);
let mut term_matches = HashMap::new();
let mut matches_for_term = HashSet::new();
matches_for_term.insert(1);
matches_for_term.insert(2);
matches_for_term.insert(3);
matches_for_term.insert(4);
term_matches.insert(0, matches_for_term);
let query_plan = create_test_query_plan(&["line"]);
let params = crate::search::file_processing::FileProcessingParams {
path: &file_path,
line_numbers: &line_numbers,
allow_tests: false,
term_matches: &term_matches,
num_queries: 1,
filename_matched_queries: HashSet::new(),
queries_terms: &[vec![("line".to_string(), "line".to_string())]],
preprocessed_queries: None,
query_plan: &query_plan,
no_merge: false,
};
let (results, _) =
process_file_with_results(¶ms).expect("Failed to process file with results");
assert!(!results.is_empty(), "Should have at least one result");
for result in &results {
assert_eq!(result.file, file_path.to_string_lossy());
}
for result in &results {
assert_eq!(result.file, file_path.to_string_lossy());
}
}
#[test]
fn test_blocks_remain_separate() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let content = r#"
function test1() {
console.log('Test 1');
}
function test2() {
console.log('Test 2');
}
function test3() {
console.log('Test 3');
}
"#;
let file_path = create_test_file(&temp_dir, "test.js", content);
let mut line_numbers = HashSet::new();
line_numbers.insert(2); line_numbers.insert(6); line_numbers.insert(10);
let mut term_matches = HashMap::new();
let mut matches_for_term1 = HashSet::new();
matches_for_term1.insert(2); term_matches.insert(0, matches_for_term1);
let mut matches_for_term2 = HashSet::new();
matches_for_term2.insert(6); term_matches.insert(1, matches_for_term2);
let mut matches_for_term3 = HashSet::new();
matches_for_term3.insert(10); term_matches.insert(2, matches_for_term3);
let query_plan = create_test_query_plan(&["test1", "test2", "test3"]);
let params = crate::search::file_processing::FileProcessingParams {
path: &file_path,
line_numbers: &line_numbers,
allow_tests: true, term_matches: &term_matches,
num_queries: 3, filename_matched_queries: HashSet::new(), queries_terms: &[vec![
("test1".to_string(), "test1".to_string()),
("test2".to_string(), "test2".to_string()),
("test3".to_string(), "test3".to_string()),
]],
preprocessed_queries: None, query_plan: &query_plan,
no_merge: false,
};
let (results, _) =
process_file_with_results(¶ms).expect("Failed to process file with results");
for result in &results {
if let Some(parent_id) = &result.parent_file_id {
assert!(parent_id.contains(&*file_path.to_string_lossy()));
}
assert!(result.block_id.is_some());
}
for result in &results {
assert_eq!(result.file, file_path.to_string_lossy());
}
let mut seen_block_ids = HashSet::new();
for result in &results {
if let Some(block_id) = result.block_id {
assert!(!seen_block_ids.contains(&block_id));
seen_block_ids.insert(block_id);
}
}
}
#[test]
fn test_block_unique_terms_with_stemming() {
use std::collections::HashMap;
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let content = r#"
function processData() {
// This function handles data processing
const data = fetchData();
return processResults(data);
}
function fetchData() {
// Fetching the data from API
return api.fetch('/data');
}
function processResults(results) {
// Processing the results
return results.map(r => r.processed);
}
"#;
let file_path = create_test_file(&temp_dir, "data_processing.js", content);
let query = "processing data";
let term_pairs = preprocess_query_for_tests(query, false);
let preprocessed_queries = vec![term_pairs.iter().map(|(_, s)| s.clone()).collect()];
let mut line_numbers = HashSet::new();
line_numbers.insert(3); line_numbers.insert(4);
let mut term_matches = HashMap::new();
let mut matches_for_term1 = HashSet::new();
matches_for_term1.insert(3);
term_matches.insert(0, matches_for_term1);
let mut matches_for_term2 = HashSet::new();
matches_for_term2.insert(4);
term_matches.insert(1, matches_for_term2);
let query_plan = create_test_query_plan(&["process", "data"]);
let params = crate::search::file_processing::FileProcessingParams {
path: &file_path,
line_numbers: &line_numbers,
allow_tests: true,
term_matches: &term_matches,
num_queries: 2, filename_matched_queries: HashSet::new(),
queries_terms: &[term_pairs.clone()],
preprocessed_queries: Some(&preprocessed_queries),
query_plan: &query_plan,
no_merge: false,
};
let (results, _) =
process_file_with_results(¶ms).expect("Failed to process file with results");
assert!(!results.is_empty());
for result in &results {
if let Some(block_unique_terms) = result.block_unique_terms {
assert!(
block_unique_terms >= 1,
"Expected at least 1 unique term, got {block_unique_terms}"
);
assert!(result.block_total_matches.is_some());
}
}
}
}
#[test]
fn test_long_lines_are_ignored() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
let normal_line = "This is a normal line with reasonable length.";
let long_line = "x".repeat(600);
let content = format!("{normal_line}\n{long_line}\n{normal_line}");
let file_path = create_test_file(&temp_dir, "mixed_length.txt", &content);
let mut line_numbers = HashSet::new();
line_numbers.insert(1); line_numbers.insert(2); line_numbers.insert(3);
let mut term_matches = HashMap::new();
let mut matches_for_term = HashSet::new();
matches_for_term.insert(1);
matches_for_term.insert(2); matches_for_term.insert(3);
term_matches.insert(0, matches_for_term);
let query_plan = create_test_query_plan(&["normal"]);
let params = crate::search::file_processing::FileProcessingParams {
path: &file_path,
line_numbers: &line_numbers,
allow_tests: true,
term_matches: &term_matches,
num_queries: 1,
filename_matched_queries: HashSet::new(),
queries_terms: &[vec![("normal".to_string(), "normal".to_string())]],
preprocessed_queries: None,
query_plan: &query_plan,
no_merge: false,
};
let (results, _) =
process_file_with_results(¶ms).expect("Failed to process file with results");
assert!(!results.is_empty());
for result in &results {
let result_content = &result.code;
assert!(
!result_content.contains(&long_line),
"Result should not contain the long line"
);
assert!(
result_content.contains(normal_line),
"Result should contain the normal lines"
);
}
}