From dfba29e4fb66457d101db295e3c356a932ac005e Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 31 Oct 2018 22:34:31 +0300 Subject: Add MockAnalysis to make testing easier --- crates/ra_analysis/Cargo.toml | 6 +- crates/ra_analysis/src/completion.rs | 17 ++---- crates/ra_analysis/src/input.rs | 2 +- crates/ra_analysis/src/lib.rs | 3 +- crates/ra_analysis/src/mock_analysis.rs | 84 +++++++++++++++++++++++-- crates/ra_analysis/tests/tests.rs | 105 +++++++++++++++++--------------- crates/test_utils/src/lib.rs | 26 ++++++-- 7 files changed, 167 insertions(+), 76 deletions(-) diff --git a/crates/ra_analysis/Cargo.toml b/crates/ra_analysis/Cargo.toml index 892e34235..deddf41f0 100644 --- a/crates/ra_analysis/Cargo.toml +++ b/crates/ra_analysis/Cargo.toml @@ -9,10 +9,8 @@ log = "0.4.5" relative-path = "0.4.0" rayon = "1.0.2" fst = "0.3.1" -ra_syntax = { path = "../ra_syntax" } -ra_editor = { path = "../ra_editor" } salsa = "0.7.0" rustc-hash = "1.0" - -[dev-dependencies] +ra_syntax = { path = "../ra_syntax" } +ra_editor = { path = "../ra_editor" } test_utils = { path = "../test_utils" } diff --git a/crates/ra_analysis/src/completion.rs b/crates/ra_analysis/src/completion.rs index 340ae3f66..286b6c376 100644 --- a/crates/ra_analysis/src/completion.rs +++ b/crates/ra_analysis/src/completion.rs @@ -368,18 +368,15 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec CrateGraph { CrateGraph::default() } - pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId{ + pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId { let crate_id = CrateId(self.crate_roots.len() as u32); let prev = self.crate_roots.insert(crate_id, file_id); assert!(prev.is_none()); diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index 776010281..e75411ec9 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs @@ -13,7 +13,7 @@ mod imp; mod symbol_index; mod completion; mod syntax_ptr; -mod mock_analysis; +pub mod mock_analysis; use std::{ fmt, @@ -33,7 +33,6 @@ pub use crate::{ descriptors::function::FnDescriptor, completion::CompletionItem, input::{FileId, FileResolver, CrateGraph, CrateId}, - mock_analysis::MockAnalysis, }; pub use ra_editor::{ FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, diff --git a/crates/ra_analysis/src/mock_analysis.rs b/crates/ra_analysis/src/mock_analysis.rs index 1c1dbee7c..f72911192 100644 --- a/crates/ra_analysis/src/mock_analysis.rs +++ b/crates/ra_analysis/src/mock_analysis.rs @@ -2,11 +2,19 @@ use std::sync::Arc; use relative_path::{RelativePath, RelativePathBuf}; +use ra_syntax::TextUnit; +use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER}; use crate::{ AnalysisChange, Analysis, AnalysisHost, FileId, FileResolver, }; +#[derive(Debug)] +pub struct FilePosition { + pub file_id: FileId, + pub offset: TextUnit, +} + /// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis /// from a set of in-memory files. #[derive(Debug, Default)] @@ -18,11 +26,57 @@ impl MockAnalysis { pub fn new() -> MockAnalysis { MockAnalysis::default() } - pub fn with_files(files: &[(&str, &str)]) -> MockAnalysis { - let files = files.iter() - .map(|it| (it.0.to_string(), it.1.to_string())) - .collect(); - MockAnalysis { files } + /// Creates `MockAnalysis` using a fixture data in the following format: + /// + /// ```notrust + /// //- /main.rs + /// mod foo; + /// fn main() {} + /// + /// //- /foo.rs + /// struct Baz; + /// ``` + pub fn with_files(fixture: &str) -> MockAnalysis { + let mut res = MockAnalysis::new(); + for entry in parse_fixture(fixture) { + res.add_file(&entry.meta, &entry.text); + } + res + } + + /// Same as `with_files`, but requires that a single file contains a `<|>` marker, + /// whose position is also returned. + pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) { + let mut position = None; + let mut res = MockAnalysis::new(); + for entry in parse_fixture(fixture) { + if entry.text.contains(CURSOR_MARKER) { + assert!(position.is_none(), "only one marker (<|>) per fixture is allowed"); + position = Some(res.add_file_with_position(&entry.meta, &entry.text)); + } else { + res.add_file(&entry.meta, &entry.text); + } + } + let position = position.expect("expected a marker (<|>)"); + (res, position) + } + + pub fn add_file(&mut self, path: &str, text: &str) -> FileId { + let file_id = FileId((self.files.len() + 1) as u32); + self.files.push((path.to_string(), text.to_string())); + file_id + } + pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition { + let (offset, text) = extract_offset(text); + let file_id = FileId((self.files.len() + 1) as u32); + self.files.push((path.to_string(), text.to_string())); + FilePosition { file_id, offset } + } + pub fn id_of(&self, path: &str) -> FileId { + let (idx, _) = self.files.iter().enumerate() + .find(|(_, (p, _text))| path == p) + .expect("no file in this mock"); + FileId(idx as u32 + 1) } pub fn analysis_host(self) -> AnalysisHost { let mut host = AnalysisHost::new(); @@ -44,6 +98,26 @@ impl MockAnalysis { } } +/// Creates analysis from a multi-file fixture, returns positions marked with <|>. +pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) { + let (mock, position) = MockAnalysis::with_files_and_position(fixture); + (mock.analysis(), position) +} + +/// Creates analysis for a single file. +pub fn single_file(code: &str) -> (Analysis, FileId) { + let mut mock = MockAnalysis::new(); + let file_id = mock.add_file("/main.rs", code); + (mock.analysis(), file_id) +} + +/// Creates analysis for a single file, returns position marked with <|>. +pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) { + let mut mock = MockAnalysis::new(); + let pos = mock.add_file_with_position("/main.rs", code); + (mock.analysis(), pos) +} + #[derive(Debug)] struct FileMap(Vec<(FileId, RelativePathBuf)>); diff --git a/crates/ra_analysis/tests/tests.rs b/crates/ra_analysis/tests/tests.rs index f5683aec5..94e025677 100644 --- a/crates/ra_analysis/tests/tests.rs +++ b/crates/ra_analysis/tests/tests.rs @@ -5,38 +5,42 @@ extern crate relative_path; extern crate rustc_hash; extern crate test_utils; -use ra_syntax::TextRange; -use test_utils::{assert_eq_dbg, extract_offset}; +use ra_syntax::{TextRange}; +use test_utils::{assert_eq_dbg}; use ra_analysis::{ - MockAnalysis, - AnalysisChange, Analysis, CrateGraph, CrateId, FileId, FnDescriptor, + AnalysisChange, CrateGraph, FileId, FnDescriptor, + mock_analysis::{MockAnalysis, single_file, single_file_with_position, analysis_and_position}, }; -fn analysis(files: &[(&str, &str)]) -> Analysis { - MockAnalysis::with_files(files).analysis() -} - fn get_signature(text: &str) -> (FnDescriptor, Option) { - let (offset, code) = extract_offset(text); - let code = code.as_str(); - - let snap = analysis(&[("/lib.rs", code)]); - - snap.resolve_callable(FileId(1), offset).unwrap().unwrap() + let (analysis, position) = single_file_with_position(text); + analysis.resolve_callable(position.file_id, position.offset).unwrap().unwrap() } #[test] fn test_resolve_module() { - let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); - let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()).unwrap(); + let (analysis, pos) = analysis_and_position(" + //- /lib.rs + mod <|>foo; + //- /foo.rs + // empty + "); + + let symbols = analysis.approximately_resolve_symbol(pos.file_id, pos.offset).unwrap(); assert_eq_dbg( r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, &symbols, ); - let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo/mod.rs", "")]); - let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()).unwrap(); + let (analysis, pos) = analysis_and_position(" + //- /lib.rs + mod <|>foo; + //- /foo/mod.rs + // empty + "); + + let symbols = analysis.approximately_resolve_symbol(pos.file_id, pos.offset).unwrap(); assert_eq_dbg( r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, &symbols, @@ -45,8 +49,8 @@ fn test_resolve_module() { #[test] fn test_unresolved_module_diagnostic() { - let snap = analysis(&[("/lib.rs", "mod foo;")]); - let diagnostics = snap.diagnostics(FileId(1)).unwrap(); + let (analysis, file_id) = single_file("mod foo;"); + let diagnostics = analysis.diagnostics(file_id).unwrap(); assert_eq_dbg( r#"[Diagnostic { message: "unresolved module", @@ -62,15 +66,20 @@ fn test_unresolved_module_diagnostic() { #[test] fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() { - let snap = analysis(&[("/lib.rs", "mod foo {}")]); - let diagnostics = snap.diagnostics(FileId(1)).unwrap(); + let (analysis, file_id) = single_file("mod foo {}"); + let diagnostics = analysis.diagnostics(file_id).unwrap(); assert_eq_dbg(r#"[]"#, &diagnostics); } #[test] fn test_resolve_parent_module() { - let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); - let symbols = snap.parent_module(FileId(2)).unwrap(); + let (analysis, pos) = analysis_and_position(" + //- /lib.rs + mod foo; + //- /foo.rs + <|>// empty + "); + let symbols = analysis.parent_module(pos.file_id).unwrap(); assert_eq_dbg( r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, &symbols, @@ -79,23 +88,24 @@ fn test_resolve_parent_module() { #[test] fn test_resolve_crate_root() { - let mut host = MockAnalysis::with_files( - &[("/lib.rs", "mod foo;"), ("/foo.rs", "")] - ).analysis_host(); - let snap = host.analysis(); - assert!(snap.crate_for(FileId(2)).unwrap().is_empty()); - - let crate_graph = { - let mut g = CrateGraph::new(); - g.add_crate_root(FileId(1)); - g - }; + let mock = MockAnalysis::with_files(" + //- /lib.rs + mod foo; + //- /foo.rs + // emtpy <|> + "); + let root_file = mock.id_of("/lib.rs"); + let mod_file = mock.id_of("/foo.rs"); + let mut host = mock.analysis_host(); + assert!(host.analysis().crate_for(mod_file).unwrap().is_empty()); + + let mut crate_graph = CrateGraph::new(); + let crate_id = crate_graph.add_crate_root(root_file); let mut change = AnalysisChange::new(); change.set_crate_graph(crate_graph); host.apply_change(change); - let snap = host.analysis(); - assert_eq!(snap.crate_for(FileId(2)).unwrap(), vec![CrateId(0)],); + assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]); } #[test] @@ -186,12 +196,8 @@ fn bar() { } fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { - let (offset, code) = extract_offset(text); - let code = code.as_str(); - - let snap = analysis(&[("/lib.rs", code)]); - - snap.find_all_refs(FileId(1), offset).unwrap() + let (analysis, position) = single_file_with_position(text); + analysis.find_all_refs(position.file_id, position.offset).unwrap() } #[test] @@ -226,11 +232,14 @@ fn test_find_all_refs_for_param_inside() { #[test] fn test_complete_crate_path() { - let snap = analysis(&[ - ("/lib.rs", "mod foo; struct Spam;"), - ("/foo.rs", "use crate::Sp"), - ]); - let completions = snap.completions(FileId(2), 13.into()).unwrap().unwrap(); + let (analysis, position) = analysis_and_position(" + //- /lib.rs + mod foo; + struct Spam; + //- /foo.rs + use crate::Sp<|> + "); + let completions = analysis.completions(position.file_id, position.offset).unwrap().unwrap(); assert_eq_dbg( r#"[CompletionItem { label: "foo", lookup: None, snippet: None }, CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs index 562dbcbb3..8980f077f 100644 --- a/crates/test_utils/src/lib.rs +++ b/crates/test_utils/src/lib.rs @@ -8,6 +8,8 @@ use text_unit::{TextRange, TextUnit}; pub use self::difference::Changeset as __Changeset; +pub const CURSOR_MARKER: &str = "<|>"; + #[macro_export] macro_rules! assert_eq_text { ($expected:expr, $actual:expr) => {{ @@ -45,11 +47,10 @@ pub fn extract_offset(text: &str) -> (TextUnit, String) { } pub fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { - let cursor = "<|>"; - let cursor_pos = text.find(cursor)?; - let mut new_text = String::with_capacity(text.len() - cursor.len()); + let cursor_pos = text.find(CURSOR_MARKER)?; + let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len()); new_text.push_str(&text[..cursor_pos]); - new_text.push_str(&text[cursor_pos + cursor.len()..]); + new_text.push_str(&text[cursor_pos + CURSOR_MARKER.len()..]); let cursor_pos = TextUnit::from(cursor_pos as u32); Some((cursor_pos, new_text)) } @@ -116,7 +117,22 @@ pub fn parse_fixture(fixture: &str) -> Vec { } }; }; - for line in fixture.lines() { + let margin = fixture.lines() + .filter(|it| it.trim_start().starts_with("//-")) + .map(|it| it.len() - it.trim_start().len()) + .next().expect("empty fixture"); + let lines = fixture.lines() + .filter_map(|line| { + if line.len() >= margin { + assert!(line[..margin].trim().is_empty()); + Some(&line[margin..]) + } else { + assert!(line.trim().is_empty()); + None + } + }); + + for line in lines { if line.starts_with("//-") { flush!(); buf.clear(); -- cgit v1.2.3