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