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