1use std::{fmt, sync::OnceLock};
2
3use arrayvec::ArrayVec;
4use ast::HasName;
5use cfg::{CfgAtom, CfgExpr};
6use hir::{
7 AsAssocItem, AttrsWithOwner, HasAttrs, HasCrate, HasSource, Semantics, Symbol, db::HirDatabase,
8 sym,
9};
10use ide_assists::utils::{has_test_related_attribute, test_related_attribute_syn};
11use ide_db::{
12 FilePosition, FxHashMap, FxIndexMap, FxIndexSet, RootDatabase, SymbolKind,
13 base_db::RootQueryDb,
14 defs::Definition,
15 documentation::docs_from_attrs,
16 helpers::visit_file_defs,
17 search::{FileReferenceNode, SearchScope},
18};
19use itertools::Itertools;
20use smallvec::SmallVec;
21use span::{Edition, TextSize};
22use stdx::format_to;
23use syntax::{
24 SmolStr, SyntaxNode, ToSmolStr,
25 ast::{self, AstNode},
26 format_smolstr,
27};
28
29use crate::{FileId, NavigationTarget, ToNav, TryToNav, references};
30
31#[derive(Debug, Clone, Hash, PartialEq, Eq)]
32pub struct Runnable {
33 pub use_name_in_title: bool,
34 pub nav: NavigationTarget,
35 pub kind: RunnableKind,
36 pub cfg: Option<CfgExpr>,
37 pub update_test: UpdateTest,
38}
39
40#[derive(Debug, Clone, Hash, PartialEq, Eq)]
41pub enum TestId {
42 Name(SmolStr),
43 Path(String),
44}
45
46impl fmt::Display for TestId {
47 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
48 match self {
49 TestId::Name(name) => name.fmt(f),
50 TestId::Path(path) => path.fmt(f),
51 }
52 }
53}
54
55#[derive(Debug, Clone, Hash, PartialEq, Eq)]
56pub enum RunnableKind {
57 TestMod { path: String },
58 Test { test_id: TestId, attr: TestAttr },
59 Bench { test_id: TestId },
60 DocTest { test_id: TestId },
61 Bin,
62}
63
64#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]
65enum RunnableDiscKind {
66 TestMod,
67 Test,
68 DocTest,
69 Bench,
70 Bin,
71}
72
73impl RunnableKind {
74 fn disc(&self) -> RunnableDiscKind {
75 match self {
76 RunnableKind::TestMod { .. } => RunnableDiscKind::TestMod,
77 RunnableKind::Test { .. } => RunnableDiscKind::Test,
78 RunnableKind::DocTest { .. } => RunnableDiscKind::DocTest,
79 RunnableKind::Bench { .. } => RunnableDiscKind::Bench,
80 RunnableKind::Bin => RunnableDiscKind::Bin,
81 }
82 }
83}
84
85impl Runnable {
86 pub fn label(&self, target: Option<&str>) -> String {
87 match &self.kind {
88 RunnableKind::Test { test_id, .. } => format!("test {test_id}"),
89 RunnableKind::TestMod { path } => format!("test-mod {path}"),
90 RunnableKind::Bench { test_id } => format!("bench {test_id}"),
91 RunnableKind::DocTest { test_id, .. } => format!("doctest {test_id}"),
92 RunnableKind::Bin => {
93 format!("run {}", target.unwrap_or("binary"))
94 }
95 }
96 }
97
98 pub fn title(&self) -> String {
99 let mut s = String::from("▶\u{fe0e} Run ");
100 if self.use_name_in_title {
101 format_to!(s, "{}", self.nav.name);
102 if !matches!(self.kind, RunnableKind::Bin) {
103 s.push(' ');
104 }
105 }
106 let suffix = match &self.kind {
107 RunnableKind::TestMod { .. } => "Tests",
108 RunnableKind::Test { .. } => "Test",
109 RunnableKind::DocTest { .. } => "Doctest",
110 RunnableKind::Bench { .. } => "Bench",
111 RunnableKind::Bin => return s,
112 };
113 s.push_str(suffix);
114 s
115 }
116}
117
118pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
130 let sema = Semantics::new(db);
131
132 let mut res = Vec::new();
133 let mut in_macro_expansion = FxIndexMap::<hir::HirFileId, Vec<Runnable>>::default();
136 let mut add_opt = |runnable: Option<Runnable>, def| {
137 if let Some(runnable) = runnable.filter(|runnable| runnable.nav.file_id == file_id) {
138 if let Some(def) = def {
139 let file_id = match def {
140 Definition::Module(it) => {
141 it.declaration_source_range(db).map(|src| src.file_id)
142 }
143 Definition::Function(it) => it.source(db).map(|src| src.file_id),
144 _ => None,
145 };
146 if let Some(file_id) = file_id.filter(|file| file.macro_file().is_some()) {
147 in_macro_expansion.entry(file_id).or_default().push(runnable);
148 return;
149 }
150 }
151 res.push(runnable);
152 }
153 };
154 visit_file_defs(&sema, file_id, &mut |def| {
155 let runnable = match def {
156 Definition::Module(it) => runnable_mod(&sema, it),
157 Definition::Function(it) => runnable_fn(&sema, it),
158 Definition::SelfType(impl_) => runnable_impl(&sema, &impl_),
159 _ => None,
160 };
161 add_opt(runnable.or_else(|| module_def_doctest(&sema, def)), Some(def));
162 if let Definition::SelfType(impl_) = def {
163 impl_.items(db).into_iter().for_each(|assoc| {
164 let runnable = match assoc {
165 hir::AssocItem::Function(it) => {
166 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
167 }
168 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
169 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
170 };
171 add_opt(runnable, Some(assoc.into()))
172 });
173 }
174 });
175
176 sema.file_to_module_defs(file_id)
177 .map(|it| runnable_mod_outline_definition(&sema, it))
178 .for_each(|it| add_opt(it, None));
179
180 res.extend(in_macro_expansion.into_iter().flat_map(|(_, runnables)| {
181 let use_name_in_title = runnables.len() != 1;
182 runnables.into_iter().map(move |mut r| {
183 r.use_name_in_title = use_name_in_title;
184 r
185 })
186 }));
187 res.sort_by(cmp_runnables);
188 res
189}
190
191pub(crate) fn related_tests(
202 db: &RootDatabase,
203 position: FilePosition,
204 search_scope: Option<SearchScope>,
205) -> Vec<Runnable> {
206 let sema = Semantics::new(db);
207 let mut res: FxIndexSet<Runnable> = FxIndexSet::default();
208 let syntax = sema.parse_guess_edition(position.file_id).syntax().clone();
209
210 find_related_tests(&sema, &syntax, position, search_scope, &mut res);
211
212 res.into_iter().sorted_by(cmp_runnables).collect()
213}
214
215fn cmp_runnables(
216 Runnable { nav, kind, .. }: &Runnable,
217 Runnable { nav: nav_b, kind: kind_b, .. }: &Runnable,
218) -> std::cmp::Ordering {
219 nav.full_range
221 .start()
222 .cmp(&nav_b.full_range.start())
223 .then_with(|| {
224 let t_0 = || TextSize::from(0);
225 nav.focus_range
226 .map_or_else(t_0, |it| it.start())
227 .cmp(&nav_b.focus_range.map_or_else(t_0, |it| it.start()))
228 })
229 .then_with(|| kind.disc().cmp(&kind_b.disc()))
230 .then_with(|| nav.name.cmp(&nav_b.name))
231}
232
233fn find_related_tests(
234 sema: &Semantics<'_, RootDatabase>,
235 syntax: &SyntaxNode,
236 position: FilePosition,
237 search_scope: Option<SearchScope>,
238 tests: &mut FxIndexSet<Runnable>,
239) {
240 let defs = match references::find_defs(sema, syntax, position.offset) {
242 Some(defs) => defs,
243 None => return,
244 };
245 for def in defs {
246 let defs = def
247 .usages(sema)
248 .set_scope(search_scope.as_ref())
249 .all()
250 .references
251 .into_values()
252 .flatten();
253 for ref_ in defs {
254 let name_ref = match ref_.name {
255 FileReferenceNode::NameRef(name_ref) => name_ref,
256 _ => continue,
257 };
258 if let Some(fn_def) =
259 sema.ancestors_with_macros(name_ref.syntax().clone()).find_map(ast::Fn::cast)
260 {
261 if let Some(runnable) = as_test_runnable(sema, &fn_def) {
262 tests.insert(runnable);
264 } else if let Some(module) = parent_test_module(sema, &fn_def) {
265 find_related_tests_in_module(sema, syntax, &fn_def, &module, tests);
267 }
268 }
269 }
270 }
271}
272
273fn find_related_tests_in_module(
274 sema: &Semantics<'_, RootDatabase>,
275 syntax: &SyntaxNode,
276 fn_def: &ast::Fn,
277 parent_module: &hir::Module,
278 tests: &mut FxIndexSet<Runnable>,
279) {
280 let fn_name = match fn_def.name() {
281 Some(it) => it,
282 _ => return,
283 };
284 let mod_source = parent_module.definition_source_range(sema.db);
285
286 let file_id = mod_source.file_id.original_file(sema.db);
287 let mod_scope = SearchScope::file_range(hir::FileRange { file_id, range: mod_source.value });
288 let fn_pos = FilePosition {
289 file_id: file_id.file_id(sema.db),
290 offset: fn_name.syntax().text_range().start(),
291 };
292 find_related_tests(sema, syntax, fn_pos, Some(mod_scope), tests)
293}
294
295fn as_test_runnable(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<Runnable> {
296 if test_related_attribute_syn(fn_def).is_some() {
297 let function = sema.to_def(fn_def)?;
298 runnable_fn(sema, function)
299 } else {
300 None
301 }
302}
303
304fn parent_test_module(sema: &Semantics<'_, RootDatabase>, fn_def: &ast::Fn) -> Option<hir::Module> {
305 fn_def.syntax().ancestors().find_map(|node| {
306 let module = ast::Module::cast(node)?;
307 let module = sema.to_def(&module)?;
308
309 if has_test_function_or_multiple_test_submodules(sema, &module, false) {
310 Some(module)
311 } else {
312 None
313 }
314 })
315}
316
317pub(crate) fn runnable_fn(
318 sema: &Semantics<'_, RootDatabase>,
319 def: hir::Function,
320) -> Option<Runnable> {
321 let edition = def.krate(sema.db).edition(sema.db);
322 let under_cfg_test = has_cfg_test(def.module(sema.db).attrs(sema.db));
323 let kind = if !under_cfg_test && def.is_main(sema.db) {
324 RunnableKind::Bin
325 } else {
326 let test_id = || {
327 let canonical_path = {
328 let def: hir::ModuleDef = def.into();
329 def.canonical_path(sema.db, edition)
330 };
331 canonical_path
332 .map(TestId::Path)
333 .unwrap_or(TestId::Name(def.name(sema.db).display_no_db(edition).to_smolstr()))
334 };
335
336 if def.is_test(sema.db) {
337 let attr = TestAttr::from_fn(sema.db, def);
338 RunnableKind::Test { test_id: test_id(), attr }
339 } else if def.is_bench(sema.db) {
340 RunnableKind::Bench { test_id: test_id() }
341 } else {
342 return None;
343 }
344 };
345
346 let fn_source = sema.source(def)?;
347 let nav = NavigationTarget::from_named(
348 sema.db,
349 fn_source.as_ref().map(|it| it as &dyn ast::HasName),
350 SymbolKind::Function,
351 )
352 .call_site();
353
354 let file_range = fn_source.syntax().original_file_range_with_macro_call_input(sema.db);
355 let update_test = UpdateTest::find_snapshot_macro(sema, file_range);
356
357 let cfg = def.attrs(sema.db).cfg();
358 Some(Runnable { use_name_in_title: false, nav, kind, cfg, update_test })
359}
360
361pub(crate) fn runnable_mod(
362 sema: &Semantics<'_, RootDatabase>,
363 def: hir::Module,
364) -> Option<Runnable> {
365 if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db)))
366 {
367 return None;
368 }
369 let path = def
370 .path_to_root(sema.db)
371 .into_iter()
372 .rev()
373 .filter_map(|module| {
374 module.name(sema.db).map(|mod_name| {
375 mod_name.display(sema.db, module.krate().edition(sema.db)).to_string()
376 })
377 })
378 .join("::");
379
380 let attrs = def.attrs(sema.db);
381 let cfg = attrs.cfg();
382 let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site();
383
384 let module_source = sema.module_definition_node(def);
385 let module_syntax = module_source.file_syntax(sema.db);
386 let file_range = hir::FileRange {
387 file_id: module_source.file_id.original_file(sema.db),
388 range: module_syntax.text_range(),
389 };
390 let update_test = UpdateTest::find_snapshot_macro(sema, file_range);
391
392 Some(Runnable {
393 use_name_in_title: false,
394 nav,
395 kind: RunnableKind::TestMod { path },
396 cfg,
397 update_test,
398 })
399}
400
401pub(crate) fn runnable_impl(
402 sema: &Semantics<'_, RootDatabase>,
403 def: &hir::Impl,
404) -> Option<Runnable> {
405 let display_target = def.module(sema.db).krate().to_display_target(sema.db);
406 let edition = display_target.edition;
407 let attrs = def.attrs(sema.db);
408 if !has_runnable_doc_test(&attrs) {
409 return None;
410 }
411 let cfg = attrs.cfg();
412 let nav = def.try_to_nav(sema)?.call_site();
413 let ty = def.self_ty(sema.db);
414 let adt_name = ty.as_adt()?.name(sema.db);
415 let mut ty_args = ty.generic_parameters(sema.db, display_target).peekable();
416 let params = hir::attach_db(sema.db, || {
417 if ty_args.peek().is_some() {
418 format!("<{}>", ty_args.format_with(",", |ty, cb| cb(&ty)))
419 } else {
420 String::new()
421 }
422 });
423 let mut test_id = format!("{}{params}", adt_name.display(sema.db, edition));
424 test_id.retain(|c| c != ' ');
425 let test_id = TestId::Path(test_id);
426
427 let impl_source = sema.source(*def)?;
428 let impl_syntax = impl_source.syntax();
429 let file_range = impl_syntax.original_file_range_with_macro_call_input(sema.db);
430 let update_test = UpdateTest::find_snapshot_macro(sema, file_range);
431
432 Some(Runnable {
433 use_name_in_title: false,
434 nav,
435 kind: RunnableKind::DocTest { test_id },
436 cfg,
437 update_test,
438 })
439}
440
441fn has_cfg_test(attrs: AttrsWithOwner) -> bool {
442 attrs.cfgs().any(|cfg| matches!(&cfg, CfgExpr::Atom(CfgAtom::Flag(s)) if *s == sym::test))
443}
444
445fn runnable_mod_outline_definition(
447 sema: &Semantics<'_, RootDatabase>,
448 def: hir::Module,
449) -> Option<Runnable> {
450 def.as_source_file_id(sema.db)?;
451
452 if !has_test_function_or_multiple_test_submodules(sema, &def, has_cfg_test(def.attrs(sema.db)))
453 {
454 return None;
455 }
456 let path = def
457 .path_to_root(sema.db)
458 .into_iter()
459 .rev()
460 .filter_map(|module| {
461 module.name(sema.db).map(|mod_name| {
462 mod_name.display(sema.db, module.krate().edition(sema.db)).to_string()
463 })
464 })
465 .join("::");
466
467 let attrs = def.attrs(sema.db);
468 let cfg = attrs.cfg();
469
470 let mod_source = sema.module_definition_node(def);
471 let mod_syntax = mod_source.file_syntax(sema.db);
472 let file_range = hir::FileRange {
473 file_id: mod_source.file_id.original_file(sema.db),
474 range: mod_syntax.text_range(),
475 };
476 let update_test = UpdateTest::find_snapshot_macro(sema, file_range);
477
478 Some(Runnable {
479 use_name_in_title: false,
480 nav: def.to_nav(sema.db).call_site(),
481 kind: RunnableKind::TestMod { path },
482 cfg,
483 update_test,
484 })
485}
486
487fn module_def_doctest(sema: &Semantics<'_, RootDatabase>, def: Definition) -> Option<Runnable> {
488 let db = sema.db;
489 let attrs = match def {
490 Definition::Module(it) => it.attrs(db),
491 Definition::Function(it) => it.attrs(db),
492 Definition::Adt(it) => it.attrs(db),
493 Definition::Variant(it) => it.attrs(db),
494 Definition::Const(it) => it.attrs(db),
495 Definition::Static(it) => it.attrs(db),
496 Definition::Trait(it) => it.attrs(db),
497 Definition::TypeAlias(it) => it.attrs(db),
498 Definition::Macro(it) => it.attrs(db),
499 Definition::SelfType(it) => it.attrs(db),
500 _ => return None,
501 };
502 let krate = def.krate(db);
503 let edition = krate.map(|it| it.edition(db)).unwrap_or(Edition::CURRENT);
504 let display_target = krate
505 .unwrap_or_else(|| (*db.all_crates().last().expect("no crate graph present")).into())
506 .to_display_target(db);
507 if !has_runnable_doc_test(&attrs) {
508 return None;
509 }
510 let def_name = def.name(db)?;
511 let path = (|| {
512 let mut path = String::new();
513 def.canonical_module_path(db)?
514 .flat_map(|it| it.name(db))
515 .for_each(|name| format_to!(path, "{}::", name.display(db, edition)));
516 if let Some(assoc_item) = def.as_assoc_item(db)
518 && let Some(ty) = assoc_item.implementing_ty(db)
519 && let Some(adt) = ty.as_adt()
520 {
521 let name = adt.name(db);
522 let mut ty_args = ty.generic_parameters(db, display_target).peekable();
523 format_to!(path, "{}", name.display(db, edition));
524 if ty_args.peek().is_some() {
525 hir::attach_db(db, || {
526 format_to!(path, "<{}>", ty_args.format_with(",", |ty, cb| cb(&ty)));
527 });
528 }
529 format_to!(path, "::{}", def_name.display(db, edition));
530 path.retain(|c| c != ' ');
531 return Some(path);
532 }
533 format_to!(path, "{}", def_name.display(db, edition));
534 Some(path)
535 })();
536
537 let test_id = path
538 .map_or_else(|| TestId::Name(def_name.display_no_db(edition).to_smolstr()), TestId::Path);
539
540 let mut nav = match def {
541 Definition::Module(def) => NavigationTarget::from_module_to_decl(db, def),
542 def => def.try_to_nav(sema)?,
543 }
544 .call_site();
545 nav.focus_range = None;
546 nav.description = None;
547 nav.docs = None;
548 nav.kind = None;
549 let res = Runnable {
550 use_name_in_title: false,
551 nav,
552 kind: RunnableKind::DocTest { test_id },
553 cfg: attrs.cfg(),
554 update_test: UpdateTest::default(),
555 };
556 Some(res)
557}
558
559#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
560pub struct TestAttr {
561 pub ignore: bool,
562}
563
564impl TestAttr {
565 fn from_fn(db: &dyn HirDatabase, fn_def: hir::Function) -> TestAttr {
566 TestAttr { ignore: fn_def.is_ignore(db) }
567 }
568}
569
570fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
571 const RUSTDOC_FENCES: [&str; 2] = ["```", "~~~"];
572 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
573 &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
574
575 docs_from_attrs(attrs).is_some_and(|doc| {
576 let mut in_code_block = false;
577
578 for line in doc.lines() {
579 if let Some(header) =
580 RUSTDOC_FENCES.into_iter().find_map(|fence| line.strip_prefix(fence))
581 {
582 in_code_block = !in_code_block;
583
584 if in_code_block
585 && header
586 .split(',')
587 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
588 {
589 return true;
590 }
591 }
592 }
593
594 false
595 })
596}
597
598fn has_test_function_or_multiple_test_submodules(
601 sema: &Semantics<'_, RootDatabase>,
602 module: &hir::Module,
603 consider_exported_main: bool,
604) -> bool {
605 let mut number_of_test_submodules = 0;
606
607 for item in module.declarations(sema.db) {
608 match item {
609 hir::ModuleDef::Function(f) => {
610 if has_test_related_attribute(&f.attrs(sema.db)) {
611 return true;
612 }
613 if consider_exported_main && f.exported_main(sema.db) {
614 return true;
617 }
618 }
619 hir::ModuleDef::Module(submodule) => {
620 if has_test_function_or_multiple_test_submodules(
621 sema,
622 &submodule,
623 consider_exported_main,
624 ) {
625 number_of_test_submodules += 1;
626 }
627 }
628 _ => (),
629 }
630 }
631
632 number_of_test_submodules > 1
633}
634
635#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash)]
636pub struct UpdateTest {
637 pub expect_test: bool,
638 pub insta: bool,
639 pub snapbox: bool,
640}
641
642static SNAPSHOT_TEST_MACROS: OnceLock<FxHashMap<&str, Vec<[Symbol; 2]>>> = OnceLock::new();
643
644impl UpdateTest {
645 const EXPECT_CRATE: &str = "expect_test";
646 const EXPECT_MACROS: &[&str] = &["expect", "expect_file"];
647
648 const INSTA_CRATE: &str = "insta";
649 const INSTA_MACROS: &[&str] = &[
650 "assert_snapshot",
651 "assert_debug_snapshot",
652 "assert_display_snapshot",
653 "assert_json_snapshot",
654 "assert_yaml_snapshot",
655 "assert_ron_snapshot",
656 "assert_toml_snapshot",
657 "assert_csv_snapshot",
658 "assert_compact_json_snapshot",
659 "assert_compact_debug_snapshot",
660 "assert_binary_snapshot",
661 ];
662
663 const SNAPBOX_CRATE: &str = "snapbox";
664 const SNAPBOX_MACROS: &[&str] = &["assert_data_eq", "file", "str"];
665
666 fn find_snapshot_macro(sema: &Semantics<'_, RootDatabase>, file_range: hir::FileRange) -> Self {
667 fn init<'a>(
668 krate_name: &'a str,
669 paths: &[&str],
670 map: &mut FxHashMap<&'a str, Vec<[Symbol; 2]>>,
671 ) {
672 let mut res = Vec::with_capacity(paths.len());
673 let krate = Symbol::intern(krate_name);
674 for path in paths {
675 let segments = [krate.clone(), Symbol::intern(path)];
676 res.push(segments);
677 }
678 map.insert(krate_name, res);
679 }
680
681 let mod_paths = SNAPSHOT_TEST_MACROS.get_or_init(|| {
682 let mut map = FxHashMap::default();
683 init(Self::EXPECT_CRATE, Self::EXPECT_MACROS, &mut map);
684 init(Self::INSTA_CRATE, Self::INSTA_MACROS, &mut map);
685 init(Self::SNAPBOX_CRATE, Self::SNAPBOX_MACROS, &mut map);
686 map
687 });
688
689 let search_scope = SearchScope::file_range(file_range);
690 let find_macro = |paths: &[[Symbol; 2]]| {
691 for path in paths {
692 let items = hir::resolve_absolute_path(sema.db, path.iter().cloned());
693 for item in items {
694 if let hir::ItemInNs::Macros(makro) = item
695 && Definition::Macro(makro)
696 .usages(sema)
697 .in_scope(&search_scope)
698 .at_least_one()
699 {
700 return true;
701 }
702 }
703 }
704 false
705 };
706
707 UpdateTest {
708 expect_test: find_macro(mod_paths.get(Self::EXPECT_CRATE).unwrap()),
709 insta: find_macro(mod_paths.get(Self::INSTA_CRATE).unwrap()),
710 snapbox: find_macro(mod_paths.get(Self::SNAPBOX_CRATE).unwrap()),
711 }
712 }
713
714 pub fn label(&self) -> Option<SmolStr> {
715 let mut builder: SmallVec<[_; 3]> = SmallVec::new();
716 if self.expect_test {
717 builder.push("Expect");
718 }
719 if self.insta {
720 builder.push("Insta");
721 }
722 if self.snapbox {
723 builder.push("Snapbox");
724 }
725
726 let res: SmolStr = builder.join(" + ").into();
727 if res.is_empty() {
728 None
729 } else {
730 Some(format_smolstr!("↺\u{fe0e} Update Tests ({res})"))
731 }
732 }
733
734 pub fn env(&self) -> ArrayVec<(&str, &str), 3> {
735 let mut env = ArrayVec::new();
736 if self.expect_test {
737 env.push(("UPDATE_EXPECT", "1"));
738 }
739 if self.insta {
740 env.push(("INSTA_UPDATE", "always"));
741 }
742 if self.snapbox {
743 env.push(("SNAPSHOTS", "overwrite"));
744 }
745 env
746 }
747}
748
749#[cfg(test)]
750mod tests {
751 use expect_test::{Expect, expect};
752
753 use crate::fixture;
754
755 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
756 let (analysis, position) = fixture::position(ra_fixture);
757 let result = analysis
758 .runnables(position.file_id)
759 .unwrap()
760 .into_iter()
761 .map(|runnable| {
762 let mut a = format!("({:?}, {:?}", runnable.kind.disc(), runnable.nav);
763 if runnable.use_name_in_title {
764 a.push_str(", true");
765 }
766 if let Some(cfg) = runnable.cfg {
767 a.push_str(&format!(", {cfg:?}"));
768 }
769 a.push(')');
770 a
771 })
772 .collect::<Vec<_>>();
773 expect.assert_debug_eq(&result);
774 }
775
776 fn check_tests(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
777 let (analysis, position) = fixture::position(ra_fixture);
778 let tests = analysis.related_tests(position, None).unwrap();
779 let navigation_targets = tests.into_iter().map(|runnable| runnable.nav).collect::<Vec<_>>();
780 expect.assert_debug_eq(&navigation_targets);
781 }
782
783 #[test]
784 fn test_runnables() {
785 check(
786 r#"
787//- /lib.rs
788$0
789fn main() {}
790
791#[export_name = "main"]
792fn __cortex_m_rt_main_trampoline() {}
793
794#[unsafe(export_name = "main")]
795fn __cortex_m_rt_main_trampoline_unsafe() {}
796
797#[test]
798fn test_foo() {}
799
800#[::core::prelude::v1::test]
801fn test_full_path() {}
802
803#[test]
804#[ignore]
805fn test_foo() {}
806
807#[bench]
808fn bench() {}
809
810mod not_a_root {
811 fn main() {}
812}
813"#,
814 expect![[r#"
815 [
816 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..331, name: \"\", kind: Module })",
817 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
818 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 15..76, focus_range: 42..71, name: \"__cortex_m_rt_main_trampoline\", kind: Function })",
819 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 78..154, focus_range: 113..149, name: \"__cortex_m_rt_main_trampoline_unsafe\", kind: Function })",
820 "(Test, NavigationTarget { file_id: FileId(0), full_range: 156..180, focus_range: 167..175, name: \"test_foo\", kind: Function })",
821 "(Test, NavigationTarget { file_id: FileId(0), full_range: 182..233, focus_range: 214..228, name: \"test_full_path\", kind: Function })",
822 "(Test, NavigationTarget { file_id: FileId(0), full_range: 235..269, focus_range: 256..264, name: \"test_foo\", kind: Function })",
823 "(Bench, NavigationTarget { file_id: FileId(0), full_range: 271..293, focus_range: 283..288, name: \"bench\", kind: Function })",
824 ]
825 "#]],
826 );
827 }
828
829 #[test]
830 fn test_runnables_doc_test() {
831 check(
832 r#"
833//- /lib.rs
834$0
835fn main() {}
836
837/// ```
838/// let x = 5;
839/// ```
840fn should_have_runnable() {}
841
842/// ```edition2018
843/// let x = 5;
844/// ```
845fn should_have_runnable_1() {}
846
847/// ```
848/// let z = 55;
849/// ```
850///
851/// ```ignore
852/// let z = 56;
853/// ```
854fn should_have_runnable_2() {}
855
856/**
857```rust
858let z = 55;
859```
860*/
861fn should_have_no_runnable_3() {}
862
863/**
864 ```rust
865 let z = 55;
866 ```
867*/
868fn should_have_no_runnable_4() {}
869
870/// ```no_run
871/// let z = 55;
872/// ```
873fn should_have_no_runnable() {}
874
875/// ```ignore
876/// let z = 55;
877/// ```
878fn should_have_no_runnable_2() {}
879
880/// ```compile_fail
881/// let z = 55;
882/// ```
883fn should_have_no_runnable_3() {}
884
885/// ```text
886/// arbitrary plain text
887/// ```
888fn should_have_no_runnable_4() {}
889
890/// ```text
891/// arbitrary plain text
892/// ```
893///
894/// ```sh
895/// $ shell code
896/// ```
897fn should_have_no_runnable_5() {}
898
899/// ```rust,no_run
900/// let z = 55;
901/// ```
902fn should_have_no_runnable_6() {}
903
904/// ```
905/// let x = 5;
906/// ```
907struct StructWithRunnable(String);
908
909/// ```
910/// let x = 5;
911/// ```
912impl StructWithRunnable {}
913
914trait Test {
915 fn test() -> usize {
916 5usize
917 }
918}
919
920/// ```
921/// let x = 5;
922/// ```
923impl Test for StructWithRunnable {}
924"#,
925 expect![[r#"
926 [
927 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
928 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 15..74, name: \"should_have_runnable\" })",
929 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 76..148, name: \"should_have_runnable_1\" })",
930 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 150..254, name: \"should_have_runnable_2\" })",
931 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 256..320, name: \"should_have_no_runnable_3\" })",
932 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 322..398, name: \"should_have_no_runnable_4\" })",
933 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 900..965, name: \"StructWithRunnable\" })",
934 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 967..1024, focus_range: 1003..1021, name: \"impl\", kind: Impl })",
935 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 1088..1154, focus_range: 1133..1151, name: \"impl\", kind: Impl })",
936 ]
937 "#]],
938 );
939 }
940
941 #[test]
942 fn test_runnables_doc_test_in_impl() {
943 check(
944 r#"
945//- /lib.rs
946$0
947fn main() {}
948
949struct Data;
950impl Data {
951 /// ```
952 /// let x = 5;
953 /// ```
954 fn foo() {}
955}
956"#,
957 expect![[r#"
958 [
959 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
960 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 44..98, name: \"foo\" })",
961 ]
962 "#]],
963 );
964 }
965
966 #[test]
967 fn test_runnables_doc_test_in_impl_with_lifetime() {
968 check(
969 r#"
970//- /lib.rs
971$0
972fn main() {}
973
974struct Data<'a>;
975impl Data<'a> {
976 /// ```
977 /// let x = 5;
978 /// ```
979 fn foo() {}
980}
981"#,
982 expect![[r#"
983 [
984 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
985 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 52..106, name: \"foo\" })",
986 ]
987 "#]],
988 );
989 }
990
991 #[test]
992 fn test_runnables_doc_test_in_impl_with_lifetime_and_types() {
993 check(
994 r#"
995//- /lib.rs
996$0
997fn main() {}
998
999struct Data<'a, T, U>;
1000impl<T, U> Data<'a, T, U> {
1001 /// ```
1002 /// let x = 5;
1003 /// ```
1004 fn foo() {}
1005}
1006"#,
1007 expect![[r#"
1008 [
1009 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
1010 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 70..124, name: \"foo\" })",
1011 ]
1012 "#]],
1013 );
1014 }
1015
1016 #[test]
1017 fn test_runnables_doc_test_in_impl_with_const() {
1018 check(
1019 r#"
1020//- /lib.rs
1021$0
1022fn main() {}
1023
1024struct Data<const N: usize>;
1025impl<const N: usize> Data<N> {
1026 /// ```
1027 /// let x = 5;
1028 /// ```
1029 fn foo() {}
1030}
1031"#,
1032 expect![[r#"
1033 [
1034 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
1035 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 79..133, name: \"foo\" })",
1036 ]
1037 "#]],
1038 );
1039 }
1040
1041 #[test]
1042 fn test_runnables_doc_test_in_impl_with_lifetime_types_and_const() {
1043 check(
1044 r#"
1045//- /lib.rs
1046$0
1047fn main() {}
1048
1049struct Data<'a, T, const N: usize>;
1050impl<'a, T, const N: usize> Data<'a, T, N> {
1051 /// ```
1052 /// let x = 5;
1053 /// ```
1054 fn foo() {}
1055}
1056"#,
1057 expect![[r#"
1058 [
1059 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
1060 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 100..154, name: \"foo\" })",
1061 ]
1062 "#]],
1063 );
1064 }
1065 #[test]
1066 fn test_runnables_module() {
1067 check(
1068 r#"
1069//- /lib.rs
1070$0
1071mod test_mod {
1072 #[test]
1073 fn test_foo1() {}
1074}
1075"#,
1076 expect![[r#"
1077 [
1078 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 1..51, focus_range: 5..13, name: \"test_mod\", kind: Module, description: \"mod test_mod\" })",
1079 "(Test, NavigationTarget { file_id: FileId(0), full_range: 20..49, focus_range: 35..44, name: \"test_foo1\", kind: Function })",
1080 ]
1081 "#]],
1082 );
1083 }
1084
1085 #[test]
1086 fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {
1087 check(
1088 r#"
1089//- /lib.rs
1090$0
1091mod root_tests {
1092 mod nested_tests_0 {
1093 mod nested_tests_1 {
1094 #[test]
1095 fn nested_test_11() {}
1096
1097 #[test]
1098 fn nested_test_12() {}
1099 }
1100
1101 mod nested_tests_2 {
1102 #[test]
1103 fn nested_test_2() {}
1104 }
1105
1106 mod nested_tests_3 {}
1107 }
1108
1109 mod nested_tests_4 {}
1110}
1111"#,
1112 expect![[r#"
1113 [
1114 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 22..323, focus_range: 26..40, name: \"nested_tests_0\", kind: Module, description: \"mod nested_tests_0\" })",
1115 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 51..192, focus_range: 55..69, name: \"nested_tests_1\", kind: Module, description: \"mod nested_tests_1\" })",
1116 "(Test, NavigationTarget { file_id: FileId(0), full_range: 84..126, focus_range: 107..121, name: \"nested_test_11\", kind: Function })",
1117 "(Test, NavigationTarget { file_id: FileId(0), full_range: 140..182, focus_range: 163..177, name: \"nested_test_12\", kind: Function })",
1118 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 202..286, focus_range: 206..220, name: \"nested_tests_2\", kind: Module, description: \"mod nested_tests_2\" })",
1119 "(Test, NavigationTarget { file_id: FileId(0), full_range: 235..276, focus_range: 258..271, name: \"nested_test_2\", kind: Function })",
1120 ]
1121 "#]],
1122 );
1123 }
1124
1125 #[test]
1126 fn test_runnables_with_feature() {
1127 check(
1128 r#"
1129//- /lib.rs crate:foo cfg:feature=foo
1130$0
1131#[test]
1132#[cfg(feature = "foo")]
1133fn test_foo1() {}
1134"#,
1135 expect![[r#"
1136 [
1137 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..51, name: \"\", kind: Module })",
1138 "(Test, NavigationTarget { file_id: FileId(0), full_range: 1..50, focus_range: 36..45, name: \"test_foo1\", kind: Function }, Atom(KeyValue { key: \"feature\", value: \"foo\" }))",
1139 ]
1140 "#]],
1141 );
1142 }
1143
1144 #[test]
1145 fn test_runnables_with_features() {
1146 check(
1147 r#"
1148//- /lib.rs crate:foo cfg:feature=foo,feature=bar
1149$0
1150#[test]
1151#[cfg(all(feature = "foo", feature = "bar"))]
1152fn test_foo1() {}
1153"#,
1154 expect![[r#"
1155 [
1156 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..73, name: \"\", kind: Module })",
1157 "(Test, NavigationTarget { file_id: FileId(0), full_range: 1..72, focus_range: 58..67, name: \"test_foo1\", kind: Function }, All([Atom(KeyValue { key: \"feature\", value: \"foo\" }), Atom(KeyValue { key: \"feature\", value: \"bar\" })]))",
1158 ]
1159 "#]],
1160 );
1161 }
1162
1163 #[test]
1164 fn test_runnables_no_test_function_in_module() {
1165 check(
1166 r#"
1167//- /lib.rs
1168$0
1169mod test_mod {
1170 fn foo1() {}
1171}
1172"#,
1173 expect![[r#"
1174 []
1175 "#]],
1176 );
1177 }
1178
1179 #[test]
1180 fn test_doc_runnables_impl_mod() {
1181 check(
1182 r#"
1183//- /lib.rs
1184mod foo;
1185//- /foo.rs
1186struct Foo;$0
1187impl Foo {
1188 /// ```
1189 /// let x = 5;
1190 /// ```
1191 fn foo() {}
1192}
1193 "#,
1194 expect![[r#"
1195 [
1196 "(DocTest, NavigationTarget { file_id: FileId(1), full_range: 27..81, name: \"foo\" })",
1197 ]
1198 "#]],
1199 );
1200 }
1201
1202 #[test]
1203 fn test_runnables_in_macro() {
1204 check(
1205 r#"
1206//- /lib.rs
1207$0
1208macro_rules! generate {
1209 () => {
1210 #[test]
1211 fn foo_test() {}
1212 }
1213}
1214macro_rules! generate2 {
1215 () => {
1216 mod tests2 {
1217 #[test]
1218 fn foo_test2() {}
1219 }
1220 }
1221}
1222macro_rules! generate_main {
1223 () => {
1224 fn main() {}
1225 }
1226}
1227mod tests {
1228 generate!();
1229}
1230generate2!();
1231generate_main!();
1232"#,
1233 expect![[r#"
1234 [
1235 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 0..345, name: \"\", kind: Module })",
1236 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 282..312, focus_range: 286..291, name: \"tests\", kind: Module, description: \"mod tests\" })",
1237 "(Test, NavigationTarget { file_id: FileId(0), full_range: 298..307, name: \"foo_test\", kind: Function })",
1238 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 313..323, name: \"tests2\", kind: Module, description: \"mod tests2\" }, true)",
1239 "(Test, NavigationTarget { file_id: FileId(0), full_range: 313..323, name: \"foo_test2\", kind: Function }, true)",
1240 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 327..341, name: \"main\", kind: Function })",
1241 ]
1242 "#]],
1243 );
1244 }
1245
1246 #[test]
1247 fn big_mac() {
1248 check(
1249 r#"
1250//- /lib.rs
1251$0
1252macro_rules! foo {
1253 () => {
1254 mod foo_tests {
1255 #[test]
1256 fn foo0() {}
1257 #[test]
1258 fn foo1() {}
1259 #[test]
1260 fn foo2() {}
1261 }
1262 };
1263}
1264foo!();
1265"#,
1266 expect![[r#"
1267 [
1268 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 210..214, name: \"foo_tests\", kind: Module, description: \"mod foo_tests\" }, true)",
1269 "(Test, NavigationTarget { file_id: FileId(0), full_range: 210..214, name: \"foo0\", kind: Function }, true)",
1270 "(Test, NavigationTarget { file_id: FileId(0), full_range: 210..214, name: \"foo1\", kind: Function }, true)",
1271 "(Test, NavigationTarget { file_id: FileId(0), full_range: 210..214, name: \"foo2\", kind: Function }, true)",
1272 ]
1273 "#]],
1274 );
1275 }
1276
1277 #[test]
1278 fn dont_recurse_in_outline_submodules() {
1279 check(
1280 r#"
1281//- /lib.rs
1282$0
1283mod m;
1284//- /m.rs
1285mod tests {
1286 #[test]
1287 fn t() {}
1288}
1289"#,
1290 expect![[r#"
1291 []
1292 "#]],
1293 );
1294 }
1295
1296 #[test]
1297 fn outline_submodule1() {
1298 check(
1299 r#"
1300//- /lib.rs
1301$0
1302mod m;
1303//- /m.rs
1304#[test]
1305fn t0() {}
1306#[test]
1307fn t1() {}
1308"#,
1309 expect![[r#"
1310 [
1311 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 1..7, focus_range: 5..6, name: \"m\", kind: Module, description: \"mod m\" })",
1312 ]
1313 "#]],
1314 );
1315 }
1316
1317 #[test]
1318 fn outline_submodule2() {
1319 check(
1320 r#"
1321//- /lib.rs
1322mod m;
1323//- /m.rs
1324$0
1325#[test]
1326fn t0() {}
1327#[test]
1328fn t1() {}
1329"#,
1330 expect![[r#"
1331 [
1332 "(TestMod, NavigationTarget { file_id: FileId(1), full_range: 0..39, name: \"m\", kind: Module })",
1333 "(Test, NavigationTarget { file_id: FileId(1), full_range: 1..19, focus_range: 12..14, name: \"t0\", kind: Function })",
1334 "(Test, NavigationTarget { file_id: FileId(1), full_range: 20..38, focus_range: 31..33, name: \"t1\", kind: Function })",
1335 ]
1336 "#]],
1337 );
1338 }
1339
1340 #[test]
1341 fn attributed_module() {
1342 check(
1343 r#"
1344//- proc_macros: identity
1345//- /lib.rs
1346$0
1347#[proc_macros::identity]
1348mod module {
1349 #[test]
1350 fn t0() {}
1351 #[test]
1352 fn t1() {}
1353}
1354"#,
1355 expect![[r#"
1356 [
1357 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 26..94, focus_range: 30..36, name: \"module\", kind: Module, description: \"mod module\" }, true)",
1358 "(Test, NavigationTarget { file_id: FileId(0), full_range: 43..65, focus_range: 58..60, name: \"t0\", kind: Function }, true)",
1359 "(Test, NavigationTarget { file_id: FileId(0), full_range: 70..92, focus_range: 85..87, name: \"t1\", kind: Function }, true)",
1360 ]
1361 "#]],
1362 );
1363 }
1364
1365 #[test]
1366 fn find_no_tests() {
1367 check_tests(
1368 r#"
1369//- /lib.rs
1370fn foo$0() { };
1371"#,
1372 expect![[r#"
1373 []
1374 "#]],
1375 );
1376 }
1377
1378 #[test]
1379 fn find_direct_fn_test() {
1380 check_tests(
1381 r#"
1382//- /lib.rs
1383fn foo$0() { };
1384
1385mod tests {
1386 #[test]
1387 fn foo_test() {
1388 super::foo()
1389 }
1390}
1391"#,
1392 expect![[r#"
1393 [
1394 NavigationTarget {
1395 file_id: FileId(
1396 0,
1397 ),
1398 full_range: 31..85,
1399 focus_range: 46..54,
1400 name: "foo_test",
1401 kind: Function,
1402 },
1403 ]
1404 "#]],
1405 );
1406 }
1407
1408 #[test]
1409 fn find_direct_struct_test() {
1410 check_tests(
1411 r#"
1412//- /lib.rs
1413struct Fo$0o;
1414fn foo(arg: &Foo) { };
1415
1416mod tests {
1417 use super::*;
1418
1419 #[test]
1420 fn foo_test() {
1421 foo(Foo);
1422 }
1423}
1424"#,
1425 expect![[r#"
1426 [
1427 NavigationTarget {
1428 file_id: FileId(
1429 0,
1430 ),
1431 full_range: 71..122,
1432 focus_range: 86..94,
1433 name: "foo_test",
1434 kind: Function,
1435 },
1436 ]
1437 "#]],
1438 );
1439 }
1440
1441 #[test]
1442 fn find_indirect_fn_test() {
1443 check_tests(
1444 r#"
1445//- /lib.rs
1446fn foo$0() { };
1447
1448mod tests {
1449 use super::foo;
1450
1451 fn check1() {
1452 check2()
1453 }
1454
1455 fn check2() {
1456 foo()
1457 }
1458
1459 #[test]
1460 fn foo_test() {
1461 check1()
1462 }
1463}
1464"#,
1465 expect![[r#"
1466 [
1467 NavigationTarget {
1468 file_id: FileId(
1469 0,
1470 ),
1471 full_range: 133..183,
1472 focus_range: 148..156,
1473 name: "foo_test",
1474 kind: Function,
1475 },
1476 ]
1477 "#]],
1478 );
1479 }
1480
1481 #[test]
1482 fn tests_are_unique() {
1483 check_tests(
1484 r#"
1485//- /lib.rs
1486fn foo$0() { };
1487
1488mod tests {
1489 use super::foo;
1490
1491 #[test]
1492 fn foo_test() {
1493 foo();
1494 foo();
1495 }
1496
1497 #[test]
1498 fn foo2_test() {
1499 foo();
1500 foo();
1501 }
1502
1503}
1504"#,
1505 expect![[r#"
1506 [
1507 NavigationTarget {
1508 file_id: FileId(
1509 0,
1510 ),
1511 full_range: 52..115,
1512 focus_range: 67..75,
1513 name: "foo_test",
1514 kind: Function,
1515 },
1516 NavigationTarget {
1517 file_id: FileId(
1518 0,
1519 ),
1520 full_range: 121..185,
1521 focus_range: 136..145,
1522 name: "foo2_test",
1523 kind: Function,
1524 },
1525 ]
1526 "#]],
1527 );
1528 }
1529
1530 #[test]
1531 fn test_runnables_doc_test_in_impl_with_lifetime_type_const_value() {
1532 check(
1533 r#"
1534//- /lib.rs
1535$0
1536fn main() {}
1537
1538struct Data<'a, A, const B: usize, C, const D: u32>;
1539impl<A, C, const D: u32> Data<'a, A, 12, C, D> {
1540 /// ```
1541 /// ```
1542 fn foo() {}
1543}
1544"#,
1545 expect![[r#"
1546 [
1547 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 1..13, focus_range: 4..8, name: \"main\", kind: Function })",
1548 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 121..156, name: \"foo\" })",
1549 ]
1550 "#]],
1551 );
1552 }
1553
1554 #[test]
1555 fn doc_test_type_params() {
1556 check(
1557 r#"
1558//- /lib.rs
1559$0
1560struct Foo<T, U>;
1561
1562/// ```
1563/// ```
1564impl<T, U> Foo<T, U> {
1565 /// ```rust
1566 /// ````
1567 fn t() {}
1568}
1569
1570/// ```
1571/// ```
1572impl Foo<Foo<(), ()>, ()> {
1573 /// ```
1574 /// ```
1575 fn t() {}
1576}
1577"#,
1578 expect![[r#"
1579 [
1580 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 20..103, focus_range: 47..56, name: \"impl\", kind: Impl })",
1581 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 63..101, name: \"t\" })",
1582 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 105..188, focus_range: 126..146, name: \"impl\", kind: Impl })",
1583 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 153..186, name: \"t\" })",
1584 ]
1585 "#]],
1586 );
1587 }
1588
1589 #[test]
1590 fn doc_test_macro_export_mbe() {
1591 check(
1592 r#"
1593//- /lib.rs
1594$0
1595mod foo;
1596
1597//- /foo.rs
1598/// ```
1599/// fn foo() {
1600/// }
1601/// ```
1602#[macro_export]
1603macro_rules! foo {
1604 () => {
1605
1606 };
1607}
1608"#,
1609 expect![[r#"
1610 []
1611 "#]],
1612 );
1613 check(
1614 r#"
1615//- /lib.rs
1616$0
1617/// ```
1618/// fn foo() {
1619/// }
1620/// ```
1621#[macro_export]
1622macro_rules! foo {
1623 () => {
1624
1625 };
1626}
1627"#,
1628 expect![[r#"
1629 [
1630 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 1..94, name: \"foo\" })",
1631 ]
1632 "#]],
1633 );
1634 }
1635
1636 #[test]
1637 fn test_paths_with_raw_ident() {
1638 check(
1639 r#"
1640//- /lib.rs
1641$0
1642mod r#mod {
1643 #[test]
1644 fn r#fn() {}
1645
1646 /// ```
1647 /// ```
1648 fn r#for() {}
1649
1650 /// ```
1651 /// ```
1652 struct r#struct<r#type>(r#type);
1653
1654 /// ```
1655 /// ```
1656 impl<r#type> r#struct<r#type> {
1657 /// ```
1658 /// ```
1659 fn r#fn() {}
1660 }
1661
1662 enum r#enum {}
1663 impl r#struct<r#enum> {
1664 /// ```
1665 /// ```
1666 fn r#fn() {}
1667 }
1668
1669 trait r#trait {}
1670
1671 /// ```
1672 /// ```
1673 impl<T> r#trait for r#struct<T> {}
1674}
1675"#,
1676 expect![[r#"
1677 [
1678 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 1..461, focus_range: 5..10, name: \"r#mod\", kind: Module, description: \"mod r#mod\" })",
1679 "(Test, NavigationTarget { file_id: FileId(0), full_range: 17..41, focus_range: 32..36, name: \"r#fn\", kind: Function })",
1680 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 47..84, name: \"r#for\", container_name: \"r#mod\" })",
1681 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 90..146, name: \"r#struct\", container_name: \"r#mod\" })",
1682 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 152..266, focus_range: 189..205, name: \"impl\", kind: Impl })",
1683 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 216..260, name: \"r#fn\" })",
1684 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 323..367, name: \"r#fn\" })",
1685 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 401..459, focus_range: 445..456, name: \"impl\", kind: Impl })",
1686 ]
1687 "#]],
1688 )
1689 }
1690
1691 #[test]
1692 fn exported_main_is_test_in_cfg_test_mod() {
1693 check(
1694 r#"
1695//- /lib.rs crate:foo cfg:test
1696$0
1697mod not_a_test_module_inline {
1698 #[export_name = "main"]
1699 fn exp_main() {}
1700}
1701#[cfg(test)]
1702mod test_mod_inline {
1703 #[export_name = "main"]
1704 fn exp_main() {}
1705}
1706mod not_a_test_module;
1707#[cfg(test)]
1708mod test_mod;
1709//- /not_a_test_module.rs
1710#[export_name = "main"]
1711fn exp_main() {}
1712//- /test_mod.rs
1713#[export_name = "main"]
1714fn exp_main() {}
1715"#,
1716 expect![[r#"
1717 [
1718 "(Bin, NavigationTarget { file_id: FileId(0), full_range: 36..80, focus_range: 67..75, name: \"exp_main\", kind: Function })",
1719 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 83..168, focus_range: 100..115, name: \"test_mod_inline\", kind: Module, description: \"mod test_mod_inline\" }, Atom(Flag(\"test\")))",
1720 "(TestMod, NavigationTarget { file_id: FileId(0), full_range: 192..218, focus_range: 209..217, name: \"test_mod\", kind: Module, description: \"mod test_mod\" }, Atom(Flag(\"test\")))",
1721 ]
1722 "#]],
1723 )
1724 }
1725}