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