Skip to content

Make modules with tests runnable #165

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Dec 31, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions crates/ra_analysis/src/imp.rs
Original file line number Diff line number Diff line change
Expand Up @@ -181,6 +181,28 @@ impl AnalysisImpl {
};
Ok(query.search(&buf))
}

pub(crate) fn module_path(&self, position: FilePosition) -> Cancelable<Option<String>> {
let descr = match source_binder::module_from_position(&*self.db, position)? {
None => return Ok(None),
Some(it) => it,
};
let name = match descr.name() {
None => return Ok(None),
Some(it) => it.to_string(),
};

let modules = descr.path_to_root();

let path = modules
.iter()
.filter_map(|s| s.name())
.skip(1) // name is already part of the string.
Copy link
Contributor

@DJMcNab DJMcNab Dec 31, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

EDIT: Maybe we can clarify that it's used as the fold root.

.fold(name, |path, it| format!("{}::{}", it, path));

Ok(Some(path.to_string()))
}

/// This returns `Vec` because a module may be included from several places. We
/// don't handle this case yet though, so the Vec has length at most one.
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
Expand Down
14 changes: 10 additions & 4 deletions crates/ra_analysis/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ mod imp;
mod completion;
mod symbol_index;
pub mod mock_analysis;
mod runnables;

mod extend_selection;
mod syntax_highlighting;
Expand All @@ -33,10 +34,12 @@ use crate::{
symbol_index::SymbolIndex,
};

pub use crate::completion::{CompletionItem, CompletionItemKind, InsertText};
pub use crate::{
completion::{CompletionItem, CompletionItemKind, InsertText},
runnables::{Runnable, RunnableKind}
};
pub use ra_editor::{
FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, RunnableKind, StructureNode,
Severity
FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, StructureNode, Severity
};
pub use hir::FnSignatureInfo;

Expand Down Expand Up @@ -336,6 +339,9 @@ impl Analysis {
pub fn parent_module(&self, position: FilePosition) -> Cancelable<Vec<(FileId, FileSymbol)>> {
self.imp.parent_module(position)
}
pub fn module_path(&self, position: FilePosition) -> Cancelable<Option<String>> {
self.imp.module_path(position)
}
pub fn crate_for(&self, file_id: FileId) -> Cancelable<Vec<CrateId>> {
self.imp.crate_for(file_id)
}
Expand All @@ -344,7 +350,7 @@ impl Analysis {
}
pub fn runnables(&self, file_id: FileId) -> Cancelable<Vec<Runnable>> {
let file = self.imp.file_syntax(file_id);
Ok(ra_editor::runnables(&file))
Ok(runnables::runnables(self, &file, file_id))
}
pub fn highlight(&self, file_id: FileId) -> Cancelable<Vec<HighlightedRange>> {
syntax_highlighting::highlight(&*self.imp.db, file_id)
Expand Down
72 changes: 72 additions & 0 deletions crates/ra_analysis/src/runnables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
use ra_syntax::{
ast::{self, AstNode, NameOwner, ModuleItemOwner},
SourceFileNode, TextRange, SyntaxNodeRef,
TextUnit,
};
use crate::{
Analysis, FileId, FilePosition
};

#[derive(Debug)]
pub struct Runnable {
pub range: TextRange,
pub kind: RunnableKind,
}

#[derive(Debug)]
pub enum RunnableKind {
Test { name: String },
TestMod { path: String },
Bin,
}

pub fn runnables(
analysis: &Analysis,
file_node: &SourceFileNode,
file_id: FileId,
) -> Vec<Runnable> {
file_node
.syntax()
.descendants()
.filter_map(|i| runnable(analysis, i, file_id))
.collect()
}

fn runnable<'a>(analysis: &Analysis, item: SyntaxNodeRef<'a>, file_id: FileId) -> Option<Runnable> {
if let Some(f) = ast::FnDef::cast(item) {
let name = f.name()?.text();
let kind = if name == "main" {
RunnableKind::Bin
} else if f.has_atom_attr("test") {
RunnableKind::Test {
name: name.to_string(),
}
} else {
return None;
};
Some(Runnable {
range: f.syntax().range(),
kind,
})
} else if let Some(m) = ast::Module::cast(item) {
if m.item_list()?
.items()
.map(ast::ModuleItem::syntax)
.filter_map(ast::FnDef::cast)
.any(|f| f.has_atom_attr("test"))
{
let postition = FilePosition {
file_id,
offset: m.syntax().range().start() + TextUnit::from_usize(1),
};
analysis.module_path(postition).ok()?.map(|path| Runnable {
range: m.syntax().range(),
kind: RunnableKind::TestMod { path },
})
} else {
None
}
} else {
None
}
}
118 changes: 118 additions & 0 deletions crates/ra_analysis/tests/runnables.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
extern crate ra_analysis;
extern crate ra_editor;
extern crate ra_syntax;
extern crate relative_path;
extern crate rustc_hash;
extern crate test_utils;

use test_utils::assert_eq_dbg;

use ra_analysis::{
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_eq_dbg(
r#"[Runnable { range: [1; 21), kind: Bin },
Runnable { range: [22; 46), kind: Test { name: "test_foo" } },
Runnable { range: [47; 81), kind: Test { name: "test_foo" } }]"#,
&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_eq_dbg(
r#"[Runnable { range: [1; 59), kind: TestMod { path: "test_mod" } },
Runnable { range: [28; 57), kind: Test { name: "test_foo1" } }]"#,
&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_eq_dbg(
r#"[Runnable { range: [23; 85), kind: TestMod { path: "foo::test_mod" } },
Runnable { range: [46; 79), kind: Test { name: "test_foo1" } }]"#,
&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_eq_dbg(
r#"[Runnable { range: [41; 115), kind: TestMod { path: "foo::bar::test_mod" } },
Runnable { range: [68; 105), kind: Test { name: "test_foo1" } }]"#,
&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_eq_dbg(r#"[]"#, &runnables)
}
50 changes: 50 additions & 0 deletions crates/ra_analysis/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,56 @@ fn test_resolve_parent_module_for_inline() {
);
}

#[test]
fn test_path_one_layer() {
let (analysis, pos) = analysis_and_position(
"
//- /lib.rs
mod foo;
//- /foo/mod.rs
mod bla;
//- /foo/bla.rs
<|> //empty
",
);
let symbols = analysis.module_path(pos).unwrap().unwrap();
assert_eq!("foo::bla", &symbols);
}

#[test]
fn test_path_two_layer() {
let (analysis, pos) = analysis_and_position(
"
//- /lib.rs
mod foo;
//- /foo/mod.rs
mod bla;
//- /foo/bla/mod.rs
mod more;
//- /foo/bla/more.rs
<|> //empty
",
);
let symbols = analysis.module_path(pos).unwrap().unwrap();
assert_eq!("foo::bla::more", &symbols);
}

#[test]
fn test_path_in_file_mod() {
let (analysis, pos) = analysis_and_position(
"
//- /lib.rs
mod foo;
//- /foo.rs
mod bar {
<|> //empty
}
",
);
let symbols = analysis.module_path(pos).unwrap().unwrap();
assert_eq!("foo::bar", &symbols);
}

#[test]
fn test_resolve_crate_root() {
let mock = MockAnalysis::with_files(
Expand Down
Loading