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