2019-01-08 19:33:36 +00:00
|
|
|
use itertools::Itertools;
|
|
|
|
use ra_syntax::{
|
|
|
|
TextRange, SyntaxNode,
|
|
|
|
ast::{self, AstNode, NameOwner, ModuleItemOwner},
|
|
|
|
};
|
|
|
|
use ra_db::{Cancelable, SyntaxDatabase};
|
|
|
|
|
|
|
|
use crate::{db::RootDatabase, FileId};
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub struct Runnable {
|
|
|
|
pub range: TextRange,
|
|
|
|
pub kind: RunnableKind,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Debug)]
|
|
|
|
pub enum RunnableKind {
|
|
|
|
Test { name: String },
|
|
|
|
TestMod { path: String },
|
2019-01-12 23:40:54 +00:00
|
|
|
Bench { name: String },
|
2019-01-08 19:33:36 +00:00
|
|
|
Bin,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub(crate) fn runnables(db: &RootDatabase, file_id: FileId) -> Cancelable<Vec<Runnable>> {
|
|
|
|
let source_file = db.source_file(file_id);
|
|
|
|
let res = source_file
|
|
|
|
.syntax()
|
|
|
|
.descendants()
|
|
|
|
.filter_map(|i| runnable(db, file_id, i))
|
|
|
|
.collect();
|
|
|
|
Ok(res)
|
|
|
|
}
|
|
|
|
|
|
|
|
fn runnable(db: &RootDatabase, file_id: FileId, item: &SyntaxNode) -> Option<Runnable> {
|
|
|
|
if let Some(fn_def) = ast::FnDef::cast(item) {
|
|
|
|
runnable_fn(fn_def)
|
|
|
|
} else if let Some(m) = ast::Module::cast(item) {
|
|
|
|
runnable_mod(db, file_id, m)
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn runnable_fn(fn_def: &ast::FnDef) -> Option<Runnable> {
|
|
|
|
let name = fn_def.name()?.text();
|
|
|
|
let kind = if name == "main" {
|
|
|
|
RunnableKind::Bin
|
|
|
|
} else if fn_def.has_atom_attr("test") {
|
|
|
|
RunnableKind::Test {
|
|
|
|
name: name.to_string(),
|
2019-01-12 23:40:54 +00:00
|
|
|
}
|
|
|
|
} else if fn_def.has_atom_attr("bench") {
|
|
|
|
RunnableKind::Bench {
|
|
|
|
name: name.to_string(),
|
2019-01-08 19:33:36 +00:00
|
|
|
}
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
};
|
|
|
|
Some(Runnable {
|
|
|
|
range: fn_def.syntax().range(),
|
|
|
|
kind,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
fn runnable_mod(db: &RootDatabase, file_id: FileId, module: &ast::Module) -> Option<Runnable> {
|
|
|
|
let has_test_function = module
|
|
|
|
.item_list()?
|
|
|
|
.items()
|
|
|
|
.filter_map(|it| match it.kind() {
|
|
|
|
ast::ModuleItemKind::FnDef(it) => Some(it),
|
|
|
|
_ => None,
|
|
|
|
})
|
|
|
|
.any(|f| f.has_atom_attr("test"));
|
|
|
|
if !has_test_function {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
let range = module.syntax().range();
|
2019-01-15 15:13:11 +00:00
|
|
|
let module = hir::source_binder::module_from_child_node(db, file_id, module.syntax())?;
|
2019-01-08 19:33:36 +00:00
|
|
|
|
|
|
|
// FIXME: thread cancellation instead of `.ok`ing
|
|
|
|
let path = module
|
|
|
|
.path_to_root(db)
|
|
|
|
.into_iter()
|
|
|
|
.rev()
|
2019-01-15 15:26:29 +00:00
|
|
|
.filter_map(|it| it.name(db))
|
2019-01-08 19:33:36 +00:00
|
|
|
.join("::");
|
|
|
|
Some(Runnable {
|
|
|
|
range,
|
|
|
|
kind: RunnableKind::TestMod { path },
|
|
|
|
})
|
|
|
|
}
|
2019-01-14 13:27:08 +00:00
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
mod tests {
|
|
|
|
use insta::assert_debug_snapshot_matches;
|
|
|
|
|
|
|
|
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();
|
|
|
|
assert_debug_snapshot_matches!("runnables", &runnables)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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();
|
|
|
|
assert_debug_snapshot_matches!("runnables_module", &runnables)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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();
|
|
|
|
assert_debug_snapshot_matches!("runnables_one_depth_layer_module", &runnables)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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();
|
|
|
|
assert_debug_snapshot_matches!("runnables_multiple_depth_module", &runnables)
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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())
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|