6 use hir::{AsAssocItem, HasAttrs, HasSource, HirDisplay, Semantics};
7 use ide_assists::utils::test_related_attribute;
9 base_db::{FilePosition, FileRange},
10 helpers::visit_file_defs,
12 RootDatabase, SymbolKind,
14 use itertools::Itertools;
15 use rustc_hash::{FxHashMap, FxHashSet};
16 use stdx::{always, format_to};
17 use syntax::ast::{self, AstNode, AttrsOwner};
19 use crate::{display::TryToNav, references, FileId, NavigationTarget};
21 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
23 pub use_name_in_title: bool,
24 pub nav: NavigationTarget,
25 pub kind: RunnableKind,
26 pub cfg: Option<CfgExpr>,
29 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
35 impl fmt::Display for TestId {
36 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
38 TestId::Name(name) => write!(f, "{}", name),
39 TestId::Path(path) => write!(f, "{}", path),
44 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
45 pub enum RunnableKind {
46 Test { test_id: TestId, attr: TestAttr },
47 TestMod { path: String },
48 Bench { test_id: TestId },
49 DocTest { test_id: TestId },
54 #[derive(Debug, Clone, Hash, PartialEq, Eq)]
55 enum RunnableTestKind {
64 // test package::module::testname
65 pub fn label(&self, target: Option<String>) -> String {
67 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
68 RunnableKind::TestMod { path } => format!("test-mod {}", path),
69 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
70 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
71 RunnableKind::Bin => {
72 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
77 pub fn can_debug(&self) -> bool {
79 RunnableKind::DocTest { .. } => false,
80 RunnableKind::TestMod { .. }
81 | RunnableKind::Test { .. }
82 | RunnableKind::Bench { .. }
83 | RunnableKind::Bin => true,
87 pub fn title(&self) -> String {
88 let mut s = String::from("▶\u{fe0e} Run ");
89 if self.use_name_in_title {
90 format_to!(s, "{}", self.nav.name);
91 if !matches!(self.kind, RunnableKind::Bin) {
95 let suffix = match &self.kind {
96 RunnableKind::TestMod { .. } => "Tests",
97 RunnableKind::Test { .. } => "Test",
98 RunnableKind::DocTest { .. } => "Doctest",
99 RunnableKind::Bench { .. } => "Bench",
100 RunnableKind::Bin => return s,
107 fn test_kind(&self) -> RunnableTestKind {
109 RunnableKind::TestMod { .. } => RunnableTestKind::TestMod,
110 RunnableKind::Test { .. } => RunnableTestKind::Test,
111 RunnableKind::DocTest { .. } => RunnableTestKind::DocTest,
112 RunnableKind::Bench { .. } => RunnableTestKind::Bench,
113 RunnableKind::Bin => RunnableTestKind::Bin,
120 // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
121 // location**. Super useful for repeatedly running just a single test. Do bind this
125 // | Editor | Action Name
127 // | VS Code | **Rust Analyzer: Run**
129 // image::https://user-images.githubusercontent.com/48062697/113065583-055aae80-91b1-11eb-958f-d67efcaf6a2f.gif[]
130 pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
131 let sema = Semantics::new(db);
133 let mut res = Vec::new();
134 // Record all runnables that come from macro expansions here instead.
135 // In case an expansion creates multiple runnables we want to name them to avoid emitting a bunch of equally named runnables.
136 let mut in_macro_expansion = FxHashMap::<hir::HirFileId, Vec<Runnable>>::default();
137 let mut add_opt = |runnable: Option<Runnable>, def| {
138 if let Some(runnable) = runnable.filter(|runnable| {
140 runnable.nav.file_id == file_id,
141 "tried adding a runnable pointing to a different file: {:?} for {:?}",
146 if let Some(def) = def {
147 let file_id = match def {
148 hir::ModuleDef::Module(it) => it.declaration_source(db).map(|src| src.file_id),
149 hir::ModuleDef::Function(it) => it.source(db).map(|src| src.file_id),
152 if let Some(file_id) = file_id.filter(|file| file.call_node(db).is_some()) {
153 in_macro_expansion.entry(file_id).or_default().push(runnable);
160 visit_file_defs(&sema, file_id, &mut |def| match def {
161 Either::Left(def) => {
162 let runnable = match def {
163 hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
164 hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
167 add_opt(runnable.or_else(|| module_def_doctest(&sema, def)), Some(def));
169 Either::Right(impl_) => {
170 add_opt(runnable_impl(&sema, &impl_), None);
177 hir::AssocItem::Function(it) => runnable_fn(&sema, it)
178 .or_else(|| module_def_doctest(&sema, it.into())),
179 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
180 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
185 .for_each(|(r, assoc)| add_opt(r, Some(assoc.into())));
189 res.extend(in_macro_expansion.into_iter().flat_map(|(_, runnables)| {
190 let use_name_in_title = runnables.len() != 1;
191 runnables.into_iter().map(move |mut r| {
192 r.use_name_in_title = use_name_in_title;
199 // Feature: Related Tests
201 // Provides a sneak peek of all tests where the current item is used.
203 // The simplest way to use this feature is via the context menu:
204 // - Right-click on the selected item. The context menu opens.
205 // - Select **Peek related tests**
208 // | Editor | Action Name
210 // | VS Code | **Rust Analyzer: Peek related tests**
212 pub(crate) fn related_tests(
214 position: FilePosition,
215 search_scope: Option<SearchScope>,
217 let sema = Semantics::new(db);
218 let mut res: FxHashSet<Runnable> = FxHashSet::default();
220 find_related_tests(&sema, position, search_scope, &mut res);
222 res.into_iter().collect_vec()
225 fn find_related_tests(
226 sema: &Semantics<RootDatabase>,
227 position: FilePosition,
228 search_scope: Option<SearchScope>,
229 tests: &mut FxHashSet<Runnable>,
231 if let Some(refs) = references::find_all_refs(sema, position, search_scope) {
232 for (file_id, refs) in refs.references {
233 let file = sema.parse(file_id);
234 let file = file.syntax();
235 let functions = refs.iter().filter_map(|(range, _)| {
236 let token = file.token_at_offset(range.start()).next()?;
237 let token = sema.descend_into_macros(token);
238 token.ancestors().find_map(ast::Fn::cast)
241 for fn_def in functions {
242 if let Some(runnable) = as_test_runnable(sema, &fn_def) {
244 tests.insert(runnable);
245 } else if let Some(module) = parent_test_module(sema, &fn_def) {
247 find_related_tests_in_module(sema, &fn_def, &module, tests);
254 fn find_related_tests_in_module(
255 sema: &Semantics<RootDatabase>,
257 parent_module: &hir::Module,
258 tests: &mut FxHashSet<Runnable>,
260 if let Some(fn_name) = fn_def.name() {
261 let mod_source = parent_module.definition_source(sema.db);
262 let range = match mod_source.value {
263 hir::ModuleSource::Module(m) => m.syntax().text_range(),
264 hir::ModuleSource::BlockExpr(b) => b.syntax().text_range(),
265 hir::ModuleSource::SourceFile(f) => f.syntax().text_range(),
268 let file_id = mod_source.file_id.original_file(sema.db);
269 let mod_scope = SearchScope::file_range(FileRange { file_id, range });
270 let fn_pos = FilePosition { file_id, offset: fn_name.syntax().text_range().start() };
271 find_related_tests(sema, fn_pos, Some(mod_scope), tests)
275 fn as_test_runnable(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
276 if test_related_attribute(fn_def).is_some() {
277 let function = sema.to_def(fn_def)?;
278 runnable_fn(sema, function)
284 fn parent_test_module(sema: &Semantics<RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
285 fn_def.syntax().ancestors().find_map(|node| {
286 let module = ast::Module::cast(node)?;
287 let module = sema.to_def(&module)?;
289 if has_test_function_or_multiple_test_submodules(sema, &module) {
297 pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
298 let func = def.source(sema.db)?;
299 let name_string = def.name(sema.db).to_string();
301 let root = def.module(sema.db).krate().root_module(sema.db);
303 let kind = if name_string == "main" && def.module(sema.db) == root {
306 let canonical_path = {
307 let def: hir::ModuleDef = def.into();
308 def.canonical_path(sema.db)
310 let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
312 if test_related_attribute(&func.value).is_some() {
313 let attr = TestAttr::from_fn(&func.value);
314 RunnableKind::Test { test_id, attr }
315 } else if func.value.has_atom_attr("bench") {
316 RunnableKind::Bench { test_id }
322 let nav = NavigationTarget::from_named(
324 func.as_ref().map(|it| it as &dyn ast::NameOwner),
325 SymbolKind::Function,
327 let cfg = def.attrs(sema.db).cfg();
328 Some(Runnable { use_name_in_title: false, nav, kind, cfg })
331 pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
332 if !has_test_function_or_multiple_test_submodules(sema, &def) {
336 def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
338 let attrs = def.attrs(sema.db);
339 let cfg = attrs.cfg();
340 let nav = NavigationTarget::from_module_to_decl(sema.db, def);
341 Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::TestMod { path }, cfg })
344 pub(crate) fn runnable_impl(sema: &Semantics<RootDatabase>, def: &hir::Impl) -> Option<Runnable> {
345 let attrs = def.attrs(sema.db);
346 if !has_runnable_doc_test(&attrs) {
349 let cfg = attrs.cfg();
350 let nav = def.try_to_nav(sema.db)?;
351 let ty = def.self_ty(sema.db);
352 let adt_name = ty.as_adt()?.name(sema.db);
353 let mut ty_args = ty.type_arguments().peekable();
354 let params = if ty_args.peek().is_some() {
355 format!("<{}>", ty_args.format_with(", ", |ty, cb| cb(&ty.display(sema.db))))
359 let test_id = TestId::Path(format!("{}{}", adt_name, params));
361 Some(Runnable { use_name_in_title: false, nav, kind: RunnableKind::DocTest { test_id }, cfg })
364 fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
365 let attrs = match def {
366 hir::ModuleDef::Module(it) => it.attrs(sema.db),
367 hir::ModuleDef::Function(it) => it.attrs(sema.db),
368 hir::ModuleDef::Adt(it) => it.attrs(sema.db),
369 hir::ModuleDef::Variant(it) => it.attrs(sema.db),
370 hir::ModuleDef::Const(it) => it.attrs(sema.db),
371 hir::ModuleDef::Static(it) => it.attrs(sema.db),
372 hir::ModuleDef::Trait(it) => it.attrs(sema.db),
373 hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
374 hir::ModuleDef::BuiltinType(_) => return None,
376 if !has_runnable_doc_test(&attrs) {
379 let def_name = def.name(sema.db).map(|it| it.to_string());
381 .canonical_path(sema.db)
382 // This probably belongs to canonical path?
384 let assoc_def = match def {
385 hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
386 hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
387 hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
390 // FIXME: this also looks very wrong
391 if let Some(assoc_def) = assoc_def {
392 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
393 let ty = imp.self_ty(sema.db);
394 if let Some(adt) = ty.as_adt() {
395 let name = adt.name(sema.db);
396 let idx = path.rfind(':').map_or(0, |idx| idx + 1);
397 let (prefix, suffix) = path.split_at(idx);
398 let mut ty_args = ty.type_arguments().peekable();
399 let params = if ty_args.peek().is_some() {
402 ty_args.format_with(", ", |ty, cb| cb(&ty.display(sema.db)))
407 return format!("{}{}{}::{}", prefix, name, params, suffix);
414 .or_else(|| def_name.clone().map(TestId::Name))?;
416 let mut nav = match def {
417 hir::ModuleDef::Module(def) => NavigationTarget::from_module_to_decl(sema.db, def),
418 def => def.try_to_nav(sema.db)?,
420 nav.focus_range = None;
421 nav.description = None;
425 use_name_in_title: false,
427 kind: RunnableKind::DocTest { test_id },
433 #[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
434 pub struct TestAttr {
439 fn from_fn(fn_def: &ast::Fn) -> TestAttr {
442 .filter_map(|attr| attr.simple_name())
443 .any(|attribute_text| attribute_text == "ignore");
448 const RUSTDOC_FENCE: &str = "```";
449 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
450 &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
452 fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
453 attrs.docs().map_or(false, |doc| {
454 let mut in_code_block = false;
456 for line in String::from(doc).lines() {
457 if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
458 in_code_block = !in_code_block;
463 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
474 // We could create runnables for modules with number_of_test_submodules > 0,
475 // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
476 fn has_test_function_or_multiple_test_submodules(
477 sema: &Semantics<RootDatabase>,
478 module: &hir::Module,
480 let mut number_of_test_submodules = 0;
482 for item in module.declarations(sema.db) {
484 hir::ModuleDef::Function(f) => {
485 if let Some(it) = f.source(sema.db) {
486 if test_related_attribute(&it.value).is_some() {
491 hir::ModuleDef::Module(submodule) => {
492 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
493 number_of_test_submodules += 1;
500 number_of_test_submodules > 1
505 use expect_test::{expect, Expect};
509 use super::{RunnableTestKind::*, *};
513 // FIXME: fold this into `expect` as well
514 actions: &[RunnableTestKind],
517 let (analysis, position) = fixture::position(ra_fixture);
518 let runnables = analysis.runnables(position.file_id).unwrap();
519 expect.assert_debug_eq(&runnables);
522 runnables.into_iter().map(|it| it.test_kind()).collect::<Vec<_>>().as_slice()
526 fn check_tests(ra_fixture: &str, expect: Expect) {
527 let (analysis, position) = fixture::position(ra_fixture);
528 let tests = analysis.related_tests(position, None).unwrap();
529 expect.assert_debug_eq(&tests);
533 fn test_runnables() {
554 &[Bin, Test, Test, Bench],
558 use_name_in_title: false,
559 nav: NavigationTarget {
572 use_name_in_title: false,
573 nav: NavigationTarget {
593 use_name_in_title: false,
594 nav: NavigationTarget {
614 use_name_in_title: false,
615 nav: NavigationTarget {
637 fn test_runnables_doc_test() {
647 fn should_have_runnable() {}
652 fn should_have_runnable_1() {}
661 fn should_have_runnable_2() {}
668 fn should_have_no_runnable_3() {}
675 fn should_have_no_runnable_4() {}
680 fn should_have_no_runnable() {}
685 fn should_have_no_runnable_2() {}
690 fn should_have_no_runnable_3() {}
693 /// arbitrary plain text
695 fn should_have_no_runnable_4() {}
698 /// arbitrary plain text
704 fn should_have_no_runnable_5() {}
709 fn should_have_no_runnable_6() {}
714 struct StructWithRunnable(String);
719 impl StructWithRunnable {}
730 impl Test for StructWithRunnable {}
732 &[Bin, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest, DocTest],
736 use_name_in_title: false,
737 nav: NavigationTarget {
750 use_name_in_title: false,
751 nav: NavigationTarget {
756 name: "should_have_runnable",
760 "should_have_runnable",
766 use_name_in_title: false,
767 nav: NavigationTarget {
772 name: "should_have_runnable_1",
776 "should_have_runnable_1",
782 use_name_in_title: false,
783 nav: NavigationTarget {
787 full_range: 150..254,
788 name: "should_have_runnable_2",
792 "should_have_runnable_2",
798 use_name_in_title: false,
799 nav: NavigationTarget {
803 full_range: 256..320,
804 name: "should_have_no_runnable_3",
808 "should_have_no_runnable_3",
814 use_name_in_title: false,
815 nav: NavigationTarget {
819 full_range: 322..398,
820 name: "should_have_no_runnable_4",
824 "should_have_no_runnable_4",
830 use_name_in_title: false,
831 nav: NavigationTarget {
835 full_range: 900..965,
836 name: "StructWithRunnable",
840 "StructWithRunnable",
846 use_name_in_title: false,
847 nav: NavigationTarget {
851 full_range: 967..1024,
852 focus_range: 1003..1021,
858 "StructWithRunnable",
864 use_name_in_title: false,
865 nav: NavigationTarget {
869 full_range: 1088..1154,
870 focus_range: 1133..1151,
876 "StructWithRunnable",
887 fn test_runnables_doc_test_in_impl() {
906 use_name_in_title: false,
907 nav: NavigationTarget {
920 use_name_in_title: false,
921 nav: NavigationTarget {
941 fn test_runnables_module() {
955 use_name_in_title: false,
956 nav: NavigationTarget {
964 description: "mod test_mod",
972 use_name_in_title: false,
973 nav: NavigationTarget {
984 "test_mod::test_foo1",
998 fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {
1004 mod nested_tests_0 {
1005 mod nested_tests_1 {
1007 fn nested_test_11() {}
1010 fn nested_test_12() {}
1013 mod nested_tests_2 {
1015 fn nested_test_2() {}
1018 mod nested_tests_3 {}
1021 mod nested_tests_4 {}
1024 &[TestMod, TestMod, TestMod, Test, Test, Test],
1028 use_name_in_title: false,
1029 nav: NavigationTarget {
1033 full_range: 22..323,
1034 focus_range: 26..40,
1035 name: "nested_tests_0",
1037 description: "mod nested_tests_0",
1040 path: "root_tests::nested_tests_0",
1045 use_name_in_title: false,
1046 nav: NavigationTarget {
1050 full_range: 51..192,
1051 focus_range: 55..69,
1052 name: "nested_tests_1",
1054 description: "mod nested_tests_1",
1057 path: "root_tests::nested_tests_0::nested_tests_1",
1062 use_name_in_title: false,
1063 nav: NavigationTarget {
1067 full_range: 202..286,
1068 focus_range: 206..220,
1069 name: "nested_tests_2",
1071 description: "mod nested_tests_2",
1074 path: "root_tests::nested_tests_0::nested_tests_2",
1079 use_name_in_title: false,
1080 nav: NavigationTarget {
1084 full_range: 84..126,
1085 focus_range: 107..121,
1086 name: "nested_test_11",
1091 "root_tests::nested_tests_0::nested_tests_1::nested_test_11",
1100 use_name_in_title: false,
1101 nav: NavigationTarget {
1105 full_range: 140..182,
1106 focus_range: 163..177,
1107 name: "nested_test_12",
1112 "root_tests::nested_tests_0::nested_tests_1::nested_test_12",
1121 use_name_in_title: false,
1122 nav: NavigationTarget {
1126 full_range: 235..276,
1127 focus_range: 258..271,
1128 name: "nested_test_2",
1133 "root_tests::nested_tests_0::nested_tests_2::nested_test_2",
1147 fn test_runnables_with_feature() {
1150 //- /lib.rs crate:foo cfg:feature=foo
1153 #[cfg(feature = "foo")]
1160 use_name_in_title: false,
1161 nav: NavigationTarget {
1166 focus_range: 36..45,
1193 fn test_runnables_with_features() {
1196 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
1199 #[cfg(all(feature = "foo", feature = "bar"))]
1206 use_name_in_title: false,
1207 nav: NavigationTarget {
1212 focus_range: 58..67,
1249 fn test_runnables_no_test_function_in_module() {
1266 fn test_doc_runnables_impl_mod() {
1284 use_name_in_title: false,
1285 nav: NavigationTarget {
1305 fn test_runnables_in_macro() {
1329 &[TestMod, TestMod, Test, Test],
1333 use_name_in_title: false,
1334 nav: NavigationTarget {
1338 full_range: 202..227,
1339 focus_range: 206..211,
1342 description: "mod tests",
1350 use_name_in_title: true,
1351 nav: NavigationTarget {
1355 full_range: 228..236,
1356 focus_range: 228..236,
1359 description: "mod tests2",
1367 use_name_in_title: true,
1368 nav: NavigationTarget {
1372 full_range: 228..236,
1373 focus_range: 228..236,
1379 "tests2::foo_test2",
1388 use_name_in_title: false,
1389 nav: NavigationTarget {
1393 full_range: 218..225,
1394 focus_range: 218..225,
1433 &[TestMod, Test, Test, Test],
1437 use_name_in_title: true,
1438 nav: NavigationTarget {
1442 full_range: 210..217,
1443 focus_range: 210..217,
1446 description: "mod foo_tests",
1454 use_name_in_title: true,
1455 nav: NavigationTarget {
1459 full_range: 210..217,
1460 focus_range: 210..217,
1475 use_name_in_title: true,
1476 nav: NavigationTarget {
1480 full_range: 210..217,
1481 focus_range: 210..217,
1496 use_name_in_title: true,
1497 nav: NavigationTarget {
1501 full_range: 210..217,
1502 focus_range: 210..217,
1522 fn dont_recurse_in_outline_submodules() {
1542 fn outline_submodule1() {
1558 use_name_in_title: false,
1559 nav: NavigationTarget {
1567 description: "mod m",
1580 fn outline_submodule2() {
1596 use_name_in_title: false,
1597 nav: NavigationTarget {
1602 focus_range: 12..14,
1617 use_name_in_title: false,
1618 nav: NavigationTarget {
1623 focus_range: 31..33,
1643 fn find_no_tests() {
1656 fn find_direct_fn_test() {
1672 use_name_in_title: false,
1673 nav: NavigationTarget {
1678 focus_range: 46..54,
1698 fn find_direct_struct_test() {
1703 fn foo(arg: &Foo) { };
1717 use_name_in_title: false,
1718 nav: NavigationTarget {
1722 full_range: 71..122,
1723 focus_range: 86..94,
1743 fn find_indirect_fn_test() {
1769 use_name_in_title: false,
1770 nav: NavigationTarget {
1774 full_range: 133..183,
1775 focus_range: 148..156,
1795 fn tests_are_unique() {
1821 use_name_in_title: false,
1822 nav: NavigationTarget {
1826 full_range: 52..115,
1827 focus_range: 67..75,
1842 use_name_in_title: false,
1843 nav: NavigationTarget {
1847 full_range: 121..185,
1848 focus_range: 136..145,
1868 fn doc_test_type_params() {
1875 impl<T, U> Foo<T, U> {
1885 use_name_in_title: false,
1886 nav: NavigationTarget {