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