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::{build_dependency_graph, DependencyNode};
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");
}
}