2019-09-30 08:58:53 +00:00
|
|
|
//! FIXME: write short doc here
|
|
|
|
|
2020-02-18 17:35:10 +00:00
|
|
|
use hir::Semantics;
|
2019-01-08 19:33:36 +00:00
|
|
|
use itertools::Itertools;
|
2020-02-06 11:52:32 +00:00
|
|
|
use ra_ide_db::RootDatabase;
|
2019-01-08 19:33:36 +00:00
|
|
|
use ra_syntax::{
|
2019-07-04 20:05:17 +00:00
|
|
|
ast::{self, AstNode, AttrsOwner, ModuleItemOwner, NameOwner},
|
2019-10-30 20:09:16 +00:00
|
|
|
match_ast, SyntaxNode, TextRange,
|
2019-01-08 19:33:36 +00:00
|
|
|
};
|
|
|
|
|
2020-02-06 11:52:32 +00:00
|
|
|
use crate::FileId;
|
2020-02-14 23:06:14 +00:00
|
|
|
use std::fmt::Display;
|
2019-01-08 19:33:36 +00:00
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Runnable {
|
|
|
|
pub range: TextRange,
|
|
|
|
pub kind: RunnableKind,
|
|
|
|
}
|
|
|
|
|
2020-02-14 23:06:14 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum TestId {
|
|
|
|
Name(String),
|
|
|
|
Path(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Display for TestId {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
|
|
match self {
|
|
|
|
TestId::Name(name) => write!(f, "{}", name),
|
|
|
|
TestId::Path(path) => write!(f, "{}", path),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2019-01-08 19:33:36 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum RunnableKind {
|
2020-04-22 20:52:12 +00:00
|
|
|
Test { test_id: TestId, attr: TestAttr },
|
2019-01-08 19:33:36 +00:00
|
|
|
TestMod { path: String },
|
2020-02-14 23:06:14 +00:00
|
|
|
Bench { test_id: TestId },
|
2019-01-08 19:33:36 +00:00
|
|
|
Bin,
|
|
|
|
}
|
|
|
|
|
2019-01-15 18:02:42 +00:00
|
|
|
pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Vec<Runnable> {
|
2020-02-18 17:35:10 +00:00
|
|
|
let sema = Semantics::new(db);
|
|
|
|
let source_file = sema.parse(file_id);
|
|
|
|
source_file.syntax().descendants().filter_map(|i| runnable(&sema, i)).collect()
|
2019-01-08 19:33:36 +00:00
|
|
|
}
|
|
|
|
|
2020-02-18 17:35:10 +00:00
|
|
|
fn runnable(sema: &Semantics<RootDatabase>, item: SyntaxNode) -> Option<Runnable> {
|
2019-10-30 20:09:16 +00:00
|
|
|
match_ast! {
|
|
|
|
match item {
|
2020-04-06 14:21:33 +00:00
|
|
|
ast::FnDef(it) => runnable_fn(sema, it),
|
|
|
|
ast::Module(it) => runnable_mod(sema, it),
|
2020-02-18 17:35:10 +00:00
|
|
|
_ => None,
|
2019-10-30 20:09:16 +00:00
|
|
|
}
|
2019-01-08 19:33:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-18 17:35:10 +00:00
|
|
|
fn runnable_fn(sema: &Semantics<RootDatabase>, fn_def: ast::FnDef) -> Option<Runnable> {
|
2020-02-14 23:06:14 +00:00
|
|
|
let name_string = fn_def.name()?.text().to_string();
|
|
|
|
|
|
|
|
let kind = if name_string == "main" {
|
2019-01-08 19:33:36 +00:00
|
|
|
RunnableKind::Bin
|
|
|
|
} else {
|
2020-02-18 17:35:10 +00:00
|
|
|
let test_id = if let Some(module) = sema.to_def(&fn_def).map(|def| def.module(sema.db)) {
|
2020-02-14 23:06:14 +00:00
|
|
|
let path = module
|
2020-02-18 17:35:10 +00:00
|
|
|
.path_to_root(sema.db)
|
2020-02-14 23:06:14 +00:00
|
|
|
.into_iter()
|
|
|
|
.rev()
|
2020-02-18 17:35:10 +00:00
|
|
|
.filter_map(|it| it.name(sema.db))
|
2020-02-14 23:06:14 +00:00
|
|
|
.map(|name| name.to_string())
|
|
|
|
.chain(std::iter::once(name_string))
|
|
|
|
.join("::");
|
|
|
|
TestId::Path(path)
|
|
|
|
} else {
|
|
|
|
TestId::Name(name_string)
|
|
|
|
};
|
|
|
|
|
|
|
|
if has_test_related_attribute(&fn_def) {
|
2020-04-22 20:52:12 +00:00
|
|
|
let attr = TestAttr::from_fn(&fn_def);
|
|
|
|
RunnableKind::Test { test_id, attr }
|
2020-02-14 23:06:14 +00:00
|
|
|
} else if fn_def.has_atom_attr("bench") {
|
|
|
|
RunnableKind::Bench { test_id }
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
2019-01-08 19:33:36 +00:00
|
|
|
};
|
2019-07-20 09:58:27 +00:00
|
|
|
Some(Runnable { range: fn_def.syntax().text_range(), kind })
|
2019-01-08 19:33:36 +00:00
|
|
|
}
|
|
|
|
|
2020-04-22 20:52:12 +00:00
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct TestAttr {
|
|
|
|
pub ignore: bool,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl TestAttr {
|
|
|
|
fn from_fn(fn_def: &ast::FnDef) -> TestAttr {
|
|
|
|
let ignore = fn_def
|
|
|
|
.attrs()
|
|
|
|
.filter_map(|attr| attr.simple_name())
|
|
|
|
.any(|attribute_text| attribute_text == "ignore");
|
|
|
|
TestAttr { ignore }
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-01-23 11:39:14 +00:00
|
|
|
/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
|
|
|
|
/// `#[test_case(...)]`, `#[tokio::test]` and similar.
|
|
|
|
/// Also a regular `#[test]` annotation is supported.
|
|
|
|
///
|
|
|
|
/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
|
|
|
|
/// but it's better than not to have the runnables for the tests at all.
|
|
|
|
fn has_test_related_attribute(fn_def: &ast::FnDef) -> bool {
|
|
|
|
fn_def
|
|
|
|
.attrs()
|
|
|
|
.filter_map(|attr| attr.path())
|
|
|
|
.map(|path| path.syntax().to_string().to_lowercase())
|
|
|
|
.any(|attribute_text| attribute_text.contains("test"))
|
|
|
|
}
|
|
|
|
|
2020-02-18 17:35:10 +00:00
|
|
|
fn runnable_mod(sema: &Semantics<RootDatabase>, module: ast::Module) -> Option<Runnable> {
|
2019-01-08 19:33:36 +00:00
|
|
|
let has_test_function = module
|
|
|
|
.item_list()?
|
|
|
|
.items()
|
2019-08-19 11:13:58 +00:00
|
|
|
.filter_map(|it| match it {
|
|
|
|
ast::ModuleItem::FnDef(it) => Some(it),
|
2019-01-08 19:33:36 +00:00
|
|
|
_ => None,
|
|
|
|
})
|
2020-02-14 23:06:14 +00:00
|
|
|
.any(|f| has_test_related_attribute(&f));
|
2019-01-08 19:33:36 +00:00
|
|
|
if !has_test_function {
|
|
|
|
return None;
|
|
|
|
}
|
2019-07-20 09:58:27 +00:00
|
|
|
let range = module.syntax().text_range();
|
2020-02-18 17:35:10 +00:00
|
|
|
let module = sema.to_def(&module)?;
|
2019-01-08 19:33:36 +00:00
|
|
|
|
2020-02-18 17:35:10 +00:00
|
|
|
let path =
|
|
|
|
module.path_to_root(sema.db).into_iter().rev().filter_map(|it| it.name(sema.db)).join("::");
|
2019-02-08 11:49:43 +00:00
|
|
|
Some(Runnable { range, kind: RunnableKind::TestMod { path } })
|
2019-01-08 19:33:36 +00:00
|
|
|
}
|
2019-01-14 13:27:08 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
2019-08-29 13:49:10 +00:00
|
|
|
use insta::assert_debug_snapshot;
|
2019-01-14 13:27:08 +00:00
|
|
|
|
|
|
|
use crate::mock_analysis::analysis_and_position;
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_runnables() {
|
|
|
|
let (analysis, pos) = analysis_and_position(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs
|
|
|
|
<|> //empty
|
|
|
|
fn main() {}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_foo() {}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
#[ignore]
|
|
|
|
fn test_foo() {}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
2019-08-29 13:49:10 +00:00
|
|
|
assert_debug_snapshot!(&runnables,
|
2019-11-15 09:56:24 +00:00
|
|
|
@r###"
|
|
|
|
[
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 1..21,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: Bin,
|
|
|
|
},
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 22..46,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: Test {
|
2020-02-16 16:39:58 +00:00
|
|
|
test_id: Path(
|
2020-02-14 23:06:14 +00:00
|
|
|
"test_foo",
|
|
|
|
),
|
2020-04-22 20:52:12 +00:00
|
|
|
attr: TestAttr {
|
|
|
|
ignore: false,
|
|
|
|
},
|
2019-11-15 09:56:24 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 47..81,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: Test {
|
2020-02-16 16:39:58 +00:00
|
|
|
test_id: Path(
|
2020-02-14 23:06:14 +00:00
|
|
|
"test_foo",
|
|
|
|
),
|
2020-04-22 20:52:12 +00:00
|
|
|
attr: TestAttr {
|
|
|
|
ignore: true,
|
|
|
|
},
|
2019-11-15 09:56:24 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
]
|
|
|
|
"###
|
2019-07-28 11:08:06 +00:00
|
|
|
);
|
2019-01-14 13:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_runnables_module() {
|
|
|
|
let (analysis, pos) = analysis_and_position(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs
|
|
|
|
<|> //empty
|
|
|
|
mod test_mod {
|
|
|
|
#[test]
|
|
|
|
fn test_foo1() {}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
2019-08-29 13:49:10 +00:00
|
|
|
assert_debug_snapshot!(&runnables,
|
2019-11-15 09:56:24 +00:00
|
|
|
@r###"
|
|
|
|
[
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 1..59,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: TestMod {
|
|
|
|
path: "test_mod",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 28..57,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: Test {
|
2020-02-14 23:06:14 +00:00
|
|
|
test_id: Path(
|
|
|
|
"test_mod::test_foo1",
|
|
|
|
),
|
2020-04-22 20:52:12 +00:00
|
|
|
attr: TestAttr {
|
|
|
|
ignore: false,
|
|
|
|
},
|
2019-11-15 09:56:24 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
]
|
|
|
|
"###
|
2019-07-28 11:08:06 +00:00
|
|
|
);
|
2019-01-14 13:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_runnables_one_depth_layer_module() {
|
|
|
|
let (analysis, pos) = analysis_and_position(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs
|
|
|
|
<|> //empty
|
|
|
|
mod foo {
|
|
|
|
mod test_mod {
|
|
|
|
#[test]
|
|
|
|
fn test_foo1() {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
2019-08-29 13:49:10 +00:00
|
|
|
assert_debug_snapshot!(&runnables,
|
2019-11-15 09:56:24 +00:00
|
|
|
@r###"
|
|
|
|
[
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 23..85,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: TestMod {
|
|
|
|
path: "foo::test_mod",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 46..79,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: Test {
|
2020-02-14 23:06:14 +00:00
|
|
|
test_id: Path(
|
|
|
|
"foo::test_mod::test_foo1",
|
|
|
|
),
|
2020-04-22 20:52:12 +00:00
|
|
|
attr: TestAttr {
|
|
|
|
ignore: false,
|
|
|
|
},
|
2019-11-15 09:56:24 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
]
|
|
|
|
"###
|
2019-07-28 11:08:06 +00:00
|
|
|
);
|
2019-01-14 13:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_runnables_multiple_depth_module() {
|
|
|
|
let (analysis, pos) = analysis_and_position(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs
|
|
|
|
<|> //empty
|
|
|
|
mod foo {
|
|
|
|
mod bar {
|
|
|
|
mod test_mod {
|
|
|
|
#[test]
|
|
|
|
fn test_foo1() {}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
2019-08-29 13:49:10 +00:00
|
|
|
assert_debug_snapshot!(&runnables,
|
2019-11-15 09:56:24 +00:00
|
|
|
@r###"
|
|
|
|
[
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 41..115,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: TestMod {
|
|
|
|
path: "foo::bar::test_mod",
|
|
|
|
},
|
|
|
|
},
|
|
|
|
Runnable {
|
2020-04-24 21:51:02 +00:00
|
|
|
range: 68..105,
|
2019-11-15 09:56:24 +00:00
|
|
|
kind: Test {
|
2020-02-14 23:06:14 +00:00
|
|
|
test_id: Path(
|
|
|
|
"foo::bar::test_mod::test_foo1",
|
|
|
|
),
|
2020-04-22 20:52:12 +00:00
|
|
|
attr: TestAttr {
|
|
|
|
ignore: false,
|
|
|
|
},
|
2019-11-15 09:56:24 +00:00
|
|
|
},
|
|
|
|
},
|
|
|
|
]
|
|
|
|
"###
|
2019-07-28 11:08:06 +00:00
|
|
|
);
|
2019-01-14 13:27:08 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
#[test]
|
|
|
|
fn test_runnables_no_test_function_in_module() {
|
|
|
|
let (analysis, pos) = analysis_and_position(
|
|
|
|
r#"
|
|
|
|
//- /lib.rs
|
|
|
|
<|> //empty
|
|
|
|
mod test_mod {
|
|
|
|
fn foo1() {}
|
|
|
|
}
|
|
|
|
"#,
|
|
|
|
);
|
|
|
|
let runnables = analysis.runnables(pos.file_id).unwrap();
|
|
|
|
assert!(runnables.is_empty())
|
|
|
|
}
|
|
|
|
}
|