Files
cex/crates/cex-core/src/analysis/tests.rs
2026-09-07 15:52:46 -04:00

1124 lines
33 KiB
Rust

use super::*;
use std::{
thread,
time::{Duration, Instant},
};
use tempfile::tempdir;
#[test]
#[ignore = "requires clangd on PATH; run with cargo test -p cex-core -- --ignored"]
fn clangd_cpp23_cross_file_navigation_and_cache_location() {
let temp = tempdir().unwrap();
let root = temp.path().join("project with spaces");
let build = root.join("build");
fs::create_dir_all(build.join("meson-info")).unwrap();
fs::write(build.join("meson-info/intro-projectinfo.json"), "{}").unwrap();
let header =
"template <typename T>\nconcept Addable = requires(T a) { a + a; };\nint add(int value);\n";
let lib = "#include \"lib.hpp\"\nint add(int value) { return value + 1; }\n";
let main = "#include \"lib.hpp\"\nstatic_assert(Addable<int>);\nconstexpr int cpp23() { if consteval { return 1; } else { return 2; } }\nint main() { return add(41); }\n";
fs::write(root.join("lib.hpp"), header).unwrap();
fs::write(root.join("lib.cpp"), lib).unwrap();
fs::write(root.join("main.cpp"), main).unwrap();
let database = json!(["lib.cpp", "main.cpp"].map(|name| json!({"directory": root, "file": root.join(name), "arguments": ["clang++", "-std=c++23", "-c", root.join(name)]})));
let original_database = serde_json::to_string(&database).unwrap();
fs::write(build.join("compile_commands.json"), &original_database).unwrap();
let workspace = crate::open(&root, Some(&build), false).unwrap();
let server = Clangd::start(workspace.clone()).unwrap();
let pos = Position {
line: 2,
character: 5,
};
let hover = server.query("lib.hpp", pos, Query::Hover).unwrap();
assert!(hover.hover.contains("add"), "{}", hover.hover);
let deadline = Instant::now() + Duration::from_secs(15);
loop {
let refs = server.query("lib.hpp", pos, Query::References).unwrap();
if refs.locations.iter().any(|l| l.path == "main.cpp") {
break;
}
assert!(
Instant::now() < deadline,
"No main.cpp reference: {:?}",
server.status()
);
thread::sleep(Duration::from_millis(100));
}
let definition = server
.query(
"main.cpp",
Position {
line: 3,
character: 21,
},
Query::Definition,
)
.unwrap();
assert!(
definition.locations.iter().any(|l| l.path == "lib.cpp"),
"{definition:?}"
);
let callers = server.query("lib.hpp", pos, Query::Callers).unwrap();
assert!(
callers
.locations
.iter()
.any(|l| l.path == "main.cpp" && l.label.contains("main")),
"{callers:?}"
);
let concept = server
.query(
"lib.hpp",
Position {
line: 1,
character: 10,
},
Query::References,
)
.unwrap();
assert!(
concept.locations.iter().any(|l| l.path == "main.cpp"),
"{concept:?}"
);
assert!(server.read_target("/etc/passwd").is_err());
let context = crate::explanation::prepare(
&workspace,
server.as_ref(),
"lib.hpp",
pos,
None,
"Explain callers",
&tokio_util::sync::CancellationToken::new(),
)
.unwrap();
assert!(
context
.bundle
.sources
.iter()
.any(|s| s.path == "main.cpp" && s.enclosing == "main" && s.code.contains("add(41)")),
"{context:?}"
);
assert!(
context
.bundle
.sources
.iter()
.any(|s| s.path == "lib.cpp" && s.code.contains("value + 1")),
"{context:?}"
);
let dependencies = server
.query(
"main.cpp",
Position {
line: 3,
character: 5,
},
Query::Callees,
)
.unwrap();
assert!(
dependencies
.locations
.iter()
.any(|l| l.label.contains("add")),
"{dependencies:?}"
);
let selected_function = crate::explanation::prepare(
&workspace,
server.as_ref(),
"main.cpp",
Position {
line: 3,
character: 0,
},
Some(Range {
start: Position {
line: 3,
character: 0,
},
end: Position {
line: 3,
character: 28,
},
}),
"",
&tokio_util::sync::CancellationToken::new(),
)
.unwrap();
assert!(
!selected_function.available_callees.is_empty(),
"{selected_function:?}"
);
let expanded = crate::explanation::expand(
&selected_function,
server.as_ref(),
&selected_function
.available_callees
.iter()
.map(|c| c.id.clone())
.collect::<Vec<_>>(),
2,
"",
&tokio_util::sync::CancellationToken::new(),
)
.unwrap();
assert!(
expanded
.bundle
.sources
.iter()
.any(|s| s.path == "lib.cpp" && s.code.contains("value + 1")),
"{expanded:?}"
);
server.stop();
assert_eq!(server.status().phase, "stopped");
assert_eq!(
fs::read_to_string(build.join("compile_commands.json")).unwrap(),
original_database
);
assert_eq!(fs::read_to_string(root.join("main.cpp")).unwrap(), main);
assert!(!build.join(".cache").exists());
assert!(!root.join(".cache").exists());
assert!(root.join(".cex/clangd/compile_commands.json").is_file());
assert!(root.join(".cex/clangd/.cache/clangd/index").is_dir());
}
#[test]
#[ignore = "requires clangd on PATH; exercises a generated 120k-line project"]
fn medium_project_indexes_unopened_translation_units() {
let temp = tempdir().unwrap();
let root = temp.path();
let build = root.join("build");
fs::create_dir_all(build.join("meson-info")).unwrap();
fs::write(build.join("meson-info/intro-projectinfo.json"), "{}").unwrap();
fs::write(root.join("api.hpp"), "int target(int);\n").unwrap();
let mut commands = Vec::new();
for i in 0..120 {
let name = format!("unit{i}.cpp");
let mut code = format!("#include \"api.hpp\"\nint caller{i}() {{ return target({i}); }}\n");
for line in 2..1000 {
code.push_str(&format!("constexpr int value{i}_{line} = {line};\n"));
}
fs::write(root.join(&name), code).unwrap();
commands.push(json!({"directory": root, "file": root.join(&name), "arguments": ["clang++", "-std=c++23", "-c", name]}));
}
fs::write(
build.join("compile_commands.json"),
serde_json::to_vec(&commands).unwrap(),
)
.unwrap();
let mut workspace = crate::open(root, Some(&build), false).unwrap();
// Search filtering must not remove translation units from clangd's index.
workspace
.save_options(vec!["unit*.cpp".into()], "clangd".into())
.unwrap();
let started = Instant::now();
let server = Clangd::start(workspace).unwrap();
let position = Position {
line: 0,
character: 5,
};
loop {
let references = server
.query("api.hpp", position, Query::References)
.unwrap();
if references
.locations
.iter()
.filter(|l| l.path.ends_with(".cpp"))
.count()
== 120
{
break;
}
assert!(
started.elapsed() < Duration::from_secs(45),
"Index incomplete: {} references",
references.locations.len()
);
thread::sleep(Duration::from_millis(200));
}
let callers = server.query("api.hpp", position, Query::Callers).unwrap();
assert_eq!(callers.locations.len(), 120);
eprintln!(
"120,000 C++ lines, 120 translation units: all callers indexed in {:?}",
started.elapsed()
);
server.stop();
}
#[test]
#[ignore = "requires clangd"]
fn composed_concept_dependencies() {
let temp = tempdir().unwrap();
let root = temp.path();
let build = root.join("build");
fs::create_dir_all(build.join("meson-info")).unwrap();
fs::write(build.join("meson-info/intro-projectinfo.json"), "{}").unwrap();
for (name, content) in [
(
"foundation.hpp",
include_str!("../../../../examples/concepts/foundation.hpp"),
),
(
"composed.hpp",
include_str!("../../../../examples/concepts/composed.hpp"),
),
(
"main.cpp",
include_str!("../../../../examples/concepts/main.cpp"),
),
(
"policies.hpp",
include_str!("../../../../examples/concepts/policies.hpp"),
),
] {
fs::write(root.join(name), content).unwrap();
}
fs::write(build.join("compile_commands.json"), serde_json::to_vec(&json!([{"directory":root,"file":root.join("main.cpp"),"arguments":["clang++","-std=c++23","-c",root.join("main.cpp")]}])).unwrap()).unwrap();
let workspace = crate::open(root, Some(&build), false).unwrap();
let server = Clangd::start(workspace.clone()).unwrap();
let policies = fs::read_to_string(root.join("policies.hpp")).unwrap();
for (concept, function, unrelated) in [
(
"IsReactionChainPolicy",
"registerReactionChainPolicyDefs",
"registerNetworkPolicyDefs",
),
(
"IsNetworkPolicy",
"registerNetworkPolicyDefs",
"registerReactionChainPolicyDefs",
),
] {
let line = policies
.lines()
.position(|l| l.contains(&format!("concept {concept} =")))
.unwrap();
let position = Position {
line: line as u32,
character: policies.lines().nth(line).unwrap().find(concept).unwrap() as u32,
};
let deadline = Instant::now() + Duration::from_secs(10);
let graph = loop {
let graph = server
.query("policies.hpp", position, Query::ConceptGraph)
.unwrap()
.graph
.unwrap();
if graph.nodes.iter().any(|n| n.label == function) {
break graph;
}
assert!(Instant::now() < deadline, "Missing {function}: {graph:?}");
thread::sleep(Duration::from_millis(100));
};
let functions: Vec<_> = graph.nodes.iter().filter(|n| n.label == function).collect();
assert_eq!(
functions.len(),
1,
"Two constrained parameters must share one function node"
);
assert!(
graph
.edges
.iter()
.any(|e| e.to == functions[0].id && e.label == "constrains"),
"{graph:?}"
);
assert!(
!graph.nodes.iter().any(|n| n.label == unrelated),
"{graph:?}"
);
if concept == "IsNetworkPolicy" {
let line = policies
.lines()
.position(|l| l.starts_with("static_assert"))
.unwrap();
let usage = graph
.nodes
.iter()
.find(|n| n.label == format!("policies.hpp:{}", line + 1))
.unwrap();
assert!(
graph
.edges
.iter()
.any(|e| e.to == usage.id && e.label == "used by")
);
}
}
let token = tokio_util::sync::CancellationToken::new();
let position = |source: &str, name: &str| {
let line = source
.lines()
.position(|line| line.contains(&format!("concept {name} =")))
.unwrap();
Position {
line: line as u32,
character: source.lines().nth(line).unwrap().find(name).unwrap() as u32,
}
};
let code = fs::read_to_string(root.join("composed.hpp")).unwrap();
let direct = server
.query(
"composed.hpp",
position(&code, "Flexible"),
Query::Constraints,
)
.unwrap();
assert!(direct.is_concept);
let labels: Vec<_> = direct.locations.iter().map(|l| l.label.as_str()).collect();
assert!(
labels.contains(&"demo::Arithmetic")
&& labels.contains(&"demo::Sized")
&& labels.contains(&"std::copyable"),
"{labels:?}"
);
let sized = server
.query("composed.hpp", position(&code, "Sized"), Query::Constraints)
.unwrap();
assert!(
sized.locations.iter().any(|l| l.label == "std::integral"),
"{sized:?}"
);
assert!(
sized.locations.iter().any(|l| l.label == "std::same_as"),
"{sized:?}"
);
let prepared = crate::explanation::prepare(
&workspace,
server.as_ref(),
"composed.hpp",
position(&code, "Flexible"),
None,
"Explain all alternatives",
&token,
)
.unwrap();
assert!(prepared.concept_dependencies && !prepared.include_standard);
assert!(
prepared
.available_callees
.iter()
.any(|c| c.is_standard && c.location.label == "std::copyable")
);
let ids: Vec<_> = prepared
.available_callees
.iter()
.map(|c| c.id.clone())
.collect();
let one = crate::explanation::expand(&prepared, server.as_ref(), &ids, 1, "", &token).unwrap();
assert!(
one.bundle
.sources
.iter()
.any(|s| s.enclosing == "Arithmetic")
);
assert!(!one.bundle.sources.iter().any(|s| s.enclosing == "Addable"));
let two = crate::explanation::expand(&prepared, server.as_ref(), &ids, 2, "", &token).unwrap();
assert!(
two.bundle.sources.iter().any(|s| s.enclosing == "Addable"),
"{two:?}"
);
assert!(
two.bundle.sources.iter().all(|s| !s.path.starts_with('/')),
"Standard definitions leaked into default expansion"
);
assert!(
two.bundle
.omissions
.iter()
.any(|s| s.contains("std namespace"))
);
let with_std = crate::explanation::expand_with_standard(
&prepared,
server.as_ref(),
&ids,
1,
true,
"",
&token,
)
.unwrap();
assert!(
with_std
.bundle
.sources
.iter()
.any(|s| s.enclosing == "copyable"),
"{with_std:?}"
);
let outer = crate::explanation::prepare(
&workspace,
server.as_ref(),
"composed.hpp",
position(&code, "Outer"),
None,
"",
&token,
)
.unwrap();
let outer_ids: Vec<_> = outer
.available_callees
.iter()
.map(|c| c.id.clone())
.collect();
let expanded =
crate::explanation::expand(&outer, server.as_ref(), &outer_ids, 2, "", &token).unwrap();
assert!(
expanded
.bundle
.sources
.iter()
.any(|s| s.enclosing == "Flexible")
);
assert!(
expanded
.bundle
.sources
.iter()
.any(|s| s.enclosing == "Sized")
);
assert_eq!(
expanded
.bundle
.sources
.iter()
.filter(|s| s.enclosing == "Addable")
.count(),
1
);
assert!(
crate::explanation::expand(&outer, server.as_ref(), &outer_ids, 3, "", &token).is_err()
);
let deadline = Instant::now() + Duration::from_secs(10);
let graph = loop {
let graph = server
.query(
"composed.hpp",
position(&code, "Flexible"),
Query::ConceptGraph,
)
.unwrap()
.graph
.unwrap();
if graph
.nodes
.iter()
.any(|n| n.label.contains("constrained_identity"))
{
break graph;
}
assert!(
Instant::now() < deadline,
"Missing constrained declaration: {graph:?}"
);
thread::sleep(Duration::from_millis(100));
};
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "operator" && n.label == "||")
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "operator" && n.label == "&&")
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "requires" && n.detail.contains("copyable"))
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "concept" && n.label.contains("Arithmetic"))
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "concept" && n.label.contains("Sized"))
);
let constrained = graph
.nodes
.iter()
.find(|n| n.label.contains("constrained_identity"))
.unwrap();
assert!(
graph
.edges
.iter()
.any(|e| e.to == constrained.id && e.label == "constrains"),
"{graph:?}"
);
assert_eq!(
graph
.nodes
.iter()
.filter(|n| n.label.contains("constrained_identity"))
.count(),
1
);
assert!(graph.nodes.iter().any(|n| n.label == "main.cpp:3"));
let type_use = graph.nodes.iter().find(|n| n.label == "type_use").unwrap();
assert!(
graph
.edges
.iter()
.any(|e| e.to == type_use.id && e.label == "used by")
);
let ordinary = graph
.nodes
.iter()
.find(|n| n.label == "ordinary_use")
.unwrap();
assert!(
graph
.edges
.iter()
.any(|e| e.to == ordinary.id && e.label == "used by"),
"{graph:?}"
);
let outer = graph.nodes.iter().find(|n| n.label == "Outer").unwrap();
assert!(
graph
.edges
.iter()
.any(|e| e.to == outer.id && e.label == "contributes to")
);
assert!(
graph
.edges
.iter()
.all(|e| graph.nodes.iter().any(|n| n.id == e.from)
&& graph.nodes.iter().any(|n| n.id == e.to))
);
let recursive_graph = server
.graph(
"composed.hpp",
position(&code, "Flexible"),
&GraphOptions {
depth: 2,
..GraphOptions::default()
},
)
.unwrap();
assert!(
recursive_graph
.nodes
.iter()
.any(|n| n.label == "demo::Addable"),
"{recursive_graph:?}"
);
assert!(
!recursive_graph
.nodes
.iter()
.any(|n| n.label.starts_with("std::")),
"{recursive_graph:?}"
);
let library_graph = server
.graph(
"composed.hpp",
position(&code, "Flexible"),
&GraphOptions {
depth: 1,
excluded_namespaces: vec![],
},
)
.unwrap();
assert!(
library_graph
.nodes
.iter()
.any(|n| n.label == "std::copyable"),
"{library_graph:?}"
);
let ordinary_position = Position {
line: fs::read_to_string(root.join("main.cpp"))
.unwrap()
.lines()
.position(|l| l.contains("bool ordinary_use"))
.unwrap() as u32,
character: 6,
};
assert!(
server
.query("main.cpp", ordinary_position, Query::ConceptGraph)
.is_err()
);
server.stop();
}
#[test]
#[ignore = "requires cmake, ninja, a C++23 compiler, and clangd"]
fn cmake_cpp23_navigation_with_generated_headers() {
let temp = tempdir().unwrap();
let root = temp.path().join("source with spaces");
let build = temp.path().join("external build");
fs::create_dir_all(root.join("src")).unwrap();
for (name, content) in [
(
"CMakeLists.txt",
include_str!("../../../../examples/cmake/CMakeLists.txt"),
),
(
"config.hpp.in",
include_str!("../../../../examples/cmake/config.hpp.in"),
),
(
"main.cpp",
include_str!("../../../../examples/cmake/main.cpp"),
),
(
"src/numbers.hpp",
include_str!("../../../../examples/cmake/src/numbers.hpp"),
),
(
"src/numbers.cpp",
include_str!("../../../../examples/cmake/src/numbers.cpp"),
),
] {
fs::write(root.join(name), content).unwrap();
}
let configure = std::process::Command::new("cmake")
.arg("-S")
.arg(&root)
.arg("-B")
.arg(&build)
.args(["-G", "Ninja", "-DCMAKE_EXPORT_COMPILE_COMMANDS=ON"])
.output()
.unwrap();
assert!(
configure.status.success(),
"{}\n{}",
String::from_utf8_lossy(&configure.stdout),
String::from_utf8_lossy(&configure.stderr)
);
let cache = fs::read(build.join("CMakeCache.txt")).unwrap();
let database = fs::read(build.join("compile_commands.json")).unwrap();
let workspace = crate::open(&root, Some(&build), false).unwrap();
let server = Clangd::start(workspace).unwrap();
let definition = server
.query(
"main.cpp",
Position {
line: 6,
character: 22,
},
Query::Definition,
)
.unwrap();
assert!(
definition
.locations
.iter()
.any(|l| l.path == "src/numbers.cpp"),
"{definition:?}"
);
let generated = server
.query(
"src/numbers.cpp",
Position {
line: 2,
character: 46,
},
Query::Definition,
)
.unwrap();
let config = generated
.locations
.iter()
.find(|l| l.path.ends_with("generated/config.hpp"))
.expect("Resolve generated header using CMake's include flags");
assert!(
server
.read_target(&config.path)
.unwrap()
.content
.contains("#define DEMO_INCREMENT 1")
);
let deadline = Instant::now() + Duration::from_secs(15);
loop {
let callers = server
.query(
"src/numbers.hpp",
Position {
line: 3,
character: 5,
},
Query::Callers,
)
.unwrap();
if callers.locations.iter().any(|l| l.path == "main.cpp") {
break;
}
assert!(Instant::now() < deadline, "{callers:?}");
thread::sleep(Duration::from_millis(100));
}
server.stop();
assert!(root.join(".cex/clangd/compile_commands.json").is_file());
assert_eq!(
fs::read(build.join("compile_commands.json")).unwrap(),
database
);
assert_eq!(fs::read(build.join("CMakeCache.txt")).unwrap(), cache);
assert!(!build.join(".cache").exists());
}
#[test]
#[ignore = "requires clangd and a C++23 compiler"]
fn function_graph_depth_namespaces_and_cycles() {
let temp = tempdir().unwrap();
let root = temp.path();
let build = root.join("build");
fs::create_dir_all(build.join("meson-info")).unwrap();
fs::write(build.join("meson-info/intro-projectinfo.json"), "{}").unwrap();
let code = "#include <utility>\nnamespace alpha {\nint leaf(int n) { return n+1; }\nint middle(int n) { return leaf(n); }\n}\nnamespace a = alpha;\nint recursive(int n) { return n ? recursive(n-1) : 0; }\nint target(int n) { return a::middle(n) + std::move(n) + recursive(n); }\nint caller(int n) { return target(n) + target(n); }\nint outer(int n) { return caller(n); }\n";
fs::write(root.join("main.cpp"), code).unwrap();
fs::write(build.join("compile_commands.json"), serde_json::to_vec(&json!([{"directory":root,"file":root.join("main.cpp"),"arguments":["clang++","-std=c++23","-c",root.join("main.cpp")]}])).unwrap()).unwrap();
let workspace = crate::open(root, Some(&build), false).unwrap();
let server = Clangd::start(workspace).unwrap();
let pos = Position {
line: 7,
character: 5,
};
let deadline = Instant::now() + Duration::from_secs(10);
let direct = loop {
let graph = server
.graph("main.cpp", pos, &GraphOptions::default())
.unwrap();
if graph.nodes.iter().any(|n| n.label == "caller") {
break graph;
}
assert!(Instant::now() < deadline, "{graph:?}");
thread::sleep(Duration::from_millis(100));
};
assert!(
direct.nodes.iter().any(|n| n.label == "alpha::middle"),
"{direct:?}"
);
assert!(
!direct.nodes.iter().any(|n| n.label == "alpha::leaf"
|| n.label == "outer"
|| n.label.starts_with("std::")),
"{direct:?}"
);
let middle = direct
.nodes
.iter()
.find(|n| n.label == "alpha::middle")
.unwrap();
let caller = direct.nodes.iter().find(|n| n.label == "caller").unwrap();
assert!(
direct
.edges
.iter()
.any(|e| e.from == middle.id && e.to == "selected")
);
assert!(
direct
.edges
.iter()
.any(|e| e.from == "selected" && e.to == caller.id)
);
assert_eq!(
direct.nodes.iter().filter(|n| n.label == "caller").count(),
1
);
let expanded = server
.graph(
"main.cpp",
pos,
&GraphOptions {
depth: 2,
..GraphOptions::default()
},
)
.unwrap();
assert!(
expanded.nodes.iter().any(|n| n.label == "alpha::leaf"),
"{expanded:?}"
);
assert!(
expanded.nodes.iter().any(|n| n.label == "outer"),
"{expanded:?}"
);
assert!(
expanded.edges.iter().any(|e| e.from == e.to),
"Recursive calls should be preserved: {expanded:?}"
);
let included = server
.graph(
"main.cpp",
pos,
&GraphOptions {
depth: 1,
excluded_namespaces: vec![],
},
)
.unwrap();
assert!(
included
.nodes
.iter()
.any(|n| n.label.starts_with("std::move")),
"{included:?}"
);
let excluded = server
.graph(
"main.cpp",
pos,
&GraphOptions {
depth: 2,
excluded_namespaces: vec!["std".into(), "alpha".into()],
},
)
.unwrap();
assert!(
!excluded
.nodes
.iter()
.any(|n| n.label.starts_with("alpha::")),
"{excluded:?}"
);
assert!(
server
.graph(
"main.cpp",
pos,
&GraphOptions {
depth: 5,
..GraphOptions::default()
}
)
.is_err()
);
server.stop();
}
#[test]
#[ignore = "requires clangd"]
fn type_graph_constraints_and_members() {
let temp = tempdir().unwrap();
let root = temp.path();
let build = root.join("build");
fs::create_dir_all(build.join("meson-info")).unwrap();
fs::write(build.join("meson-info/intro-projectinfo.json"), "{}").unwrap();
fs::write(
root.join("types.hpp"),
include_str!("../../../../examples/types/types.hpp"),
)
.unwrap();
fs::write(
root.join("main.cpp"),
include_str!("../../../../examples/types/main.cpp"),
)
.unwrap();
fs::write(build.join("compile_commands.json"),serde_json::to_vec(&json!([{"directory":root,"file":root.join("main.cpp"),"arguments":["clang++","-std=c++23","-c",root.join("main.cpp")]}])).unwrap()).unwrap();
let server = Clangd::start(crate::open(root, Some(&build), false).unwrap()).unwrap();
let source = include_str!("../../../../examples/types/types.hpp");
let position = |needle: &str| {
let (line, text) = source
.lines()
.enumerate()
.find(|(_, l)| l.contains(needle))
.unwrap();
Position {
line: line as u32,
character: (text.find(needle).unwrap() + 1) as u32,
}
};
let pos = position("Box :");
let graph = server
.graph(
"types.hpp",
pos,
&GraphOptions {
depth: 1,
excluded_namespaces: vec![],
},
)
.unwrap();
for label in [
"demo::Base",
"demo::Helper",
"demo::Box::value",
"demo::Box::helper",
"demo::Box::set",
"demo::Box::run",
"demo::Addable",
"demo::Sized",
"std::copyable",
"std::constructible_from",
"std::integral",
"std::convertible_to",
] {
assert!(
graph.nodes.iter().any(|n| n.label == label),
"Missing {label}: {graph:?}"
);
}
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "requires" && n.detail.contains("u.size()"))
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "parameter" && n.label == "argument x")
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "template-parameter" && n.label == "template T")
);
let default_graph = server
.graph("types.hpp", pos, &GraphOptions::default())
.unwrap();
assert!(
!default_graph
.nodes
.iter()
.any(|n| n.label.starts_with("std::"))
);
let node = |label: &str| graph.nodes.iter().find(|n| n.label == label).unwrap();
let edge = |from: &str, to: &str, label: &str| {
graph
.edges
.iter()
.any(|e| e.from == from && e.to == to && e.label == label)
};
assert!(edge(&node("demo::Base").id, "selected", "base of"));
assert!(edge(
&node("demo::Helper").id,
&node("demo::Box::helper").id,
"type of"
));
assert!(edge(
&node("template T").id,
&node("demo::Box::value").id,
"type of"
));
assert!(edge(
&node("std::integral").id,
&node("argument n").id,
"constrains"
));
let u = graph
.nodes
.iter()
.find(|n| {
n.label == "template U" && edge(&n.id, &node("demo::Box::set").id, "template parameter")
})
.unwrap();
let argument = graph
.nodes
.iter()
.find(|n| n.label == "argument u" && edge(&n.id, &node("demo::Box::set").id, "argument"))
.unwrap();
assert!(edge(&node("demo::Sized").id, &u.id, "constrains"));
assert!(edge(&u.id, &argument.id, "type of"));
let calls = server
.graph(
"types.hpp",
pos,
&GraphOptions {
depth: 2,
..Default::default()
},
)
.unwrap();
let work = calls
.nodes
.iter()
.find(|n| n.label == "demo::Helper::work")
.unwrap();
let set = calls
.nodes
.iter()
.find(|n| n.label == "demo::Box::set")
.unwrap();
assert!(
calls
.edges
.iter()
.any(|e| e.from == work.id && e.to == set.id && e.label == "called by")
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "operator" && n.label == "||")
);
assert!(
graph
.nodes
.iter()
.any(|n| n.kind == "type" && n.label.starts_with("std::vector")),
"{graph:?}"
);
let method = server
.graph(
"types.hpp",
position("set("),
&GraphOptions {
depth: 1,
excluded_namespaces: vec![],
},
)
.unwrap();
assert!(method.title.ends_with("::set"), "{method:?}");
assert!(
method.nodes.iter().any(|n| n.label == "demo::Sized"),
"{method:?}"
);
let constructor = server
.graph(
"types.hpp",
position("Box(Addable"),
&GraphOptions {
depth: 1,
excluded_namespaces: vec![],
},
)
.unwrap();
assert!(
constructor
.nodes
.iter()
.any(|n| n.label == "std::constructible_from"),
"{constructor:?}"
);
let alias = server
.graph("types.hpp", position("IntBox ="), &GraphOptions::default())
.unwrap();
assert!(
alias.title == "demo::Box"
|| alias
.nodes
.iter()
.any(|n| n.kind == "type" && n.label == "demo::Box"),
"{alias:?}"
);
let enumeration = server
.graph("types.hpp", position("State {"), &GraphOptions::default())
.unwrap();
assert!(
enumeration.nodes.iter().any(|n| n.label == "idle"),
"{enumeration:?}"
);
server.stop();
}