1use std::{fmt, sync::OnceLock};23use 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};2627use crate::{FileId, NavigationTarget, ToNav, TryToNav, references};2829#[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}3738impl_empty_upmap_from_ra_fixture!(RunnableKind, UpdateTest);3940#[derive(Debug, Clone, Hash, PartialEq, Eq)]41pub enum TestId {42 Name(SmolStr),43 Path(String),44}4546impl 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}5455#[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}6364#[derive(Debug, Clone, Hash, PartialEq, Eq, PartialOrd, Ord)]65enum RunnableDiscKind {66 TestMod,67 Test,68 DocTest,69 Bench,70 Bin,71}7273impl 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}8485impl 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 }9798 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 s115 }116}117118// Feature: Run119//120// Shows a popup suggesting to run a test/benchmark/binary **at the current cursor121// location**. Super useful for repeatedly running just a single test. Do bind this122// to a shortcut!123//124// | Editor | Action Name |125// |---------|-------------|126// | VS Code | **rust-analyzer: Run** |127//128// 129pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {130 let sema = Semantics::new(db);131132 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 });175176 sema.file_to_module_defs(file_id)177 .map(|it| runnable_mod_outline_definition(&sema, it))178 .for_each(|it| add_opt(it, None));179180 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 r185 })186 }));187 res.sort_by(cmp_runnables);188 res189}190191// Feature: Related Tests192//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 on196// 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();209210 find_related_tests(&sema, &syntax, position, search_scope, &mut res);211212 res.into_iter().sorted_by(cmp_runnables).collect()213}214215fn 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 ordering220 nav.full_range221 .start()222 .cmp(&nav_b.full_range.start())223 .then_with(|| {224 let t_0 = || TextSize::from(0);225 nav.focus_range226 .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}232233fn 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::search241 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 = def247 .usages(sema)248 .set_scope(search_scope.as_ref())249 .all()250 .references251 .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 test263 tests.insert(runnable);264 } else if let Some(module) = parent_test_module(sema, &fn_def) {265 // indirect test266 find_related_tests_in_module(sema, syntax, &fn_def, &module, tests);267 }268 }269 }270 }271}272273fn 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);285286 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}294295fn 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 None301 }302}303304fn 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)?;308309 if has_test_function_or_multiple_test_submodules(sema, &module, false) {310 Some(module)311 } else {312 None313 }314 })315}316317pub(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::Bin325 } 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_path332 .map(TestId::Path)333 .unwrap_or(TestId::Name(def.name(sema.db).display_no_db(edition).to_smolstr()))334 };335336 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 };344345 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();352353 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);355356 let cfg = def.attrs(sema.db).cfgs(sema.db).cloned();357 Some(Runnable { use_name_in_title: false, nav, kind, cfg, update_test })358}359360pub(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 = def369 .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("::");378379 let cfg = cfg.cloned();380 let nav = NavigationTarget::from_module_to_decl(sema.db, def).call_site();381382 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);389390 Some(Runnable {391 use_name_in_title: false,392 nav,393 kind: RunnableKind::TestMod { path },394 cfg,395 update_test,396 })397}398399pub(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);422423 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);427428 Some(Runnable {429 use_name_in_title: false,430 nav,431 kind: RunnableKind::DocTest { test_id },432 cfg,433 update_test,434 })435}436437fn has_cfg_test(cfg: Option<&CfgExpr>) -> bool {438 return cfg.is_some_and(has_cfg_test_impl);439440 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}448449/// 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)?;455456 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 = def461 .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("::");470471 let cfg = cfg.cloned();472473 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);480481 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}489490fn 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 = krate508 .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 })();537538 let test_id = path539 .map_or_else(|| TestId::Name(def_name.display_no_db(edition).to_smolstr()), TestId::Path);540541 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}559560fn 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"];564565 attrs.hir_docs(db).is_some_and(|doc| {566 let mut in_code_block = false;567568 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;573574 if in_code_block575 && header576 .split(',')577 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))578 {579 return true;580 }581 }582 }583584 false585 })586}587588// 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 already590fn 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;596597 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 test605 // runners606 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 }621622 number_of_test_submodules > 1623}624625#[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}631632static SNAPSHOT_TEST_MACROS: OnceLock<FxHashMap<&str, Vec<[Symbol; 2]>>> = OnceLock::new();633634impl UpdateTest {635 const EXPECT_CRATE: &str = "expect_test";636 const EXPECT_MACROS: &[&str] = &["expect", "expect_file"];637638 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 ];652653 const SNAPBOX_CRATE: &str = "snapbox";654 const SNAPBOX_MACROS: &[&str] = &["assert_data_eq", "file", "str"];655656 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 }670671 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 map677 });678679 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) = item685 && Definition::Macro(makro)686 .usages(sema)687 .in_scope(&search_scope)688 .at_least_one()689 {690 return true;691 }692 }693 }694 false695 };696697 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 }703704 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 }715716 let res: SmolStr = builder.join(" + ").into();717 if res.is_empty() {718 None719 } else {720 Some(format_smolstr!("↺\u{fe0e} Update Tests ({res})"))721 }722 }723724 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 env736 }737}738739#[cfg(test)]740mod tests {741 use expect_test::{Expect, expect};742743 use crate::fixture;744745 fn check(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {746 let (analysis, position) = fixture::position(ra_fixture);747 let result = analysis748 .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 a761 })762 .collect::<Vec<_>>();763 expect.assert_debug_eq(&result);764 }765766 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 }772773 #[test]774 fn test_runnables() {775 check(776 r#"777//- /lib.rs778$0779fn main() {}780781#[export_name = "main"]782fn __cortex_m_rt_main_trampoline() {}783784#[unsafe(export_name = "main")]785fn __cortex_m_rt_main_trampoline_unsafe() {}786787#[test]788fn test_foo() {}789790#[::core::prelude::v1::test]791fn test_full_path() {}792793#[test]794#[ignore]795fn test_foo() {}796797#[bench]798fn bench() {}799800mod 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 }818819 #[test]820 fn test_runnables_doc_test() {821 check(822 r#"823//- /lib.rs824$0825fn main() {}826827/// ```828/// let x = 5;829/// ```830fn should_have_runnable() {}831832/// ```edition2018833/// let x = 5;834/// ```835fn should_have_runnable_1() {}836837/// ```838/// let z = 55;839/// ```840///841/// ```ignore842/// let z = 56;843/// ```844fn should_have_runnable_2() {}845846/**847```rust848let z = 55;849```850*/851fn should_have_no_runnable_3() {}852853/**854 ```rust855 let z = 55;856 ```857*/858fn should_have_no_runnable_4() {}859860/// ```no_run861/// let z = 55;862/// ```863fn should_have_no_runnable() {}864865/// ```ignore866/// let z = 55;867/// ```868fn should_have_no_runnable_2() {}869870/// ```compile_fail871/// let z = 55;872/// ```873fn should_have_no_runnable_3() {}874875/// ```text876/// arbitrary plain text877/// ```878fn should_have_no_runnable_4() {}879880/// ```text881/// arbitrary plain text882/// ```883///884/// ```sh885/// $ shell code886/// ```887fn should_have_no_runnable_5() {}888889/// ```rust,no_run890/// let z = 55;891/// ```892fn should_have_no_runnable_6() {}893894/// ```895/// let x = 5;896/// ```897struct StructWithRunnable(String);898899/// ```900/// let x = 5;901/// ```902impl StructWithRunnable {}903904trait Test {905 fn test() -> usize {906 5usize907 }908}909910/// ```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 }930931 #[test]932 fn test_runnables_doc_test_in_impl() {933 check(934 r#"935//- /lib.rs936$0937fn main() {}938939struct 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 }955956 #[test]957 fn test_runnables_doc_test_in_impl_with_lifetime() {958 check(959 r#"960//- /lib.rs961$0962fn main() {}963964struct 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 }980981 #[test]982 fn test_runnables_doc_test_in_impl_with_lifetime_and_types() {983 check(984 r#"985//- /lib.rs986$0987fn main() {}988989struct 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 }10051006 #[test]1007 fn test_runnables_doc_test_in_impl_with_const() {1008 check(1009 r#"1010//- /lib.rs1011$01012fn main() {}10131014struct 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 }10301031 #[test]1032 fn test_runnables_doc_test_in_impl_with_lifetime_types_and_const() {1033 check(1034 r#"1035//- /lib.rs1036$01037fn main() {}10381039struct 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.rs1060$01061mod 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 }10741075 #[test]1076 fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {1077 check(1078 r#"1079//- /lib.rs1080$01081mod root_tests {1082 mod nested_tests_0 {1083 mod nested_tests_1 {1084 #[test]1085 fn nested_test_11() {}10861087 #[test]1088 fn nested_test_12() {}1089 }10901091 mod nested_tests_2 {1092 #[test]1093 fn nested_test_2() {}1094 }10951096 mod nested_tests_3 {}1097 }10981099 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 }11141115 #[test]1116 fn test_runnables_with_feature() {1117 check(1118 r#"1119//- /lib.rs crate:foo cfg:feature=foo1120$01121#[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 }11331134 #[test]1135 fn test_runnables_with_features() {1136 check(1137 r#"1138//- /lib.rs crate:foo cfg:feature=foo,feature=bar1139$01140#[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 }11521153 #[test]1154 fn test_runnables_no_test_function_in_module() {1155 check(1156 r#"1157//- /lib.rs1158$01159mod test_mod {1160 fn foo1() {}1161}1162"#,1163 expect![[r#"1164 []1165 "#]],1166 );1167 }11681169 #[test]1170 fn test_doc_runnables_impl_mod() {1171 check(1172 r#"1173//- /lib.rs1174mod foo;1175//- /foo.rs1176struct Foo;$01177impl 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 }11911192 #[test]1193 fn test_runnables_in_macro() {1194 check(1195 r#"1196//- /lib.rs1197$01198macro_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 }12351236 #[test]1237 fn big_mac() {1238 check(1239 r#"1240//- /lib.rs1241$01242macro_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 }12661267 #[test]1268 fn dont_recurse_in_outline_submodules() {1269 check(1270 r#"1271//- /lib.rs1272$01273mod m;1274//- /m.rs1275mod tests {1276 #[test]1277 fn t() {}1278}1279"#,1280 expect![[r#"1281 []1282 "#]],1283 );1284 }12851286 #[test]1287 fn outline_submodule1() {1288 check(1289 r#"1290//- /lib.rs1291$01292mod m;1293//- /m.rs1294#[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 }13061307 #[test]1308 fn outline_submodule2() {1309 check(1310 r#"1311//- /lib.rs1312mod m;1313//- /m.rs1314$01315#[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 }13291330 #[test]1331 fn attributed_module() {1332 check(1333 r#"1334//- proc_macros: identity1335//- /lib.rs1336$01337#[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 }13541355 #[test]1356 fn find_no_tests() {1357 check_tests(1358 r#"1359//- /lib.rs1360fn foo$0() { };1361"#,1362 expect![[r#"1363 []1364 "#]],1365 );1366 }13671368 #[test]1369 fn find_direct_fn_test() {1370 check_tests(1371 r#"1372//- /lib.rs1373fn foo$0() { };13741375mod 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 }13971398 #[test]1399 fn find_direct_struct_test() {1400 check_tests(1401 r#"1402//- /lib.rs1403struct Fo$0o;1404fn foo(arg: &Foo) { };14051406mod tests {1407 use super::*;14081409 #[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 }14301431 #[test]1432 fn find_indirect_fn_test() {1433 check_tests(1434 r#"1435//- /lib.rs1436fn foo$0() { };14371438mod tests {1439 use super::foo;14401441 fn check1() {1442 check2()1443 }14441445 fn check2() {1446 foo()1447 }14481449 #[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 }14701471 #[test]1472 fn tests_are_unique() {1473 check_tests(1474 r#"1475//- /lib.rs1476fn foo$0() { };14771478mod tests {1479 use super::foo;14801481 #[test]1482 fn foo_test() {1483 foo();1484 foo();1485 }14861487 #[test]1488 fn foo2_test() {1489 foo();1490 foo();1491 }14921493}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 }15191520 #[test]1521 fn test_runnables_doc_test_in_impl_with_lifetime_type_const_value() {1522 check(1523 r#"1524//- /lib.rs1525$01526fn main() {}15271528struct 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 }15431544 #[test]1545 fn doc_test_type_params() {1546 check(1547 r#"1548//- /lib.rs1549$01550struct Foo<T, U>;15511552/// ```1553/// ```1554impl<T, U> Foo<T, U> {1555 /// ```rust1556 /// ````1557 fn t() {}1558}15591560/// ```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 }15781579 #[test]1580 fn doc_test_macro_export_mbe() {1581 check(1582 r#"1583//- /lib.rs1584$01585mod foo;15861587//- /foo.rs1588/// ```1589/// fn foo() {1590/// }1591/// ```1592#[macro_export]1593macro_rules! foo {1594 () => {15951596 };1597}1598"#,1599 expect![[r#"1600 []1601 "#]],1602 );1603 check(1604 r#"1605//- /lib.rs1606$01607/// ```1608/// fn foo() {1609/// }1610/// ```1611#[macro_export]1612macro_rules! foo {1613 () => {16141615 };1616}1617"#,1618 expect![[r#"1619 [1620 "(DocTest, NavigationTarget { file_id: FileId(0), full_range: 1..94, name: \"foo\" })",1621 ]1622 "#]],1623 );1624 }16251626 #[test]1627 fn test_paths_with_raw_ident() {1628 check(1629 r#"1630//- /lib.rs1631$01632mod r#mod {1633 #[test]1634 fn r#fn() {}16351636 /// ```1637 /// ```1638 fn r#for() {}16391640 /// ```1641 /// ```1642 struct r#struct<r#type>(r#type);16431644 /// ```1645 /// ```1646 impl<r#type> r#struct<r#type> {1647 /// ```1648 /// ```1649 fn r#fn() {}1650 }16511652 enum r#enum {}1653 impl r#struct<r#enum> {1654 /// ```1655 /// ```1656 fn r#fn() {}1657 }16581659 trait r#trait {}16601661 /// ```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 }16801681 #[test]1682 fn exported_main_is_test_in_cfg_test_mod() {1683 check(1684 r#"1685//- /lib.rs crate:foo cfg:test1686$01687mod 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.rs1700#[export_name = "main"]1701fn exp_main() {}1702//- /test_mod.rs1703#[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}