Skip to main content

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