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