4 use hir::{AsAssocItem, HasAttrs, HasSource, Semantics};
5 use ide_assists::utils::test_related_attribute;
6 use ide_db::{defs::Definition, RootDatabase, SymbolKind};
7 use itertools::Itertools;
9 ast::{self, AstNode, AttrsOwner},
10 match_ast, SyntaxNode,
15 display::{ToNav, TryToNav},
16 FileId, NavigationTarget,
19 #[derive(Debug, Clone)]
21 pub nav: NavigationTarget,
22 pub kind: RunnableKind,
23 pub cfg: Option<CfgExpr>,
26 #[derive(Debug, Clone)]
32 impl fmt::Display for TestId {
33 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
35 TestId::Name(name) => write!(f, "{}", name),
36 TestId::Path(path) => write!(f, "{}", path),
41 #[derive(Debug, Clone)]
42 pub enum RunnableKind {
43 Test { test_id: TestId, attr: TestAttr },
44 TestMod { path: String },
45 Bench { test_id: TestId },
46 DocTest { test_id: TestId },
50 #[derive(Debug, Eq, PartialEq)]
51 pub struct RunnableAction {
52 pub run_title: &'static str,
56 const TEST: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Test", debugee: true };
57 const DOCTEST: RunnableAction =
58 RunnableAction { run_title: "▶\u{fe0e} Run Doctest", debugee: false };
59 const BENCH: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run Bench", debugee: true };
60 const BIN: RunnableAction = RunnableAction { run_title: "▶\u{fe0e} Run", debugee: true };
63 // test package::module::testname
64 pub fn label(&self, target: Option<String>) -> String {
66 RunnableKind::Test { test_id, .. } => format!("test {}", test_id),
67 RunnableKind::TestMod { path } => format!("test-mod {}", path),
68 RunnableKind::Bench { test_id } => format!("bench {}", test_id),
69 RunnableKind::DocTest { test_id, .. } => format!("doctest {}", test_id),
70 RunnableKind::Bin => {
71 target.map_or_else(|| "run binary".to_string(), |t| format!("run {}", t))
76 pub fn action(&self) -> &'static RunnableAction {
78 RunnableKind::Test { .. } | RunnableKind::TestMod { .. } => &TEST,
79 RunnableKind::DocTest { .. } => &DOCTEST,
80 RunnableKind::Bench { .. } => &BENCH,
81 RunnableKind::Bin => &BIN,
88 // Shows a popup suggesting to run a test/benchmark/binary **at the current cursor
89 // location**. Super useful for repeatedly running just a single test. Do bind this
93 // | Editor | Action Name
95 // | VS Code | **Rust Analyzer: Run**
97 pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
98 let sema = Semantics::new(db);
99 let module = match sema.to_module_def(file_id) {
100 None => return Vec::new(),
104 let mut res = Vec::new();
105 runnables_mod(&sema, &mut res, module);
109 fn runnables_mod(sema: &Semantics<RootDatabase>, acc: &mut Vec<Runnable>, module: hir::Module) {
110 acc.extend(module.declarations(sema.db).into_iter().filter_map(|def| {
111 let runnable = match def {
112 hir::ModuleDef::Module(it) => runnable_mod(&sema, it),
113 hir::ModuleDef::Function(it) => runnable_fn(&sema, it),
116 runnable.or_else(|| module_def_doctest(&sema, def))
119 acc.extend(module.impl_defs(sema.db).into_iter().flat_map(|it| it.items(sema.db)).filter_map(
121 hir::AssocItem::Function(it) => {
122 runnable_fn(&sema, it).or_else(|| module_def_doctest(&sema, it.into()))
124 hir::AssocItem::Const(it) => module_def_doctest(&sema, it.into()),
125 hir::AssocItem::TypeAlias(it) => module_def_doctest(&sema, it.into()),
129 for def in module.declarations(sema.db) {
130 if let hir::ModuleDef::Module(submodule) = def {
131 match submodule.definition_source(sema.db).value {
132 hir::ModuleSource::Module(_) => runnables_mod(sema, acc, submodule),
133 hir::ModuleSource::SourceFile(_) => mark::hit!(dont_recurse_in_outline_submodules),
134 hir::ModuleSource::BlockExpr(_) => {} // inner items aren't runnable
140 pub(crate) fn runnable_fn(sema: &Semantics<RootDatabase>, def: hir::Function) -> Option<Runnable> {
141 let func = def.source(sema.db)?;
142 let name_string = def.name(sema.db).to_string();
144 let kind = if name_string == "main" {
147 let canonical_path = {
148 let def: hir::ModuleDef = def.into();
149 def.canonical_path(sema.db)
151 let test_id = canonical_path.map(TestId::Path).unwrap_or(TestId::Name(name_string));
153 if test_related_attribute(&func.value).is_some() {
154 let attr = TestAttr::from_fn(&func.value);
155 RunnableKind::Test { test_id, attr }
156 } else if func.value.has_atom_attr("bench") {
157 RunnableKind::Bench { test_id }
163 let nav = NavigationTarget::from_named(
165 func.as_ref().map(|it| it as &dyn ast::NameOwner),
166 SymbolKind::Function,
168 let cfg = def.attrs(sema.db).cfg();
169 Some(Runnable { nav, kind, cfg })
172 pub(crate) fn runnable_mod(sema: &Semantics<RootDatabase>, def: hir::Module) -> Option<Runnable> {
173 if !has_test_function_or_multiple_test_submodules(sema, &def) {
177 def.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
179 let attrs = def.attrs(sema.db);
180 let cfg = attrs.cfg();
181 let nav = def.to_nav(sema.db);
182 Some(Runnable { nav, kind: RunnableKind::TestMod { path }, cfg })
185 // FIXME: figure out a proper API here.
186 pub(crate) fn doc_owner_to_def(
187 sema: &Semantics<RootDatabase>,
189 ) -> Option<Definition> {
190 let res: hir::ModuleDef = match_ast! {
192 ast::SourceFile(it) => sema.scope(&item).module()?.into(),
193 ast::Fn(it) => sema.to_def(&it)?.into(),
194 ast::Struct(it) => sema.to_def(&it)?.into(),
195 ast::Enum(it) => sema.to_def(&it)?.into(),
196 ast::Union(it) => sema.to_def(&it)?.into(),
197 ast::Trait(it) => sema.to_def(&it)?.into(),
198 ast::Const(it) => sema.to_def(&it)?.into(),
199 ast::Static(it) => sema.to_def(&it)?.into(),
200 ast::TypeAlias(it) => sema.to_def(&it)?.into(),
204 Some(Definition::ModuleDef(res))
207 fn module_def_doctest(sema: &Semantics<RootDatabase>, def: hir::ModuleDef) -> Option<Runnable> {
208 let attrs = match def {
209 hir::ModuleDef::Module(it) => it.attrs(sema.db),
210 hir::ModuleDef::Function(it) => it.attrs(sema.db),
211 hir::ModuleDef::Adt(it) => it.attrs(sema.db),
212 hir::ModuleDef::Variant(it) => it.attrs(sema.db),
213 hir::ModuleDef::Const(it) => it.attrs(sema.db),
214 hir::ModuleDef::Static(it) => it.attrs(sema.db),
215 hir::ModuleDef::Trait(it) => it.attrs(sema.db),
216 hir::ModuleDef::TypeAlias(it) => it.attrs(sema.db),
217 hir::ModuleDef::BuiltinType(_) => return None,
219 if !has_runnable_doc_test(&attrs) {
222 let def_name = def.name(sema.db).map(|it| it.to_string());
224 .canonical_path(sema.db)
225 // This probably belongs to canonical path?
227 let assoc_def = match def {
228 hir::ModuleDef::Function(it) => it.as_assoc_item(sema.db),
229 hir::ModuleDef::Const(it) => it.as_assoc_item(sema.db),
230 hir::ModuleDef::TypeAlias(it) => it.as_assoc_item(sema.db),
233 // FIXME: this also looks very wrong
234 if let Some(assoc_def) = assoc_def {
235 if let hir::AssocItemContainer::Impl(imp) = assoc_def.container(sema.db) {
236 if let Some(adt) = imp.target_ty(sema.db).as_adt() {
237 let name = adt.name(sema.db).to_string();
238 let idx = path.rfind(':').map_or(0, |idx| idx + 1);
239 let (prefix, suffix) = path.split_at(idx);
240 return format!("{}{}::{}", prefix, name, suffix);
247 .or_else(|| def_name.clone().map(TestId::Name))?;
249 let mut nav = def.try_to_nav(sema.db)?;
250 nav.focus_range = None;
251 nav.description = None;
254 let res = Runnable { nav, kind: RunnableKind::DocTest { test_id }, cfg: attrs.cfg() };
258 #[derive(Debug, Copy, Clone)]
259 pub struct TestAttr {
264 fn from_fn(fn_def: &ast::Fn) -> TestAttr {
267 .filter_map(|attr| attr.simple_name())
268 .any(|attribute_text| attribute_text == "ignore");
273 const RUSTDOC_FENCE: &str = "```";
274 const RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE: &[&str] =
275 &["", "rust", "should_panic", "edition2015", "edition2018", "edition2021"];
277 fn has_runnable_doc_test(attrs: &hir::Attrs) -> bool {
278 attrs.docs().map_or(false, |doc| {
279 let mut in_code_block = false;
281 for line in String::from(doc).lines() {
282 if let Some(header) = line.strip_prefix(RUSTDOC_FENCE) {
283 in_code_block = !in_code_block;
288 .all(|sub| RUSTDOC_CODE_BLOCK_ATTRIBUTES_RUNNABLE.contains(&sub.trim()))
299 // We could create runnables for modules with number_of_test_submodules > 0,
300 // but that bloats the runnables for no real benefit, since all tests can be run by the submodule already
301 fn has_test_function_or_multiple_test_submodules(
302 sema: &Semantics<RootDatabase>,
303 module: &hir::Module,
305 let mut number_of_test_submodules = 0;
307 for item in module.declarations(sema.db) {
309 hir::ModuleDef::Function(f) => {
310 if let Some(it) = f.source(sema.db) {
311 if test_related_attribute(&it.value).is_some() {
316 hir::ModuleDef::Module(submodule) => {
317 if has_test_function_or_multiple_test_submodules(sema, &submodule) {
318 number_of_test_submodules += 1;
325 number_of_test_submodules > 1
330 use expect_test::{expect, Expect};
331 use test_utils::mark;
339 // FIXME: fold this into `expect` as well
340 actions: &[&RunnableAction],
343 let (analysis, position) = fixture::position(ra_fixture);
344 let runnables = analysis.runnables(position.file_id).unwrap();
345 expect.assert_debug_eq(&runnables);
348 runnables.into_iter().map(|it| it.action()).collect::<Vec<_>>().as_slice()
353 fn test_runnables() {
370 &[&BIN, &TEST, &TEST, &BENCH],
374 nav: NavigationTarget {
387 nav: NavigationTarget {
407 nav: NavigationTarget {
427 nav: NavigationTarget {
449 fn test_runnables_doc_test() {
459 fn should_have_runnable() {}
464 fn should_have_runnable_1() {}
473 fn should_have_runnable_2() {}
478 fn should_have_no_runnable() {}
483 fn should_have_no_runnable_2() {}
488 fn should_have_no_runnable_3() {}
491 /// arbitrary plain text
493 fn should_have_no_runnable_4() {}
496 /// arbitrary plain text
502 fn should_have_no_runnable_5() {}
507 fn should_have_no_runnable_6() {}
512 struct StructWithRunnable(String);
515 &[&BIN, &DOCTEST, &DOCTEST, &DOCTEST, &DOCTEST],
519 nav: NavigationTarget {
532 nav: NavigationTarget {
537 name: "should_have_runnable",
541 "should_have_runnable",
547 nav: NavigationTarget {
552 name: "should_have_runnable_1",
556 "should_have_runnable_1",
562 nav: NavigationTarget {
566 full_range: 150..254,
567 name: "should_have_runnable_2",
571 "should_have_runnable_2",
577 nav: NavigationTarget {
581 full_range: 756..821,
582 name: "StructWithRunnable",
586 "StructWithRunnable",
597 fn test_runnables_doc_test_in_impl() {
616 nav: NavigationTarget {
629 nav: NavigationTarget {
649 fn test_runnables_module() {
663 nav: NavigationTarget {
678 nav: NavigationTarget {
689 "test_mod::test_foo1",
703 fn only_modules_with_test_functions_or_more_than_one_test_submodule_have_runners() {
712 fn nested_test_11() {}
715 fn nested_test_12() {}
720 fn nested_test_2() {}
723 mod nested_tests_3 {}
726 mod nested_tests_4 {}
729 &[&TEST, &TEST, &TEST, &TEST, &TEST, &TEST],
733 nav: NavigationTarget {
739 name: "nested_tests_0",
743 path: "root_tests::nested_tests_0",
748 nav: NavigationTarget {
754 name: "nested_tests_1",
758 path: "root_tests::nested_tests_0::nested_tests_1",
763 nav: NavigationTarget {
767 full_range: 202..286,
768 focus_range: 206..220,
769 name: "nested_tests_2",
773 path: "root_tests::nested_tests_0::nested_tests_2",
778 nav: NavigationTarget {
783 focus_range: 107..121,
784 name: "nested_test_11",
789 "root_tests::nested_tests_0::nested_tests_1::nested_test_11",
798 nav: NavigationTarget {
802 full_range: 140..182,
803 focus_range: 163..177,
804 name: "nested_test_12",
809 "root_tests::nested_tests_0::nested_tests_1::nested_test_12",
818 nav: NavigationTarget {
822 full_range: 235..276,
823 focus_range: 258..271,
824 name: "nested_test_2",
829 "root_tests::nested_tests_0::nested_tests_2::nested_test_2",
843 fn test_runnables_with_feature() {
846 //- /lib.rs crate:foo cfg:feature=foo
849 #[cfg(feature = "foo")]
856 nav: NavigationTarget {
888 fn test_runnables_with_features() {
891 //- /lib.rs crate:foo cfg:feature=foo,feature=bar
894 #[cfg(all(feature = "foo", feature = "bar"))]
901 nav: NavigationTarget {
943 fn test_runnables_no_test_function_in_module() {
960 fn test_doc_runnables_impl_mod() {
978 nav: NavigationTarget {
998 fn test_runnables_in_macro() {
1018 nav: NavigationTarget {
1022 full_range: 90..115,
1023 focus_range: 94..99,
1033 nav: NavigationTarget {
1037 full_range: 106..113,
1038 focus_range: 106..113,
1058 fn dont_recurse_in_outline_submodules() {
1059 mark::check!(dont_recurse_in_outline_submodules);