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