use std::fs;
use tempfile::TempDir;
#[cfg(test)]
mod tests {
use super::*;
use std::process::Command;
fn create_test_files() -> TempDir {
let temp_dir = TempDir::new().unwrap();
let test_dir = temp_dir.path();
fs::write(
test_dir.join("test.rs"),
"fn main() {\n println!(\"Hello, world!\");\n}",
)
.unwrap();
fs::write(
test_dir.join("test.py"),
"def hello():\n print('Hello, world!')\n",
)
.unwrap();
fs::write(
test_dir.join("test.js"),
"function hello() {\n console.log('Hello, world!');\n}\n",
)
.unwrap();
fs::write(
test_dir.join("README.md"),
"# Test Project\nThis is a test project.\n",
)
.unwrap();
fs::create_dir(test_dir.join("src")).unwrap();
fs::write(
test_dir.join("src/lib.rs"),
"pub fn add(a: i32, b: i32) -> i32 {\n a + b\n}\n",
)
.unwrap();
temp_dir
}
#[test]
fn test_search_basic() {
let temp_dir = create_test_files();
let binary_path = std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch");
let output = if binary_path.exists() {
Command::new(binary_path)
.args([
"search",
"Hello",
temp_dir.path().to_str().unwrap(),
"--no-auto-exclude",
])
.output()
.unwrap()
} else {
Command::new("cargo")
.args([
"run",
"--",
"search",
"Hello",
temp_dir.path().to_str().unwrap(),
"--no-auto-exclude",
])
.output()
.unwrap()
};
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Hello, world!"));
}
#[test]
fn test_search_with_extensions() {
let temp_dir = create_test_files();
let binary_path = std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch");
let output = if binary_path.exists() {
Command::new(binary_path)
.args([
"search",
"hello",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs,py",
"--ignore-case",
"--no-auto-exclude",
])
.output()
.unwrap()
} else {
Command::new("cargo")
.args([
"run",
"--",
"search",
"hello",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs,py",
"--ignore-case",
"--no-auto-exclude",
])
.output()
.unwrap()
};
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains(".rs"));
assert!(stdout.contains(".py"));
assert!(!stdout.contains(".js"));
}
#[test]
fn test_search_case_insensitive() {
let temp_dir = create_test_files();
let binary_path = std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch");
let output = if binary_path.exists() {
Command::new(binary_path)
.args([
"search",
"hello",
temp_dir.path().to_str().unwrap(),
"--ignore-case",
"--no-auto-exclude",
])
.output()
.unwrap()
} else {
Command::new("cargo")
.args([
"run",
"--",
"search",
"hello",
temp_dir.path().to_str().unwrap(),
"--ignore-case",
"--no-auto-exclude",
])
.output()
.unwrap()
};
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("Hello, world!"));
}
#[test]
fn test_search_regex() {
let temp_dir = create_test_files();
let binary_path = std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch");
let output = if binary_path.exists() {
Command::new(binary_path)
.args([
"search",
r"fn\s+\w+",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
"--no-auto-exclude",
])
.output()
.unwrap()
} else {
Command::new("cargo")
.args([
"run",
"--",
"search",
r"fn\s+\w+",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
"--no-auto-exclude",
])
.output()
.unwrap()
};
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("fn main"));
assert!(stdout.contains("fn add"));
}
#[test]
fn test_list_files() {
let temp_dir = create_test_files();
let binary_path = std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch");
let output = if binary_path.exists() {
Command::new(binary_path)
.args([
"files",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
])
.output()
.unwrap()
} else {
Command::new("cargo")
.args([
"run",
"--",
"files",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
])
.output()
.unwrap()
};
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains(".rs"));
assert!(stdout.contains("src"));
}
#[test]
fn test_search_json_output() {
let temp_dir = create_test_files();
let binary_path = std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch");
let output = if binary_path.exists() {
Command::new(binary_path)
.args([
"search",
"Hello",
temp_dir.path().to_str().unwrap(),
"--format",
"json",
"--no-auto-exclude",
])
.output()
.unwrap()
} else {
Command::new("cargo")
.args([
"run",
"--",
"search",
"Hello",
temp_dir.path().to_str().unwrap(),
"--format",
"json",
"--no-auto-exclude",
])
.output()
.unwrap()
};
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("\"file\""));
assert!(stdout.contains("\"line_number\""));
assert!(stdout.contains("\"content\""));
}
fn get_binary_path() -> std::path::PathBuf {
std::env::current_dir()
.unwrap()
.join("target")
.join("debug")
.join("codesearch")
}
fn run_command(args: &[&str]) -> std::process::Output {
let binary_path = get_binary_path();
if binary_path.exists() {
Command::new(binary_path).args(args).output().unwrap()
} else {
let mut cargo_args = vec!["run", "--"];
cargo_args.extend(args);
Command::new("cargo").args(cargo_args).output().unwrap()
}
}
fn create_complex_test_files() -> TempDir {
let temp_dir = TempDir::new().unwrap();
let test_dir = temp_dir.path();
fs::write(
test_dir.join("complex.rs"),
r#"pub struct UserManager {
users: Vec<User>,
}
impl UserManager {
pub fn new() -> Self {
UserManager { users: Vec::new() }
}
pub fn add_user(&mut self, user: User) {
self.users.push(user);
}
pub fn get_user_by_id(&self, id: u64) -> Option<&User> {
self.users.iter().find(|u| u.id == id)
}
}
pub struct User {
pub id: u64,
pub name: String,
pub email: String,
}
fn main() {
let mut manager = UserManager::new();
let user = User {
id: 1,
name: "Alice".to_string(),
email: "alice@example.com".to_string(),
};
manager.add_user(user);
println!("User added successfully");
}
"#,
)
.unwrap();
fs::write(
test_dir.join("complex.py"),
r#"class UserManager:
def __init__(self):
self.users = []
def add_user(self, user):
self.users.append(user)
def get_user_by_id(self, user_id):
for user in self.users:
if user.id == user_id:
return user
return None
class User:
def __init__(self, user_id, name, email):
self.id = user_id
self.name = name
self.email = email
# TODO: Add error handling
# FIXME: Implement caching
def main():
manager = UserManager()
user = User(1, "Alice", "alice@example.com")
manager.add_user(user)
print("User added successfully")
"#,
)
.unwrap();
fs::write(
test_dir.join("complex.js"),
r#"class UserManager {
constructor() {
this.users = [];
}
addUser(user) {
this.users.push(user);
}
getUserById(id) {
return this.users.find(u => u.id === id);
}
}
class User {
constructor(id, name, email) {
this.id = id;
this.name = name;
this.email = email;
}
}
// TODO: Add validation
// FIXME: Handle edge cases
function main() {
const manager = new UserManager();
const user = new User(1, "Alice", "alice@example.com");
manager.addUser(user);
console.log("User added successfully");
}
"#,
)
.unwrap();
fs::write(test_dir.join("empty.rs"), "").unwrap();
fs::write(
test_dir.join("special.rs"),
r#"// File with special characters: émojis 🚀, unicode 䏿–‡, and symbols @#$%
fn test_special() {
let s = "Hello, 世界! 🎉";
println!("{}", s);
}
"#,
)
.unwrap();
fs::create_dir_all(test_dir.join("src/models")).unwrap();
fs::write(
test_dir.join("src/models/user.rs"),
r#"pub struct User {
pub id: u64,
pub name: String,
}
"#,
)
.unwrap();
fs::write(
test_dir.join("many_matches.rs"),
"fn test1() {}\nfn test2() {}\nfn test3() {}\nfn test4() {}\nfn test5() {}\nfn test6() {}\nfn test7() {}\nfn test8() {}\nfn test9() {}\nfn test10() {}\nfn test11() {}\nfn test12() {}\n",
).unwrap();
temp_dir
}
#[test]
fn test_fuzzy_search() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"usrmngr",
temp_dir.path().to_str().unwrap(),
"--fuzzy",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("UserManager") || stdout.contains("user_manager"));
}
#[test]
fn test_ranking() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"user",
temp_dir.path().to_str().unwrap(),
"--rank",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.is_empty());
}
#[test]
fn test_max_results_limit() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
r"fn\s+test\d+",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
"--max-results",
"5",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
let lines: Vec<&str> = stdout.lines().collect();
let mut line_numbers = std::collections::HashSet::new();
for line in lines {
if let Some(colon_pos) = line.find(':') {
if let Some(space_pos) = line[..colon_pos].rfind(' ') {
if let Ok(num) = line[space_pos + 1..colon_pos].parse::<usize>() {
line_numbers.insert(num);
}
}
}
}
assert!(!stdout.is_empty(), "Should have some results");
}
#[test]
fn test_exclude_directories() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"User",
temp_dir.path().to_str().unwrap(),
"--exclude",
"src",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.contains("src/models"));
}
#[test]
fn test_stats_output() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"User",
temp_dir.path().to_str().unwrap(),
"--stats",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(
stdout.contains("matches") || stdout.contains("files") || stdout.contains("Statistics")
);
}
#[test]
fn test_complex_regex_pattern() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
r"pub\s+(struct|fn|enum)\s+\w+",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("pub struct") || stdout.contains("pub fn"));
}
#[test]
fn test_multiple_extensions() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"class",
temp_dir.path().to_str().unwrap(),
"--extensions",
"py,js",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains(".py") || stdout.contains(".js"));
}
#[test]
fn test_special_characters() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"世界",
temp_dir.path().to_str().unwrap(),
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("世界") || stdout.contains("special.rs"));
}
#[test]
fn test_empty_file_handling() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"anything",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
}
#[test]
fn test_todo_fixme_comments() {
let temp_dir = create_complex_test_files();
let output_todo = run_command(&[
"search",
"TODO",
temp_dir.path().to_str().unwrap(),
"--ignore-case",
"--no-auto-exclude",
]);
assert!(
output_todo.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output_todo.stderr)
);
let stdout_todo = String::from_utf8(output_todo.stdout).unwrap();
let output_fixme = run_command(&[
"search",
"FIXME",
temp_dir.path().to_str().unwrap(),
"--ignore-case",
"--no-auto-exclude",
]);
assert!(
output_fixme.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output_fixme.stderr)
);
let stdout_fixme = String::from_utf8(output_fixme.stdout).unwrap();
let has_todo =
stdout_todo.to_uppercase().contains("TODO") && !stdout_todo.contains("No matches");
let has_fixme =
stdout_fixme.to_uppercase().contains("FIXME") && !stdout_fixme.contains("No matches");
assert!(
has_todo || has_fixme,
"Should find TODO or FIXME comments. TODO output: {stdout_todo}, FIXME output: {stdout_fixme}"
);
}
#[test]
fn test_simple_search_without_subcommand() {
let temp_dir = create_test_files();
let binary_path = get_binary_path();
let output = if binary_path.exists() {
Command::new(binary_path)
.args([temp_dir.path().to_str().unwrap(), "Hello"])
.current_dir(temp_dir.path())
.output()
.unwrap()
} else {
return;
};
let _ = output;
}
#[test]
fn test_analyze_command() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"analyze",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs,py,js",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.is_empty());
}
#[test]
fn test_files_command_with_extensions() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"files",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs,py,js",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains(".rs") || stdout.contains(".py") || stdout.contains(".js"));
}
#[test]
fn test_fuzzy_threshold() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"usrmngr",
temp_dir.path().to_str().unwrap(),
"--fuzzy",
"--fuzzy-threshold",
"0.5",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.is_empty());
}
#[test]
fn test_case_sensitive_search() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"User",
temp_dir.path().to_str().unwrap(),
"--no-auto-exclude", ]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("User"));
}
#[test]
fn test_nested_directory_search() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"User",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.contains("User") || stdout.contains("user"));
}
#[test]
fn test_json_format_structure() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"User",
temp_dir.path().to_str().unwrap(),
"--format",
"json",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(stdout.starts_with("[") || stdout.starts_with("{"));
assert!(stdout.contains("\"file\""));
assert!(stdout.contains("\"line_number\""));
assert!(stdout.contains("\"content\""));
let json_result: Result<serde_json::Value, _> = serde_json::from_str(&stdout);
assert!(json_result.is_ok(), "Invalid JSON output: {stdout}");
}
#[test]
fn test_no_line_numbers_flag() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
"User",
temp_dir.path().to_str().unwrap(),
"--no-line-numbers",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.is_empty());
}
#[test]
fn test_multiple_patterns_in_single_file() {
let temp_dir = create_complex_test_files();
let output = run_command(&[
"search",
r"(struct|class|function)\s+\w+",
temp_dir.path().to_str().unwrap(),
"--extensions",
"rs,py,js",
"--no-auto-exclude",
]);
assert!(
output.status.success(),
"Command failed: {}",
String::from_utf8_lossy(&output.stderr)
);
let stdout = String::from_utf8(output.stdout).unwrap();
assert!(!stdout.is_empty());
}
}