use std::fs;
use tempfile::TempDir;
fn create_mixed_language_project() -> TempDir {
let temp_dir = tempfile::tempdir().unwrap();
let base = temp_dir.path();
fs::create_dir_all(base.join("src")).unwrap();
fs::create_dir_all(base.join("lib")).unwrap();
fs::create_dir_all(base.join("scripts")).unwrap();
fs::write(
base.join("src/main.rs"),
r#"
fn main() {
unsafe { c_add(1, 2); }
java_helper();
}
fn java_helper() {
println!("calling java");
}
"#,
)
.unwrap();
fs::write(
base.join("lib/math.c"),
r#"
#include <stdio.h>
int c_add(int a, int b) {
return a + b;
}
void c_print(int value) {
printf("%d", value);
}
"#,
)
.unwrap();
fs::write(
base.join("lib/Helper.java"),
r#"
public class Helper {
public static int multiply(int a, int b) {
return a * b;
}
private void compute() {
multiply(2, 3);
}
}
"#,
)
.unwrap();
fs::write(
base.join("lib/utils.py"),
r#"
import os
import json
def process_data(data):
return json.dumps(data)
def run_python():
result = process_data({"key": "value"})
print(result)
"#,
)
.unwrap();
fs::write(
base.join("lib/app.js"),
r#"
const helper = require('./helper');
function greet(name) {
return `Hello, ${name}`;
}
function run_app() {
const message = greet('world');
helper.log(message);
}
module.exports = { greet, run_app };
"#,
)
.unwrap();
fs::write(
base.join("lib/server.go"),
r#"
package main
import "fmt"
import "net/http"
func handler(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "Hello")
}
func run_server() {
http.HandleFunc("/", handler)
}
"#,
)
.unwrap();
fs::write(
base.join("scripts/deploy.sh"),
r#"
#!/bin/bash
source ./common.sh
deploy() {
echo "Deploying..."
check_health
}
check_health() {
echo "OK"
}
"#,
)
.unwrap();
temp_dir
}
#[test]
fn test_callgraph_detects_c_functions() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("c_add"),
"should detect C function c_add"
);
assert!(
graph.nodes.contains_key("c_print"),
"should detect C function c_print"
);
}
#[test]
fn test_callgraph_detects_java_methods() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("multiply"),
"should detect Java method multiply"
);
assert!(
graph.nodes.contains_key("compute"),
"should detect Java method compute"
);
}
#[test]
fn test_callgraph_detects_python_functions() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("process_data"),
"should detect Python function process_data"
);
assert!(
graph.nodes.contains_key("run_python"),
"should detect Python function run_python"
);
}
#[test]
fn test_callgraph_detects_js_functions() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("greet"),
"should detect JS function greet"
);
assert!(
graph.nodes.contains_key("run_app"),
"should detect JS function run_app"
);
}
#[test]
fn test_callgraph_detects_go_functions() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("handler"),
"should detect Go function handler"
);
assert!(
graph.nodes.contains_key("run_server"),
"should detect Go function run_server"
);
}
#[test]
fn test_callgraph_detects_shell_functions() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("deploy"),
"should detect shell function deploy"
);
assert!(
graph.nodes.contains_key("check_health"),
"should detect shell function check_health"
);
}
#[test]
fn test_callgraph_creates_cross_language_edges() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.has_edge("main", "java_helper"),
"Rust main should call java_helper"
);
}
#[test]
fn test_callgraph_detects_java_internal_calls() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.has_edge("compute", "multiply"),
"Java compute should call multiply"
);
}
#[test]
fn test_callgraph_detects_python_calls() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.has_edge("run_python", "process_data"),
"Python run_python should call process_data"
);
}
#[test]
fn test_callgraph_detects_js_calls() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.has_edge("run_app", "greet"),
"JS run_app should call greet"
);
}
#[test]
fn test_callgraph_detects_go_calls() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.nodes.contains_key("handler"),
"Go handler should be a node"
);
assert!(
graph.nodes.contains_key("run_server"),
"Go run_server should be a node"
);
}
#[test]
fn test_callgraph_detects_shell_calls() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
assert!(
graph.has_edge("deploy", "check_health"),
"shell deploy should call check_health"
);
}
#[test]
fn test_depgraph_detects_rust_imports() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let main_node = graph.nodes.values().find(|n| n.path.ends_with("main.rs"));
assert!(main_node.is_some(), "should find main.rs node");
}
#[test]
fn test_depgraph_detects_c_includes() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let c_node = graph.nodes.values().find(|n| n.path.ends_with("math.c"));
assert!(c_node.is_some(), "should find math.c node");
if let Some(node) = c_node {
assert!(
node.imports.iter().any(|i| i.contains("stdio")),
"C should include stdio"
);
}
}
#[test]
fn test_depgraph_detects_python_imports() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let py_node = graph.nodes.values().find(|n| n.path.ends_with("utils.py"));
assert!(py_node.is_some(), "should find utils.py node");
if let Some(node) = py_node {
assert!(
node.imports.iter().any(|i| i == "os"),
"Python should import os"
);
assert!(
node.imports.iter().any(|i| i == "json"),
"Python should import json"
);
}
}
#[test]
fn test_depgraph_detects_js_requires() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let js_node = graph.nodes.values().find(|n| n.path.ends_with("app.js"));
assert!(js_node.is_some(), "should find app.js node");
if let Some(node) = js_node {
assert!(
node.imports.iter().any(|i| i.contains("helper")),
"JS should require helper"
);
}
}
#[test]
fn test_depgraph_detects_go_imports() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let go_node = graph.nodes.values().find(|n| n.path.ends_with("server.go"));
assert!(go_node.is_some(), "should find server.go node");
if let Some(node) = go_node {
assert!(
node.imports.iter().any(|i| i.contains("fmt")),
"Go should import fmt"
);
assert!(
node.imports.iter().any(|i| i.contains("http")),
"Go should import http"
);
}
}
#[test]
fn test_depgraph_detects_shell_sources() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let sh_node = graph.nodes.values().find(|n| n.path.ends_with("deploy.sh"));
assert!(sh_node.is_some(), "should find deploy.sh node");
if let Some(node) = sh_node {
assert!(
node.imports.iter().any(|i| i.contains("common")),
"Shell should source common"
);
}
}
#[test]
fn test_depgraph_has_nodes_for_all_languages() {
use codesearch::depgraph::build_dependency_graph;
let temp = create_mixed_language_project();
let graph = build_dependency_graph(temp.path(), None, None).unwrap();
let extensions = ["rs", "c", "java", "py", "js", "go", "sh"];
for ext in &extensions {
let found = graph.nodes.values().any(|n| n.path.ends_with(ext));
assert!(
found,
"should have at least one node for extension: {}",
ext
);
}
}
#[test]
fn test_callgraph_extension_filtering() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let extensions = vec!["rs".to_string(), "go".to_string()];
let graph = build_call_graph(temp.path(), Some(&extensions), None).unwrap();
assert!(graph.nodes.contains_key("main"), "should include Rust main");
assert!(
graph.nodes.contains_key("handler"),
"should include Go handler"
);
assert!(
!graph.nodes.contains_key("greet"),
"should exclude JS greet"
);
assert!(
!graph.nodes.contains_key("process_data"),
"should exclude Python process_data"
);
}
#[test]
fn test_callgraph_exclude_directory() {
use codesearch::callgraph::build_call_graph;
use std::fs;
let temp = tempfile::tempdir().unwrap();
let base = temp.path();
fs::create_dir_all(base.join("src")).unwrap();
fs::create_dir_all(base.join("vendor")).unwrap();
fs::write(
base.join("src/app.rs"),
r#"
fn app_main() {
vendor_helper();
}
"#,
)
.unwrap();
fs::write(
base.join("vendor/lib.rs"),
r#"
fn vendor_helper() {}
"#,
)
.unwrap();
let graph = build_call_graph(base, None, Some(&["vendor".to_string()])).unwrap();
assert!(
graph.nodes.contains_key("app_main"),
"should include src/app.rs"
);
assert!(
!graph.nodes.contains_key("vendor_helper"),
"should exclude vendor/ directory"
);
}
#[test]
fn test_callgraph_svg_output_contains_nodes() {
use codesearch::callgraph::build_call_graph;
let temp = create_mixed_language_project();
let graph = build_call_graph(temp.path(), None, None).unwrap();
let svg = graph.to_svg();
assert!(svg.contains("<svg"), "SVG should start with svg tag");
assert!(svg.contains("</svg>"), "SVG should end with svg tag");
assert!(svg.contains("main"), "SVG should contain main node");
assert!(svg.contains("c_add"), "SVG should contain c_add node");
}
#[test]
fn test_depgraph_cycle_detection_with_real_files() {
use codesearch::depgraph::{DependencyNode, build_dependency_graph};
use std::fs;
let temp = tempfile::tempdir().unwrap();
let base = temp.path();
fs::create_dir_all(base.join("src")).unwrap();
fs::write(
base.join("src/a.rs"),
r#"
use crate::b;
pub fn a_func() {}
"#,
)
.unwrap();
fs::write(
base.join("src/b.rs"),
r#"
use crate::a;
pub fn b_func() {}
"#,
)
.unwrap();
let graph = build_dependency_graph(base, None, None).unwrap();
let cycles = graph.find_circular_dependencies();
assert!(
!cycles.is_empty(),
"should detect cycle between a.rs and b.rs"
);
}
#[test]
fn test_callgraph_recursive_detection() {
use codesearch::callgraph::build_call_graph;
use std::fs;
let temp = tempfile::tempdir().unwrap();
let base = temp.path();
fs::create_dir_all(base.join("src")).unwrap();
fs::write(
base.join("src/fact.rs"),
r#"
fn factorial(n: u64) -> u64 {
if n <= 1 {
1
} else {
n * factorial(n - 1)
}
}
"#,
)
.unwrap();
let graph = build_call_graph(base, None, None).unwrap();
let node = graph.nodes.get("factorial");
assert!(node.is_some(), "should detect factorial function");
if let Some(n) = node {
assert!(n.is_recursive, "factorial should be marked recursive");
}
}