ide/
runnables.rs

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