use codegraph::{helpers, CodeGraph};
fn create_linear_chain() -> codegraph::Result<(CodeGraph, Vec<u64>)> {
let mut graph = CodeGraph::in_memory()?;
let a = helpers::add_file(&mut graph, "a.py", "python")?;
let b = helpers::add_file(&mut graph, "b.py", "python")?;
let c = helpers::add_file(&mut graph, "c.py", "python")?;
let d = helpers::add_file(&mut graph, "d.py", "python")?;
helpers::add_import(&mut graph, a, b, vec![])?;
helpers::add_import(&mut graph, b, c, vec![])?;
helpers::add_import(&mut graph, c, d, vec![])?;
Ok((graph, vec![a, b, c, d]))
}
fn create_circular_chain() -> codegraph::Result<(CodeGraph, Vec<u64>)> {
let mut graph = CodeGraph::in_memory()?;
let a = helpers::add_file(&mut graph, "a.py", "python")?;
let b = helpers::add_file(&mut graph, "b.py", "python")?;
let c = helpers::add_file(&mut graph, "c.py", "python")?;
helpers::add_import(&mut graph, a, b, vec![])?;
helpers::add_import(&mut graph, b, c, vec![])?;
helpers::add_import(&mut graph, c, a, vec![])?;
Ok((graph, vec![a, b, c]))
}
#[test]
fn test_bfs_traversal_with_max_depth() {
let (graph, nodes) = create_linear_chain().unwrap();
let [a, b, c, d] = [nodes[0], nodes[1], nodes[2], nodes[3]];
let result = graph
.bfs(a, codegraph::Direction::Outgoing, Some(1))
.unwrap();
assert_eq!(result.len(), 1);
assert!(result.contains(&b));
let result = graph
.bfs(a, codegraph::Direction::Outgoing, Some(2))
.unwrap();
assert_eq!(result.len(), 2);
assert!(result.contains(&b));
assert!(result.contains(&c));
let result = graph
.bfs(a, codegraph::Direction::Outgoing, Some(3))
.unwrap();
assert_eq!(result.len(), 3);
assert!(result.contains(&b));
assert!(result.contains(&c));
assert!(result.contains(&d));
let result = graph.bfs(a, codegraph::Direction::Outgoing, None).unwrap();
assert_eq!(result.len(), 3);
}
#[test]
fn test_dfs_traversal() {
let (graph, nodes) = create_linear_chain().unwrap();
let [a, _, _, _] = [nodes[0], nodes[1], nodes[2], nodes[3]];
let result = graph
.dfs(a, codegraph::Direction::Outgoing, Some(10))
.unwrap();
assert_eq!(result.len(), 3);
let result = graph
.dfs(a, codegraph::Direction::Outgoing, Some(1))
.unwrap();
assert_eq!(result.len(), 1); }
#[test]
fn test_tarjans_scc_cycle_detection() {
let (graph, nodes) = create_circular_chain().unwrap();
let [a, b, c] = [nodes[0], nodes[1], nodes[2]];
let sccs = graph.find_strongly_connected_components().unwrap();
let found_cycle = sccs
.iter()
.any(|scc| scc.len() == 3 && scc.contains(&a) && scc.contains(&b) && scc.contains(&c));
assert!(
found_cycle,
"Should detect the circular dependency as an SCC"
);
}
#[test]
fn test_find_all_paths() {
let (graph, nodes) = create_linear_chain().unwrap();
let [a, _, _, d] = [nodes[0], nodes[1], nodes[2], nodes[3]];
let paths = graph.find_all_paths(a, d, Some(5)).unwrap();
assert_eq!(paths.len(), 1);
assert_eq!(paths[0].len(), 4); assert_eq!(paths[0][0], a);
assert_eq!(paths[0][3], d);
}
#[test]
fn test_transitive_dependencies() {
let (graph, nodes) = create_linear_chain().unwrap();
let [a, b, c, d] = [nodes[0], nodes[1], nodes[2], nodes[3]];
let deps = helpers::transitive_dependencies(&graph, a, Some(10)).unwrap();
assert_eq!(deps.len(), 3);
assert!(deps.contains(&b));
assert!(deps.contains(&c));
assert!(deps.contains(&d));
let deps = helpers::transitive_dependencies(&graph, a, Some(1)).unwrap();
assert_eq!(deps.len(), 1);
assert!(deps.contains(&b));
let deps = helpers::transitive_dependencies(&graph, d, Some(10)).unwrap();
assert_eq!(deps.len(), 0);
}
#[test]
fn test_transitive_dependents() {
let (graph, nodes) = create_linear_chain().unwrap();
let [a, b, c, d] = [nodes[0], nodes[1], nodes[2], nodes[3]];
let dependents = helpers::transitive_dependents(&graph, d, Some(10)).unwrap();
assert_eq!(dependents.len(), 3);
assert!(dependents.contains(&a));
assert!(dependents.contains(&b));
assert!(dependents.contains(&c));
let dependents = helpers::transitive_dependents(&graph, d, Some(1)).unwrap();
assert_eq!(dependents.len(), 1);
assert!(dependents.contains(&c));
let dependents = helpers::transitive_dependents(&graph, a, Some(10)).unwrap();
assert_eq!(dependents.len(), 0);
}
#[test]
fn test_call_chain() {
let mut graph = CodeGraph::in_memory().unwrap();
let file = helpers::add_file(&mut graph, "main.py", "python").unwrap();
let func_a = helpers::add_function_with_metadata(
&mut graph,
file,
helpers::FunctionMetadata {
name: "func_a",
line_start: 1,
line_end: 5,
visibility: "public",
signature: "def func_a():",
is_async: false,
is_test: false,
},
)
.unwrap();
let func_b = helpers::add_function_with_metadata(
&mut graph,
file,
helpers::FunctionMetadata {
name: "func_b",
line_start: 7,
line_end: 11,
visibility: "public",
signature: "def func_b():",
is_async: false,
is_test: false,
},
)
.unwrap();
let func_c = helpers::add_function_with_metadata(
&mut graph,
file,
helpers::FunctionMetadata {
name: "func_c",
line_start: 13,
line_end: 17,
visibility: "public",
signature: "def func_c():",
is_async: false,
is_test: false,
},
)
.unwrap();
helpers::add_call(&mut graph, func_a, func_b, 3).unwrap();
helpers::add_call(&mut graph, func_b, func_c, 9).unwrap();
let chains = helpers::call_chain(&graph, func_a, func_c, Some(5)).unwrap();
assert_eq!(chains.len(), 1);
assert_eq!(chains[0].len(), 3); assert_eq!(chains[0][0], func_a);
assert_eq!(chains[0][1], func_b);
assert_eq!(chains[0][2], func_c);
}
#[test]
fn test_circular_deps() {
let (graph, nodes) = create_circular_chain().unwrap();
let [a, b, c] = [nodes[0], nodes[1], nodes[2]];
let cycles = helpers::circular_deps(&graph).unwrap();
let found_cycle = cycles.iter().any(|cycle| {
cycle.len() >= 3 && cycle.contains(&a) && cycle.contains(&b) && cycle.contains(&c)
});
assert!(found_cycle, "Should detect the circular import");
}
#[test]
fn test_actual_circular_imports() {
let mut graph = CodeGraph::in_memory().unwrap();
let utils = helpers::add_file(&mut graph, "utils.py", "python").unwrap();
let models = helpers::add_file(&mut graph, "models.py", "python").unwrap();
let views = helpers::add_file(&mut graph, "views.py", "python").unwrap();
helpers::add_import(&mut graph, utils, models, vec!["Model"]).unwrap();
helpers::add_import(&mut graph, models, views, vec!["render"]).unwrap();
helpers::add_import(&mut graph, views, utils, vec!["helper"]).unwrap();
let cycles = helpers::circular_deps(&graph).unwrap();
assert!(!cycles.is_empty(), "Should detect circular imports");
let found = cycles
.iter()
.any(|cycle| cycle.contains(&utils) && cycle.contains(&models) && cycle.contains(&views));
assert!(found, "Cycle should include all three files");
let deps = helpers::transitive_dependencies(&graph, utils, None).unwrap();
assert!(deps.contains(&models));
assert!(deps.contains(&views));
assert_eq!(deps.len(), 2); }
#[test]
fn test_bfs_handles_cycles() {
let (graph, nodes) = create_circular_chain().unwrap();
let a = nodes[0];
let result = graph.bfs(a, codegraph::Direction::Outgoing, None).unwrap();
assert_eq!(result.len(), 2); }