ide/
runnables.rs

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
118// Feature: Run
119//
120// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
121// location**. Super useful for repeatedly running just a single test. Do bind this
122// to a shortcut!
123//
124// | Editor  | Action Name |
125// |---------|-------------|
126// | VS Code | **rust-analyzer: Run** |
127//
128// ![Run](https://user-images.githubusercontent.com/48062697/113065583-055aae80-91b1-11eb-958f-d67efcaf6a2f.gif)
129pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
130    let sema = Semantics::new(db);
131
132    let mut res = Vec::new();
133    // Record all runnables that come from macro expansions here instead.
134    // In case an expansion creates multiple runnables we want to name them to avoid emitting a bunch of equally named runnables.
135    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
191// Feature: Related Tests
192//
193// Provides a sneak peek of all tests where the current item is used.
194//
195// The simplest way to use this feature is via the context menu. Right-click on
196// the selected item. The context menu opens. Select **Peek Related Tests**.
197//
198// | Editor  | Action Name |
199// |---------|-------------|
200// | VS Code | **rust-analyzer: Peek Related Tests** |
201pub(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    // full_range.start < focus_range.start < name, should give us a decent unique ordering
220    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    // FIXME: why is this using references::find_defs, this should use ide_db::search
241    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                    // direct test
263                    tests.insert(runnable);
264                } else if let Some(module) = parent_test_module(sema, &fn_def) {
265                    // indirect test
266                    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
450/// Creates a test mod runnable for outline modules at the top of their definition.
451fn 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        // 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                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
600// We could create runnables for modules with number_of_test_submodules > 0,
601// but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
602fn 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                    // an exported main in a test module can be considered a test wrt to custom test
617                    // runners
618                    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}