aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Cargo.lock1
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/main.rs71
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/support.rs27
-rw-r--r--crates/ra_syntax/src/grammar/items/mod.rs2
-rw-r--r--crates/test_utils/Cargo.toml1
-rw-r--r--crates/test_utils/src/lib.rs94
6 files changed, 177 insertions, 19 deletions
diff --git a/Cargo.lock b/Cargo.lock
index 42a962cf6..4f46e26fb 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1075,6 +1075,7 @@ version = "0.1.0"
1075dependencies = [ 1075dependencies = [
1076 "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)", 1076 "difference 2.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
1077 "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)", 1077 "itertools 0.7.8 (registry+https://github.com/rust-lang/crates.io-index)",
1078 "serde_json 1.0.32 (registry+https://github.com/rust-lang/crates.io-index)",
1078 "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", 1079 "text_unit 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)",
1079] 1080]
1080 1081
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs
index cbc0c8844..26f5e3f20 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/main.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/main.rs
@@ -1,6 +1,10 @@
1mod support; 1mod support;
2 2
3use ra_lsp_server::req::{Runnables, RunnablesParams}; 3use serde_json::json;
4
5use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams};
6
7use languageserver_types::{Position, Range, CodeActionContext};
4 8
5use crate::support::project; 9use crate::support::project;
6 10
@@ -21,7 +25,7 @@ fn foo() {
21 text_document: server.doc_id("lib.rs"), 25 text_document: server.doc_id("lib.rs"),
22 position: None, 26 position: None,
23 }, 27 },
24 r#"[ 28 json!([
25 { 29 {
26 "args": [ "test", "--", "foo", "--nocapture" ], 30 "args": [ "test", "--", "foo", "--nocapture" ],
27 "bin": "cargo", 31 "bin": "cargo",
@@ -51,7 +55,7 @@ fn foo() {
51 } 55 }
52 } 56 }
53 } 57 }
54 ]"#, 58 ]),
55 ); 59 );
56} 60}
57 61
@@ -78,7 +82,7 @@ fn test_eggs() {}
78 text_document: server.doc_id("tests/spam.rs"), 82 text_document: server.doc_id("tests/spam.rs"),
79 position: None, 83 position: None,
80 }, 84 },
81 r#"[ 85 json!([
82 { 86 {
83 "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ], 87 "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--nocapture" ],
84 "bin": "cargo", 88 "bin": "cargo",
@@ -111,6 +115,63 @@ fn test_eggs() {}
111 } 115 }
112 } 116 }
113 } 117 }
114 ]"# 118 ])
119 );
120}
121
122#[test]
123fn test_missing_module_code_action() {
124 let server = project(
125 r#"
126//- Cargo.toml
127[package]
128name = "foo"
129version = "0.0.0"
130
131//- src/lib.rs
132mod bar;
133
134fn main() {}
135"#,
136 );
137 server.wait_for_feedback("workspace loaded");
138 let empty_context = || CodeActionContext {
139 diagnostics: Vec::new(),
140 only: None,
141 };
142 server.request::<CodeActionRequest>(
143 CodeActionParams {
144 text_document: server.doc_id("src/lib.rs"),
145 range: Range::new(Position::new(0, 0), Position::new(0, 7)),
146 context: empty_context(),
147 },
148 json!([
149 {
150 "arguments": [
151 {
152 "cursorPosition": null,
153 "fileSystemEdits": [
154 {
155 "type": "createFile",
156 "uri": "file:///[..]/src/bar.rs"
157 }
158 ],
159 "label": "create module",
160 "sourceFileEdits": []
161 }
162 ],
163 "command": "ra-lsp.applySourceChange",
164 "title": "create module"
165 }
166 ]),
167 );
168
169 server.request::<CodeActionRequest>(
170 CodeActionParams {
171 text_document: server.doc_id("src/lib.rs"),
172 range: Range::new(Position::new(2, 0), Position::new(2, 7)),
173 context: empty_context(),
174 },
175 json!([]),
115 ); 176 );
116} 177}
diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs
index 019048a3a..4b75be3ee 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/support.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs
@@ -15,9 +15,9 @@ use languageserver_types::{
15 DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url, 15 DidOpenTextDocumentParams, TextDocumentIdentifier, TextDocumentItem, Url,
16}; 16};
17use serde::Serialize; 17use serde::Serialize;
18use serde_json::{from_str, to_string_pretty, Value}; 18use serde_json::{to_string_pretty, Value};
19use tempdir::TempDir; 19use tempdir::TempDir;
20use test_utils::parse_fixture; 20use test_utils::{parse_fixture, find_mismatch};
21 21
22use ra_lsp_server::{ 22use ra_lsp_server::{
23 main_loop, req, 23 main_loop, req,
@@ -88,23 +88,24 @@ impl Server {
88 } 88 }
89 } 89 }
90 90
91 pub fn request<R>(&self, params: R::Params, expected_resp: &str) 91 pub fn request<R>(&self, params: R::Params, expected_resp: Value)
92 where 92 where
93 R: Request, 93 R: Request,
94 R::Params: Serialize, 94 R::Params: Serialize,
95 { 95 {
96 let id = self.req_id.get(); 96 let id = self.req_id.get();
97 self.req_id.set(id + 1); 97 self.req_id.set(id + 1);
98 let expected_resp: Value = from_str(expected_resp).unwrap();
99 let actual = self.send_request::<R>(id, params); 98 let actual = self.send_request::<R>(id, params);
100 assert_eq!( 99 match find_mismatch(&expected_resp, &actual) {
101 expected_resp, 100 Some((expected_part, actual_part)) => panic!(
102 actual, 101 "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
103 "Expected:\n{}\n\ 102 to_string_pretty(&expected_resp).unwrap(),
104 Actual:\n{}\n", 103 to_string_pretty(&actual).unwrap(),
105 to_string_pretty(&expected_resp).unwrap(), 104 to_string_pretty(expected_part).unwrap(),
106 to_string_pretty(&actual).unwrap(), 105 to_string_pretty(actual_part).unwrap(),
107 ); 106 ),
107 None => {}
108 }
108 } 109 }
109 110
110 fn send_request<R>(&self, id: u64, params: R::Params) -> Value 111 fn send_request<R>(&self, id: u64, params: R::Params) -> Value
@@ -139,7 +140,7 @@ impl Server {
139 pub fn wait_for_feedback_n(&self, feedback: &str, n: usize) { 140 pub fn wait_for_feedback_n(&self, feedback: &str, n: usize) {
140 let f = |msg: &RawMessage| match msg { 141 let f = |msg: &RawMessage| match msg {
141 RawMessage::Notification(n) if n.method == "internalFeedback" => { 142 RawMessage::Notification(n) if n.method == "internalFeedback" => {
142 return n.clone().cast::<req::InternalFeedback>().unwrap() == feedback 143 return n.clone().cast::<req::InternalFeedback>().unwrap() == feedback;
143 } 144 }
144 _ => false, 145 _ => false,
145 }; 146 };
diff --git a/crates/ra_syntax/src/grammar/items/mod.rs b/crates/ra_syntax/src/grammar/items/mod.rs
index 682266908..4473c2fab 100644
--- a/crates/ra_syntax/src/grammar/items/mod.rs
+++ b/crates/ra_syntax/src/grammar/items/mod.rs
@@ -159,7 +159,7 @@ pub(super) fn maybe_item(p: &mut Parser, flavor: ItemFlavor) -> MaybeItem {
159 MaybeItem::Modifiers 159 MaybeItem::Modifiers
160 } else { 160 } else {
161 MaybeItem::None 161 MaybeItem::None
162 } 162 };
163 } 163 }
164 }; 164 };
165 165
diff --git a/crates/test_utils/Cargo.toml b/crates/test_utils/Cargo.toml
index fe0998ab8..8c8fcd7ae 100644
--- a/crates/test_utils/Cargo.toml
+++ b/crates/test_utils/Cargo.toml
@@ -8,3 +8,4 @@ authors = ["Aleksey Kladov <[email protected]>"]
8difference = "2.0.0" 8difference = "2.0.0"
9itertools = "0.7.8" 9itertools = "0.7.8"
10text_unit = "0.1.2" 10text_unit = "0.1.2"
11serde_json = "1.0.24"
diff --git a/crates/test_utils/src/lib.rs b/crates/test_utils/src/lib.rs
index e72ec9c47..0a94adb74 100644
--- a/crates/test_utils/src/lib.rs
+++ b/crates/test_utils/src/lib.rs
@@ -2,6 +2,7 @@ use std::fmt;
2 2
3use itertools::Itertools; 3use itertools::Itertools;
4use text_unit::{TextRange, TextUnit}; 4use text_unit::{TextRange, TextUnit};
5use serde_json::Value;
5 6
6pub use difference::Changeset as __Changeset; 7pub use difference::Changeset as __Changeset;
7 8
@@ -145,3 +146,96 @@ pub fn parse_fixture(fixture: &str) -> Vec<FixtureEntry> {
145 flush!(); 146 flush!();
146 res 147 res
147} 148}
149
150// Comparison functionality borrowed from cargo:
151
152/// Compare a line with an expected pattern.
153/// - Use `[..]` as a wildcard to match 0 or more characters on the same line
154/// (similar to `.*` in a regex).
155pub fn lines_match(expected: &str, actual: &str) -> bool {
156 // Let's not deal with / vs \ (windows...)
157 // First replace backslash-escaped backslashes with forward slashes
158 // which can occur in, for example, JSON output
159 let expected = expected.replace("\\\\", "/").replace("\\", "/");
160 let mut actual: &str = &actual.replace("\\\\", "/").replace("\\", "/");
161 for (i, part) in expected.split("[..]").enumerate() {
162 match actual.find(part) {
163 Some(j) => {
164 if i == 0 && j != 0 {
165 return false;
166 }
167 actual = &actual[j + part.len()..];
168 }
169 None => return false,
170 }
171 }
172 actual.is_empty() || expected.ends_with("[..]")
173}
174
175#[test]
176fn lines_match_works() {
177 assert!(lines_match("a b", "a b"));
178 assert!(lines_match("a[..]b", "a b"));
179 assert!(lines_match("a[..]", "a b"));
180 assert!(lines_match("[..]", "a b"));
181 assert!(lines_match("[..]b", "a b"));
182
183 assert!(!lines_match("[..]b", "c"));
184 assert!(!lines_match("b", "c"));
185 assert!(!lines_match("b", "cb"));
186}
187
188// Compares JSON object for approximate equality.
189// You can use `[..]` wildcard in strings (useful for OS dependent things such
190// as paths). You can use a `"{...}"` string literal as a wildcard for
191// arbitrary nested JSON (useful for parts of object emitted by other programs
192// (e.g. rustc) rather than Cargo itself). Arrays are sorted before comparison.
193pub fn find_mismatch<'a>(expected: &'a Value, actual: &'a Value) -> Option<(&'a Value, &'a Value)> {
194 use serde_json::Value::*;
195 match (expected, actual) {
196 (&Number(ref l), &Number(ref r)) if l == r => None,
197 (&Bool(l), &Bool(r)) if l == r => None,
198 (&String(ref l), &String(ref r)) if lines_match(l, r) => None,
199 (&Array(ref l), &Array(ref r)) => {
200 if l.len() != r.len() {
201 return Some((expected, actual));
202 }
203
204 let mut l = l.iter().collect::<Vec<_>>();
205 let mut r = r.iter().collect::<Vec<_>>();
206
207 l.retain(
208 |l| match r.iter().position(|r| find_mismatch(l, r).is_none()) {
209 Some(i) => {
210 r.remove(i);
211 false
212 }
213 None => true,
214 },
215 );
216
217 if !l.is_empty() {
218 assert!(!r.is_empty());
219 Some((&l[0], &r[0]))
220 } else {
221 assert_eq!(r.len(), 0);
222 None
223 }
224 }
225 (&Object(ref l), &Object(ref r)) => {
226 let same_keys = l.len() == r.len() && l.keys().all(|k| r.contains_key(k));
227 if !same_keys {
228 return Some((expected, actual));
229 }
230
231 l.values()
232 .zip(r.values())
233 .filter_map(|(l, r)| find_mismatch(l, r))
234 .nth(0)
235 }
236 (&Null, &Null) => None,
237 // magic string literal "{...}" acts as wildcard for any sub-JSON
238 (&String(ref l), _) if l == "{...}" => None,
239 _ => Some((expected, actual)),
240 }
241}