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