use super::*;
use code_ranker_plugin_api::{edge::Edge, node::Node};
fn node(id: &str, name: &str) -> Node {
Node {
id: id.into(),
kind: "file".into(),
name: name.into(),
parent: None,
attrs: Default::default(),
}
}
fn edge(from: &str, to: &str, kind: &str) -> Edge {
Edge {
source: from.into(),
target: to.into(),
kind: kind.into(),
line: None,
attrs: Default::default(),
}
}
fn flow() -> HashSet<String> {
HashSet::from(["uses".to_string(), "reexports".to_string()])
}
fn node_crate(id: &str, name: &str, krate: &str) -> Node {
let mut n = node(id, name);
n.attrs.insert("crate".into(), AttrValue::Str(krate.into()));
n
}
#[test]
fn cross_crate_via_attr_is_dropped() {
let mut g = Graph {
nodes: vec![
node_crate("{t}/cli/a.rs", "a", "deno"),
node_crate("{t}/runtime/b.rs", "b", "deno_runtime"),
],
edges: vec![
edge("{t}/cli/a.rs", "{t}/runtime/b.rs", "uses"),
edge("{t}/runtime/b.rs", "{t}/cli/a.rs", "uses"),
],
};
assert!(annotate_cycles(&mut g, &flow()).is_empty());
}
#[test]
fn same_crate_via_attr_is_kept() {
let mut g = Graph {
nodes: vec![
node_crate("{t}/cli/a.rs", "a", "deno"),
node_crate("{t}/cli/sub/b.rs", "b", "deno"),
],
edges: vec![
edge("{t}/cli/a.rs", "{t}/cli/sub/b.rs", "uses"),
edge("{t}/cli/sub/b.rs", "{t}/cli/a.rs", "uses"),
],
};
assert_eq!(annotate_cycles(&mut g, &flow()).len(), 1);
}
#[test]
fn two_node_cycle_is_mutual() {
let mut g = Graph {
nodes: vec![node("a", "a"), node("b", "b")],
edges: vec![edge("a", "b", "uses"), edge("b", "a", "uses")],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].kind, "mutual");
assert_eq!(
g.nodes[0].attrs.get("cycle"),
Some(&AttrValue::Str("mutual".into()))
);
}
#[test]
fn contains_edge_excluded_from_cycles() {
let mut g = Graph {
nodes: vec![node("m", "m"), node("c", "c")],
edges: vec![edge("m", "c", "contains"), edge("c", "m", "uses")],
};
let groups = annotate_cycles(&mut g, &flow());
assert!(groups.is_empty(), "contains is structural, not flow");
}
#[test]
fn cross_crate_scc_is_dropped() {
let mut g = Graph {
nodes: vec![
node("{t}/crateA/src/a.rs", "a"),
node("{t}/crateB/src/b.rs", "b"),
],
edges: vec![
edge("{t}/crateA/src/a.rs", "{t}/crateB/src/b.rs", "uses"),
edge("{t}/crateB/src/b.rs", "{t}/crateA/src/a.rs", "uses"),
],
};
let groups = annotate_cycles(&mut g, &flow());
assert!(groups.is_empty(), "cross-crate cycle must be dropped");
}
#[test]
fn intra_crate_scc_is_kept() {
let mut g = Graph {
nodes: vec![
node("{t}/crateA/src/a.rs", "a"),
node("{t}/crateA/src/b.rs", "b"),
],
edges: vec![
edge("{t}/crateA/src/a.rs", "{t}/crateA/src/b.rs", "uses"),
edge("{t}/crateA/src/b.rs", "{t}/crateA/src/a.rs", "uses"),
],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].kind, "mutual");
}
#[test]
fn three_node_scc_is_chain() {
let mut g = Graph {
nodes: vec![node("a", "a"), node("b", "b"), node("c", "c")],
edges: vec![
edge("a", "b", "uses"),
edge("b", "c", "uses"),
edge("c", "a", "uses"),
],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(groups.len(), 1);
assert_eq!(groups[0].kind, "chain");
}
#[test]
fn test_named_node_no_longer_special_cased() {
let mut g = Graph {
nodes: vec![node("a", "a"), node("b", "foo_tests")],
edges: vec![edge("a", "b", "uses"), edge("b", "a", "uses")],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(groups[0].kind, "mutual");
}
#[test]
fn self_loop_does_not_form_cycle() {
let mut g = Graph {
nodes: vec![node("a", "a")],
edges: vec![edge("a", "a", "uses")],
};
let groups = annotate_cycles(&mut g, &flow());
assert!(groups.is_empty(), "a self-loop is not a cycle");
assert!(
!g.nodes[0].attrs.contains_key("cycle"),
"no cycle attribute on a self-looping node"
);
}
#[test]
fn cycle_among_disconnected_components_is_isolated() {
let mut g = Graph {
nodes: vec![
node("a", "a"),
node("b", "b"),
node("x", "x"),
node("y", "y"),
node("z", "z"),
],
edges: vec![edge("a", "b", "uses"), edge("b", "a", "uses")],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(groups.len(), 1, "exactly one cycle group");
assert_eq!(groups[0].kind, "mutual");
for n in &g.nodes {
let in_cycle = n.id == "a" || n.id == "b";
assert_eq!(
n.attrs.contains_key("cycle"),
in_cycle,
"node {} cycle-annotation should be {in_cycle}",
n.id
);
}
}
#[test]
fn nested_cycles_form_single_scc() {
let mut g = Graph {
nodes: vec![
node("a", "a"),
node("b", "b"),
node("c", "c"),
node("d", "d"),
],
edges: vec![
edge("a", "b", "uses"),
edge("b", "a", "uses"),
edge("b", "c", "uses"),
edge("c", "d", "uses"),
edge("d", "b", "uses"),
],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(groups.len(), 1, "one merged SCC, not two cycles");
assert_eq!(groups[0].kind, "chain", "4-member SCC is a chain");
assert_eq!(groups[0].nodes.len(), 4, "all four nodes in the one group");
}
#[test]
fn unknown_crate_member_keeps_cycle_conservatively() {
let mut g = Graph {
nodes: vec![
node_crate("{t}/cli/a.rs", "a", "deno"),
node("opaque-id-without-src", "b"),
],
edges: vec![
edge("{t}/cli/a.rs", "opaque-id-without-src", "uses"),
edge("opaque-id-without-src", "{t}/cli/a.rs", "uses"),
],
};
let groups = annotate_cycles(&mut g, &flow());
assert_eq!(
groups.len(),
1,
"an undeterminable crate must not drop the cycle"
);
assert_eq!(groups[0].kind, "mutual");
}
#[test]
fn crate_of_falls_back_to_src_segment() {
let mut g = Graph {
nodes: vec![
node("{t}/alpha/src/a.rs", "a"),
node("{t}/beta/src/b.rs", "b"),
],
edges: vec![
edge("{t}/alpha/src/a.rs", "{t}/beta/src/b.rs", "uses"),
edge("{t}/beta/src/b.rs", "{t}/alpha/src/a.rs", "uses"),
],
};
assert!(
annotate_cycles(&mut g, &flow()).is_empty(),
"different /src/ prefixes are different crates → cycle dropped"
);
let mut g2 = Graph {
nodes: vec![
node("{t}/alpha/src/a.rs", "a"),
node("{t}/alpha/src/b.rs", "b"),
],
edges: vec![
edge("{t}/alpha/src/a.rs", "{t}/alpha/src/b.rs", "uses"),
edge("{t}/alpha/src/b.rs", "{t}/alpha/src/a.rs", "uses"),
],
};
assert_eq!(
annotate_cycles(&mut g2, &flow()).len(),
1,
"same /src/ prefix is one crate → cycle kept"
);
}