1 //! The most high-level integrated tests for rust-analyzer.
3 //! This tests run a full LSP event loop, spawn cargo and process stdlib from
4 //! sysroot. For this reason, the tests here are very slow, and should be
5 //! avoided unless absolutely necessary.
7 //! In particular, it's fine *not* to test that client & server agree on
8 //! specific JSON shapes here -- there's little value in such tests, as we can't
9 //! be sure without a real client anyway.
14 use std::{collections::HashMap, path::PathBuf, time::Instant};
16 use expect_test::expect;
18 notification::DidOpenTextDocument,
20 CodeActionRequest, Completion, Formatting, GotoTypeDefinition, HoverRequest,
23 CodeActionContext, CodeActionParams, CompletionParams, DidOpenTextDocumentParams,
24 DocumentFormattingParams, FileRename, FormattingOptions, GotoDefinitionParams, HoverParams,
25 PartialResultParams, Position, Range, RenameFilesParams, TextDocumentItem,
26 TextDocumentPositionParams, WorkDoneProgressParams,
28 use rust_analyzer::lsp_ext::{OnEnter, Runnables, RunnablesParams};
30 use test_utils::skip_slow_tests;
33 support::{project, Project},
37 const PROFILE: &str = "";
38 // const PROFILE: &'static str = "*@3>100";
41 fn completes_items_from_standard_library() {
42 if skip_slow_tests() {
46 let server = Project::with_fixture(
54 use std::collections::Spam;
57 .with_config(serde_json::json!({
58 "cargo": { "noSysroot": false }
61 .wait_until_workspace_is_loaded();
63 let res = server.send_request::<Completion>(CompletionParams {
64 text_document_position: TextDocumentPositionParams::new(
65 server.doc_id("src/lib.rs"),
69 partial_result_params: PartialResultParams::default(),
70 work_done_progress_params: WorkDoneProgressParams::default(),
72 assert!(res.to_string().contains("HashMap"));
76 fn test_runnables_project() {
77 if skip_slow_tests() {
81 let server = Project::with_fixture(
91 //- /foo/tests/spam.rs
107 .wait_until_workspace_is_loaded();
109 server.request::<Runnables>(
110 RunnablesParams { text_document: server.doc_id("foo/tests/spam.rs"), position: None },
114 "cargoArgs": ["test", "--package", "foo", "--test", "spam"],
115 "executableArgs": ["test_eggs", "--exact", "--nocapture"],
116 "cargoExtraArgs": [],
117 "overrideCargo": null,
118 "workspaceRoot": server.path().join("foo")
121 "label": "test test_eggs",
124 "end": { "character": 17, "line": 1 },
125 "start": { "character": 0, "line": 0 }
127 "targetSelectionRange": {
128 "end": { "character": 12, "line": 1 },
129 "start": { "character": 3, "line": 1 }
131 "targetUri": "file:///[..]/tests/spam.rs"
136 "cargoArgs": ["check", "--package", "foo", "--all-targets"],
137 "executableArgs": [],
138 "cargoExtraArgs": [],
139 "overrideCargo": null,
140 "workspaceRoot": server.path().join("foo")
143 "label": "cargo check -p foo --all-targets"
147 "cargoArgs": ["test", "--package", "foo", "--all-targets"],
148 "executableArgs": [],
149 "cargoExtraArgs": [],
150 "overrideCargo": null,
151 "workspaceRoot": server.path().join("foo")
154 "label": "cargo test -p foo --all-targets"
161 fn test_format_document() {
162 if skip_slow_tests() {
166 let server = project(
179 pub use std::collections::HashMap;
182 .wait_until_workspace_is_loaded();
184 server.request::<Formatting>(
185 DocumentFormattingParams {
186 text_document: server.doc_id("src/lib.rs"),
187 options: FormattingOptions {
189 insert_spaces: false,
190 insert_final_newline: None,
191 trim_final_newlines: None,
192 trim_trailing_whitespace: None,
193 properties: HashMap::new(),
195 work_done_progress_params: WorkDoneProgressParams::default(),
201 "end": { "character": 0, "line": 3 },
202 "start": { "character": 11, "line": 2 }
210 fn test_format_document_2018() {
211 if skip_slow_tests() {
215 let server = project(
232 pub use std::collections::HashMap;
235 .wait_until_workspace_is_loaded();
237 server.request::<Formatting>(
238 DocumentFormattingParams {
239 text_document: server.doc_id("src/lib.rs"),
240 options: FormattingOptions {
242 insert_spaces: false,
243 properties: HashMap::new(),
244 insert_final_newline: None,
245 trim_final_newlines: None,
246 trim_trailing_whitespace: None,
248 work_done_progress_params: WorkDoneProgressParams::default(),
254 "end": { "character": 0, "line": 3 },
255 "start": { "character": 17, "line": 2 }
261 "end": { "character": 0, "line": 6 },
262 "start": { "character": 11, "line": 5 }
270 fn test_format_document_unchanged() {
271 if skip_slow_tests() {
275 let server = project(
286 .wait_until_workspace_is_loaded();
288 server.request::<Formatting>(
289 DocumentFormattingParams {
290 text_document: server.doc_id("src/lib.rs"),
291 options: FormattingOptions {
293 insert_spaces: false,
294 insert_final_newline: None,
295 trim_final_newlines: None,
296 trim_trailing_whitespace: None,
297 properties: HashMap::new(),
299 work_done_progress_params: WorkDoneProgressParams::default(),
306 fn test_missing_module_code_action() {
307 if skip_slow_tests() {
311 let server = project(
324 .wait_until_workspace_is_loaded();
326 server.request::<CodeActionRequest>(
328 text_document: server.doc_id("src/lib.rs"),
329 range: Range::new(Position::new(0, 4), Position::new(0, 7)),
330 context: CodeActionContext::default(),
331 partial_result_params: PartialResultParams::default(),
332 work_done_progress_params: WorkDoneProgressParams::default(),
339 "uri": "file:///[..]/src/bar.rs"
344 "title": "Create module"
348 server.request::<CodeActionRequest>(
350 text_document: server.doc_id("src/lib.rs"),
351 range: Range::new(Position::new(2, 4), Position::new(2, 7)),
352 context: CodeActionContext::default(),
353 partial_result_params: PartialResultParams::default(),
354 work_done_progress_params: WorkDoneProgressParams::default(),
361 fn test_missing_module_code_action_in_json_project() {
362 if skip_slow_tests() {
366 let tmp_dir = TestDir::new();
368 let path = tmp_dir.path();
370 let project = json!({
373 "root_module": path.join("src/lib.rs"),
376 "cfg": [ "cfg_atom_1", "feature=\"cfg_1\""],
382 //- /rust-project.json
390 PROJECT = project.to_string(),
394 Project::with_fixture(&code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded();
396 server.request::<CodeActionRequest>(
398 text_document: server.doc_id("src/lib.rs"),
399 range: Range::new(Position::new(0, 4), Position::new(0, 7)),
400 context: CodeActionContext::default(),
401 partial_result_params: PartialResultParams::default(),
402 work_done_progress_params: WorkDoneProgressParams::default(),
409 "uri": "file://[..]/src/bar.rs"
414 "title": "Create module"
418 server.request::<CodeActionRequest>(
420 text_document: server.doc_id("src/lib.rs"),
421 range: Range::new(Position::new(2, 4), Position::new(2, 7)),
422 context: CodeActionContext::default(),
423 partial_result_params: PartialResultParams::default(),
424 work_done_progress_params: WorkDoneProgressParams::default(),
431 fn diagnostics_dont_block_typing() {
432 if skip_slow_tests() {
436 let librs: String = (0..10).map(|i| format!("mod m{};", i)).collect();
437 let libs: String = (0..10).map(|i| format!("//- /src/m{}.rs\nfn foo() {{}}\n\n", i)).collect();
438 let server = Project::with_fixture(&format!(
454 .with_config(serde_json::json!({
455 "cargo": { "noSysroot": false }
458 .wait_until_workspace_is_loaded();
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(),
466 text: "/// Docs\nfn foo() {}".to_string(),
470 let start = Instant::now();
471 server.request::<OnEnter>(
472 TextDocumentPositionParams {
473 text_document: server.doc_id("src/m0.rs"),
474 position: Position { line: 0, character: 5 },
477 "insertTextFormat": 2,
478 "newText": "\n/// $0",
480 "end": { "character": 5, "line": 0 },
481 "start": { "character": 5, "line": 0 }
485 let elapsed = start.elapsed();
486 assert!(elapsed.as_millis() < 2000, "typing enter took {:?}", elapsed);
490 fn preserves_dos_line_endings() {
491 if skip_slow_tests() {
495 let server = Project::with_fixture(
503 /// Some Docs\r\nfn main() {}
507 .wait_until_workspace_is_loaded();
509 server.request::<OnEnter>(
510 TextDocumentPositionParams {
511 text_document: server.doc_id("src/main.rs"),
512 position: Position { line: 0, character: 8 },
515 "insertTextFormat": 2,
516 "newText": "\r\n/// $0",
518 "end": { "line": 0, "character": 8 },
519 "start": { "line": 0, "character": 8 }
526 fn out_dirs_check() {
527 if skip_slow_tests() {
531 let server = Project::with_fixture(
539 use std::{env, fs, path::Path};
542 let out_dir = env::var_os("OUT_DIR").unwrap();
543 let dest_path = Path::new(&out_dir).join("hello.rs");
546 r#"pub fn message() -> &'static str { "Hello, World!" }"#,
549 println!("cargo:rustc-cfg=atom_cfg");
550 println!("cargo:rustc-cfg=featlike=\"set\"");
551 println!("cargo:rerun-if-changed=build.rs");
554 #[rustc_builtin_macro] macro_rules! include {}
555 #[rustc_builtin_macro] macro_rules! include_str {}
556 #[rustc_builtin_macro] macro_rules! concat {}
557 #[rustc_builtin_macro] macro_rules! env {}
559 include!(concat!(env!("OUT_DIR"), "/hello.rs"));
565 #[cfg(featlike = "set")]
567 #[cfg(featlike = "not_set")]
573 let should_be_str = message();
574 let another_str = include_str!("main.rs");
578 .with_config(serde_json::json!({
580 "loadOutDirsFromCheck": true,
585 .wait_until_workspace_is_loaded();
587 let res = server.send_request::<HoverRequest>(HoverParams {
588 text_document_position_params: TextDocumentPositionParams::new(
589 server.doc_id("src/main.rs"),
590 Position::new(19, 10),
592 work_done_progress_params: Default::default(),
594 assert!(res.to_string().contains("&str"));
596 let res = server.send_request::<HoverRequest>(HoverParams {
597 text_document_position_params: TextDocumentPositionParams::new(
598 server.doc_id("src/main.rs"),
599 Position::new(20, 10),
601 work_done_progress_params: Default::default(),
603 assert!(res.to_string().contains("&str"));
605 server.request::<GotoTypeDefinition>(
606 GotoDefinitionParams {
607 text_document_position_params: TextDocumentPositionParams::new(
608 server.doc_id("src/main.rs"),
609 Position::new(17, 9),
611 work_done_progress_params: Default::default(),
612 partial_result_params: Default::default(),
615 "originSelectionRange": {
616 "end": { "character": 10, "line": 17 },
617 "start": { "character": 8, "line": 17 }
620 "end": { "character": 9, "line": 8 },
621 "start": { "character": 0, "line": 7 }
623 "targetSelectionRange": {
624 "end": { "character": 8, "line": 8 },
625 "start": { "character": 7, "line": 8 }
627 "targetUri": "file:///[..]src/main.rs"
631 server.request::<GotoTypeDefinition>(
632 GotoDefinitionParams {
633 text_document_position_params: TextDocumentPositionParams::new(
634 server.doc_id("src/main.rs"),
635 Position::new(18, 9),
637 work_done_progress_params: Default::default(),
638 partial_result_params: Default::default(),
641 "originSelectionRange": {
642 "end": { "character": 10, "line": 18 },
643 "start": { "character": 8, "line": 18 }
646 "end": { "character": 9, "line": 12 },
647 "start": { "character": 0, "line":11 }
649 "targetSelectionRange": {
650 "end": { "character": 8, "line": 12 },
651 "start": { "character": 7, "line": 12 }
653 "targetUri": "file:///[..]src/main.rs"
659 fn resolve_proc_macro() {
660 if skip_slow_tests() {
664 let server = Project::with_fixture(
672 bar = {path = "../bar"}
695 extern crate proc_macro;
696 use proc_macro::{Delimiter, Group, Ident, Span, TokenStream, TokenTree};
699 TokenTree::from(Ident::new($n, Span::call_site()))
702 TokenTree::from(Group::new(Delimiter::Brace, TokenStream::new()))
705 TokenTree::from(Group::new(Delimiter::Parenthesis, TokenStream::new()))
708 #[proc_macro_derive(Bar)]
709 pub fn foo(_input: TokenStream) -> TokenStream {
710 // We hard code the output here for preventing to use any deps
711 let mut res = TokenStream::new();
713 // ill behaved proc-macro will use the stdout
714 // we should ignore it
715 println!("I am bad guy");
717 // impl Bar for Foo { fn bar() {} }
718 let mut tokens = vec![t!("impl"), t!("Bar"), t!("for"), t!("Foo")];
719 let mut fn_stream = TokenStream::new();
720 fn_stream.extend(vec![t!("fn"), t!("bar"), t!(()), t!({})]);
721 tokens.push(Group::new(Delimiter::Brace, fn_stream).into());
728 .with_config(serde_json::json!({
730 "loadOutDirsFromCheck": true,
735 "server": PathBuf::from(env!("CARGO_BIN_EXE_rust-analyzer")),
741 .wait_until_workspace_is_loaded();
743 let res = server.send_request::<HoverRequest>(HoverParams {
744 text_document_position_params: TextDocumentPositionParams::new(
745 server.doc_id("foo/src/main.rs"),
748 work_done_progress_params: Default::default(),
750 let value = res.get("contents").unwrap().get("value").unwrap().as_str().unwrap();
765 fn test_will_rename_files_same_level() {
766 if skip_slow_tests() {
770 let tmp_dir = TestDir::new();
771 let tmp_dir_path = tmp_dir.path().to_owned();
772 let tmp_dir_str = tmp_dir_path.to_str().unwrap();
773 let base_path = PathBuf::from(format!("file://{}", tmp_dir_str));
790 //- /src/old_folder/mod.rs
792 //- /src/from_mod/mod.rs
794 //- /src/to_mod/foo.rs
798 Project::with_fixture(code).tmp_dir(tmp_dir).server().wait_until_workspace_is_loaded();
800 //rename same level file
801 server.request::<WillRenameFiles>(
803 files: vec![FileRename {
804 old_uri: base_path.join("src/old_file.rs").to_str().unwrap().to_string(),
805 new_uri: base_path.join("src/new_file.rs").to_str().unwrap().to_string(),
812 "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace("\\", "/")),
827 "newText": "new_file"
835 //rename file from mod.rs to foo.rs
836 server.request::<WillRenameFiles>(
838 files: vec![FileRename {
839 old_uri: base_path.join("src/from_mod/mod.rs").to_str().unwrap().to_string(),
840 new_uri: base_path.join("src/from_mod/foo.rs").to_str().unwrap().to_string(),
846 //rename file from foo.rs to mod.rs
847 server.request::<WillRenameFiles>(
849 files: vec![FileRename {
850 old_uri: base_path.join("src/to_mod/foo.rs").to_str().unwrap().to_string(),
851 new_uri: base_path.join("src/to_mod/mod.rs").to_str().unwrap().to_string(),
857 //rename same level file
858 server.request::<WillRenameFiles>(
860 files: vec![FileRename {
861 old_uri: base_path.join("src/old_folder").to_str().unwrap().to_string(),
862 new_uri: base_path.join("src/new_folder").to_str().unwrap().to_string(),
869 "uri": format!("file://{}", tmp_dir_path.join("src").join("lib.rs").to_str().unwrap().to_string().replace("C:\\", "/c:/").replace("\\", "/")),
884 "newText": "new_folder"