mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 21:28:51 +00:00
Add MockAnalysis to make testing easier
This commit is contained in:
parent
41adf1bc4f
commit
dfba29e4fb
7 changed files with 166 additions and 75 deletions
|
@ -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" }
|
||||
|
|
|
@ -368,18 +368,15 @@ fn complete_fn(name_ref: ast::NameRef, scopes: &FnScopes, acc: &mut Vec<Completi
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use test_utils::{assert_eq_dbg, extract_offset};
|
||||
use test_utils::{assert_eq_dbg};
|
||||
|
||||
use crate::FileId;
|
||||
use crate::mock_analysis::MockAnalysis;
|
||||
use crate::mock_analysis::{single_file_with_position};
|
||||
|
||||
use super::*;
|
||||
|
||||
fn check_scope_completion(code: &str, expected_completions: &str) {
|
||||
let (off, code) = extract_offset(&code);
|
||||
let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis();
|
||||
let file_id = FileId(1);
|
||||
let completions = scope_completion(&analysis.imp.db, file_id, off)
|
||||
let (analysis, position) = single_file_with_position(code);
|
||||
let completions = scope_completion(&analysis.imp.db, position.file_id, position.offset)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|c| c.snippet.is_none())
|
||||
|
@ -388,10 +385,8 @@ mod tests {
|
|||
}
|
||||
|
||||
fn check_snippet_completion(code: &str, expected_completions: &str) {
|
||||
let (off, code) = extract_offset(&code);
|
||||
let analysis = MockAnalysis::with_files(&[("/main.rs", &code)]).analysis();
|
||||
let file_id = FileId(1);
|
||||
let completions = scope_completion(&analysis.imp.db, file_id, off)
|
||||
let (analysis, position) = single_file_with_position(code);
|
||||
let completions = scope_completion(&analysis.imp.db, position.file_id, position.offset)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.filter(|c| c.snippet.is_some())
|
||||
|
|
|
@ -25,7 +25,7 @@ impl CrateGraph {
|
|||
pub fn new() -> 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());
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)>);
|
||||
|
||||
|
|
|
@ -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<usize>) {
|
||||
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 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 crate_graph = {
|
||||
let mut g = CrateGraph::new();
|
||||
g.add_crate_root(FileId(1));
|
||||
g
|
||||
};
|
||||
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 }]"#,
|
||||
|
|
|
@ -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<FixtureEntry> {
|
|||
}
|
||||
};
|
||||
};
|
||||
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();
|
||||
|
|
Loading…
Reference in a new issue