]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/tests/heavy_tests/main.rs
Merge #4580
[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             "kind": "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             "kind": "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               "kind": "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               "kind": "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               "kind": "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             "edit": {
338               "documentChanges": [
339                 {
340                   "kind": "create",
341                   "uri": "file:///[..]/src/bar.rs"
342                 }
343               ]
344             },
345             "title": "Create module"
346         }]),
347     );
348
349     server.request::<CodeActionRequest>(
350         CodeActionParams {
351             text_document: server.doc_id("src/lib.rs"),
352             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
353             context: empty_context(),
354             partial_result_params: PartialResultParams::default(),
355             work_done_progress_params: WorkDoneProgressParams::default(),
356         },
357         json!([]),
358     );
359 }
360
361 #[test]
362 fn test_missing_module_code_action_in_json_project() {
363     if skip_slow_tests() {
364         return;
365     }
366
367     let tmp_dir = TempDir::new().unwrap();
368
369     let path = tmp_dir.path();
370
371     let project = json!({
372         "roots": [path],
373         "crates": [ {
374             "root_module": path.join("src/lib.rs"),
375             "deps": [],
376             "edition": "2015",
377             "atom_cfgs": [],
378             "key_value_cfgs": {}
379         } ]
380     });
381
382     let code = format!(
383         r#"
384 //- rust-project.json
385 {PROJECT}
386
387 //- src/lib.rs
388 mod bar;
389
390 fn main() {{}}
391 "#,
392         PROJECT = project.to_string(),
393     );
394
395     let server = Project::with_fixture(&code).tmp_dir(tmp_dir).server();
396
397     server.wait_until_workspace_is_loaded();
398     let empty_context = || CodeActionContext { diagnostics: Vec::new(), only: None };
399     server.request::<CodeActionRequest>(
400         CodeActionParams {
401             text_document: server.doc_id("src/lib.rs"),
402             range: Range::new(Position::new(0, 4), Position::new(0, 7)),
403             context: empty_context(),
404             partial_result_params: PartialResultParams::default(),
405             work_done_progress_params: WorkDoneProgressParams::default(),
406         },
407         json!([{
408             "edit": {
409               "documentChanges": [
410                 {
411                   "kind": "create",
412                   "uri": "file://[..]/src/bar.rs"
413                 }
414               ]
415             },
416             "title": "Create module"
417         }]),
418     );
419
420     server.request::<CodeActionRequest>(
421         CodeActionParams {
422             text_document: server.doc_id("src/lib.rs"),
423             range: Range::new(Position::new(2, 4), Position::new(2, 7)),
424             context: empty_context(),
425             partial_result_params: PartialResultParams::default(),
426             work_done_progress_params: WorkDoneProgressParams::default(),
427         },
428         json!([]),
429     );
430 }
431
432 #[test]
433 fn diagnostics_dont_block_typing() {
434     if skip_slow_tests() {
435         return;
436     }
437
438     let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
439     let libs: String = (0..10).map(|i| format!("//- src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
440     let server = Project::with_fixture(&format!(
441         r#"
442 //- Cargo.toml
443 [package]
444 name = "foo"
445 version = "0.0.0"
446
447 //- src/lib.rs
448 {}
449
450 {}
451
452 fn main() {{}}
453 "#,
454         librs, libs
455     ))
456     .with_sysroot(true)
457     .server();
458
459     server.wait_until_workspace_is_loaded();
460     for i in 0..10 {
461         server.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
462             text_document: TextDocumentItem {
463                 uri: server.doc_id(&format!("src/m{}.rs", i)).uri,
464                 language_id: "rust".to_string(),
465                 version: 0,
466                 text: "/// Docs\nfn foo() {}".to_string(),
467             },
468         });
469     }
470     let start = std::time::Instant::now();
471     server.request::<OnEnter>(
472         TextDocumentPositionParams {
473             text_document: server.doc_id("src/m0.rs"),
474             position: Position { line: 0, character: 5 },
475         },
476         json!([{
477             "insertTextFormat": 2,
478             "newText": "\n/// $0",
479             "range": {
480             "end": { "character": 5, "line": 0 },
481             "start": { "character": 5, "line": 0 }
482             }
483         }]),
484     );
485     let elapsed = start.elapsed();
486     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
487 }
488
489 #[test]
490 fn preserves_dos_line_endings() {
491     if skip_slow_tests() {
492         return;
493     }
494
495     let server = Project::with_fixture(
496         &"
497 //- Cargo.toml
498 [package]
499 name = \"foo\"
500 version = \"0.0.0\"
501
502 //- src/main.rs
503 /// Some Docs\r\nfn main() {}
504 ",
505     )
506     .server();
507
508     server.request::<OnEnter>(
509         TextDocumentPositionParams {
510             text_document: server.doc_id("src/main.rs"),
511             position: Position { line: 0, character: 8 },
512         },
513         json!([{
514             "insertTextFormat": 2,
515             "newText": "\r\n/// $0",
516             "range": {
517             "end": { "line": 0, "character": 8 },
518             "start": { "line": 0, "character": 8 }
519             }
520         }]),
521     );
522 }
523
524 #[test]
525 fn out_dirs_check() {
526     if skip_slow_tests() {
527         return;
528     }
529
530     let server = Project::with_fixture(
531         r###"
532 //- Cargo.toml
533 [package]
534 name = "foo"
535 version = "0.0.0"
536
537 //- build.rs
538 use std::{env, fs, path::Path};
539
540 fn main() {
541     let out_dir = env::var_os("OUT_DIR").unwrap();
542     let dest_path = Path::new(&out_dir).join("hello.rs");
543     fs::write(
544         &dest_path,
545         r#"pub fn message() -> &'static str { "Hello, World!" }"#,
546     )
547     .unwrap();
548     println!("cargo:rustc-cfg=atom_cfg");
549     println!("cargo:rustc-cfg=featlike=\"set\"");
550     println!("cargo:rerun-if-changed=build.rs");
551 }
552 //- src/main.rs
553 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
554
555 #[cfg(atom_cfg)]
556 struct A;
557 #[cfg(bad_atom_cfg)]
558 struct A;
559 #[cfg(featlike = "set")]
560 struct B;
561 #[cfg(featlike = "not_set")]
562 struct B;
563
564 fn main() {
565     let va = A;
566     let vb = B;
567     message();
568 }
569
570 fn main() { message(); }
571 "###,
572     )
573     .with_config(|config| {
574         config.cargo.load_out_dirs_from_check = true;
575     })
576     .server();
577     server.wait_until_workspace_is_loaded();
578     let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
579         text_document_position_params: TextDocumentPositionParams::new(
580             server.doc_id("src/main.rs"),
581             Position::new(14, 8),
582         ),
583         work_done_progress_params: Default::default(),
584         partial_result_params: Default::default(),
585     });
586     assert!(format!("{}", res).contains("hello.rs"));
587     server.request::<GotoTypeDefinition>(
588         GotoDefinitionParams {
589             text_document_position_params: TextDocumentPositionParams::new(
590                 server.doc_id("src/main.rs"),
591                 Position::new(12, 9),
592             ),
593             work_done_progress_params: Default::default(),
594             partial_result_params: Default::default(),
595         },
596         json!([{
597             "originSelectionRange": {
598                 "end": {
599                     "character": 10,
600                     "line": 12
601                 },
602                 "start": {
603                     "character": 8,
604                     "line": 12
605                 }
606             },
607             "targetRange": {
608                 "end": {
609                     "character": 9,
610                     "line": 3
611                 },
612                 "start": {
613                     "character": 0,
614                     "line": 2
615                 }
616             },
617             "targetSelectionRange": {
618                 "end": {
619                     "character": 8,
620                     "line": 3
621                 },
622                 "start": {
623                     "character": 7,
624                     "line": 3
625                 }
626             },
627             "targetUri": "file:///[..]src/main.rs"
628         }]),
629     );
630     server.request::<GotoTypeDefinition>(
631         GotoDefinitionParams {
632             text_document_position_params: TextDocumentPositionParams::new(
633                 server.doc_id("src/main.rs"),
634                 Position::new(13, 9),
635             ),
636             work_done_progress_params: Default::default(),
637             partial_result_params: Default::default(),
638         },
639         json!([{
640             "originSelectionRange": {
641                 "end": {
642                     "character": 10,
643                     "line": 13
644                 },
645                 "start": {
646                     "character": 8,
647                     "line":13
648                 }
649             },
650             "targetRange": {
651                 "end": {
652                     "character": 9,
653                     "line": 7
654                 },
655                 "start": {
656                     "character": 0,
657                     "line":6
658                 }
659             },
660             "targetSelectionRange": {
661                 "end": {
662                     "character": 8,
663                     "line": 7
664                 },
665                 "start": {
666                     "character": 7,
667                     "line": 7
668                 }
669             },
670             "targetUri": "file:///[..]src/main.rs"
671         }]),
672     );
673 }
674
675 #[test]
676 fn resolve_proc_macro() {
677     if skip_slow_tests() {
678         return;
679     }
680     let server = Project::with_fixture(
681         r###"
682 //- foo/Cargo.toml
683 [package]
684 name = "foo"
685 version = "0.0.0"
686 edition = "2018"
687 [dependencies]
688 bar = {path = "../bar"}
689
690 //- foo/src/main.rs
691 use bar::Bar;
692 trait Bar {
693   fn bar();
694 }
695 #[derive(Bar)]
696 struct Foo {}
697 fn main() {
698   Foo::bar();
699 }
700
701 //- bar/Cargo.toml
702 [package]
703 name = "bar"
704 version = "0.0.0"
705 edition = "2018"
706
707 [lib]
708 proc-macro = true
709
710 //- bar/src/lib.rs
711 extern crate proc_macro;
712 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
713 macro_rules! t {
714     ($n:literal) => {
715         TokenTree::from(Ident::new($n, Span::call_site()))
716     };
717     ({}) => {
718         TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
719     };
720     (()) => {
721         TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
722     };
723 }
724 #[proc_macro_derive(Bar)]
725 pub fn foo(_input: TokenStream) -> TokenStream {
726     // We hard code the output here for preventing to use any deps
727     let mut res = TokenStream::new();
728
729     // impl Bar for Foo { fn bar() {} }
730     let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
731     let mut fn_stream = TokenStream::new();
732     fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
733     tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
734     res.extend(tokens);
735     res
736 }
737
738 "###,
739     )
740     .with_config(|config| {
741         let macro_srv_path = PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer"));
742
743         config.cargo.load_out_dirs_from_check = true;
744         config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".into()]));
745     })
746     .root("foo")
747     .root("bar")
748     .server();
749     server.wait_until_workspace_is_loaded();
750     let res = server.send_request::<HoverRequest>(HoverParams {
751         text_document_position_params: TextDocumentPositionParams::new(
752             server.doc_id("foo/src/main.rs"),
753             Position::new(7, 9),
754         ),
755         work_done_progress_params: Default::default(),
756     });
757
758     let value = res.get("contents").unwrap().get("value").unwrap().to_string();
759     assert_eq!(value, r#""```rust\nfoo::Bar\n```\n\n```rust\nfn bar()\n```""#)
760 }