aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server/tests
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_lsp_server/tests')
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/main.rs582
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/support.rs254
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 @@
1mod support;
2
3use std::{collections::HashMap, time::Instant};
4
5use lsp_types::{
6 CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions,
7 PartialResultParams, Position, Range, TextDocumentItem, TextDocumentPositionParams,
8 WorkDoneProgressParams,
9};
10use ra_lsp_server::req::{
11 CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12 Formatting, OnEnter, Runnables, RunnablesParams,
13};
14use serde_json::json;
15use tempfile::TempDir;
16use test_utils::skip_slow_tests;
17
18use crate::support::{project, Project};
19
20const PROFILE: &'static str = "";
21// const PROFILE: &'static str = "*@3>100";
22
23#[test]
24fn 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]
34name = "foo"
35version = "0.0.0"
36
37//- src/lib.rs
38use 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]
60fn test_runnables_no_project() {
61 if skip_slow_tests() {
62 return;
63 }
64
65 let server = project(
66 r"
67//- lib.rs
68#[test]
69fn 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]
113fn test_runnables_project() {
114 if skip_slow_tests() {
115 return;
116 }
117
118 let code = r#"
119//- foo/Cargo.toml
120[package]
121name = "foo"
122version = "0.0.0"
123
124//- foo/src/lib.rs
125pub fn foo() {}
126
127//- foo/tests/spam.rs
128#[test]
129fn test_eggs() {}
130
131//- bar/Cargo.toml
132[package]
133name = "bar"
134version = "0.0.0"
135
136//- bar/src/main.rs
137fn 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]
188fn test_format_document() {
189 if skip_slow_tests() {
190 return;
191 }
192
193 let server = project(
194 r#"
195//- Cargo.toml
196[package]
197name = "foo"
198version = "0.0.0"
199
200//- src/lib.rs
201mod bar;
202
203fn main() {
204}
205
206pub 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
228fn main() {}
229
230pub 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]
248fn test_format_document_2018() {
249 if skip_slow_tests() {
250 return;
251 }
252
253 let server = project(
254 r#"
255//- Cargo.toml
256[package]
257name = "foo"
258version = "0.0.0"
259edition = "2018"
260
261//- src/lib.rs
262mod bar;
263
264async fn test() {
265}
266
267fn main() {
268}
269
270pub 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
292async fn test() {}
293
294fn main() {}
295
296pub 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]
314fn 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]
323name = "foo"
324version = "0.0.0"
325
326//- src/lib.rs
327mod bar;
328
329fn 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]
380fn 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
406mod bar;
407
408fn 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]
463fn 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]
474name = "foo"
475version = "0.0.0"
476
477//- src/lib.rs
478{}
479
480{}
481
482fn 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]
535fn 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]
544name = \"foo\"
545version = \"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 @@
1use std::{
2 cell::{Cell, RefCell},
3 fs,
4 path::{Path, PathBuf},
5 sync::Once,
6 time::Duration,
7};
8
9use crossbeam_channel::{after, select, Receiver};
10use lsp_server::{Connection, Message, Notification, Request};
11use lsp_types::{
12 notification::{DidOpenTextDocument, Exit},
13 request::Shutdown,
14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
15 TextDocumentIdentifier, TextDocumentItem, Url,
16};
17use serde::Serialize;
18use serde_json::{to_string_pretty, Value};
19use tempfile::TempDir;
20use test_utils::{find_mismatch, parse_fixture};
21
22use ra_lsp_server::{main_loop, req, ServerConfig};
23
24pub struct Project<'a> {
25 fixture: &'a str,
26 with_sysroot: bool,
27 tmp_dir: Option<TempDir>,
28 roots: Vec<PathBuf>,
29}
30
31impl<'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
78pub fn project(fixture: &str) -> Server {
79 Project::with_fixture(fixture).server()
80}
81
82pub 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
90impl 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
241impl Drop for Server {
242 fn drop(&mut self) {
243 self.request::<Shutdown>((), Value::Null);
244 self.notification::<Exit>(());
245 }
246}
247
248fn 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}