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 \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);\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::>(), 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 \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(); }