diff options
-rw-r--r-- | Cargo.lock | 1 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/main.rs | 71 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/support.rs | 27 | ||||
-rw-r--r-- | crates/ra_syntax/src/grammar/items/mod.rs | 2 | ||||
-rw-r--r-- | crates/test_utils/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/test_utils/src/lib.rs | 94 |
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" | |||
1075 | dependencies = [ | 1075 | dependencies = [ |
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 @@ | |||
1 | mod support; | 1 | mod support; |
2 | 2 | ||
3 | use ra_lsp_server::req::{Runnables, RunnablesParams}; | 3 | use serde_json::json; |
4 | |||
5 | use ra_lsp_server::req::{Runnables, RunnablesParams, CodeActionRequest, CodeActionParams}; | ||
6 | |||
7 | use languageserver_types::{Position, Range, CodeActionContext}; | ||
4 | 8 | ||
5 | use crate::support::project; | 9 | use 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] | ||
123 | fn test_missing_module_code_action() { | ||
124 | let server = project( | ||
125 | r#" | ||
126 | //- Cargo.toml | ||
127 | [package] | ||
128 | name = "foo" | ||
129 | version = "0.0.0" | ||
130 | |||
131 | //- src/lib.rs | ||
132 | mod bar; | ||
133 | |||
134 | fn 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 | }; |
17 | use serde::Serialize; | 17 | use serde::Serialize; |
18 | use serde_json::{from_str, to_string_pretty, Value}; | 18 | use serde_json::{to_string_pretty, Value}; |
19 | use tempdir::TempDir; | 19 | use tempdir::TempDir; |
20 | use test_utils::parse_fixture; | 20 | use test_utils::{parse_fixture, find_mismatch}; |
21 | 21 | ||
22 | use ra_lsp_server::{ | 22 | use 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]>"] | |||
8 | difference = "2.0.0" | 8 | difference = "2.0.0" |
9 | itertools = "0.7.8" | 9 | itertools = "0.7.8" |
10 | text_unit = "0.1.2" | 10 | text_unit = "0.1.2" |
11 | serde_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 | ||
3 | use itertools::Itertools; | 3 | use itertools::Itertools; |
4 | use text_unit::{TextRange, TextUnit}; | 4 | use text_unit::{TextRange, TextUnit}; |
5 | use serde_json::Value; | ||
5 | 6 | ||
6 | pub use difference::Changeset as __Changeset; | 7 | pub 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). | ||
155 | pub 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] | ||
176 | fn 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. | ||
193 | pub 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 | } | ||