use std::io::Read;
use std::path::PathBuf;
use std::process::Command;
fn get_test_sqlite_db() -> PathBuf {
let existing_db = PathBuf::from(".magellan/llmgrep.db");
if existing_db.exists() {
if let Ok(mut file) = std::fs::File::open(&existing_db) {
let mut header = [0u8; 16];
if file.read_exact(&mut header).is_ok() {
let header_str = std::str::from_utf8(&header).unwrap_or("");
if header_str.starts_with("SQLite format 3") {
return existing_db;
}
}
}
}
let temp_file =
std::env::temp_dir().join(format!("llmgrep_test_sqlite_{}.db", std::process::id()));
let _ = std::fs::remove_file(&temp_file);
if let Ok(conn) = rusqlite::Connection::open(&temp_file) {
let _ = conn.execute(
"CREATE TABLE IF NOT EXISTS graph_entities (
id INTEGER PRIMARY KEY,
kind TEXT NOT NULL,
name TEXT NOT NULL,
file_path TEXT,
data TEXT NOT NULL
)",
[],
);
let _ = conn.execute(
"CREATE TABLE IF NOT EXISTS graph_edges (
id INTEGER PRIMARY KEY,
from_id INTEGER NOT NULL,
to_id INTEGER NOT NULL,
edge_type TEXT NOT NULL
)",
[],
);
let _ = conn.execute(
"CREATE TABLE IF NOT EXISTS ast_nodes (
id INTEGER PRIMARY KEY,
file_id INTEGER NOT NULL,
kind TEXT NOT NULL,
byte_start INTEGER NOT NULL,
byte_end INTEGER NOT NULL,
line_number INTEGER,
parent_id INTEGER,
data TEXT
)",
[],
);
let _ = conn.execute(
"CREATE TABLE IF NOT EXISTS symbol_metrics (
symbol_id INTEGER PRIMARY KEY,
fan_in INTEGER DEFAULT 0,
fan_out INTEGER DEFAULT 0,
cyclomatic_complexity INTEGER DEFAULT 0
)",
[],
);
let _ = conn.execute(
"INSERT INTO symbol_metrics (symbol_id, fan_in, fan_out, cyclomatic_complexity) VALUES (2, 0, 0, 1)",
[],
);
let _ = conn.execute(
"CREATE TABLE IF NOT EXISTS code_chunks (
symbol_name TEXT,
file_path TEXT,
byte_start INTEGER,
byte_end INTEGER,
snippet TEXT
)",
[],
);
let _ = conn.execute(
"INSERT INTO graph_entities (id, kind, name, file_path, data) VALUES (1, 'File', 'test.rs', 'test.rs', '{\"path\":\"test.rs\"}')",
[],
);
let _ = conn.execute(
"INSERT INTO graph_entities (id, kind, name, file_path, data) VALUES (2, 'Symbol', 'test', 'test.rs', '{\"name\":\"test\",\"fqn\":\"test::function\",\"display_fqn\":\"test::function\",\"canonical_fqn\":\"test::function\",\"byte_start\":0,\"byte_end\":10,\"line_start\":1,\"line_end\":2,\"start_line\":1,\"start_col\":0,\"language\":\"Rust\",\"symbol_id\":\"2\"}')",
[],
);
let _ = conn.execute(
"INSERT INTO graph_edges (from_id, to_id, edge_type) VALUES (1, 2, 'DEFINES')",
[],
);
}
temp_file
}
fn llmgrep_binary() -> Option<PathBuf> {
let release_path = PathBuf::from("./target/release/llmgrep");
if release_path.exists() {
return Some(release_path);
}
let debug_path = PathBuf::from("./target/debug/llmgrep");
if debug_path.exists() {
return Some(debug_path);
}
None
}
#[test]
fn test_search_with_sqlite_backend() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"search",
"--query",
"main",
"--limit",
"5",
])
.output()
.expect("Failed to execute llmgrep");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
if stderr.contains("No symbols found")
|| stderr.contains("total_count")
|| stdout.contains("total_count")
{
return;
}
panic!(
"llmgrep search failed: {}\nstdout: {}\nstderr: {}",
output.status, stdout, stderr
);
}
assert!(
!stdout.trim().is_empty() || !stderr.trim().is_empty(),
"Expected output from search command"
);
}
#[test]
fn test_ast_with_sqlite_backend() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"ast",
"--file",
"src/main.rs",
"--limit",
"10",
])
.output()
.expect("Failed to execute llmgrep");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
if stderr.contains("No AST nodes found")
|| stderr.contains("File not indexed")
|| stderr.contains("connection error")
|| stderr.contains("Invalid magic number")
{
return;
}
panic!(
"llmgrep ast failed: {}\nstdout: {}\nstderr: {}",
output.status, stdout, stderr
);
}
assert!(
stdout.contains("{") || stderr.contains("{"),
"Expected JSON output from ast command"
);
}
#[test]
fn test_find_ast_with_sqlite_backend() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"find-ast",
"--kind",
"function_item",
])
.output()
.expect("Failed to execute llmgrep");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
if !output.status.success() {
if stderr.contains("No AST nodes found")
|| stderr.contains("connection error")
|| stderr.contains("Invalid magic number")
{
return;
}
panic!(
"llmgrep find-ast failed: {}\nstdout: {}\nstderr: {}",
output.status, stdout, stderr
);
}
}
#[test]
fn test_backend_detection_via_cli() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"search",
"--query",
"test",
"--output",
"json",
])
.output()
.expect("Failed to execute llmgrep");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("Backend detection failed"),
"Backend should be detected successfully for SQLite database"
);
assert!(
!stderr.contains("LLM-E109"),
"Should not report backend error for SQLite database"
);
}
#[test]
fn test_search_mode_symbols_via_cli() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"search",
"--query",
"main",
"--mode",
"symbols",
"--limit",
"3",
])
.output()
.expect("Failed to execute llmgrep");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("invalid"),
"Symbols mode should be accepted: {}",
stderr
);
}
#[test]
fn test_search_mode_references_via_cli() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"search",
"--query",
"main",
"--mode",
"references",
"--limit",
"3",
])
.output()
.expect("Failed to execute llmgrep");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("invalid"),
"References mode should be accepted: {}",
stderr
);
}
#[test]
fn test_search_mode_calls_via_cli() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"search",
"--query",
"main",
"--mode",
"calls",
"--limit",
"3",
])
.output()
.expect("Failed to execute llmgrep");
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
!stderr.contains("invalid"),
"Calls mode should be accepted: {}",
stderr
);
}
#[test]
fn test_json_output_format_via_cli() {
let binary = match llmgrep_binary() {
Some(b) => b,
None => {
eprintln!("SKIP: llmgrep binary not found. Run: cargo build --release");
return;
}
};
let db_path = get_test_sqlite_db();
let output = Command::new(&binary)
.args([
"--db",
db_path.to_str().expect("failed to convert path to string"),
"search",
"--query",
"main",
"--output",
"json",
])
.output()
.expect("Failed to execute llmgrep");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
stdout.contains("{") || stdout.contains("results"),
"JSON output should contain braces or 'results' field"
);
}