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