]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/tests/heavy_tests/main.rs
Merge #4207 #4253
[rust.git] / crates / rust-analyzer / tests / heavy_tests / main.rs
1 mod support;
2
3 use std::{collections::HashMap, path::PathBuf, time::Instant};
4
5 use lsp_types::{
6     CodeActionContext, DidOpenTextDocumentParams, DocumentFormattingParams, FormattingOptions,
7     GotoDefinitionParams, HoverParams, PartialResultParams, Position, Range, TextDocumentItem,
8     TextDocumentPositionParams, WorkDoneProgressParams,
9 };
10 use rust_analyzer::req::{
11     CodeActionParams, CodeActionRequest, Completion, CompletionParams, DidOpenTextDocument,
12     Formatting, GotoDefinition, HoverRequest, 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: &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" ],
79             "extraArgs": [ "foo", "--nocapture" ],
80             "bin": "cargo",
81             "env": { "RUST_BACKTRACE": "short" },
82             "cwd": null,
83             "label": "test foo",
84             "range": {
85               "end": { "character": 1, "line": 2 },
86               "start": { "character": 0, "line": 0 }
87             }
88           },
89           {
90             "args": ["check", "--workspace"],
91             "extraArgs": [],
92             "bin": "cargo",
93             "env": {},
94             "cwd": null,
95             "label": "cargo check --workspace",
96             "range": {
97               "end": { "character": 0, "line": 0 },
98               "start": { "character": 0, "line": 0 }
99             }
100           }
101         ]),
102     );
103 }
104
105 #[test]
106 fn test_runnables_project() {
107     if skip_slow_tests() {
108         return;
109     }
110
111     let code = r#"
112 //- foo/Cargo.toml
113 [package]
114 name = "foo"
115 version = "0.0.0"
116
117 //- foo/src/lib.rs
118 pub fn foo() {}
119
120 //- foo/tests/spam.rs
121 #[test]
122 fn test_eggs() {}
123
124 //- bar/Cargo.toml
125 [package]
126 name = "bar"
127 version = "0.0.0"
128
129 //- bar/src/main.rs
130 fn main() {}
131 "#;
132
133     let server = Project::with_fixture(code).root("foo").root("bar").server();
134
135     server.wait_until_workspace_is_loaded();
136     server.request::<Runnables>(
137         RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
138         json!([
139             {
140               "args": [ "test", "--package", "foo", "--test", "spam" ],
141               "extraArgs": [ "test_eggs", "--exact", "--nocapture" ],
142               "bin": "cargo",
143               "env": { "RUST_BACKTRACE": "short" },
144               "label": "test test_eggs",
145               "range": {
146                 "end": { "character": 17, "line": 1 },
147                 "start": { "character": 0, "line": 0 }
148               },
149               "cwd": server.path().join("foo")
150             },
151             {
152               "args": [ "check", "--package", "foo" ],
153               "extraArgs": [],
154               "bin": "cargo",
155               "env": {},
156               "label": "cargo check -p foo",
157               "range": {
158                 "end": { "character": 0, "line": 0 },
159                 "start": { "character": 0, "line": 0 }
160               },
161               "cwd": server.path().join("foo")
162             },
163             {
164               "args": [ "test", "--package", "foo" ],
165               "extraArgs": [],
166               "bin": "cargo",
167               "env": {},
168               "label": "cargo test -p foo",
169               "range": {
170                 "end": { "character": 0, "line": 0 },
171                 "start": { "character": 0, "line": 0 }
172               },
173               "cwd": server.path().join("foo")
174             }
175         ]),
176     );
177 }
178
179 #[test]
180 fn test_format_document() {
181     if skip_slow_tests() {
182         return;
183     }
184
185     let server = project(
186         r#"
187 //- Cargo.toml
188 [package]
189 name = "foo"
190 version = "0.0.0"
191
192 //- src/lib.rs
193 mod bar;
194
195 fn main() {
196 }
197
198 pub use std::collections::HashMap;
199 "#,
200     );
201     server.wait_until_workspace_is_loaded();
202
203     server.request::<Formatting>(
204         DocumentFormattingParams {
205             text_document: server.doc_id("src/lib.rs"),
206             options: FormattingOptions {
207                 tab_size: 4,
208                 insert_spaces: false,
209                 insert_final_newline: None,
210                 trim_final_newlines: None,
211                 trim_trailing_whitespace: None,
212                 properties: HashMap::new(),
213             },
214             work_done_progress_params: WorkDoneProgressParams::default(),
215         },
216         json!([
217             {
218                 "newText": r#"mod bar;
219
220 fn main() {}
221
222 pub use std::collections::HashMap;
223 "#,
224                 "range": {
225                     "end": {
226                         "character": 0,
227                         "line": 7
228                     },
229                     "start": {
230                         "character": 0,
231                         "line": 0
232                     }
233                 }
234             }
235         ]),
236     );
237 }
238
239 #[test]
240 fn test_format_document_2018() {
241     if skip_slow_tests() {
242         return;
243     }
244
245     let server = project(
246         r#"
247 //- Cargo.toml
248 [package]
249 name = "foo"
250 version = "0.0.0"
251 edition = "2018"
252
253 //- src/lib.rs
254 mod bar;
255
256 async fn test() {
257 }
258
259 fn main() {
260 }
261
262 pub use std::collections::HashMap;
263 "#,
264     );
265     server.wait_until_workspace_is_loaded();
266
267     server.request::<Formatting>(
268         DocumentFormattingParams {
269             text_document: server.doc_id("src/lib.rs"),
270             options: FormattingOptions {
271                 tab_size: 4,
272                 insert_spaces: false,
273                 properties: HashMap::new(),
274                 insert_final_newline: None,
275                 trim_final_newlines: None,
276                 trim_trailing_whitespace: None,
277             },
278             work_done_progress_params: WorkDoneProgressParams::default(),
279         },
280         json!([
281             {
282                 "newText": r#"mod bar;
283
284 async fn test() {}
285
286 fn main() {}
287
288 pub use std::collections::HashMap;
289 "#,
290                 "range": {
291                     "end": {
292                         "character": 0,
293                         "line": 10
294                     },
295                     "start": {
296                         "character": 0,
297                         "line": 0
298                     }
299                 }
300             }
301         ]),
302     );
303 }
304
305 #[test]
306 fn test_missing_module_code_action() {
307     if skip_slow_tests() {
308         return;
309     }
310
311     let server = project(
312         r#"
313 //- Cargo.toml
314 [package]
315 name = "foo"
316 version = "0.0.0"
317
318 //- src/lib.rs
319 mod bar;
320
321 fn main() {}
322 "#,
323     );
324     server.wait_until_workspace_is_loaded();
325     let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
326     server.request::<CodeActionRequest>(
327         CodeActionParams {
328             text_document: server.doc_id("src/lib.rs"),
329             range: Range::new(Position::new(0, 4), Position::new(0, 7)),
330             context: empty_context(),
331             partial_result_params: PartialResultParams::default(),
332             work_done_progress_params: WorkDoneProgressParams::default(),
333         },
334         json!([
335           {
336             "command": {
337               "arguments": [
338                 {
339                   "cursorPosition": null,
340                   "label": "create module",
341                   "workspaceEdit": {
342                     "documentChanges": [
343                       {
344                         "kind": "create",
345                         "uri": "file:///[..]/src/bar.rs"
346                       }
347                     ]
348                   }
349                 }
350               ],
351               "command": "rust-analyzer.applySourceChange",
352               "title": "create module"
353             },
354             "title": "create module"
355           }
356         ]),
357     );
358
359     server.request::<CodeActionRequest>(
360         CodeActionParams {
361             text_document: server.doc_id("src/lib.rs"),
362             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
363             context: empty_context(),
364             partial_result_params: PartialResultParams::default(),
365             work_done_progress_params: WorkDoneProgressParams::default(),
366         },
367         json!([]),
368     );
369 }
370
371 #[test]
372 fn test_missing_module_code_action_in_json_project() {
373     if skip_slow_tests() {
374         return;
375     }
376
377     let tmp_dir = TempDir::new().unwrap();
378
379     let path = tmp_dir.path();
380
381     let project = json!({
382         "roots": [path],
383         "crates": [ {
384             "root_module": path.join("src/lib.rs"),
385             "deps": [],
386             "edition": "2015",
387             "atom_cfgs": [],
388             "key_value_cfgs": {}
389         } ]
390     });
391
392     let code = format!(
393         r#"
394 //- rust-project.json
395 {PROJECT}
396
397 //- src/lib.rs
398 mod bar;
399
400 fn main() {{}}
401 "#,
402         PROJECT = project.to_string(),
403     );
404
405     let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();
406
407     server.wait_until_workspace_is_loaded();
408     let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
409     server.request::<CodeActionRequest>(
410         CodeActionParams {
411             text_document: server.doc_id("src/lib.rs"),
412             range: Range::new(Position::new(0, 4), Position::new(0, 7)),
413             context: empty_context(),
414             partial_result_params: PartialResultParams::default(),
415             work_done_progress_params: WorkDoneProgressParams::default(),
416         },
417         json!([
418           {
419             "command": {
420               "arguments": [
421                 {
422                   "cursorPosition": null,
423                   "label": "create module",
424                   "workspaceEdit": {
425                     "documentChanges": [
426                       {
427                         "kind": "create",
428                         "uri": "file:///[..]/src/bar.rs"
429                       }
430                     ]
431                   }
432                 }
433               ],
434               "command": "rust-analyzer.applySourceChange",
435               "title": "create module"
436             },
437             "title": "create module"
438           }
439         ]),
440     );
441
442     server.request::<CodeActionRequest>(
443         CodeActionParams {
444             text_document: server.doc_id("src/lib.rs"),
445             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
446             context: empty_context(),
447             partial_result_params: PartialResultParams::default(),
448             work_done_progress_params: WorkDoneProgressParams::default(),
449         },
450         json!([]),
451     );
452 }
453
454 #[test]
455 fn diagnostics_dont_block_typing() {
456     if skip_slow_tests() {
457         return;
458     }
459
460     let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
461     let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
462     let server = Project::with_fixture(&format!(
463         r#"
464 //- Cargo.toml
465 [package]
466 name = "foo"
467 version = "0.0.0"
468
469 //- src/lib.rs
470 {}
471
472 {}
473
474 fn main() {{}}
475 "#,
476         librs, libs
477     ))
478     .with_sysroot(true)
479     .server();
480
481     server.wait_until_workspace_is_loaded();
482     for i in 0..10 {
483         server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
484             text_document: TextDocumentItem {
485                 uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
486                 language_id: "rust".to_string(),
487                 version: 0,
488                 text: "/// Docs\nfn foo() {}".to_string(),
489             },
490         });
491     }
492     let start = std::time::Instant::now();
493     server.request::<OnEnter>(
494         TextDocumentPositionParams {
495             text_document: server.doc_id("src/m0.rs"),
496             position: Position { line: 0, character: 5 },
497         },
498         json!({
499           "cursorPosition": {
500             "position": { "character": 4, "line": 1 },
501             "textDocument": { "uri": "file:///[..]src/m0.rs" }
502           },
503           "label": "on enter",
504           "workspaceEdit": {
505             "documentChanges": [
506               {
507                 "edits": [
508                   {
509                     "newText": "\n/// ",
510                     "range": {
511                       "end": { "character": 5, "line": 0 },
512                       "start": { "character": 5, "line": 0 }
513                     }
514                   }
515                 ],
516                 "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
517               }
518             ]
519           }
520         }),
521     );
522     let elapsed = start.elapsed();
523     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
524 }
525
526 #[test]
527 fn preserves_dos_line_endings() {
528     if skip_slow_tests() {
529         return;
530     }
531
532     let server = Project::with_fixture(
533         &"
534 //- Cargo.toml
535 [package]
536 name = \"foo\"
537 version = \"0.0.0\"
538
539 //- src/main.rs
540 /// Some Docs\r\nfn main() {}
541 ",
542     )
543     .server();
544
545     server.request::<OnEnter>(
546         TextDocumentPositionParams {
547             text_document: server.doc_id("src/main.rs"),
548             position: Position { line: 0, character: 8 },
549         },
550         json!({
551           "cursorPosition": {
552             "position": { "line": 1, "character": 4 },
553             "textDocument": { "uri": "file:///[..]src/main.rs" }
554           },
555           "label": "on enter",
556           "workspaceEdit": {
557             "documentChanges": [
558               {
559                 "edits": [
560                   {
561                     "newText": "\r\n/// ",
562                     "range": {
563                       "end": { "line": 0, "character": 8 },
564                       "start": { "line": 0, "character": 8 }
565                     }
566                   }
567                 ],
568                 "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
569               }
570             ]
571           }
572         }),
573     );
574 }
575
576 #[test]
577 fn resolve_include_concat_env() {
578     if skip_slow_tests() {
579         return;
580     }
581
582     let server = Project::with_fixture(
583         r###"
584 //- Cargo.toml
585 [package]
586 name = "foo"
587 version = "0.0.0"
588
589 //- build.rs
590 use std::{env, fs, path::Path};
591
592 fn main() {
593     let out_dir = env::var_os("OUT_DIR").unwrap();
594     let dest_path = Path::new(&out_dir).join("hello.rs");
595     fs::write(
596         &dest_path,
597         r#"pub fn message() -> &'static str { "Hello, World!" }"#,
598     )
599     .unwrap();
600     println!("cargo:rerun-if-changed=build.rs");
601 }
602 //- src/main.rs
603 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
604
605 fn main() { message(); }
606 "###,
607     )
608     .with_config(|config| {
609         config.cargo.load_out_dirs_from_check = true;
610     })
611     .server();
612     server.wait_until_workspace_is_loaded();
613     let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
614         text_document_position_params: TextDocumentPositionParams::new(
615             server.doc_id("src/main.rs"),
616             Position::new(2, 15),
617         ),
618         work_done_progress_params: Default::default(),
619         partial_result_params: Default::default(),
620     });
621     assert!(format!("{}", res).contains("hello.rs"));
622 }
623
624 #[test]
625 fn resolve_proc_macro() {
626     if skip_slow_tests() {
627         return;
628     }
629     let server = Project::with_fixture(
630         r###"
631 //- foo/Cargo.toml
632 [package]
633 name = "foo"
634 version = "0.0.0"
635 edition = "2018"
636 [dependencies]
637 bar = {path = "../bar"}
638
639 //- foo/src/main.rs
640 use bar::Bar;
641 trait Bar {
642   fn bar();
643 }
644 #[derive(Bar)]
645 struct Foo {}
646 fn main() {
647   Foo::bar();
648 }
649
650 //- bar/Cargo.toml
651 [package]
652 name = "bar"
653 version = "0.0.0"
654 edition = "2018"
655
656 [lib]
657 proc-macro = true
658
659 //- bar/src/lib.rs
660 extern crate proc_macro;
661 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
662 macro_rules! t {
663     ($n:literal) => {
664         TokenTree::from(Ident::new($n, Span::call_site()))
665     };
666     ({}) => {
667         TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
668     };
669     (()) => {
670         TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
671     };
672 }
673 #[proc_macro_derive(Bar)]
674 pub fn foo(_input: TokenStream) -> TokenStream {
675     // We hard code the output here for preventing to use any deps
676     let mut res = TokenStream::new();
677
678     // impl Bar for Foo { fn bar() {} }
679     let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
680     let mut fn_stream = TokenStream::new();
681     fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
682     tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
683     res.extend(tokens);
684     res
685 }
686
687 "###,
688     )
689     .with_config(|config| {
690         let macro_srv_path = PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer"));
691
692         config.cargo.load_out_dirs_from_check = true;
693         config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".into()]));
694     })
695     .root("foo")
696     .root("bar")
697     .server();
698     server.wait_until_workspace_is_loaded();
699     let res = server.send_request::<HoverRequest>(HoverParams {
700         text_document_position_params: TextDocumentPositionParams::new(
701             server.doc_id("foo/src/main.rs"),
702             Position::new(7, 9),
703         ),
704         work_done_progress_params: Default::default(),
705     });
706
707     let value = res.get("contents").unwrap().get("value").unwrap().to_string();
708     assert_eq!(value, r#""```rust\nfoo::Bar\nfn bar()\n```""#)
709 }