]> git.lizzy.rs Git - rust.git/blob - crates/ide/src/runnables.rs
1e7baed20469aeea5b254867527d5020511d3e98
[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 use test_utils::mark;
13
14 use crate::{
15     display::{ToNav, TryToNav},
16     FileId, NavigationTarget,
17 };
18
19 #[derive(Debug, Clone)]
20 pub struct Runnable {
21     pub nav: NavigationTarget,
22     pub kind: RunnableKind,
23     pub cfg: Option<CfgExpr>,
24 }
25
26 #[derive(Debug, Clone)]
27 pub enum TestId {
28     Name(String),
29     Path(String),
30 }
31
32 impl fmt::Display for TestId {
33     fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
34         match self {
35             TestId::Name(name) => write!(f, "{}", name),
36             TestId::Path(path) => write!(f, "{}", path),
37         }
38     }
39 }
40
41 #[derive(Debug, Clone)]
42 pub enum RunnableKind {
43     Test { test_id: TestId, attr: TestAttr },
44     TestMod { path: String },
45     Bench { test_id: TestId },
46     DocTest { test_id: TestId },
47     Bin,
48 }
49
50 #[derive(Debug, Eq, PartialEq)]
51 pub struct RunnableAction {
52     pub run_title: &'static str,
53     pub debugee: bool,
54 }
55
56 const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
57 const DOCTEST: RunnableAction =
58     RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
59 const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
60 const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
61
62 impl Runnable {
63     // test package::module::testname
64     pub fn label(&self, target: Option<String>) -> String {
65         match &self.kind {
66             RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
67             RunnableKind::TestMod { path } => format!("test-mod {}", path),
68             RunnableKind::Bench { test_id } => format!("bench {}", test_id),
69             RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
70             RunnableKind::Bin => {
71                 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
72             }
73         }
74     }
75
76     pub fn action(&self) -> &'static RunnableAction {
77         match &self.kind {
78             RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
79             RunnableKind::DocTest { .. } => &DOCTEST,
80             RunnableKind::Bench { .. } => &BENCH,
81             RunnableKind::Bin => &BIN,
82         }
83     }
84 }
85
86 // Feature: Run
87 //
88 // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
89 // location**. Super useful for repeatedly running just a single test. Do bind this
90 // to a shortcut!
91 //
92 // |===
93 // | Editor  | Action Name
94 //
95 // | VS Code | **Rust Analyzer: Run**
96 // |===
97 pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
98     let sema = Semantics::new(db);
99     let module = match sema.to_module_def(file_id) {
100         None => return Vec::new(),
101         Some(it) => it,
102     };
103
104     let mut res = Vec::new();
105     runnables_mod(&sema, &mut res, module);
106     res
107 }
108
109 fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
110     acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
111         let runnable = match def {
112             hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
113             hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
114             _ => None,
115         };
116         runnable.or_else(|| module_def_doctest(&sema, def))
117     }));
118
119     acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map(
120         |def| match def {
121             hir::AssocItem::Function(it) => {
122                 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
123             }
124             hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
125             hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
126         },
127     ));
128
129     for def in module.declarations(sema.db) {
130         if let hir::ModuleDef::Module(submodule) = def {
131             match submodule.definition_source(sema.db).value {
132                 hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule),
133                 hir::ModuleSource::SourceFile(_) => mark::hit!(dont_recurse_in_outline_submodules),
134                 hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable
135             }
136         }
137     }
138 }
139
140 pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
141     let func = def.source(sema.db)?;
142     let name_string = def.name(sema.db).to_string();
143
144     let kind = if name_string == "main" {
145         RunnableKind::Bin
146     } else {
147         let canonical_path = {
148             let def: hir::ModuleDef = def.into();
149             def.canonical_path(sema.db)
150         };
151         let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
152
153         if test_related_attribute(&func.value).is_some() {
154             let attr = TestAttr::from_fn(&func.value);
155             RunnableKind::Test { test_id, attr }
156         } else if func.value.has_atom_attr("bench") {
157             RunnableKind::Bench { test_id }
158         } else {
159             return None;
160         }
161     };
162
163     let nav = NavigationTarget::from_named(
164         sema.db,
165         func.as_ref().map(|it| it as &dyn ast::NameOwner),
166         SymbolKind::Function,
167     );
168     let cfg = def.attrs(sema.db).cfg();
169     Some(Runnable { nav, kind, cfg })
170 }
171
172 pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
173     if !has_test_function_or_multiple_test_submodules(sema, &def) {
174         return None;
175     }
176     let path =
177         def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
178
179     let attrs = def.attrs(sema.db);
180     let cfg = attrs.cfg();
181     let nav = def.to_nav(sema.db);
182     Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
183 }
184
185 // FIXME: figure out a proper API here.
186 pub(crate) fn doc_owner_to_def(
187     sema: &Semantics<RootDatabase>,
188     item: SyntaxNode,
189 ) -> Option<Definition> {
190     let res: hir::ModuleDef = match_ast! {
191         match item {
192             ast::SourceFile(it) => sema.scope(&item).module()?.into(),
193             ast::Fn(it) => sema.to_def(&it)?.into(),
194             ast::Struct(it) => sema.to_def(&it)?.into(),
195             ast::Enum(it) => sema.to_def(&it)?.into(),
196             ast::Union(it) => sema.to_def(&it)?.into(),
197             ast::Trait(it) => sema.to_def(&it)?.into(),
198             ast::Const(it) => sema.to_def(&it)?.into(),
199             ast::Static(it) => sema.to_def(&it)?.into(),
200             ast::TypeAlias(it) => sema.to_def(&it)?.into(),
201             _ => return None,
202         }
203     };
204     Some(Definition::ModuleDef(res))
205 }
206
207 fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
208     let attrs = match def {
209         hir::ModuleDef::Module(it) => it.attrs(sema.db),
210         hir::ModuleDef::Function(it) => it.attrs(sema.db),
211         hir::ModuleDef::Adt(it) => it.attrs(sema.db),
212         hir::ModuleDef::Variant(it) => it.attrs(sema.db),
213         hir::ModuleDef::Const(it) => it.attrs(sema.db),
214         hir::ModuleDef::Static(it) => it.attrs(sema.db),
215         hir::ModuleDef::Trait(it) => it.attrs(sema.db),
216         hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
217         hir::ModuleDef::BuiltinType(_) => return None,
218     };
219     if !has_runnable_doc_test(&attrs) {
220         return None;
221     }
222     let def_name = def.name(sema.db).map(|it| it.to_string());
223     let test_id = def
224         .canonical_path(sema.db)
225         // This probably belongs to canonical path?
226         .map(|path| {
227             let assoc_def = match def {
228                 hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
229                 hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
230                 hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
231                 _ => None,
232             };
233             // FIXME: this also looks very wrong
234             if let Some(assoc_def) = assoc_def {
235                 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
236                     if let Some(adt) = imp.target_ty(sema.db).as_adt() {
237                         let name = adt.name(sema.db).to_string();
238                         let idx = path.rfind(':').map_or(0, |idx| idx + 1);
239                         let (prefix, suffix) = path.split_at(idx);
240                         return format!("{}{}::{}", prefix, name, suffix);
241                     }
242                 }
243             }
244             path
245         })
246         .map(TestId::Path)
247         .or_else(|| def_name.clone().map(TestId::Name))?;
248
249     let mut nav = def.try_to_nav(sema.db)?;
250     nav.focus_range = None;
251     nav.description = None;
252     nav.docs = None;
253     nav.kind = None;
254     let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
255     Some(res)
256 }
257
258 #[derive(Debug, Copy, Clone)]
259 pub struct TestAttr {
260     pub ignore: bool,
261 }
262
263 impl TestAttr {
264     fn from_fn(fn_def: &ast::Fn) -> TestAttr {
265         let ignore = fn_def
266             .attrs()
267             .filter_map(|attr| attr.simple_name())
268             .any(|attribute_text| attribute_text == "ignore");
269         TestAttr { ignore }
270     }
271 }
272
273 const RUSTDOC_FENCE: &str = "```";
274 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
275     &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
276
277 fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
278     attrs.docs().map_or(false, |doc| {
279         let mut in_code_block = false;
280
281         for line in String::from(doc).lines() {
282             if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
283                 in_code_block = !in_code_block;
284
285                 if in_code_block
286                     && header
287                         .split(',')
288                         .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
289                 {
290                     return true;
291                 }
292             }
293         }
294
295         false
296     })
297 }
298
299 // We could create runnables for modules with number_of_test_submodules > 0,
300 // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
301 fn has_test_function_or_multiple_test_submodules(
302     sema: &Semantics<RootDatabase>,
303     module: &hir::Module,
304 ) -> bool {
305     let mut number_of_test_submodules = 0;
306
307     for item in module.declarations(sema.db) {
308         match item {
309             hir::ModuleDef::Function(f) => {
310                 if let Some(it) = f.source(sema.db) {
311                     if test_related_attribute(&it.value).is_some() {
312                         return true;
313                     }
314                 }
315             }
316             hir::ModuleDef::Module(submodule) => {
317                 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
318                     number_of_test_submodules += 1;
319                 }
320             }
321             _ => (),
322         }
323     }
324
325     number_of_test_submodules > 1
326 }
327
328 #[cfg(test)]
329 mod tests {
330     use expect_test::{expect, Expect};
331     use test_utils::mark;
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         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 }