]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/tests/heavy_tests/main.rs
Fix resolve_proc_macro heavy test
[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             "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           "documentChanges": [
478             {
479               "edits": [
480                 {
481                   "insertTextFormat": 2,
482                   "newText": "\n/// $0",
483                   "range": {
484                     "end": { "character": 5, "line": 0 },
485                     "start": { "character": 5, "line": 0 }
486                   }
487                 }
488               ],
489               "textDocument": { "uri": "file:///[..]src/m0.rs", "version": null }
490             }
491           ]
492         }),
493     );
494     let elapsed = start.elapsed();
495     assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
496 }
497
498 #[test]
499 fn preserves_dos_line_endings() {
500     if skip_slow_tests() {
501         return;
502     }
503
504     let server = Project::with_fixture(
505         &"
506 //- Cargo.toml
507 [package]
508 name = \"foo\"
509 version = \"0.0.0\"
510
511 //- src/main.rs
512 /// Some Docs\r\nfn main() {}
513 ",
514     )
515     .server();
516
517     server.request::<OnEnter>(
518         TextDocumentPositionParams {
519             text_document: server.doc_id("src/main.rs"),
520             position: Position { line: 0, character: 8 },
521         },
522         json!({
523           "documentChanges": [
524             {
525               "edits": [
526                 {
527                   "insertTextFormat": 2,
528                   "newText": "\r\n/// $0",
529                   "range": {
530                     "end": { "line": 0, "character": 8 },
531                     "start": { "line": 0, "character": 8 }
532                   }
533                 }
534               ],
535               "textDocument": { "uri": "file:///[..]src/main.rs", "version": null }
536             }
537           ]
538         }),
539     );
540 }
541
542 #[test]
543 fn out_dirs_check() {
544     if skip_slow_tests() {
545         return;
546     }
547
548     let server = Project::with_fixture(
549         r###"
550 //- Cargo.toml
551 [package]
552 name = "foo"
553 version = "0.0.0"
554
555 //- build.rs
556 use std::{env, fs, path::Path};
557
558 fn main() {
559     let out_dir = env::var_os("OUT_DIR").unwrap();
560     let dest_path = Path::new(&out_dir).join("hello.rs");
561     fs::write(
562         &dest_path,
563         r#"pub fn message() -> &'static str { "Hello, World!" }"#,
564     )
565     .unwrap();
566     println!("cargo:rustc-cfg=atom_cfg");
567     println!("cargo:rustc-cfg=featlike=\"set\"");
568     println!("cargo:rerun-if-changed=build.rs");
569 }
570 //- src/main.rs
571 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
572
573 #[cfg(atom_cfg)]
574 struct A;
575 #[cfg(bad_atom_cfg)]
576 struct A;
577 #[cfg(featlike = "set")]
578 struct B;
579 #[cfg(featlike = "not_set")]
580 struct B;
581
582 fn main() {
583     let va = A;
584     let vb = B;
585     message();
586 }
587
588 fn main() { message(); }
589 "###,
590     )
591     .with_config(|config| {
592         config.cargo.load_out_dirs_from_check = true;
593     })
594     .server();
595     server.wait_until_workspace_is_loaded();
596     let res = server.send_request::<GotoDefinition>(GotoDefinitionParams {
597         text_document_position_params: TextDocumentPositionParams::new(
598             server.doc_id("src/main.rs"),
599             Position::new(14, 8),
600         ),
601         work_done_progress_params: Default::default(),
602         partial_result_params: Default::default(),
603     });
604     assert!(format!("{}", res).contains("hello.rs"));
605     server.request::<GotoTypeDefinition>(
606         GotoDefinitionParams {
607             text_document_position_params: TextDocumentPositionParams::new(
608                 server.doc_id("src/main.rs"),
609                 Position::new(12, 9),
610             ),
611             work_done_progress_params: Default::default(),
612             partial_result_params: Default::default(),
613         },
614         json!([{
615             "originSelectionRange": {
616                 "end": {
617                     "character": 10,
618                     "line": 12
619                 },
620                 "start": {
621                     "character": 8,
622                     "line": 12
623                 }
624             },
625             "targetRange": {
626                 "end": {
627                     "character": 9,
628                     "line": 3
629                 },
630                 "start": {
631                     "character": 0,
632                     "line": 2
633                 }
634             },
635             "targetSelectionRange": {
636                 "end": {
637                     "character": 8,
638                     "line": 3
639                 },
640                 "start": {
641                     "character": 7,
642                     "line": 3
643                 }
644             },
645             "targetUri": "file:///[..]src/main.rs"
646         }]),
647     );
648     server.request::<GotoTypeDefinition>(
649         GotoDefinitionParams {
650             text_document_position_params: TextDocumentPositionParams::new(
651                 server.doc_id("src/main.rs"),
652                 Position::new(13, 9),
653             ),
654             work_done_progress_params: Default::default(),
655             partial_result_params: Default::default(),
656         },
657         json!([{
658             "originSelectionRange": {
659                 "end": {
660                     "character": 10,
661                     "line": 13
662                 },
663                 "start": {
664                     "character": 8,
665                     "line":13
666                 }
667             },
668             "targetRange": {
669                 "end": {
670                     "character": 9,
671                     "line": 7
672                 },
673                 "start": {
674                     "character": 0,
675                     "line":6
676                 }
677             },
678             "targetSelectionRange": {
679                 "end": {
680                     "character": 8,
681                     "line": 7
682                 },
683                 "start": {
684                     "character": 7,
685                     "line": 7
686                 }
687             },
688             "targetUri": "file:///[..]src/main.rs"
689         }]),
690     );
691 }
692
693 #[test]
694 fn resolve_proc_macro() {
695     if skip_slow_tests() {
696         return;
697     }
698     let server = Project::with_fixture(
699         r###"
700 //- foo/Cargo.toml
701 [package]
702 name = "foo"
703 version = "0.0.0"
704 edition = "2018"
705 [dependencies]
706 bar = {path = "../bar"}
707
708 //- foo/src/main.rs
709 use bar::Bar;
710 trait Bar {
711   fn bar();
712 }
713 #[derive(Bar)]
714 struct Foo {}
715 fn main() {
716   Foo::bar();
717 }
718
719 //- bar/Cargo.toml
720 [package]
721 name = "bar"
722 version = "0.0.0"
723 edition = "2018"
724
725 [lib]
726 proc-macro = true
727
728 //- bar/src/lib.rs
729 extern crate proc_macro;
730 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
731 macro_rules! t {
732     ($n:literal) => {
733         TokenTree::from(Ident::new($n, Span::call_site()))
734     };
735     ({}) => {
736         TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
737     };
738     (()) => {
739         TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
740     };
741 }
742 #[proc_macro_derive(Bar)]
743 pub fn foo(_input: TokenStream) -> TokenStream {
744     // We hard code the output here for preventing to use any deps
745     let mut res = TokenStream::new();
746
747     // impl Bar for Foo { fn bar() {} }
748     let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
749     let mut fn_stream = TokenStream::new();
750     fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
751     tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
752     res.extend(tokens);
753     res
754 }
755
756 "###,
757     )
758     .with_config(|config| {
759         let macro_srv_path = PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer"));
760
761         config.cargo.load_out_dirs_from_check = true;
762         config.proc_macro_srv = Some((macro_srv_path, vec!["proc-macro".into()]));
763     })
764     .root("foo")
765     .root("bar")
766     .server();
767     server.wait_until_workspace_is_loaded();
768     let res = server.send_request::<HoverRequest>(HoverParams {
769         text_document_position_params: TextDocumentPositionParams::new(
770             server.doc_id("foo/src/main.rs"),
771             Position::new(7, 9),
772         ),
773         work_done_progress_params: Default::default(),
774     });
775
776     let value = res.get("contents").unwrap().get("value").unwrap().to_string();
777     assert_eq!(value, r#""foo::Bar\n___\n\n```rust\nfn bar()\n```""#)
778 }