diff options
Diffstat (limited to 'crates/ra_lsp_server/tests')
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/main.rs | 582 | ||||
-rw-r--r-- | crates/ra_lsp_server/tests/heavy_tests/support.rs | 254 |
2 files changed, 0 insertions, 836 deletions
diff --git a/crates/ra_lsp_server/tests/heavy_tests/main.rs b/crates/ra_lsp_server/tests/heavy_tests/main.rs deleted file mode 100644 index 9ca31cbcc..000000000 --- a/crates/ra_lsp_server/tests/heavy_tests/main.rs +++ /dev/null | |||
@@ -1,582 +0,0 @@ | |||
1 | mod support; | ||
2 | |||
3 | use std::{collections::HashMap, time::Instant}; | ||
4 | |||
5 | use lsp_types::{ | ||
6 | CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions, | ||
7 | PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams, | ||
8 | WorkDoneProgressParams, | ||
9 | }; | ||
10 | use ra_lsp_server::req::{ | ||
11 | CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument, | ||
12 | Formatting, OnEnter, Runnables, RunnablesParams, | ||
13 | }; | ||
14 | use serde_json::json; | ||
15 | use tempfile::TempDir; | ||
16 | use test_utils::skip_slow_tests; | ||
17 | |||
18 | use crate::support::{project, Project}; | ||
19 | |||
20 | const PROFILE: &'static str = ""; | ||
21 | // const PROFILE: &'static str = "*@3>100"; | ||
22 | |||
23 | #[test] | ||
24 | fn completes_items_from_standard_library() { | ||
25 | if skip_slow_tests() { | ||
26 | return; | ||
27 | } | ||
28 | |||
29 | let project_start = Instant::now(); | ||
30 | let server = Project::with_fixture( | ||
31 | r#" | ||
32 | //- Cargo.toml | ||
33 | [package] | ||
34 | name = "foo" | ||
35 | version = "0.0.0" | ||
36 | |||
37 | //- src/lib.rs | ||
38 | use std::collections::Spam; | ||
39 | "#, | ||
40 | ) | ||
41 | .with_sysroot(true) | ||
42 | .server(); | ||
43 | server.wait_until_workspace_is_loaded(); | ||
44 | eprintln!("loading took {:?}", project_start.elapsed()); | ||
45 | let completion_start = Instant::now(); | ||
46 | let res = server.send_request::<Completion>(CompletionParams { | ||
47 | text_document_position: TextDocumentPositionParams::new( | ||
48 | server.doc_id("src/lib.rs"), | ||
49 | Position::new(0, 23), | ||
50 | ), | ||
51 | context: None, | ||
52 | partial_result_params: PartialResultParams::default(), | ||
53 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
54 | }); | ||
55 | assert!(format!("{}", res).contains("HashMap")); | ||
56 | eprintln!("completion took {:?}", completion_start.elapsed()); | ||
57 | } | ||
58 | |||
59 | #[test] | ||
60 | fn test_runnables_no_project() { | ||
61 | if skip_slow_tests() { | ||
62 | return; | ||
63 | } | ||
64 | |||
65 | let server = project( | ||
66 | r" | ||
67 | //- lib.rs | ||
68 | #[test] | ||
69 | fn foo() { | ||
70 | } | ||
71 | ", | ||
72 | ); | ||
73 | server.wait_until_workspace_is_loaded(); | ||
74 | server.request::<Runnables>( | ||
75 | RunnablesParams { text_document: server.doc_id("lib.rs"), position: None }, | ||
76 | json!([ | ||
77 | { | ||
78 | "args": [ "test", "--", "foo", "--nocapture" ], | ||
79 | "bin": "cargo", | ||
80 | "env": { "RUST_BACKTRACE": "short" }, | ||
81 | "cwd": null, | ||
82 | "label": "test foo", | ||
83 | "range": { | ||
84 | "end": { "character": 1, "line": 2 }, | ||
85 | "start": { "character": 0, "line": 0 } | ||
86 | } | ||
87 | }, | ||
88 | { | ||
89 | "args": [ | ||
90 | "check", | ||
91 | "--all" | ||
92 | ], | ||
93 | "bin": "cargo", | ||
94 | "env": {}, | ||
95 | "cwd": null, | ||
96 | "label": "cargo check --all", | ||
97 | "range": { | ||
98 | "end": { | ||
99 | "character": 0, | ||
100 | "line": 0 | ||
101 | }, | ||
102 | "start": { | ||
103 | "character": 0, | ||
104 | "line": 0 | ||
105 | } | ||
106 | } | ||
107 | } | ||
108 | ]), | ||
109 | ); | ||
110 | } | ||
111 | |||
112 | #[test] | ||
113 | fn test_runnables_project() { | ||
114 | if skip_slow_tests() { | ||
115 | return; | ||
116 | } | ||
117 | |||
118 | let code = r#" | ||
119 | //- foo/Cargo.toml | ||
120 | [package] | ||
121 | name = "foo" | ||
122 | version = "0.0.0" | ||
123 | |||
124 | //- foo/src/lib.rs | ||
125 | pub fn foo() {} | ||
126 | |||
127 | //- foo/tests/spam.rs | ||
128 | #[test] | ||
129 | fn test_eggs() {} | ||
130 | |||
131 | //- bar/Cargo.toml | ||
132 | [package] | ||
133 | name = "bar" | ||
134 | version = "0.0.0" | ||
135 | |||
136 | //- bar/src/main.rs | ||
137 | fn main() {} | ||
138 | "#; | ||
139 | |||
140 | let server = Project::with_fixture(code).root("foo").root("bar").server(); | ||
141 | |||
142 | server.wait_until_workspace_is_loaded(); | ||
143 | server.request::<Runnables>( | ||
144 | RunnablesParams { | ||
145 | text_document: server.doc_id("foo/tests/spam.rs"), | ||
146 | position: None, | ||
147 | }, | ||
148 | json!([ | ||
149 | { | ||
150 | "args": [ "test", "--package", "foo", "--test", "spam", "--", "test_eggs", "--exact", "--nocapture" ], | ||
151 | "bin": "cargo", | ||
152 | "env": { "RUST_BACKTRACE": "short" }, | ||
153 | "label": "test test_eggs", | ||
154 | "range": { | ||
155 | "end": { "character": 17, "line": 1 }, | ||
156 | "start": { "character": 0, "line": 0 } | ||
157 | }, | ||
158 | "cwd": server.path().join("foo") | ||
159 | }, | ||
160 | { | ||
161 | "args": [ | ||
162 | "check", | ||
163 | "--package", | ||
164 | "foo", | ||
165 | "--test", | ||
166 | "spam" | ||
167 | ], | ||
168 | "bin": "cargo", | ||
169 | "env": {}, | ||
170 | "cwd": server.path().join("foo"), | ||
171 | "label": "cargo check -p foo", | ||
172 | "range": { | ||
173 | "end": { | ||
174 | "character": 0, | ||
175 | "line": 0 | ||
176 | }, | ||
177 | "start": { | ||
178 | "character": 0, | ||
179 | "line": 0 | ||
180 | } | ||
181 | } | ||
182 | } | ||
183 | ]) | ||
184 | ); | ||
185 | } | ||
186 | |||
187 | #[test] | ||
188 | fn test_format_document() { | ||
189 | if skip_slow_tests() { | ||
190 | return; | ||
191 | } | ||
192 | |||
193 | let server = project( | ||
194 | r#" | ||
195 | //- Cargo.toml | ||
196 | [package] | ||
197 | name = "foo" | ||
198 | version = "0.0.0" | ||
199 | |||
200 | //- src/lib.rs | ||
201 | mod bar; | ||
202 | |||
203 | fn main() { | ||
204 | } | ||
205 | |||
206 | pub use std::collections::HashMap; | ||
207 | "#, | ||
208 | ); | ||
209 | server.wait_until_workspace_is_loaded(); | ||
210 | |||
211 | server.request::<Formatting>( | ||
212 | DocumentFormattingParams { | ||
213 | text_document: server.doc_id("src/lib.rs"), | ||
214 | options: FormattingOptions { | ||
215 | tab_size: 4, | ||
216 | insert_spaces: false, | ||
217 | insert_final_newline: None, | ||
218 | trim_final_newlines: None, | ||
219 | trim_trailing_whitespace: None, | ||
220 | properties: HashMap::new(), | ||
221 | }, | ||
222 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
223 | }, | ||
224 | json!([ | ||
225 | { | ||
226 | "newText": r#"mod bar; | ||
227 | |||
228 | fn main() {} | ||
229 | |||
230 | pub use std::collections::HashMap; | ||
231 | "#, | ||
232 | "range": { | ||
233 | "end": { | ||
234 | "character": 0, | ||
235 | "line": 7 | ||
236 | }, | ||
237 | "start": { | ||
238 | "character": 0, | ||
239 | "line": 0 | ||
240 | } | ||
241 | } | ||
242 | } | ||
243 | ]), | ||
244 | ); | ||
245 | } | ||
246 | |||
247 | #[test] | ||
248 | fn test_format_document_2018() { | ||
249 | if skip_slow_tests() { | ||
250 | return; | ||
251 | } | ||
252 | |||
253 | let server = project( | ||
254 | r#" | ||
255 | //- Cargo.toml | ||
256 | [package] | ||
257 | name = "foo" | ||
258 | version = "0.0.0" | ||
259 | edition = "2018" | ||
260 | |||
261 | //- src/lib.rs | ||
262 | mod bar; | ||
263 | |||
264 | async fn test() { | ||
265 | } | ||
266 | |||
267 | fn main() { | ||
268 | } | ||
269 | |||
270 | pub use std::collections::HashMap; | ||
271 | "#, | ||
272 | ); | ||
273 | server.wait_until_workspace_is_loaded(); | ||
274 | |||
275 | server.request::<Formatting>( | ||
276 | DocumentFormattingParams { | ||
277 | text_document: server.doc_id("src/lib.rs"), | ||
278 | options: FormattingOptions { | ||
279 | tab_size: 4, | ||
280 | insert_spaces: false, | ||
281 | properties: HashMap::new(), | ||
282 | insert_final_newline: None, | ||
283 | trim_final_newlines: None, | ||
284 | trim_trailing_whitespace: None, | ||
285 | }, | ||
286 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
287 | }, | ||
288 | json!([ | ||
289 | { | ||
290 | "newText": r#"mod bar; | ||
291 | |||
292 | async fn test() {} | ||
293 | |||
294 | fn main() {} | ||
295 | |||
296 | pub use std::collections::HashMap; | ||
297 | "#, | ||
298 | "range": { | ||
299 | "end": { | ||
300 | "character": 0, | ||
301 | "line": 10 | ||
302 | }, | ||
303 | "start": { | ||
304 | "character": 0, | ||
305 | "line": 0 | ||
306 | } | ||
307 | } | ||
308 | } | ||
309 | ]), | ||
310 | ); | ||
311 | } | ||
312 | |||
313 | #[test] | ||
314 | fn test_missing_module_code_action() { | ||
315 | if skip_slow_tests() { | ||
316 | return; | ||
317 | } | ||
318 | |||
319 | let server = project( | ||
320 | r#" | ||
321 | //- Cargo.toml | ||
322 | [package] | ||
323 | name = "foo" | ||
324 | version = "0.0.0" | ||
325 | |||
326 | //- src/lib.rs | ||
327 | mod bar; | ||
328 | |||
329 | fn main() {} | ||
330 | "#, | ||
331 | ); | ||
332 | server.wait_until_workspace_is_loaded(); | ||
333 | let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None }; | ||
334 | server.request::<CodeActionRequest>( | ||
335 | CodeActionParams { | ||
336 | text_document: server.doc_id("src/lib.rs"), | ||
337 | range: Range::new(Position::new(0, 4), Position::new(0, 7)), | ||
338 | context: empty_context(), | ||
339 | partial_result_params: PartialResultParams::default(), | ||
340 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
341 | }, | ||
342 | json!([ | ||
343 | { | ||
344 | "command": { | ||
345 | "arguments": [ | ||
346 | { | ||
347 | "cursorPosition": null, | ||
348 | "label": "create module", | ||
349 | "workspaceEdit": { | ||
350 | "documentChanges": [ | ||
351 | { | ||
352 | "kind": "create", | ||
353 | "uri": "file:///[..]/src/bar.rs" | ||
354 | } | ||
355 | ] | ||
356 | } | ||
357 | } | ||
358 | ], | ||
359 | "command": "rust-analyzer.applySourceChange", | ||
360 | "title": "create module" | ||
361 | }, | ||
362 | "title": "create module" | ||
363 | } | ||
364 | ]), | ||
365 | ); | ||
366 | |||
367 | server.request::<CodeActionRequest>( | ||
368 | CodeActionParams { | ||
369 | text_document: server.doc_id("src/lib.rs"), | ||
370 | range: Range::new(Position::new(2, 4), Position::new(2, 7)), | ||
371 | context: empty_context(), | ||
372 | partial_result_params: PartialResultParams::default(), | ||
373 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
374 | }, | ||
375 | json!([]), | ||
376 | ); | ||
377 | } | ||
378 | |||
379 | #[test] | ||
380 | fn test_missing_module_code_action_in_json_project() { | ||
381 | if skip_slow_tests() { | ||
382 | return; | ||
383 | } | ||
384 | |||
385 | let tmp_dir = TempDir::new().unwrap(); | ||
386 | |||
387 | let path = tmp_dir.path(); | ||
388 | |||
389 | let project = json!({ | ||
390 | "roots": [path], | ||
391 | "crates": [ { | ||
392 | "root_module": path.join("src/lib.rs"), | ||
393 | "deps": [], | ||
394 | "edition": "2015", | ||
395 | "atom_cfgs": [], | ||
396 | "key_value_cfgs": {} | ||
397 | } ] | ||
398 | }); | ||
399 | |||
400 | let code = format!( | ||
401 | r#" | ||
402 | //- rust-project.json | ||
403 | {PROJECT} | ||
404 | |||
405 | //- src/lib.rs | ||
406 | mod bar; | ||
407 | |||
408 | fn main() {{}} | ||
409 | "#, | ||
410 | PROJECT = project.to_string(), | ||
411 | ); | ||
412 | |||
413 | let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server(); | ||
414 | |||
415 | server.wait_until_workspace_is_loaded(); | ||
416 | let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None }; | ||
417 | server.request::<CodeActionRequest>( | ||
418 | CodeActionParams { | ||
419 | text_document: server.doc_id("src/lib.rs"), | ||
420 | range: Range::new(Position::new(0, 4), Position::new(0, 7)), | ||
421 | context: empty_context(), | ||
422 | partial_result_params: PartialResultParams::default(), | ||
423 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
424 | }, | ||
425 | json!([ | ||
426 | { | ||
427 | "command": { | ||
428 | "arguments": [ | ||
429 | { | ||
430 | "cursorPosition": null, | ||
431 | "label": "create module", | ||
432 | "workspaceEdit": { | ||
433 | "documentChanges": [ | ||
434 | { | ||
435 | "kind": "create", | ||
436 | "uri": "file:///[..]/src/bar.rs" | ||
437 | } | ||
438 | ] | ||
439 | } | ||
440 | } | ||
441 | ], | ||
442 | "command": "rust-analyzer.applySourceChange", | ||
443 | "title": "create module" | ||
444 | }, | ||
445 | "title": "create module" | ||
446 | } | ||
447 | ]), | ||
448 | ); | ||
449 | |||
450 | server.request::<CodeActionRequest>( | ||
451 | CodeActionParams { | ||
452 | text_document: server.doc_id("src/lib.rs"), | ||
453 | range: Range::new(Position::new(2, 4), Position::new(2, 7)), | ||
454 | context: empty_context(), | ||
455 | partial_result_params: PartialResultParams::default(), | ||
456 | work_done_progress_params: WorkDoneProgressParams::default(), | ||
457 | }, | ||
458 | json!([]), | ||
459 | ); | ||
460 | } | ||
461 | |||
462 | #[test] | ||
463 | fn diagnostics_dont_block_typing() { | ||
464 | if skip_slow_tests() { | ||
465 | return; | ||
466 | } | ||
467 | |||
468 | let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect(); | ||
469 | let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect(); | ||
470 | let server = Project::with_fixture(&format!( | ||
471 | r#" | ||
472 | //- Cargo.toml | ||
473 | [package] | ||
474 | name = "foo" | ||
475 | version = "0.0.0" | ||
476 | |||
477 | //- src/lib.rs | ||
478 | {} | ||
479 | |||
480 | {} | ||
481 | |||
482 | fn main() {{}} | ||
483 | "#, | ||
484 | librs, libs | ||
485 | )) | ||
486 | .with_sysroot(true) | ||
487 | .server(); | ||
488 | |||
489 | server.wait_until_workspace_is_loaded(); | ||
490 | for i in 0..10 { | ||
491 | server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams { | ||
492 | text_document: TextDocumentItem { | ||
493 | uri: server.doc_id(&format!("src/m{}.rs", i)).uri, | ||
494 | language_id: "rust".to_string(), | ||
495 | version: 0, | ||
496 | text: "/// Docs\nfn foo() {}".to_string(), | ||
497 | }, | ||
498 | }); | ||
499 | } | ||
500 | let start = std::time::Instant::now(); | ||
501 | server.request::<OnEnter>( | ||
502 | TextDocumentPositionParams { | ||
503 | text_document: server.doc_id("src/m0.rs"), | ||
504 | position: Position { line: 0, character: 5 }, | ||
505 | }, | ||
506 | json!({ | ||
507 | "cursorPosition": { | ||
508 | "position": { "character": 4, "line": 1 }, | ||
509 | "textDocument": { "uri": "file:///[..]src/m0.rs" } | ||
510 | }, | ||
511 | "label": "on enter", | ||
512 | "workspaceEdit": { | ||
513 | "documentChanges": [ | ||
514 | { | ||
515 | "edits": [ | ||
516 | { | ||
517 | "newText": "\n/// ", | ||
518 | "range": { | ||
519 | "end": { "character": 5, "line": 0 }, | ||
520 | "start": { "character": 5, "line": 0 } | ||
521 | } | ||
522 | } | ||
523 | ], | ||
524 | "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null } | ||
525 | } | ||
526 | ] | ||
527 | } | ||
528 | }), | ||
529 | ); | ||
530 | let elapsed = start.elapsed(); | ||
531 | assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed); | ||
532 | } | ||
533 | |||
534 | #[test] | ||
535 | fn preserves_dos_line_endings() { | ||
536 | if skip_slow_tests() { | ||
537 | return; | ||
538 | } | ||
539 | |||
540 | let server = Project::with_fixture( | ||
541 | &" | ||
542 | //- Cargo.toml | ||
543 | [package] | ||
544 | name = \"foo\" | ||
545 | version = \"0.0.0\" | ||
546 | |||
547 | //- src/main.rs | ||
548 | /// Some Docs\r\nfn main() {} | ||
549 | ", | ||
550 | ) | ||
551 | .server(); | ||
552 | |||
553 | server.request::<OnEnter>( | ||
554 | TextDocumentPositionParams { | ||
555 | text_document: server.doc_id("src/main.rs"), | ||
556 | position: Position { line: 0, character: 8 }, | ||
557 | }, | ||
558 | json!({ | ||
559 | "cursorPosition": { | ||
560 | "position": { "line": 1, "character": 4 }, | ||
561 | "textDocument": { "uri": "file:///[..]src/main.rs" } | ||
562 | }, | ||
563 | "label": "on enter", | ||
564 | "workspaceEdit": { | ||
565 | "documentChanges": [ | ||
566 | { | ||
567 | "edits": [ | ||
568 | { | ||
569 | "newText": "\r\n/// ", | ||
570 | "range": { | ||
571 | "end": { "line": 0, "character": 8 }, | ||
572 | "start": { "line": 0, "character": 8 } | ||
573 | } | ||
574 | } | ||
575 | ], | ||
576 | "textDocument": { "uri": "file:///[..]src/main.rs", "version": null } | ||
577 | } | ||
578 | ] | ||
579 | } | ||
580 | }), | ||
581 | ); | ||
582 | } | ||
diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs deleted file mode 100644 index d5ea52fa9..000000000 --- a/crates/ra_lsp_server/tests/heavy_tests/support.rs +++ /dev/null | |||
@@ -1,254 +0,0 @@ | |||
1 | use std::{ | ||
2 | cell::{Cell, RefCell}, | ||
3 | fs, | ||
4 | path::{Path, PathBuf}, | ||
5 | sync::Once, | ||
6 | time::Duration, | ||
7 | }; | ||
8 | |||
9 | use crossbeam_channel::{after, select, Receiver}; | ||
10 | use lsp_server::{Connection, Message, Notification, Request}; | ||
11 | use lsp_types::{ | ||
12 | notification::{DidOpenTextDocument, Exit}, | ||
13 | request::Shutdown, | ||
14 | ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities, | ||
15 | TextDocumentIdentifier, TextDocumentItem, Url, | ||
16 | }; | ||
17 | use serde::Serialize; | ||
18 | use serde_json::{to_string_pretty, Value}; | ||
19 | use tempfile::TempDir; | ||
20 | use test_utils::{find_mismatch, parse_fixture}; | ||
21 | |||
22 | use ra_lsp_server::{main_loop, req, ServerConfig}; | ||
23 | |||
24 | pub struct Project<'a> { | ||
25 | fixture: &'a str, | ||
26 | with_sysroot: bool, | ||
27 | tmp_dir: Option<TempDir>, | ||
28 | roots: Vec<PathBuf>, | ||
29 | } | ||
30 | |||
31 | impl<'a> Project<'a> { | ||
32 | pub fn with_fixture(fixture: &str) -> Project { | ||
33 | Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false } | ||
34 | } | ||
35 | |||
36 | pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> { | ||
37 | self.tmp_dir = Some(tmp_dir); | ||
38 | self | ||
39 | } | ||
40 | |||
41 | pub fn root(mut self, path: &str) -> Project<'a> { | ||
42 | self.roots.push(path.into()); | ||
43 | self | ||
44 | } | ||
45 | |||
46 | pub fn with_sysroot(mut self, sysroot: bool) -> Project<'a> { | ||
47 | self.with_sysroot = sysroot; | ||
48 | self | ||
49 | } | ||
50 | |||
51 | pub fn server(self) -> Server { | ||
52 | let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap()); | ||
53 | static INIT: Once = Once::new(); | ||
54 | INIT.call_once(|| { | ||
55 | let _ = env_logger::builder().is_test(true).try_init().unwrap(); | ||
56 | ra_prof::set_filter(if crate::PROFILE.is_empty() { | ||
57 | ra_prof::Filter::disabled() | ||
58 | } else { | ||
59 | ra_prof::Filter::from_spec(&crate::PROFILE) | ||
60 | }); | ||
61 | }); | ||
62 | |||
63 | let mut paths = vec![]; | ||
64 | |||
65 | for entry in parse_fixture(self.fixture) { | ||
66 | let path = tmp_dir.path().join(entry.meta); | ||
67 | fs::create_dir_all(path.parent().unwrap()).unwrap(); | ||
68 | fs::write(path.as_path(), entry.text.as_bytes()).unwrap(); | ||
69 | paths.push((path, entry.text)); | ||
70 | } | ||
71 | |||
72 | let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect(); | ||
73 | |||
74 | Server::new(tmp_dir, self.with_sysroot, roots, paths) | ||
75 | } | ||
76 | } | ||
77 | |||
78 | pub fn project(fixture: &str) -> Server { | ||
79 | Project::with_fixture(fixture).server() | ||
80 | } | ||
81 | |||
82 | pub struct Server { | ||
83 | req_id: Cell<u64>, | ||
84 | messages: RefCell<Vec<Message>>, | ||
85 | dir: TempDir, | ||
86 | _thread: jod_thread::JoinHandle<()>, | ||
87 | client: Connection, | ||
88 | } | ||
89 | |||
90 | impl Server { | ||
91 | fn new( | ||
92 | dir: TempDir, | ||
93 | with_sysroot: bool, | ||
94 | roots: Vec<PathBuf>, | ||
95 | files: Vec<(PathBuf, String)>, | ||
96 | ) -> Server { | ||
97 | let path = dir.path().to_path_buf(); | ||
98 | |||
99 | let roots = if roots.is_empty() { vec![path] } else { roots }; | ||
100 | let (connection, client) = Connection::memory(); | ||
101 | |||
102 | let _thread = jod_thread::Builder::new() | ||
103 | .name("test server".to_string()) | ||
104 | .spawn(move || { | ||
105 | main_loop( | ||
106 | roots, | ||
107 | ClientCapabilities { | ||
108 | workspace: None, | ||
109 | text_document: Some(TextDocumentClientCapabilities { | ||
110 | definition: Some(GotoCapability { | ||
111 | dynamic_registration: None, | ||
112 | link_support: Some(true), | ||
113 | }), | ||
114 | ..Default::default() | ||
115 | }), | ||
116 | window: None, | ||
117 | experimental: None, | ||
118 | }, | ||
119 | ServerConfig { with_sysroot, ..ServerConfig::default() }, | ||
120 | connection, | ||
121 | ) | ||
122 | .unwrap() | ||
123 | }) | ||
124 | .expect("failed to spawn a thread"); | ||
125 | |||
126 | let res = | ||
127 | Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread }; | ||
128 | |||
129 | for (path, text) in files { | ||
130 | res.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams { | ||
131 | text_document: TextDocumentItem { | ||
132 | uri: Url::from_file_path(path).unwrap(), | ||
133 | language_id: "rust".to_string(), | ||
134 | version: 0, | ||
135 | text, | ||
136 | }, | ||
137 | }) | ||
138 | } | ||
139 | res | ||
140 | } | ||
141 | |||
142 | pub fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier { | ||
143 | let path = self.dir.path().join(rel_path); | ||
144 | TextDocumentIdentifier { uri: Url::from_file_path(path).unwrap() } | ||
145 | } | ||
146 | |||
147 | pub fn notification<N>(&self, params: N::Params) | ||
148 | where | ||
149 | N: lsp_types::notification::Notification, | ||
150 | N::Params: Serialize, | ||
151 | { | ||
152 | let r = Notification::new(N::METHOD.to_string(), params); | ||
153 | self.send_notification(r) | ||
154 | } | ||
155 | |||
156 | pub fn request<R>(&self, params: R::Params, expected_resp: Value) | ||
157 | where | ||
158 | R: lsp_types::request::Request, | ||
159 | R::Params: Serialize, | ||
160 | { | ||
161 | let actual = self.send_request::<R>(params); | ||
162 | if let Some((expected_part, actual_part)) = find_mismatch(&expected_resp, &actual) { | ||
163 | panic!( | ||
164 | "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n", | ||
165 | to_string_pretty(&expected_resp).unwrap(), | ||
166 | to_string_pretty(&actual).unwrap(), | ||
167 | to_string_pretty(expected_part).unwrap(), | ||
168 | to_string_pretty(actual_part).unwrap(), | ||
169 | ); | ||
170 | } | ||
171 | } | ||
172 | |||
173 | pub fn send_request<R>(&self, params: R::Params) -> Value | ||
174 | where | ||
175 | R: lsp_types::request::Request, | ||
176 | R::Params: Serialize, | ||
177 | { | ||
178 | let id = self.req_id.get(); | ||
179 | self.req_id.set(id + 1); | ||
180 | |||
181 | let r = Request::new(id.into(), R::METHOD.to_string(), params); | ||
182 | self.send_request_(r) | ||
183 | } | ||
184 | fn send_request_(&self, r: Request) -> Value { | ||
185 | let id = r.id.clone(); | ||
186 | self.client.sender.send(r.into()).unwrap(); | ||
187 | while let Some(msg) = self.recv() { | ||
188 | match msg { | ||
189 | Message::Request(req) => panic!("unexpected request: {:?}", req), | ||
190 | Message::Notification(_) => (), | ||
191 | Message::Response(res) => { | ||
192 | assert_eq!(res.id, id); | ||
193 | if let Some(err) = res.error { | ||
194 | panic!("error response: {:#?}", err); | ||
195 | } | ||
196 | return res.result.unwrap(); | ||
197 | } | ||
198 | } | ||
199 | } | ||
200 | panic!("no response"); | ||
201 | } | ||
202 | pub fn wait_until_workspace_is_loaded(&self) { | ||
203 | self.wait_for_message_cond(1, &|msg: &Message| match msg { | ||
204 | Message::Notification(n) if n.method == "window/showMessage" => { | ||
205 | let msg = | ||
206 | n.clone().extract::<req::ShowMessageParams>("window/showMessage").unwrap(); | ||
207 | msg.message.starts_with("workspace loaded") | ||
208 | } | ||
209 | _ => false, | ||
210 | }) | ||
211 | } | ||
212 | fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&Message) -> bool) { | ||
213 | let mut total = 0; | ||
214 | for msg in self.messages.borrow().iter() { | ||
215 | if cond(msg) { | ||
216 | total += 1 | ||
217 | } | ||
218 | } | ||
219 | while total < n { | ||
220 | let msg = self.recv().expect("no response"); | ||
221 | if cond(&msg) { | ||
222 | total += 1; | ||
223 | } | ||
224 | } | ||
225 | } | ||
226 | fn recv(&self) -> Option<Message> { | ||
227 | recv_timeout(&self.client.receiver).map(|msg| { | ||
228 | self.messages.borrow_mut().push(msg.clone()); | ||
229 | msg | ||
230 | }) | ||
231 | } | ||
232 | fn send_notification(&self, not: Notification) { | ||
233 | self.client.sender.send(Message::Notification(not)).unwrap(); | ||
234 | } | ||
235 | |||
236 | pub fn path(&self) -> &Path { | ||
237 | self.dir.path() | ||
238 | } | ||
239 | } | ||
240 | |||
241 | impl Drop for Server { | ||
242 | fn drop(&mut self) { | ||
243 | self.request::<Shutdown>((), Value::Null); | ||
244 | self.notification::<Exit>(()); | ||
245 | } | ||
246 | } | ||
247 | |||
248 | fn recv_timeout(receiver: &Receiver<Message>) -> Option<Message> { | ||
249 | let timeout = Duration::from_secs(120); | ||
250 | select! { | ||
251 | recv(receiver) -> msg => msg.ok(), | ||
252 | recv(after(timeout)) -> _ => panic!("timed out"), | ||
253 | } | ||
254 | } | ||