]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/runnables.rs
Merge #7941
[rust.git] / crates / ide / src / runnables.rs
1 use std::fmt;
2
3 use cfg::CfgExpr;
4 use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
5 use ide_assists::utils::test_related_attribute;
6 use ide_db::{defs::Definition, RootDatabase, SymbolKind};
7 use itertools::Itertools;
8 use syntax::{
9     ast::{self, AstNode, AttrsOwner},
10     match_ast, SyntaxNode,
11 };
12
13 use crate::{
14     display::{ToNav, TryToNav},
15     FileId, NavigationTarget,
16 };
17
18 #[derive(Debug, Clone)]
19 pub struct Runnable {
20     pub nav: NavigationTarget,
21     pub kind: RunnableKind,
22     pub cfg: Option<CfgExpr>,
23 }
24
25 #[derive(Debug, Clone)]
26 pub enum TestId {
27     Name(String),
28     Path(String),
29 }
30
31 impl fmt::Display for TestId {
32     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
33         match self {
34             TestId::Name(name) => write!(f, "{}", name),
35             TestId::Path(path) => write!(f, "{}", path),
36         }
37     }
38 }
39
40 #[derive(Debug, Clone)]
41 pub enum RunnableKind {
42     Test { test_id: TestId, attr: TestAttr },
43     TestMod { path: String },
44     Bench { test_id: TestId },
45     DocTest { test_id: TestId },
46     Bin,
47 }
48
49 #[derive(Debug, Eq, PartialEq)]
50 pub struct RunnableAction {
51     pub run_title: &'static str,
52     pub debugee: bool,
53 }
54
55 const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
56 const DOCTEST: RunnableAction =
57     RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
58 const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
59 const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
60
61 impl Runnable {
62     // test package::module::testname
63     pub fn label(&self, target: Option<String>) -> String {
64         match &self.kind {
65             RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
66             RunnableKind::TestMod { path } => format!("test-mod {}", path),
67             RunnableKind::Bench { test_id } => format!("bench {}", test_id),
68             RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
69             RunnableKind::Bin => {
70                 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
71             }
72         }
73     }
74
75     pub fn action(&self) -> &'static RunnableAction {
76         match &self.kind {
77             RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
78             RunnableKind::DocTest { .. } => &DOCTEST,
79             RunnableKind::Bench { .. } => &BENCH,
80             RunnableKind::Bin => &BIN,
81         }
82     }
83 }
84
85 // Feature: Run
86 //
87 // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
88 // location**. Super useful for repeatedly running just a single test. Do bind this
89 // to a shortcut!
90 //
91 // |===
92 // | Editor  | Action Name
93 //
94 // | VS Code | **Rust Analyzer: Run**
95 // |===
96 pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
97     let sema = Semantics::new(db);
98     let module = match sema.to_module_def(file_id) {
99         None => return Vec::new(),
100         Some(it) => it,
101     };
102
103     let mut res = Vec::new();
104     runnables_mod(&sema, &mut res, module);
105     res
106 }
107
108 fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
109     acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
110         let runnable = match def {
111             hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
112             hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
113             _ => None,
114         };
115         runnable.or_else(|| module_def_doctest(&sema, def))
116     }));
117
118     acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map(
119         |def| match def {
120             hir::AssocItem::Function(it) => {
121                 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
122             }
123             hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
124             hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
125         },
126     ));
127
128     for def in module.declarations(sema.db) {
129         if let hir::ModuleDef::Module(submodule) = def {
130             match submodule.definition_source(sema.db).value {
131                 hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule),
132                 hir::ModuleSource::SourceFile(_) => {
133                     cov_mark::hit!(dont_recurse_in_outline_submodules)
134                 }
135                 hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable
136             }
137         }
138     }
139 }
140
141 pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
142     let func = def.source(sema.db)?;
143     let name_string = def.name(sema.db).to_string();
144
145     let kind = if name_string == "main" {
146         RunnableKind::Bin
147     } else {
148         let canonical_path = {
149             let def: hir::ModuleDef = def.into();
150             def.canonical_path(sema.db)
151         };
152         let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
153
154         if test_related_attribute(&func.value).is_some() {
155             let attr = TestAttr::from_fn(&func.value);
156             RunnableKind::Test { test_id, attr }
157         } else if func.value.has_atom_attr("bench") {
158             RunnableKind::Bench { test_id }
159         } else {
160             return None;
161         }
162     };
163
164     let nav = NavigationTarget::from_named(
165         sema.db,
166         func.as_ref().map(|it| it as &dyn ast::NameOwner),
167         SymbolKind::Function,
168     );
169     let cfg = def.attrs(sema.db).cfg();
170     Some(Runnable { nav, kind, cfg })
171 }
172
173 pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
174     if !has_test_function_or_multiple_test_submodules(sema, &def) {
175         return None;
176     }
177     let path =
178         def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
179
180     let attrs = def.attrs(sema.db);
181     let cfg = attrs.cfg();
182     let nav = def.to_nav(sema.db);
183     Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
184 }
185
186 // FIXME: figure out a proper API here.
187 pub(crate) fn doc_owner_to_def(
188     sema: &Semantics<RootDatabase>,
189     item: SyntaxNode,
190 ) -> Option<Definition> {
191     let res: hir::ModuleDef = match_ast! {
192         match item {
193             ast::SourceFile(_it) => sema.scope(&item).module()?.into(),
194             ast::Fn(it) => sema.to_def(&it)?.into(),
195             ast::Struct(it) => sema.to_def(&it)?.into(),
196             ast::Enum(it) => sema.to_def(&it)?.into(),
197             ast::Union(it) => sema.to_def(&it)?.into(),
198             ast::Trait(it) => sema.to_def(&it)?.into(),
199             ast::Const(it) => sema.to_def(&it)?.into(),
200             ast::Static(it) => sema.to_def(&it)?.into(),
201             ast::TypeAlias(it) => sema.to_def(&it)?.into(),
202             _ => return None,
203         }
204     };
205     Some(Definition::ModuleDef(res))
206 }
207
208 fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
209     let attrs = match def {
210         hir::ModuleDef::Module(it) => it.attrs(sema.db),
211         hir::ModuleDef::Function(it) => it.attrs(sema.db),
212         hir::ModuleDef::Adt(it) => it.attrs(sema.db),
213         hir::ModuleDef::Variant(it) => it.attrs(sema.db),
214         hir::ModuleDef::Const(it) => it.attrs(sema.db),
215         hir::ModuleDef::Static(it) => it.attrs(sema.db),
216         hir::ModuleDef::Trait(it) => it.attrs(sema.db),
217         hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
218         hir::ModuleDef::BuiltinType(_) => return None,
219     };
220     if !has_runnable_doc_test(&attrs) {
221         return None;
222     }
223     let def_name = def.name(sema.db).map(|it| it.to_string());
224     let test_id = def
225         .canonical_path(sema.db)
226         // This probably belongs to canonical path?
227         .map(|path| {
228             let assoc_def = match def {
229                 hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
230                 hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
231                 hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
232                 _ => None,
233             };
234             // FIXME: this also looks very wrong
235             if let Some(assoc_def) = assoc_def {
236                 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
237                     if let Some(adt) = imp.target_ty(sema.db).as_adt() {
238                         let name = adt.name(sema.db).to_string();
239                         let idx = path.rfind(':').map_or(0, |idx| idx + 1);
240                         let (prefix, suffix) = path.split_at(idx);
241                         return format!("{}{}::{}", prefix, name, suffix);
242                     }
243                 }
244             }
245             path
246         })
247         .map(TestId::Path)
248         .or_else(|| def_name.clone().map(TestId::Name))?;
249
250     let mut nav = def.try_to_nav(sema.db)?;
251     nav.focus_range = None;
252     nav.description = None;
253     nav.docs = None;
254     nav.kind = None;
255     let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
256     Some(res)
257 }
258
259 #[derive(Debug, Copy, Clone)]
260 pub struct TestAttr {
261     pub ignore: bool,
262 }
263
264 impl TestAttr {
265     fn from_fn(fn_def: &ast::Fn) -> TestAttr {
266         let ignore = fn_def
267             .attrs()
268             .filter_map(|attr| attr.simple_name())
269             .any(|attribute_text| attribute_text == "ignore");
270         TestAttr { ignore }
271     }
272 }
273
274 const RUSTDOC_FENCE: &str = "```";
275 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
276     &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
277
278 fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
279     attrs.docs().map_or(false, |doc| {
280         let mut in_code_block = false;
281
282         for line in String::from(doc).lines() {
283             if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
284                 in_code_block = !in_code_block;
285
286                 if in_code_block
287                     && header
288                         .split(',')
289                         .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
290                 {
291                     return true;
292                 }
293             }
294         }
295
296         false
297     })
298 }
299
300 // We could create runnables for modules with number_of_test_submodules > 0,
301 // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
302 fn has_test_function_or_multiple_test_submodules(
303     sema: &Semantics<RootDatabase>,
304     module: &hir::Module,
305 ) -> bool {
306     let mut number_of_test_submodules = 0;
307
308     for item in module.declarations(sema.db) {
309         match item {
310             hir::ModuleDef::Function(f) => {
311                 if let Some(it) = f.source(sema.db) {
312                     if test_related_attribute(&it.value).is_some() {
313                         return true;
314                     }
315                 }
316             }
317             hir::ModuleDef::Module(submodule) => {
318                 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
319                     number_of_test_submodules += 1;
320                 }
321             }
322             _ => (),
323         }
324     }
325
326     number_of_test_submodules > 1
327 }
328
329 #[cfg(test)]
330 mod tests {
331     use expect_test::{expect, Expect};
332
333     use crate::fixture;
334
335     use super::*;
336
337     fn check(
338         ra_fixture: &str,
339         // FIXME: fold this into `expect` as well
340         actions: &[&RunnableAction],
341         expect: Expect,
342     ) {
343         let (analysis, position) = fixture::position(ra_fixture);
344         let runnables = analysis.runnables(position.file_id).unwrap();
345         expect.assert_debug_eq(&runnables);
346         assert_eq!(
347             actions,
348             runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
349         );
350     }
351
352     #[test]
353     fn test_runnables() {
354         check(
355             r#"
356 //- /lib.rs
357 $0
358 fn main() {}
359
360 #[test]
361 fn test_foo() {}
362
363 #[test]
364 #[ignore]
365 fn test_foo() {}
366
367 #[bench]
368 fn bench() {}
369 "#,
370             &[&BIN, &TEST, &TEST, &BENCH],
371             expect![[r#"
372                 [
373                     Runnable {
374                         nav: NavigationTarget {
375                             file_id: FileId(
376                                 0,
377                             ),
378                             full_range: 1..13,
379                             focus_range: 4..8,
380                             name: "main",
381                             kind: Function,
382                         },
383                         kind: Bin,
384                         cfg: None,
385                     },
386                     Runnable {
387                         nav: NavigationTarget {
388                             file_id: FileId(
389                                 0,
390                             ),
391                             full_range: 15..39,
392                             focus_range: 26..34,
393                             name: "test_foo",
394                             kind: Function,
395                         },
396                         kind: Test {
397                             test_id: Path(
398                                 "test_foo",
399                             ),
400                             attr: TestAttr {
401                                 ignore: false,
402                             },
403                         },
404                         cfg: None,
405                     },
406                     Runnable {
407                         nav: NavigationTarget {
408                             file_id: FileId(
409                                 0,
410                             ),
411                             full_range: 41..75,
412                             focus_range: 62..70,
413                             name: "test_foo",
414                             kind: Function,
415                         },
416                         kind: Test {
417                             test_id: Path(
418                                 "test_foo",
419                             ),
420                             attr: TestAttr {
421                                 ignore: true,
422                             },
423                         },
424                         cfg: None,
425                     },
426                     Runnable {
427                         nav: NavigationTarget {
428                             file_id: FileId(
429                                 0,
430                             ),
431                             full_range: 77..99,
432                             focus_range: 89..94,
433                             name: "bench",
434                             kind: Function,
435                         },
436                         kind: Bench {
437                             test_id: Path(
438                                 "bench",
439                             ),
440                         },
441                         cfg: None,
442                     },
443                 ]
444             "#]],
445         );
446     }
447
448     #[test]
449     fn test_runnables_doc_test() {
450         check(
451             r#"
452 //- /lib.rs
453 $0
454 fn main() {}
455
456 /// ```
457 /// let x = 5;
458 /// ```
459 fn should_have_runnable() {}
460
461 /// ```edition2018
462 /// let x = 5;
463 /// ```
464 fn should_have_runnable_1() {}
465
466 /// ```
467 /// let z = 55;
468 /// ```
469 ///
470 /// ```ignore
471 /// let z = 56;
472 /// ```
473 fn should_have_runnable_2() {}
474
475 /// ```no_run
476 /// let z = 55;
477 /// ```
478 fn should_have_no_runnable() {}
479
480 /// ```ignore
481 /// let z = 55;
482 /// ```
483 fn should_have_no_runnable_2() {}
484
485 /// ```compile_fail
486 /// let z = 55;
487 /// ```
488 fn should_have_no_runnable_3() {}
489
490 /// ```text
491 /// arbitrary plain text
492 /// ```
493 fn should_have_no_runnable_4() {}
494
495 /// ```text
496 /// arbitrary plain text
497 /// ```
498 ///
499 /// ```sh
500 /// $ shell code
501 /// ```
502 fn should_have_no_runnable_5() {}
503
504 /// ```rust,no_run
505 /// let z = 55;
506 /// ```
507 fn should_have_no_runnable_6() {}
508
509 /// ```
510 /// let x = 5;
511 /// ```
512 struct StructWithRunnable(String);
513
514 "#,
515             &[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
516             expect![[r#"
517                 [
518                     Runnable {
519                         nav: NavigationTarget {
520                             file_id: FileId(
521                                 0,
522                             ),
523                             full_range: 1..13,
524                             focus_range: 4..8,
525                             name: "main",
526                             kind: Function,
527                         },
528                         kind: Bin,
529                         cfg: None,
530                     },
531                     Runnable {
532                         nav: NavigationTarget {
533                             file_id: FileId(
534                                 0,
535                             ),
536                             full_range: 15..74,
537                             name: "should_have_runnable",
538                         },
539                         kind: DocTest {
540                             test_id: Path(
541                                 "should_have_runnable",
542                             ),
543                         },
544                         cfg: None,
545                     },
546                     Runnable {
547                         nav: NavigationTarget {
548                             file_id: FileId(
549                                 0,
550                             ),
551                             full_range: 76..148,
552                             name: "should_have_runnable_1",
553                         },
554                         kind: DocTest {
555                             test_id: Path(
556                                 "should_have_runnable_1",
557                             ),
558                         },
559                         cfg: None,
560                     },
561                     Runnable {
562                         nav: NavigationTarget {
563                             file_id: FileId(
564                                 0,
565                             ),
566                             full_range: 150..254,
567                             name: "should_have_runnable_2",
568                         },
569                         kind: DocTest {
570                             test_id: Path(
571                                 "should_have_runnable_2",
572                             ),
573                         },
574                         cfg: None,
575                     },
576                     Runnable {
577                         nav: NavigationTarget {
578                             file_id: FileId(
579                                 0,
580                             ),
581                             full_range: 756..821,
582                             name: "StructWithRunnable",
583                         },
584                         kind: DocTest {
585                             test_id: Path(
586                                 "StructWithRunnable",
587                             ),
588                         },
589                         cfg: None,
590                     },
591                 ]
592             "#]],
593         );
594     }
595
596     #[test]
597     fn test_runnables_doc_test_in_impl() {
598         check(
599             r#"
600 //- /lib.rs
601 $0
602 fn main() {}
603
604 struct Data;
605 impl Data {
606     /// ```
607     /// let x = 5;
608     /// ```
609     fn foo() {}
610 }
611 "#,
612             &[&BIN, &DOCTEST],
613             expect![[r#"
614                 [
615                     Runnable {
616                         nav: NavigationTarget {
617                             file_id: FileId(
618                                 0,
619                             ),
620                             full_range: 1..13,
621                             focus_range: 4..8,
622                             name: "main",
623                             kind: Function,
624                         },
625                         kind: Bin,
626                         cfg: None,
627                     },
628                     Runnable {
629                         nav: NavigationTarget {
630                             file_id: FileId(
631                                 0,
632                             ),
633                             full_range: 44..98,
634                             name: "foo",
635                         },
636                         kind: DocTest {
637                             test_id: Path(
638                                 "Data::foo",
639                             ),
640                         },
641                         cfg: None,
642                     },
643                 ]
644             "#]],
645         );
646     }
647
648     #[test]
649     fn test_runnables_module() {
650         check(
651             r#"
652 //- /lib.rs
653 $0
654 mod test_mod {
655     #[test]
656     fn test_foo1() {}
657 }
658 "#,
659             &[&TEST, &TEST],
660             expect![[r#"
661                 [
662                     Runnable {
663                         nav: NavigationTarget {
664                             file_id: FileId(
665                                 0,
666                             ),
667                             full_range: 1..51,
668                             focus_range: 5..13,
669                             name: "test_mod",
670                             kind: Module,
671                         },
672                         kind: TestMod {
673                             path: "test_mod",
674                         },
675                         cfg: None,
676                     },
677                     Runnable {
678                         nav: NavigationTarget {
679                             file_id: FileId(
680                                 0,
681                             ),
682                             full_range: 20..49,
683                             focus_range: 35..44,
684                             name: "test_foo1",
685                             kind: Function,
686                         },
687                         kind: Test {
688                             test_id: Path(
689                                 "test_mod::test_foo1",
690                             ),
691                             attr: TestAttr {
692                                 ignore: false,
693                             },
694                         },
695                         cfg: None,
696                     },
697                 ]
698             "#]],
699         );
700     }
701
702     #[test]
703     fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {
704         check(
705             r#"
706 //- /lib.rs
707 $0
708 mod root_tests {
709     mod nested_tests_0 {
710         mod nested_tests_1 {
711             #[test]
712             fn nested_test_11() {}
713
714             #[test]
715             fn nested_test_12() {}
716         }
717
718         mod nested_tests_2 {
719             #[test]
720             fn nested_test_2() {}
721         }
722
723         mod nested_tests_3 {}
724     }
725
726     mod nested_tests_4 {}
727 }
728 "#,
729             &[&TEST, &TEST, &TEST, &TEST, &TEST, &TEST],
730             expect![[r#"
731                 [
732                     Runnable {
733                         nav: NavigationTarget {
734                             file_id: FileId(
735                                 0,
736                             ),
737                             full_range: 22..323,
738                             focus_range: 26..40,
739                             name: "nested_tests_0",
740                             kind: Module,
741                         },
742                         kind: TestMod {
743                             path: "root_tests::nested_tests_0",
744                         },
745                         cfg: None,
746                     },
747                     Runnable {
748                         nav: NavigationTarget {
749                             file_id: FileId(
750                                 0,
751                             ),
752                             full_range: 51..192,
753                             focus_range: 55..69,
754                             name: "nested_tests_1",
755                             kind: Module,
756                         },
757                         kind: TestMod {
758                             path: "root_tests::nested_tests_0::nested_tests_1",
759                         },
760                         cfg: None,
761                     },
762                     Runnable {
763                         nav: NavigationTarget {
764                             file_id: FileId(
765                                 0,
766                             ),
767                             full_range: 202..286,
768                             focus_range: 206..220,
769                             name: "nested_tests_2",
770                             kind: Module,
771                         },
772                         kind: TestMod {
773                             path: "root_tests::nested_tests_0::nested_tests_2",
774                         },
775                         cfg: None,
776                     },
777                     Runnable {
778                         nav: NavigationTarget {
779                             file_id: FileId(
780                                 0,
781                             ),
782                             full_range: 84..126,
783                             focus_range: 107..121,
784                             name: "nested_test_11",
785                             kind: Function,
786                         },
787                         kind: Test {
788                             test_id: Path(
789                                 "root_tests::nested_tests_0::nested_tests_1::nested_test_11",
790                             ),
791                             attr: TestAttr {
792                                 ignore: false,
793                             },
794                         },
795                         cfg: None,
796                     },
797                     Runnable {
798                         nav: NavigationTarget {
799                             file_id: FileId(
800                                 0,
801                             ),
802                             full_range: 140..182,
803                             focus_range: 163..177,
804                             name: "nested_test_12",
805                             kind: Function,
806                         },
807                         kind: Test {
808                             test_id: Path(
809                                 "root_tests::nested_tests_0::nested_tests_1::nested_test_12",
810                             ),
811                             attr: TestAttr {
812                                 ignore: false,
813                             },
814                         },
815                         cfg: None,
816                     },
817                     Runnable {
818                         nav: NavigationTarget {
819                             file_id: FileId(
820                                 0,
821                             ),
822                             full_range: 235..276,
823                             focus_range: 258..271,
824                             name: "nested_test_2",
825                             kind: Function,
826                         },
827                         kind: Test {
828                             test_id: Path(
829                                 "root_tests::nested_tests_0::nested_tests_2::nested_test_2",
830                             ),
831                             attr: TestAttr {
832                                 ignore: false,
833                             },
834                         },
835                         cfg: None,
836                     },
837                 ]
838             "#]],
839         );
840     }
841
842     #[test]
843     fn test_runnables_with_feature() {
844         check(
845             r#"
846 //- /lib.rs crate:foo cfg:feature=foo
847 $0
848 #[test]
849 #[cfg(feature = "foo")]
850 fn test_foo1() {}
851 "#,
852             &[&TEST],
853             expect![[r#"
854                 [
855                     Runnable {
856                         nav: NavigationTarget {
857                             file_id: FileId(
858                                 0,
859                             ),
860                             full_range: 1..50,
861                             focus_range: 36..45,
862                             name: "test_foo1",
863                             kind: Function,
864                         },
865                         kind: Test {
866                             test_id: Path(
867                                 "test_foo1",
868                             ),
869                             attr: TestAttr {
870                                 ignore: false,
871                             },
872                         },
873                         cfg: Some(
874                             Atom(
875                                 KeyValue {
876                                     key: "feature",
877                                     value: "foo",
878                                 },
879                             ),
880                         ),
881                     },
882                 ]
883             "#]],
884         );
885     }
886
887     #[test]
888     fn test_runnables_with_features() {
889         check(
890             r#"
891 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
892 $0
893 #[test]
894 #[cfg(all(feature = "foo", feature = "bar"))]
895 fn test_foo1() {}
896 "#,
897             &[&TEST],
898             expect![[r#"
899                 [
900                     Runnable {
901                         nav: NavigationTarget {
902                             file_id: FileId(
903                                 0,
904                             ),
905                             full_range: 1..72,
906                             focus_range: 58..67,
907                             name: "test_foo1",
908                             kind: Function,
909                         },
910                         kind: Test {
911                             test_id: Path(
912                                 "test_foo1",
913                             ),
914                             attr: TestAttr {
915                                 ignore: false,
916                             },
917                         },
918                         cfg: Some(
919                             All(
920                                 [
921                                     Atom(
922                                         KeyValue {
923                                             key: "feature",
924                                             value: "foo",
925                                         },
926                                     ),
927                                     Atom(
928                                         KeyValue {
929                                             key: "feature",
930                                             value: "bar",
931                                         },
932                                     ),
933                                 ],
934                             ),
935                         ),
936                     },
937                 ]
938             "#]],
939         );
940     }
941
942     #[test]
943     fn test_runnables_no_test_function_in_module() {
944         check(
945             r#"
946 //- /lib.rs
947 $0
948 mod test_mod {
949     fn foo1() {}
950 }
951 "#,
952             &[],
953             expect![[r#"
954                 []
955             "#]],
956         );
957     }
958
959     #[test]
960     fn test_doc_runnables_impl_mod() {
961         check(
962             r#"
963 //- /lib.rs
964 mod foo;
965 //- /foo.rs
966 struct Foo;$0
967 impl Foo {
968     /// ```
969     /// let x = 5;
970     /// ```
971     fn foo() {}
972 }
973         "#,
974             &[&DOCTEST],
975             expect![[r#"
976                 [
977                     Runnable {
978                         nav: NavigationTarget {
979                             file_id: FileId(
980                                 1,
981                             ),
982                             full_range: 27..81,
983                             name: "foo",
984                         },
985                         kind: DocTest {
986                             test_id: Path(
987                                 "foo::Foo::foo",
988                             ),
989                         },
990                         cfg: None,
991                     },
992                 ]
993             "#]],
994         );
995     }
996
997     #[test]
998     fn test_runnables_in_macro() {
999         check(
1000             r#"
1001 //- /lib.rs
1002 $0
1003 macro_rules! gen {
1004     () => {
1005         #[test]
1006         fn foo_test() {
1007         }
1008     }
1009 }
1010 mod tests {
1011     gen!();
1012 }
1013 "#,
1014             &[&TEST, &TEST],
1015             expect![[r#"
1016                 [
1017                     Runnable {
1018                         nav: NavigationTarget {
1019                             file_id: FileId(
1020                                 0,
1021                             ),
1022                             full_range: 90..115,
1023                             focus_range: 94..99,
1024                             name: "tests",
1025                             kind: Module,
1026                         },
1027                         kind: TestMod {
1028                             path: "tests",
1029                         },
1030                         cfg: None,
1031                     },
1032                     Runnable {
1033                         nav: NavigationTarget {
1034                             file_id: FileId(
1035                                 0,
1036                             ),
1037                             full_range: 106..113,
1038                             focus_range: 106..113,
1039                             name: "foo_test",
1040                             kind: Function,
1041                         },
1042                         kind: Test {
1043                             test_id: Path(
1044                                 "tests::foo_test",
1045                             ),
1046                             attr: TestAttr {
1047                                 ignore: false,
1048                             },
1049                         },
1050                         cfg: None,
1051                     },
1052                 ]
1053             "#]],
1054         );
1055     }
1056
1057     #[test]
1058     fn dont_recurse_in_outline_submodules() {
1059         cov_mark::check!(dont_recurse_in_outline_submodules);
1060         check(
1061             r#"
1062 //- /lib.rs
1063 $0
1064 mod m;
1065 //- /m.rs
1066 mod tests {
1067     #[test]
1068     fn t() {}
1069 }
1070 "#,
1071             &[],
1072             expect![[r#"
1073                 []
1074             "#]],
1075         );
1076     }
1077 }