ide/
runnables.rs

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