aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-10-31 19:34:31 +0000
committerAleksey Kladov <[email protected]>2018-10-31 19:35:01 +0000
commitdfba29e4fb66457d101db295e3c356a932ac005e (patch)
tree57907040a40ce65a6ed2377469204a6c79bb645e
parent41adf1bc4f8b88139afd550209c0be612c10ffa8 (diff)
Add MockAnalysis to make testing easier
-rw-r--r--crates/ra_analysis/Cargo.toml6
-rw-r--r--crates/ra_analysis/src/completion.rs17
-rw-r--r--crates/ra_analysis/src/input.rs2
-rw-r--r--crates/ra_analysis/src/lib.rs3
-rw-r--r--crates/ra_analysis/src/mock_analysis.rs84
-rw-r--r--crates/ra_analysis/tests/tests.rs105
-rw-r--r--crates/test_utils/src/lib.rs26
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"
9relative-path = "0.4.0" 9relative-path = "0.4.0"
10rayon = "1.0.2" 10rayon = "1.0.2"
11fst = "0.3.1" 11fst = "0.3.1"
12ra_syntax = { path = "../ra_syntax" }
13ra_editor = { path = "../ra_editor" }
14salsa = "0.7.0" 12salsa = "0.7.0"
15rustc-hash = "1.0" 13rustc-hash = "1.0"
16 14ra_syntax = { path = "../ra_syntax" }
17[dev-dependencies] 15ra_editor = { path = "../ra_editor" }
18test_utils = { path = "../test_utils" } 16test_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<Completi
368 368
369#[cfg(test)] 369#[cfg(test)]
370mod tests { 370mod tests {
371 use test_utils::{assert_eq_dbg, extract_offset}; 371 use test_utils::{assert_eq_dbg};
372 372
373 use crate::FileId; 373 use crate::mock_analysis::{single_file_with_position};
374 use crate::mock_analysis::MockAnalysis;
375 374
376 use super::*; 375 use super::*;
377 376
378 fn check_scope_completion(code: &str, expected_completions: &str) { 377 fn check_scope_completion(code: &str, expected_completions: &str) {
379 let (off, code) = extract_offset(&code); 378 let (analysis, position) = single_file_with_position(code);
380 let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis(); 379 let completions = scope_completion(&analysis.imp.db, position.file_id, position.offset)
381 let file_id = FileId(1);
382 let completions = scope_completion(&analysis.imp.db, file_id, off)
383 .unwrap() 380 .unwrap()
384 .into_iter() 381 .into_iter()
385 .filter(|c| c.snippet.is_none()) 382 .filter(|c| c.snippet.is_none())
@@ -388,10 +385,8 @@ mod tests {
388 } 385 }
389 386
390 fn check_snippet_completion(code: &str, expected_completions: &str) { 387 fn check_snippet_completion(code: &str, expected_completions: &str) {
391 let (off, code) = extract_offset(&code); 388 let (analysis, position) = single_file_with_position(code);
392 let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis(); 389 let completions = scope_completion(&analysis.imp.db, position.file_id, position.offset)
393 let file_id = FileId(1);
394 let completions = scope_completion(&analysis.imp.db, file_id, off)
395 .unwrap() 390 .unwrap()
396 .into_iter() 391 .into_iter()
397 .filter(|c| c.snippet.is_some()) 392 .filter(|c| c.snippet.is_some())
diff --git a/crates/ra_analysis/src/input.rs b/crates/ra_analysis/src/input.rs
index fd63182c7..b89b45133 100644
--- a/crates/ra_analysis/src/input.rs
+++ b/crates/ra_analysis/src/input.rs
@@ -25,7 +25,7 @@ impl CrateGraph {
25 pub fn new() -> CrateGraph { 25 pub fn new() -> CrateGraph {
26 CrateGraph::default() 26 CrateGraph::default()
27 } 27 }
28 pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId{ 28 pub fn add_crate_root(&mut self, file_id: FileId) -> CrateId {
29 let crate_id = CrateId(self.crate_roots.len() as u32); 29 let crate_id = CrateId(self.crate_roots.len() as u32);
30 let prev = self.crate_roots.insert(crate_id, file_id); 30 let prev = self.crate_roots.insert(crate_id, file_id);
31 assert!(prev.is_none()); 31 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;
13mod symbol_index; 13mod symbol_index;
14mod completion; 14mod completion;
15mod syntax_ptr; 15mod syntax_ptr;
16mod mock_analysis; 16pub mod mock_analysis;
17 17
18use std::{ 18use std::{
19 fmt, 19 fmt,
@@ -33,7 +33,6 @@ pub use crate::{
33 descriptors::function::FnDescriptor, 33 descriptors::function::FnDescriptor,
34 completion::CompletionItem, 34 completion::CompletionItem,
35 input::{FileId, FileResolver, CrateGraph, CrateId}, 35 input::{FileId, FileResolver, CrateGraph, CrateId},
36 mock_analysis::MockAnalysis,
37}; 36};
38pub use ra_editor::{ 37pub use ra_editor::{
39 FileSymbol, Fold, FoldKind, HighlightedRange, LineIndex, Runnable, 38 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 @@
2use std::sync::Arc; 2use std::sync::Arc;
3 3
4use relative_path::{RelativePath, RelativePathBuf}; 4use relative_path::{RelativePath, RelativePathBuf};
5use ra_syntax::TextUnit;
6use test_utils::{extract_offset, parse_fixture, CURSOR_MARKER};
5 7
6use crate::{ 8use crate::{
7 AnalysisChange, Analysis, AnalysisHost, FileId, FileResolver, 9 AnalysisChange, Analysis, AnalysisHost, FileId, FileResolver,
8}; 10};
9 11
12#[derive(Debug)]
13pub struct FilePosition {
14 pub file_id: FileId,
15 pub offset: TextUnit,
16}
17
10/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis 18/// Mock analysis is used in test to bootstrap an AnalysisHost/Analysis
11/// from a set of in-memory files. 19/// from a set of in-memory files.
12#[derive(Debug, Default)] 20#[derive(Debug, Default)]
@@ -18,11 +26,57 @@ impl MockAnalysis {
18 pub fn new() -> MockAnalysis { 26 pub fn new() -> MockAnalysis {
19 MockAnalysis::default() 27 MockAnalysis::default()
20 } 28 }
21 pub fn with_files(files: &[(&str, &str)]) -> MockAnalysis { 29 /// Creates `MockAnalysis` using a fixture data in the following format:
22 let files = files.iter() 30 ///
23 .map(|it| (it.0.to_string(), it.1.to_string())) 31 /// ```notrust
24 .collect(); 32 /// //- /main.rs
25 MockAnalysis { files } 33 /// mod foo;
34 /// fn main() {}
35 ///
36 /// //- /foo.rs
37 /// struct Baz;
38 /// ```
39 pub fn with_files(fixture: &str) -> MockAnalysis {
40 let mut res = MockAnalysis::new();
41 for entry in parse_fixture(fixture) {
42 res.add_file(&entry.meta, &entry.text);
43 }
44 res
45 }
46
47 /// Same as `with_files`, but requires that a single file contains a `<|>` marker,
48 /// whose position is also returned.
49 pub fn with_files_and_position(fixture: &str) -> (MockAnalysis, FilePosition) {
50 let mut position = None;
51 let mut res = MockAnalysis::new();
52 for entry in parse_fixture(fixture) {
53 if entry.text.contains(CURSOR_MARKER) {
54 assert!(position.is_none(), "only one marker (<|>) per fixture is allowed");
55 position = Some(res.add_file_with_position(&entry.meta, &entry.text));
56 } else {
57 res.add_file(&entry.meta, &entry.text);
58 }
59 }
60 let position = position.expect("expected a marker (<|>)");
61 (res, position)
62 }
63
64 pub fn add_file(&mut self, path: &str, text: &str) -> FileId {
65 let file_id = FileId((self.files.len() + 1) as u32);
66 self.files.push((path.to_string(), text.to_string()));
67 file_id
68 }
69 pub fn add_file_with_position(&mut self, path: &str, text: &str) -> FilePosition {
70 let (offset, text) = extract_offset(text);
71 let file_id = FileId((self.files.len() + 1) as u32);
72 self.files.push((path.to_string(), text.to_string()));
73 FilePosition { file_id, offset }
74 }
75 pub fn id_of(&self, path: &str) -> FileId {
76 let (idx, _) = self.files.iter().enumerate()
77 .find(|(_, (p, _text))| path == p)
78 .expect("no file in this mock");
79 FileId(idx as u32 + 1)
26 } 80 }
27 pub fn analysis_host(self) -> AnalysisHost { 81 pub fn analysis_host(self) -> AnalysisHost {
28 let mut host = AnalysisHost::new(); 82 let mut host = AnalysisHost::new();
@@ -44,6 +98,26 @@ impl MockAnalysis {
44 } 98 }
45} 99}
46 100
101/// Creates analysis from a multi-file fixture, returns positions marked with <|>.
102pub fn analysis_and_position(fixture: &str) -> (Analysis, FilePosition) {
103 let (mock, position) = MockAnalysis::with_files_and_position(fixture);
104 (mock.analysis(), position)
105}
106
107/// Creates analysis for a single file.
108pub fn single_file(code: &str) -> (Analysis, FileId) {
109 let mut mock = MockAnalysis::new();
110 let file_id = mock.add_file("/main.rs", code);
111 (mock.analysis(), file_id)
112}
113
114/// Creates analysis for a single file, returns position marked with <|>.
115pub fn single_file_with_position(code: &str) -> (Analysis, FilePosition) {
116 let mut mock = MockAnalysis::new();
117 let pos = mock.add_file_with_position("/main.rs", code);
118 (mock.analysis(), pos)
119}
120
47#[derive(Debug)] 121#[derive(Debug)]
48struct FileMap(Vec<(FileId, RelativePathBuf)>); 122struct FileMap(Vec<(FileId, RelativePathBuf)>);
49 123
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;
5extern crate rustc_hash; 5extern crate rustc_hash;
6extern crate test_utils; 6extern crate test_utils;
7 7
8use ra_syntax::TextRange; 8use ra_syntax::{TextRange};
9use test_utils::{assert_eq_dbg, extract_offset}; 9use test_utils::{assert_eq_dbg};
10 10
11use ra_analysis::{ 11use ra_analysis::{
12 MockAnalysis, 12 AnalysisChange, CrateGraph, FileId, FnDescriptor,
13 AnalysisChange, Analysis, CrateGraph, CrateId, FileId, FnDescriptor, 13 mock_analysis::{MockAnalysis, single_file, single_file_with_position, analysis_and_position},
14}; 14};
15 15
16fn analysis(files: &[(&str, &str)]) -> Analysis {
17 MockAnalysis::with_files(files).analysis()
18}
19
20fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) { 16fn get_signature(text: &str) -> (FnDescriptor, Option<usize>) {
21 let (offset, code) = extract_offset(text); 17 let (analysis, position) = single_file_with_position(text);
22 let code = code.as_str(); 18 analysis.resolve_callable(position.file_id, position.offset).unwrap().unwrap()
23
24 let snap = analysis(&[("/lib.rs", code)]);
25
26 snap.resolve_callable(FileId(1), offset).unwrap().unwrap()
27} 19}
28 20
29#[test] 21#[test]
30fn test_resolve_module() { 22fn test_resolve_module() {
31 let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); 23 let (analysis, pos) = analysis_and_position("
32 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()).unwrap(); 24 //- /lib.rs
25 mod <|>foo;
26 //- /foo.rs
27 // empty
28 ");
29
30 let symbols = analysis.approximately_resolve_symbol(pos.file_id, pos.offset).unwrap();
33 assert_eq_dbg( 31 assert_eq_dbg(
34 r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, 32 r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#,
35 &symbols, 33 &symbols,
36 ); 34 );
37 35
38 let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo/mod.rs", "")]); 36 let (analysis, pos) = analysis_and_position("
39 let symbols = snap.approximately_resolve_symbol(FileId(1), 4.into()).unwrap(); 37 //- /lib.rs
38 mod <|>foo;
39 //- /foo/mod.rs
40 // empty
41 ");
42
43 let symbols = analysis.approximately_resolve_symbol(pos.file_id, pos.offset).unwrap();
40 assert_eq_dbg( 44 assert_eq_dbg(
41 r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#, 45 r#"[(FileId(2), FileSymbol { name: "foo", node_range: [0; 0), kind: MODULE })]"#,
42 &symbols, 46 &symbols,
@@ -45,8 +49,8 @@ fn test_resolve_module() {
45 49
46#[test] 50#[test]
47fn test_unresolved_module_diagnostic() { 51fn test_unresolved_module_diagnostic() {
48 let snap = analysis(&[("/lib.rs", "mod foo;")]); 52 let (analysis, file_id) = single_file("mod foo;");
49 let diagnostics = snap.diagnostics(FileId(1)).unwrap(); 53 let diagnostics = analysis.diagnostics(file_id).unwrap();
50 assert_eq_dbg( 54 assert_eq_dbg(
51 r#"[Diagnostic { 55 r#"[Diagnostic {
52 message: "unresolved module", 56 message: "unresolved module",
@@ -62,15 +66,20 @@ fn test_unresolved_module_diagnostic() {
62 66
63#[test] 67#[test]
64fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() { 68fn test_unresolved_module_diagnostic_no_diag_for_inline_mode() {
65 let snap = analysis(&[("/lib.rs", "mod foo {}")]); 69 let (analysis, file_id) = single_file("mod foo {}");
66 let diagnostics = snap.diagnostics(FileId(1)).unwrap(); 70 let diagnostics = analysis.diagnostics(file_id).unwrap();
67 assert_eq_dbg(r#"[]"#, &diagnostics); 71 assert_eq_dbg(r#"[]"#, &diagnostics);
68} 72}
69 73
70#[test] 74#[test]
71fn test_resolve_parent_module() { 75fn test_resolve_parent_module() {
72 let snap = analysis(&[("/lib.rs", "mod foo;"), ("/foo.rs", "")]); 76 let (analysis, pos) = analysis_and_position("
73 let symbols = snap.parent_module(FileId(2)).unwrap(); 77 //- /lib.rs
78 mod foo;
79 //- /foo.rs
80 <|>// empty
81 ");
82 let symbols = analysis.parent_module(pos.file_id).unwrap();
74 assert_eq_dbg( 83 assert_eq_dbg(
75 r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#, 84 r#"[(FileId(1), FileSymbol { name: "foo", node_range: [0; 8), kind: MODULE })]"#,
76 &symbols, 85 &symbols,
@@ -79,23 +88,24 @@ fn test_resolve_parent_module() {
79 88
80#[test] 89#[test]
81fn test_resolve_crate_root() { 90fn test_resolve_crate_root() {
82 let mut host = MockAnalysis::with_files( 91 let mock = MockAnalysis::with_files("
83 &[("/lib.rs", "mod foo;"), ("/foo.rs", "")] 92 //- /lib.rs
84 ).analysis_host(); 93 mod foo;
85 let snap = host.analysis(); 94 //- /foo.rs
86 assert!(snap.crate_for(FileId(2)).unwrap().is_empty()); 95 // emtpy <|>
87 96 ");
88 let crate_graph = { 97 let root_file = mock.id_of("/lib.rs");
89 let mut g = CrateGraph::new(); 98 let mod_file = mock.id_of("/foo.rs");
90 g.add_crate_root(FileId(1)); 99 let mut host = mock.analysis_host();
91 g 100 assert!(host.analysis().crate_for(mod_file).unwrap().is_empty());
92 }; 101
102 let mut crate_graph = CrateGraph::new();
103 let crate_id = crate_graph.add_crate_root(root_file);
93 let mut change = AnalysisChange::new(); 104 let mut change = AnalysisChange::new();
94 change.set_crate_graph(crate_graph); 105 change.set_crate_graph(crate_graph);
95 host.apply_change(change); 106 host.apply_change(change);
96 let snap = host.analysis();
97 107
98 assert_eq!(snap.crate_for(FileId(2)).unwrap(), vec![CrateId(0)],); 108 assert_eq!(host.analysis().crate_for(mod_file).unwrap(), vec![crate_id]);
99} 109}
100 110
101#[test] 111#[test]
@@ -186,12 +196,8 @@ fn bar() {
186} 196}
187 197
188fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> { 198fn get_all_refs(text: &str) -> Vec<(FileId, TextRange)> {
189 let (offset, code) = extract_offset(text); 199 let (analysis, position) = single_file_with_position(text);
190 let code = code.as_str(); 200 analysis.find_all_refs(position.file_id, position.offset).unwrap()
191
192 let snap = analysis(&[("/lib.rs", code)]);
193
194 snap.find_all_refs(FileId(1), offset).unwrap()
195} 201}
196 202
197#[test] 203#[test]
@@ -226,11 +232,14 @@ fn test_find_all_refs_for_param_inside() {
226 232
227#[test] 233#[test]
228fn test_complete_crate_path() { 234fn test_complete_crate_path() {
229 let snap = analysis(&[ 235 let (analysis, position) = analysis_and_position("
230 ("/lib.rs", "mod foo; struct Spam;"), 236 //- /lib.rs
231 ("/foo.rs", "use crate::Sp"), 237 mod foo;
232 ]); 238 struct Spam;
233 let completions = snap.completions(FileId(2), 13.into()).unwrap().unwrap(); 239 //- /foo.rs
240 use crate::Sp<|>
241 ");
242 let completions = analysis.completions(position.file_id, position.offset).unwrap().unwrap();
234 assert_eq_dbg( 243 assert_eq_dbg(
235 r#"[CompletionItem { label: "foo", lookup: None, snippet: None }, 244 r#"[CompletionItem { label: "foo", lookup: None, snippet: None },
236 CompletionItem { label: "Spam", lookup: None, snippet: None }]"#, 245 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};
8 8
9pub use self::difference::Changeset as __Changeset; 9pub use self::difference::Changeset as __Changeset;
10 10
11pub const CURSOR_MARKER: &str = "<|>";
12
11#[macro_export] 13#[macro_export]
12macro_rules! assert_eq_text { 14macro_rules! assert_eq_text {
13 ($expected:expr, $actual:expr) => {{ 15 ($expected:expr, $actual:expr) => {{
@@ -45,11 +47,10 @@ pub fn extract_offset(text: &str) -> (TextUnit, String) {
45} 47}
46 48
47pub fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> { 49pub fn try_extract_offset(text: &str) -> Option<(TextUnit, String)> {
48 let cursor = "<|>"; 50 let cursor_pos = text.find(CURSOR_MARKER)?;
49 let cursor_pos = text.find(cursor)?; 51 let mut new_text = String::with_capacity(text.len() - CURSOR_MARKER.len());
50 let mut new_text = String::with_capacity(text.len() - cursor.len());
51 new_text.push_str(&text[..cursor_pos]); 52 new_text.push_str(&text[..cursor_pos]);
52 new_text.push_str(&text[cursor_pos + cursor.len()..]); 53 new_text.push_str(&text[cursor_pos + CURSOR_MARKER.len()..]);
53 let cursor_pos = TextUnit::from(cursor_pos as u32); 54 let cursor_pos = TextUnit::from(cursor_pos as u32);
54 Some((cursor_pos, new_text)) 55 Some((cursor_pos, new_text))
55} 56}
@@ -116,7 +117,22 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
116 } 117 }
117 }; 118 };
118 }; 119 };
119 for line in fixture.lines() { 120 let margin = fixture.lines()
121 .filter(|it| it.trim_start().starts_with("//-"))
122 .map(|it| it.len() - it.trim_start().len())
123 .next().expect("empty fixture");
124 let lines = fixture.lines()
125 .filter_map(|line| {
126 if line.len() >= margin {
127 assert!(line[..margin].trim().is_empty());
128 Some(&line[margin..])
129 } else {
130 assert!(line.trim().is_empty());
131 None
132 }
133 });
134
135 for line in lines {
120 if line.starts_with("//-") { 136 if line.starts_with("//-") {
121 flush!(); 137 flush!();
122 buf.clear(); 138 buf.clear();