use std::fs;
use std::process::Command;
use tempfile::TempDir;
#[test]
fn test_export_json_basic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
struct Point {
x: i32,
y: i32,
}
fn distance(p1: &Point, p2: &Point) -> i32 {
0
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
assert!(json.get("files").is_some(), "Should have files array");
assert!(json.get("symbols").is_some(), "Should have symbols array");
}
#[test]
fn test_export_json_includes_symbol_ids() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
fn helper() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
let symbols = json["symbols"].as_array().expect("symbols should be array");
let mut found_symbol_id = false;
for symbol in symbols {
if let Some(id) = symbol.get("symbol_id").and_then(|v| v.as_str()) {
if !id.is_empty() {
found_symbol_id = true;
break;
}
}
}
assert!(
found_symbol_id,
"At least one symbol should have a non-empty symbol_id"
);
}
#[test]
fn test_export_jsonl_format() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("jsonl")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
let lines: Vec<&str> = stdout.lines().collect();
for (i, line) in lines.iter().enumerate() {
if line.is_empty() {
continue;
}
let json: serde_json::Value = serde_json::from_str(line)
.unwrap_or_else(|e| panic!("Line {} should be valid JSON: {}\nLine: '{}'", i, e, line));
assert!(
json.get("type").is_some(),
"Line {} should have 'type' field: '{}'",
i,
line
);
}
}
#[test]
fn test_export_deterministic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn alpha() {}
fn beta() {}
fn gamma() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output1 = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute magellan export");
let output2 = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("json")
.output()
.expect("Failed to execute magellan export");
let stdout1 = String::from_utf8_lossy(&output1.stdout);
let stdout2 = String::from_utf8_lossy(&output2.stdout);
assert_eq!(
stdout1, stdout2,
"Same input should produce identical output"
);
}
#[test]
fn test_export_minify() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--minify")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
let newline_count = stdout.matches('\n').count();
assert!(
newline_count < 10,
"Minified JSON should have minimal newlines, found {}",
newline_count
);
}
#[test]
fn test_export_to_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let export_path = temp_dir.path().join("export.json");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--output")
.arg(&export_path)
.output()
.expect("Failed to execute magellan export");
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let exported_content =
fs::read_to_string(&export_path).expect("Should be able to read export file");
let json: serde_json::Value =
serde_json::from_str(&exported_content).expect("Export file should contain valid JSON");
assert!(json.get("files").is_some(), "Export should have files");
}
#[test]
fn test_export_content_filters() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
helper();
}
fn helper() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--no-calls")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
let json: serde_json::Value =
serde_json::from_str(&stdout).expect("Output should be valid JSON");
let calls = json.get("calls").and_then(|v| v.as_array());
match calls {
Some(arr) => assert_eq!(arr.len(), 0, "Calls should be empty with --no-calls"),
None => {} }
let symbols = json["symbols"]
.as_array()
.expect("Symbols should be present");
assert!(!symbols.is_empty(), "Symbols should be included");
}
#[test]
fn test_export_csv_basic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
helper();
}
fn helper() {
println!("Helper");
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let lines: Vec<&str> = stdout.lines().collect();
assert!(
!lines.is_empty(),
"CSV output should have at least a header row"
);
let header = lines
.iter()
.find(|line| !line.starts_with('#') && !line.is_empty())
.expect("CSV should have a header row");
assert!(
header.starts_with("record_type"),
"CSV header should start with record_type column, got: {}",
header
);
let header_idx = lines
.iter()
.position(|line| !line.starts_with('#') && !line.is_empty())
.expect("Should find header line");
for (i, line) in lines.iter().skip(header_idx + 1).enumerate() {
if line.is_empty() || line.starts_with('#') {
continue;
}
let record_type = line.split(',').next().unwrap_or("");
assert!(
record_type == "Symbol"
|| record_type == "Reference"
|| record_type == "Call"
|| record_type.contains("Symbol")
|| record_type.contains("Reference")
|| record_type.contains("Call"),
"Line {} should have valid record_type, got: '{}'",
i + 1,
record_type
);
}
}
#[test]
fn test_export_csv_proper_quoting() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
let csv_data: String = stdout
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
if csv_data.trim().is_empty() {
return;
}
let mut rdr = csv::Reader::from_reader(csv_data.as_bytes());
let headers = rdr.headers().expect("Should have valid CSV headers");
assert!(!headers.is_empty(), "CSV should have at least one column");
let mut record_count = 0;
for result in rdr.records() {
let _record = result.expect("Each CSV record should be valid");
record_count += 1;
}
assert!(record_count > 0, "CSV should have at least one data record");
}
#[test]
fn test_export_csv_deterministic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn alpha() {}
fn beta() {}
fn gamma() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output1 = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.output()
.expect("Failed to execute magellan export");
let output2 = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.output()
.expect("Failed to execute magellan export");
let stdout1 = String::from_utf8_lossy(&output1.stdout);
let stdout2 = String::from_utf8_lossy(&output2.stdout);
assert_eq!(
stdout1, stdout2,
"Same input should produce identical CSV output"
);
}
#[test]
fn test_export_csv_includes_symbol_ids() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
fn helper() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
let csv_data: String = stdout
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
let mut rdr = csv::Reader::from_reader(csv_data.as_bytes());
let headers = rdr.headers().expect("Should have valid CSV headers");
let has_symbol_id = headers.iter().any(|h| h == "symbol_id");
assert!(
has_symbol_id,
"CSV should have symbol_id column. Headers: {:?}",
headers
);
}
#[test]
fn test_export_csv_to_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let export_path = temp_dir.path().join("export.csv");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.arg("--output")
.arg(&export_path)
.output()
.expect("Failed to execute magellan export");
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let exported_content =
fs::read_to_string(&export_path).expect("Should be able to read export file");
let mut rdr = csv::Reader::from_reader(exported_content.as_bytes());
let headers = rdr
.headers()
.expect("Export file should have valid CSV headers");
assert!(
!headers.is_empty(),
"Export file should have at least one column"
);
}
#[test]
fn test_export_dot_basic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
helper();
}
fn helper() {
println!("Help");
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.starts_with("strict digraph call_graph {"),
"DOT output should start with 'strict digraph call_graph {{', got: {}",
&stdout[..stdout.len().min(30)]
);
assert!(
stdout.contains("}\n"),
"DOT output should contain closing brace"
);
}
#[test]
fn test_csv_export_symbols_only() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello");
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.arg("--no-references")
.arg("--no-calls")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let csv_data: String = stdout
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
let mut rdr = csv::Reader::from_reader(csv_data.as_bytes());
let headers = rdr.headers().expect("CSV should have valid headers");
assert!(
headers.iter().any(|h| h == "record_type"),
"CSV header should contain record_type column. Headers: {:?}",
headers
);
for result in rdr.records() {
let record = result.expect("CSV record should be valid");
let record_type = record
.get(0)
.expect("Record should have record_type column");
assert_eq!(
record_type, "Symbol",
"All exported rows should have record_type='Symbol' when using --no-references --no-calls. Got: '{}'",
record_type
);
assert_ne!(
record_type, "Reference",
"Should not have Reference records when using --no-references"
);
assert_ne!(
record_type, "Call",
"Should not have Call records when using --no-calls"
);
}
}
#[test]
fn test_export_dot_deterministic() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn alpha() {}
fn beta() {}
fn gamma() {}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output1 = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.output()
.expect("Failed to execute magellan export");
let output2 = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.output()
.expect("Failed to execute magellan export");
let stdout1 = String::from_utf8_lossy(&output1.stdout);
let stdout2 = String::from_utf8_lossy(&output2.stdout);
assert_eq!(
stdout1, stdout2,
"Same input should produce identical DOT output"
);
}
#[test]
fn test_export_dot_label_escaping() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test with \"quotes\".rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
let stderr = String::from_utf8_lossy(&output.stderr);
assert!(
output.status.success(),
"Process should exit successfully\nstdout: {}\nstderr: {}",
stdout,
stderr
);
assert!(
stdout.starts_with("strict digraph call_graph {"),
"DOT output should start with 'strict digraph call_graph {{', got: {}",
&stdout[..stdout.len().min(30)]
);
assert!(
stdout.contains("}\n"),
"DOT output should contain closing brace"
);
}
#[test]
fn test_export_dot_cluster() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path1 = temp_dir.path().join("module_a.rs");
let file_path2 = temp_dir.path().join("module_b.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source1 = r#"fn func_a() {}"#;
let source2 = r#"fn func_b() { func_a(); }"#;
fs::write(&file_path1, source1).unwrap();
fs::write(&file_path2, source2).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
for file_path in &[&file_path1, &file_path2] {
let source_bytes = fs::read(file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.arg("--cluster")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.contains("subgraph cluster_"),
"DOT output with --cluster should contain subgraph clusters"
);
}
#[test]
fn test_export_dot_filter_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path1 = temp_dir.path().join("target_file.rs");
let file_path2 = temp_dir.path().join("other_file.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source1 = r#"fn target_func() {}"#;
let source2 = r#"fn other_func() {}"#;
fs::write(&file_path1, source1).unwrap();
fs::write(&file_path2, source2).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
for file_path in &[&file_path1, &file_path2] {
let source_bytes = fs::read(file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.arg("--file")
.arg("target_file")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(output.status.success(), "Process should exit successfully");
assert!(
stdout.starts_with("strict digraph call_graph {"),
"DOT output should start with 'strict digraph call_graph {{', got: {}",
&stdout[..stdout.len().min(30)]
);
}
#[test]
fn test_export_dot_to_file() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let export_path = temp_dir.path().join("export.dot");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"fn main() {}"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("dot")
.arg("--output")
.arg(&export_path)
.output()
.expect("Failed to execute magellan export");
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let exported_content =
fs::read_to_string(&export_path).expect("Should be able to read export file");
assert!(
exported_content.starts_with("strict digraph"),
"Export file should start with 'strict digraph'"
);
}
#[test]
fn test_csv_export_references_only() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn main() {
println!("Hello, world!");
let x: i32 = 42;
println!("{}", x);
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.arg("--no-symbols")
.arg("--no-calls")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let csv_data: String = stdout
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
if csv_data.trim().is_empty() {
return;
}
let mut rdr = csv::Reader::from_reader(csv_data.as_bytes());
let headers = rdr.headers().expect("Should have valid CSV headers");
assert!(!headers.is_empty(), "CSV should have at least one column");
assert!(
headers.iter().any(|h| h == "record_type"),
"CSV should have record_type column. Headers: {:?}",
headers
);
assert!(
headers.iter().any(|h| h == "referenced_symbol"),
"CSV should have referenced_symbol column. Headers: {:?}",
headers
);
let mut record_count = 0;
for result in rdr.records() {
let record = result.expect("CSV record should be valid");
let record_type = record
.get(0)
.unwrap_or_else(|| panic!("Record should have record_type column"));
assert_eq!(
record_type, "Reference",
"All records should be Reference type when using --no-symbols --no-calls, got: {}",
record_type
);
assert_ne!(record_type, "Symbol", "Should not have Symbol records");
assert_ne!(record_type, "Call", "Should not have Call records");
record_count += 1;
}
if record_count > 0 {
eprintln!("Successfully exported {} Reference records", record_count);
}
}
#[test]
fn test_csv_export_mixed_records() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn helper(x: i32) -> i32 {
x + 1
}
fn main() {
let result = helper(42);
println!("{}", result);
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let csv_data: String = stdout
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
let mut rdr = csv::Reader::from_reader(csv_data.as_bytes());
let headers = rdr.headers().expect("Should have valid CSV headers");
assert!(
headers.iter().any(|h| h == "record_type"),
"CSV header should contain record_type column. Headers: {:?}",
headers
);
let mut record_types = std::collections::HashSet::new();
let mut all_column_counts = std::collections::HashSet::new();
for result in rdr.records() {
let record = result.expect("CSV record should be valid");
let record_type = record
.get(0)
.expect("Each record should have record_type column");
record_types.insert(record_type.to_string());
all_column_counts.insert(record.len());
}
assert!(
!record_types.is_empty(),
"CSV should contain at least one record type"
);
for record_type in &record_types {
assert!(
record_type == "Symbol" || record_type == "Reference" || record_type == "Call",
"Invalid record_type found: {}. Expected one of: Symbol, Reference, Call",
record_type
);
}
assert!(
all_column_counts.len() <= 1,
"All rows should have same column count (consistent headers). Found: {:?}",
all_column_counts
);
if record_types.len() > 1 {
eprintln!(
"Found {} record types: {:?}",
record_types.len(),
record_types
);
}
}
#[test]
fn test_csv_export_calls_only() {
let temp_dir = TempDir::new().unwrap();
let db_path = temp_dir.path().join("magellan.db");
let file_path = temp_dir.path().join("test.rs");
let bin_path = std::env::var("CARGO_BIN_EXE_magellan").unwrap_or_else(|_| {
let mut path = std::env::current_exe().unwrap();
path.pop();
path.pop();
path.push("magellan");
path.to_str().unwrap().to_string()
});
let source = r#"
fn helper() {}
fn main() {
helper();
}
"#;
fs::write(&file_path, source).unwrap();
{
let mut graph = magellan::CodeGraph::open(&db_path).unwrap();
let source_bytes = fs::read(&file_path).unwrap();
let path_str = file_path.to_string_lossy().to_string();
graph.index_file(&path_str, &source_bytes).unwrap();
}
let output = Command::new(&bin_path)
.arg("export")
.arg("--db")
.arg(&db_path)
.arg("--format")
.arg("csv")
.arg("--no-symbols")
.arg("--no-references")
.output()
.expect("Failed to execute magellan export");
let stdout = String::from_utf8_lossy(&output.stdout);
assert!(
output.status.success(),
"Process should exit successfully: {}",
String::from_utf8_lossy(&output.stderr)
);
let csv_data: String = stdout
.lines()
.filter(|line| !line.starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
let mut rdr = csv::Reader::from_reader(csv_data.as_bytes());
let headers = rdr.headers().expect("Should have valid CSV headers");
assert!(
headers.iter().any(|h| h == "record_type"),
"CSV should have record_type column. Headers: {:?}",
headers
);
assert!(
headers.iter().any(|h| h == "caller"),
"CSV should have caller column. Headers: {:?}",
headers
);
assert!(
headers.iter().any(|h| h == "callee"),
"CSV should have callee column. Headers: {:?}",
headers
);
for result in rdr.records() {
let record = result.expect("Each CSV record should be valid");
let record_type = record
.get(0)
.expect("Each record should have record_type column");
assert_eq!(
record_type, "Call",
"All rows should have record_type='Call' when using --no-symbols --no-references, got: '{}'",
record_type
);
assert_ne!(record_type, "Symbol", "Should not have Symbol records");
assert_ne!(
record_type, "Reference",
"Should not have Reference records"
);
}
}