//! Discovers tests use hir::{Crate, Module, ModuleDef, Semantics}; use ide_db::{ base_db::{CrateGraph, CrateId, FileId, SourceDatabase}, RootDatabase, }; use syntax::TextRange; use crate::{navigation_target::ToNav, runnables::runnable_fn, Runnable, TryToNav}; #[derive(Debug)] pub enum TestItemKind { Crate, Module, Function, } #[derive(Debug)] pub struct TestItem { pub id: String, pub kind: TestItemKind, pub label: String, pub parent: Option, pub file: Option, pub text_range: Option, pub runnable: Option, } pub(crate) fn discover_test_roots(db: &RootDatabase) -> Vec { let crate_graph = db.crate_graph(); crate_graph .iter() .filter(|&id| crate_graph[id].origin.is_local()) .filter_map(|id| Some(crate_graph[id].display_name.as_ref()?.to_string())) .map(|id| TestItem { kind: TestItemKind::Crate, label: id.clone(), id, parent: None, file: None, text_range: None, runnable: None, }) .collect() } fn find_crate_by_id(crate_graph: &CrateGraph, crate_id: &str) -> Option { // here, we use display_name as the crate id. This is not super ideal, but it works since we // only show tests for the local crates. crate_graph.iter().find(|&id| { crate_graph[id].origin.is_local() && crate_graph[id].display_name.as_ref().is_some_and(|x| x.to_string() == crate_id) }) } fn discover_tests_in_module(db: &RootDatabase, module: Module, prefix_id: String) -> Vec { let sema = Semantics::new(db); let mut r = vec![]; for c in module.children(db) { let module_name = c.name(db).as_ref().and_then(|n| n.as_str()).unwrap_or("[mod without name]").to_owned(); let module_id = format!("{prefix_id}::{module_name}"); let module_children = discover_tests_in_module(db, c, module_id.clone()); if !module_children.is_empty() { let nav = c.to_nav(db).call_site; r.push(TestItem { id: module_id, kind: TestItemKind::Module, label: module_name, parent: Some(prefix_id.clone()), file: Some(nav.file_id), text_range: Some(nav.focus_or_full_range()), runnable: None, }); r.extend(module_children); } } for def in module.declarations(db) { let ModuleDef::Function(f) = def else { continue; }; if !f.is_test(db) { continue; } let nav = f.try_to_nav(db).map(|r| r.call_site); let fn_name = f.name(db).as_str().unwrap_or("[function without name]").to_owned(); r.push(TestItem { id: format!("{prefix_id}::{fn_name}"), kind: TestItemKind::Function, label: fn_name, parent: Some(prefix_id.clone()), file: nav.as_ref().map(|n| n.file_id), text_range: nav.as_ref().map(|n| n.focus_or_full_range()), runnable: runnable_fn(&sema, f), }); } r } pub(crate) fn discover_tests_in_crate_by_test_id( db: &RootDatabase, crate_test_id: &str, ) -> Vec { let crate_graph = db.crate_graph(); let Some(crate_id) = find_crate_by_id(&crate_graph, crate_test_id) else { return vec![]; }; discover_tests_in_crate(db, crate_id) } pub(crate) fn discover_tests_in_crate(db: &RootDatabase, crate_id: CrateId) -> Vec { let crate_graph = db.crate_graph(); if !crate_graph[crate_id].origin.is_local() { return vec![]; } let Some(crate_test_id) = &crate_graph[crate_id].display_name else { return vec![]; }; let crate_test_id = crate_test_id.to_string(); let crate_id: Crate = crate_id.into(); let module = crate_id.root_module(); let mut r = vec![TestItem { id: crate_test_id.clone(), kind: TestItemKind::Crate, label: crate_test_id.clone(), parent: None, file: None, text_range: None, runnable: None, }]; r.extend(discover_tests_in_module(db, module, crate_test_id)); r }