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