ide/
runnables.rs

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