From de8a00e3f762cfebbf949f3ca665a585c61fe65b Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Tue, 19 Feb 2019 19:10:57 +0100 Subject: [PATCH] Move old integration tests to RLS process-spawning style By spawning separate processes into temporary directories the in-process RLS invocations don't need to fight over global env vars and working directory, allowing us to better utilize multiple test threads and isolate RLS behaviour in test cases. --- tests/client.rs | 999 ++++++++++++++++++++++-- tests/support/client.rs | 6 +- tests/support/paths.rs | 3 +- tests/tests_old.rs | 1604 --------------------------------------- 4 files changed, 937 insertions(+), 1675 deletions(-) delete mode 100644 tests/tests_old.rs diff --git a/tests/client.rs b/tests/client.rs index 696ce795c0e..8cff3231aa0 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -1,4 +1,5 @@ use std::path::Path; +use std::time::{Duration, Instant}; use futures::future::Future; use lsp_types::{notification::*, request::*, *}; @@ -12,12 +13,12 @@ use crate::support::{basic_bin_manifest, fixtures_dir}; mod support; fn initialize_params(root_path: &Path) -> InitializeParams { - lsp_types::InitializeParams { + InitializeParams { process_id: None, root_uri: None, root_path: Some(root_path.display().to_string()), initialization_options: None, - capabilities: lsp_types::ClientCapabilities { + capabilities: ClientCapabilities { workspace: None, text_document: None, experimental: None, @@ -27,31 +28,55 @@ fn initialize_params(root_path: &Path) -> InitializeParams { } } +fn initialize_params_with_opts(root_path: &Path, opts: serde_json::Value) -> InitializeParams { + InitializeParams { initialization_options: Some(opts), ..initialize_params(root_path) } +} + #[test] fn client_test_infer_bin() { - let p = project("simple_workspace") - .file("Cargo.toml", &basic_bin_manifest("foo")) - .file( - "src/main.rs", - r#" - struct UnusedBin; - fn main() { - println!("Hello world!"); - } - "#, - ) - .build(); - + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_bin")).unwrap().build(); let root_path = p.root(); let mut rls = p.spawn_rls_async(); rls.request::(0, initialize_params(root_path)); let diag = rls.wait_for_diagnostics(); + + assert!(diag.uri.as_str().ends_with("src/main.rs")); assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedBin`")); - rls.wait_for_indexing(); - assert!(rls.messages().iter().filter(|msg| msg["method"] != "window/progress").count() > 1); + rls.shutdown(); +} + +#[test] +fn client_test_infer_lib() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_lib")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + let diag = rls.wait_for_diagnostics(); + + assert!(diag.uri.as_str().ends_with("src/lib.rs")); + assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedLib`")); + + rls.shutdown(); +} + +#[test] +fn client_test_infer_custom_bin() { + let p = + ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_custom_bin")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + let diag = rls.wait_for_diagnostics(); + + assert!(diag.uri.as_str().ends_with("src/custom_bin.rs")); + assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedCustomBin`")); rls.shutdown(); } @@ -1018,24 +1043,8 @@ fn client_find_definitions() { let root_path = p.root(); let mut rls = p.spawn_rls_async(); - rls.request::( - 0, - lsp_types::InitializeParams { - process_id: None, - root_uri: None, - root_path: Some(root_path.display().to_string()), - initialization_options: Some(json!({ - "settings": { - "rust": { - "racer_completion": false - } - } - })), - capabilities: Default::default(), - trace: None, - workspace_folders: None, - }, - ); + let opts = json!({"settings": {"rust": {"racer_completion": false } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); rls.wait_for_indexing(); @@ -1310,7 +1319,7 @@ fn client_init_duplicated_and_unknown_settings() { let root_path = p.root(); let mut rls = p.spawn_rls_async(); - let init_options = json!({ + let opts = json!({ "settings": { "rust": { "features": ["some_feature"], @@ -1326,22 +1335,7 @@ fn client_init_duplicated_and_unknown_settings() { } }); - rls.request::( - 0, - lsp_types::InitializeParams { - process_id: None, - root_uri: None, - root_path: Some(root_path.display().to_string()), - initialization_options: Some(init_options), - capabilities: lsp_types::ClientCapabilities { - workspace: None, - text_document: None, - experimental: None, - }, - trace: None, - workspace_folders: None, - }, - ); + rls.request::(0, initialize_params_with_opts(root_path, opts)); assert!(rls.messages().iter().any(is_notification_for_unknown_config)); assert!(rls.messages().iter().any(is_notification_for_duplicated_config)); @@ -1365,22 +1359,7 @@ fn client_did_change_configuration_duplicated_and_unknown_settings() { let root_path = p.root(); let mut rls = p.spawn_rls_async(); - rls.request::( - 0, - lsp_types::InitializeParams { - process_id: None, - root_uri: None, - root_path: Some(root_path.display().to_string()), - initialization_options: None, - capabilities: lsp_types::ClientCapabilities { - workspace: None, - text_document: None, - experimental: None, - }, - trace: None, - workspace_folders: None, - }, - ); + rls.request::(0, initialize_params(root_path)); assert!(!rls.messages().iter().any(is_notification_for_unknown_config)); assert!(!rls.messages().iter().any(is_notification_for_duplicated_config)); @@ -1408,3 +1387,887 @@ fn client_did_change_configuration_duplicated_and_unknown_settings() { rls.shutdown(); } + +#[test] +fn client_shutdown() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.shutdown(); +} + +#[test] +fn client_goto_def() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let result = rls.request::( + 11, + TextDocumentPositionParams { + position: Position { line: 12, character: 27 }, + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + }, + ); + + let ranges: Vec<_> = result + .into_iter() + .flat_map(|x| match x { + GotoDefinitionResponse::Scalar(loc) => vec![loc].into_iter(), + GotoDefinitionResponse::Array(locs) => locs.into_iter(), + _ => unreachable!(), + }) + .map(|x| x.range) + .collect(); + + assert!(ranges.iter().any(|r| r.start == Position { line: 11, character: 8 })); + + rls.shutdown(); +} + +#[test] +fn client_hover() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let result = rls + .request::( + 11, + TextDocumentPositionParams { + position: Position { line: 12, character: 27 }, + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + }, + ) + .unwrap(); + + let contents = ["&str", "let world = \"world\";"]; + let mut contents: Vec<_> = contents.iter().map(ToString::to_string).collect(); + let contents = + contents.drain(..).map(|value| LanguageString { language: "rust".to_string(), value }); + let contents = contents.map(MarkedString::LanguageString).collect(); + + assert_eq!(result.contents, HoverContents::Array(contents)); + + rls.shutdown(); +} + +/// Test hover continues to work after the source has moved line +#[ignore] // FIXME(#1265): Spurious failure - sometimes we lose the semantic information from Rust - why? +#[test] +fn client_hover_after_src_line_change() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": {"racer_completion": false } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + let world_src_pos = Position { line: 12, character: 27 }; + let world_src_pos_after = Position { line: 13, character: 27 }; + + let result = rls + .request::( + 11, + TextDocumentPositionParams { + position: world_src_pos, + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + }, + ) + .unwrap(); + + let contents = ["&str", "let world = \"world\";"]; + let contents: Vec<_> = contents + .iter() + .map(|value| LanguageString { language: "rust".to_string(), value: value.to_string() }) + .map(MarkedString::LanguageString) + .collect(); + + assert_eq!(result.contents, HoverContents::Array(contents.clone())); + + rls.notify::(DidChangeTextDocumentParams { + content_changes: vec![TextDocumentContentChangeEvent { + range: Some(Range { + start: Position { line: 10, character: 15 }, + end: Position { line: 10, character: 15 }, + }), + range_length: Some(0), + text: "\n ".to_string(), + }], + text_document: VersionedTextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + version: Some(2), + }, + }); + + rls.wait_for_indexing(); + + let result = rls + .request::( + 11, + TextDocumentPositionParams { + position: world_src_pos_after, + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + }, + ) + .unwrap(); + + assert_eq!(result.contents, HoverContents::Array(contents)); + + rls.shutdown(); +} + +#[test] +fn client_workspace_symbol() { + let p = + ProjectBuilder::try_from_fixture(fixtures_dir().join("workspace_symbol")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": { "cfg_test": true } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + let symbols = rls + .request::(42, WorkspaceSymbolParams { query: "nemo".to_owned() }) + .unwrap(); + + let mut nemos = vec![ + ("src/main.rs", "nemo", SymbolKind::Function, 1, 11, 1, 15, Some("x")), + ("src/foo.rs", "nemo", SymbolKind::Module, 0, 4, 0, 8, Some("foo")), + ]; + + for (file, name, kind, start_l, start_c, end_l, end_c, container_name) in nemos.drain(..) { + let sym = SymbolInformation { + name: name.to_string(), + kind, + container_name: container_name.map(ToString::to_string), + location: Location { + uri: Url::from_file_path(p.root().join(file)).unwrap(), + range: Range { + start: Position { line: start_l, character: start_c }, + end: Position { line: end_l, character: end_c }, + }, + }, + deprecated: None, + }; + dbg!(&sym); + assert!(symbols.iter().any(|s| *s == sym)); + } + + rls.shutdown(); +} + +#[test] +fn client_workspace_symbol_duplicates() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("workspace_symbol_duplicates")) + .unwrap() + .build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": { "cfg_test": true } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + let symbols = rls + .request::(42, WorkspaceSymbolParams { query: "Frobnicator".to_owned() }) + .unwrap(); + + let symbol = SymbolInformation { + name: "Frobnicator".to_string(), + kind: SymbolKind::Struct, + container_name: Some("a".to_string()), + location: Location { + uri: Url::from_file_path(p.root().join("src/shared.rs")).unwrap(), + range: Range { + start: Position { line: 1, character: 7 }, + end: Position { line: 1, character: 18 }, + }, + }, + deprecated: None, + }; + + assert_eq!(symbols, vec![symbol]); + + rls.shutdown(); +} + +#[ignore] // FIXME(#1265): This is spurious (we don't pick up reference under #[cfg(test)])-ed code - why? +#[test] +fn client_find_all_refs_test() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": {"all_targets": true } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + let result = rls + .request::( + 42, + ReferenceParams { + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + position: Position { line: 0, character: 7 }, + context: ReferenceContext { include_declaration: true }, + }, + ) + .unwrap(); + + let ranges = [((0, 7), (0, 10)), ((6, 14), (6, 17)), ((14, 15), (14, 18))]; + for ((sl, sc), (el, ec)) in &ranges { + let range = Range { + start: Position { line: *sl, character: *sc }, + end: Position { line: *el, character: *ec }, + }; + + dbg!(range); + assert!(result.iter().any(|x| x.range == range)); + } + + rls.shutdown(); +} + +#[test] +fn client_find_all_refs_no_cfg_test() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("find_all_refs_no_cfg_test")) + .unwrap() + .build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": { "all_targets": false } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + let result = rls + .request::( + 42, + ReferenceParams { + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + position: Position { line: 0, character: 7 }, + context: ReferenceContext { include_declaration: true }, + }, + ) + .unwrap(); + + let ranges = [((0, 7), (0, 10)), ((13, 15), (13, 18))]; + for ((sl, sc), (el, ec)) in &ranges { + let range = Range { + start: Position { line: *sl, character: *sc }, + end: Position { line: *el, character: *ec }, + }; + + dbg!(range); + assert!(result.iter().any(|x| x.range == range)); + } + + rls.shutdown(); +} + +#[test] +fn client_borrow_error() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("borrow_error")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + let diag = rls.wait_for_diagnostics(); + + let msg = "cannot borrow `x` as mutable more than once at a time"; + assert!(diag.diagnostics.iter().any(|diag| diag.message.contains(msg))); + + rls.shutdown(); +} + +#[test] +fn client_highlight() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let result = rls + .request::( + 42, + TextDocumentPositionParams { + position: Position { line: 12, character: 27 }, + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + }, + ) + .unwrap(); + + let ranges = [((11, 8), (11, 13)), ((12, 27), (12, 32))]; + for ((sl, sc), (el, ec)) in &ranges { + let range = Range { + start: Position { line: *sl, character: *sc }, + end: Position { line: *el, character: *ec }, + }; + + dbg!(range); + assert!(result.iter().any(|x| x.range == range)); + } + + rls.shutdown(); +} + +#[test] +fn client_rename() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let result = rls + .request::( + 42, + RenameParams { + position: Position { line: 12, character: 27 }, + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + new_name: "foo".to_owned(), + }, + ) + .unwrap(); + + dbg!(&result); + + let uri = Url::from_file_path(p.root().join("src/main.rs")).unwrap(); + let ranges = [((11, 8), (11, 13)), ((12, 27), (12, 32))]; + let ranges = ranges + .iter() + .map(|((sl, sc), (el, ec))| Range { + start: Position { line: *sl, character: *sc }, + end: Position { line: *el, character: *ec }, + }) + .map(|range| TextEdit { range, new_text: "foo".to_string() }); + + let changes = std::iter::once((uri, ranges.collect())).collect(); + + assert_eq!(result.changes, Some(changes)); + + rls.shutdown(); +} + +#[test] +fn client_reformat() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("reformat")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let result = rls.request::( + 42, + DocumentFormattingParams { + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + options: FormattingOptions { + tab_size: 4, + insert_spaces: true, + properties: Default::default(), + }, + }, + ); + + assert_eq!(result.unwrap()[0], TextEdit { + range: Range { + start: Position { line: 0, character: 0 }, + end: Position { line: 12, character: 0 }, + }, + new_text: "// Copyright 2017 The Rust Project Developers. See the COPYRIGHT\n// file at the top-level directory of this distribution and at\n// http://rust-lang.org/COPYRIGHT.\n//\n// Licensed under the Apache License, Version 2.0 or the MIT license\n// , at your\n// option. This file may not be copied, modified, or distributed\n// except according to those terms.\n\npub mod foo;\npub fn main() {\n let world = \"world\";\n println!(\"Hello, {}!\", world);\n}\n".to_string(), + }); + + rls.shutdown(); +} + +#[test] +fn client_reformat_with_range() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("reformat_with_range")) + .unwrap() + .build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let result = rls.request::( + 42, + DocumentRangeFormattingParams { + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + range: Range { + start: Position { line: 12, character: 0 }, + end: Position { line: 13, character: 0 }, + }, + options: FormattingOptions { + tab_size: 4, + insert_spaces: true, + properties: Default::default(), + }, + }, + ); + + let newline = if cfg!(windows) { "\r\n" } else { "\n" }; + let formatted = "// Copyright 2017 The Rust Project Developers. See the COPYRIGHT\n// file at the top-level directory of this distribution and at\n// http://rust-lang.org/COPYRIGHT.\n//\n// Licensed under the Apache License, Version 2.0 or the MIT license\n// , at your\n// option. This file may not be copied, modified, or distributed\n// except according to those terms.\n\npub fn main() {\n let world1 = \"world\";\n println!(\"Hello, {}!\", world1);\n let world2 = \"world\";\n println!(\"Hello, {}!\", world2);\n let world3 = \"world\";\n println!(\"Hello, {}!\", world3);\n}\n" + .replace("\n", newline); + + assert_eq!(result.unwrap()[0].new_text, formatted); + + rls.shutdown(); +} + +#[test] +fn client_multiple_binaries() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("multiple_bins")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": { "build_bin": "bin2" } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + { + let msgs = rls.messages(); + let diags = msgs + .iter() + .filter(|x| x["method"] == PublishDiagnostics::METHOD) + .flat_map(|msg| msg["params"]["diagnostics"].as_array().unwrap()) + .map(|diag| diag["message"].as_str().unwrap()) + .collect::>(); + + for i in 1..3 { + let msg = &format!("unused variable: `bin_name{}`", i); + assert!(diags.iter().any(|message| message.starts_with(msg))); + } + } + + rls.shutdown(); +} + +#[ignore] // Requires `rust-src` component, which isn't available in Rust CI. +#[test] +fn client_completion() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let text_document = + TextDocumentIdentifier { uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap() }; + + let completions = |x: CompletionResponse| match x { + CompletionResponse::Array(items) => items, + CompletionResponse::List(CompletionList { items, .. }) => items, + }; + + macro_rules! item_eq { + ($item:expr, $expected:expr) => {{ + let (label, kind, detail) = $expected; + ($item.label == *label && $item.kind == *kind && $item.detail == *detail) + }}; + } + + let expected = [ + // FIXME(https://github.com/rust-lang/rls/issues/1205) - empty " " string + ("world", &Some(CompletionItemKind::Variable), &Some("let world = \" \";".to_string())), + ("x", &Some(CompletionItemKind::Field), &Some("x: u64".to_string())), + ]; + + let result = rls.request::( + 11, + CompletionParams { + text_document: text_document.clone(), + position: Position { line: 12, character: 30 }, + context: None, + }, + ); + let items = completions(result.unwrap()); + assert!(items.iter().any(|item| item_eq!(item, expected[0]))); + let result = rls.request::( + 11, + CompletionParams { + text_document: text_document.clone(), + position: Position { line: 15, character: 30 }, + context: None, + }, + ); + let items = completions(result.unwrap()); + assert!(items.iter().any(|item| item_eq!(item, expected[1]))); + + rls.shutdown(); +} + +#[test] +fn client_bin_lib_project() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("bin_lib")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": { "cfg_test": true, "build_bin": "bin_lib" } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics(); + + assert!(diag.uri.as_str().ends_with("bin_lib/tests/tests.rs")); + assert_eq!(diag.diagnostics.len(), 1); + assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Warning)); + assert!(diag.diagnostics[0].message.contains("unused variable: `unused_var`")); + + rls.shutdown(); +} + +#[test] +fn client_infer_lib() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("infer_lib")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + let diag = rls.wait_for_diagnostics(); + + assert!(diag.uri.as_str().ends_with("src/lib.rs")); + assert_eq!(diag.diagnostics.len(), 1); + assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Warning)); + assert!(diag.diagnostics[0].message.contains("struct is never constructed: `UnusedLib`")); + + rls.shutdown(); +} + +#[test] +fn client_omit_init_build() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + const ID: u64 = 1337; + let response = rls.future_msg(|msg| msg["id"] == json!(ID)); + + let opts = json!({ "omitInitBuild": true }); + rls.request::(ID, initialize_params_with_opts(root_path, opts)); + + // We need to assert that no other messages are received after a short + // period of time (e.g. no build progress messages). + std::thread::sleep(std::time::Duration::from_secs(1)); + rls.block_on(response).unwrap(); + + assert_eq!(rls.messages().iter().count(), 1); + + rls.shutdown(); +} + +#[test] +fn client_find_impls() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("find_impls")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + let uri = Url::from_file_path(p.root().join("src/main.rs")).unwrap(); + + let locations = |result: Option| match result.unwrap() { + GotoDefinitionResponse::Scalar(loc) => vec![loc], + GotoDefinitionResponse::Array(locations) => locations, + GotoDefinitionResponse::Link(mut links) => { + links.drain(..).map(|l| Location { uri: l.target_uri, range: l.target_range }).collect() + } + }; + + let result = rls.request::( + 1, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier::new(uri.clone()), + position: Position { line: 12, character: 7 }, // "Bar" + }, + ); + let expected = [(18, 15, 18, 18), (19, 12, 19, 15)]; + let expected = expected.iter().map(|(a, b, c, d)| Location { + uri: uri.clone(), + range: Range { + start: Position { line: *a, character: *b }, + end: Position { line: *c, character: *d }, + }, + }); + let locs = locations(result); + for exp in expected { + assert!(locs.iter().any(|x| *x == exp)); + } + + let result = rls.request::( + 1, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier::new(uri.clone()), + position: Position { line: 15, character: 6 }, // "Super" + }, + ); + let expected = [(18, 15, 18, 18), (22, 15, 22, 18)]; + let expected = expected.iter().map(|(a, b, c, d)| Location { + uri: uri.clone(), + range: Range { + start: Position { line: *a, character: *b }, + end: Position { line: *c, character: *d }, + }, + }); + let locs = locations(result); + for exp in expected { + assert!(locs.iter().any(|x| *x == exp)); + } + + rls.shutdown(); +} + +#[test] +fn client_features() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("features")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": {"features": ["bar", "baz"] } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + let diag = rls.wait_for_diagnostics(); + + assert_eq!(diag.diagnostics.len(), 1); + assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error)); + let msg = "cannot find struct, variant or union type `Foo` in this scope"; + assert!(diag.diagnostics[0].message.contains(msg)); + + rls.shutdown(); +} + +#[test] +fn client_all_features() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("features")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": {"all_features": true } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + rls.wait_for_indexing(); + + assert_eq!( + rls.messages().iter().filter(|x| x["method"] == PublishDiagnostics::METHOD).count(), + 0 + ); + + rls.shutdown(); +} + +#[test] +fn client_no_default_features() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("features")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": + { "no_default_features": true, "features": ["foo", "bar"] } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + let diag = rls.wait_for_diagnostics(); + + assert_eq!(diag.diagnostics.len(), 1); + assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error)); + let msg = "cannot find struct, variant or union type `Baz` in this scope"; + assert!(diag.diagnostics[0].message.contains(msg)); + + rls.shutdown(); +} + +#[test] +fn client_all_targets() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("bin_lib")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({"settings": {"rust": { "cfg_test": true, "all_targets": true } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + let diag: PublishDiagnosticsParams = rls.wait_for_diagnostics(); + + assert!(diag.uri.as_str().ends_with("bin_lib/tests/tests.rs")); + assert_eq!(diag.diagnostics.len(), 1); + assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Warning)); + assert!(diag.diagnostics[0].message.contains("unused variable: `unused_var`")); + + rls.shutdown(); +} + +/// Handle receiving a notification before the `initialize` request by ignoring and +/// continuing to run +#[test] +fn client_ignore_uninitialized_notification() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + rls.notify::(DidChangeConfigurationParams { settings: json!({}) }); + rls.request::(0, initialize_params(root_path)); + + rls.wait_for_indexing(); + + rls.shutdown(); +} + +/// Handle receiving requests before the `initialize` request by returning an error response +/// and continuing to run +#[test] +fn client_fail_uninitialized_request() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("common")).unwrap().build(); + let mut rls = p.spawn_rls_async(); + + const ID: u64 = 1337; + + rls.request::( + ID, + TextDocumentPositionParams { + text_document: TextDocumentIdentifier { + uri: Url::from_file_path(p.root().join("src/main.rs")).unwrap(), + }, + position: Position { line: 0, character: 0 }, + }, + ); + + let delay = tokio_timer::Delay::new(Instant::now() + Duration::from_secs(1)); + rls.block_on(delay).unwrap(); + + let err = jsonrpc_core::Failure::deserialize(rls.messages().last().unwrap()).unwrap(); + assert_eq!(err.id, jsonrpc_core::Id::Num(ID)); + assert_eq!(err.error.code, jsonrpc_core::ErrorCode::ServerError(-32002)); + assert_eq!(err.error.message, "not yet received `initialize` request"); + + rls.shutdown(); +} + +// Test that RLS can accept configuration with config keys in 4 different cases: +// - mixedCase +// - CamelCase +// - snake_case +// - kebab-case +fn client_init_impl(convert_case: fn(&str) -> String) { + let p = project("config_cases") + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file( + "src/main.rs", + r#" + struct NonCfg; + #[cfg(test)] + struct CfgTest { inner: PathBuf } + "#, + ) + .build(); + + let root_path = p.root(); + let mut rls = p.spawn_rls_async(); + + let opts = json!({ "settings": { "rust": { convert_case("all_targets"): true } } }); + rls.request::(0, initialize_params_with_opts(root_path, opts)); + + let diag = rls.wait_for_diagnostics(); + + assert_eq!(diag.diagnostics.len(), 1); + assert_eq!(diag.diagnostics[0].severity, Some(DiagnosticSeverity::Error)); + let msg = "cannot find type `PathBuf` in this scope"; + assert!(diag.diagnostics[0].message.contains(msg)); + + rls.shutdown(); +} + +#[test] +fn client_init_with_configuration_mixed_case() { + client_init_impl(heck::MixedCase::to_mixed_case); +} + +#[test] +fn client_init_with_configuration_camel_case() { + client_init_impl(heck::CamelCase::to_camel_case); +} + +#[test] +fn client_init_with_configuration_snake_case() { + client_init_impl(heck::SnakeCase::to_snake_case); +} + +#[test] +fn client_init_with_configuration_kebab_case() { + client_init_impl(heck::KebabCase::to_kebab_case); +} + +#[test] +fn client_parse_error_on_malformed_input() { + use crate::support::rls_exe; + use std::io::{Read, Write}; + use std::process::{Command, Stdio}; + + let mut cmd = Command::new(rls_exe()) + .stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .stderr(Stdio::null()) + .spawn() + .unwrap(); + + cmd.stdin.take().unwrap().write_all(b"Malformed input").unwrap(); + let mut output = vec![]; + cmd.stdout.take().unwrap().read_to_end(&mut output).unwrap(); + let output = String::from_utf8(output).unwrap(); + + assert_eq!(output, "Content-Length: 75\r\n\r\n{\"jsonrpc\":\"2.0\",\"error\":{\"code\":-32700,\"message\":\"Parse error\"},\"id\":null}"); + + // Right now parse errors shutdown the RLS, which we might want to revisit + // to provide better fault tolerance. + cmd.wait().unwrap(); +} diff --git a/tests/support/client.rs b/tests/support/client.rs index cfb66ea3036..84dd4090b11 100644 --- a/tests/support/client.rs +++ b/tests/support/client.rs @@ -155,8 +155,9 @@ impl RlsHandle { "params": params, })); - let msg = self.wait_for_message(move |val| val["id"] == id && val.get("result").is_some()); + let msg = self.wait_for_message(move |val| val["id"] == id); + // TODO: Bubble up errors R::Result::deserialize(&msg["result"]) .unwrap_or_else(|_| panic!("Can't deserialize results: {:?}", msg)) } @@ -187,7 +188,7 @@ impl RlsHandle { /// Enqueues a channel that is notified and consumed when a given predicate /// `f` is true for a received message. - fn future_msg( + pub fn future_msg( &mut self, f: impl Fn(&Value) -> bool + 'static, ) -> impl Future { @@ -219,6 +220,7 @@ impl RlsHandle { } /// Blocks until the processing (building + indexing) is done by the RLS. + #[allow(clippy::bool_comparison)] pub fn wait_for_indexing(&mut self) { self.wait_for_message(|msg| { msg["params"]["title"] == "Indexing" && msg["params"]["done"] == true diff --git a/tests/support/paths.rs b/tests/support/paths.rs index 1c926cd0f85..c1c5b78e7df 100644 --- a/tests/support/paths.rs +++ b/tests/support/paths.rs @@ -2,6 +2,7 @@ use std::cell::Cell; use std::env; +use std::ffi::OsStr; use std::fs; use std::io::{self, ErrorKind}; use std::path::{Path, PathBuf}; @@ -38,7 +39,7 @@ fn global_root() -> PathBuf { // `target`. If, however, `cargo test --target $target` is used then the // output is `target/$target/debug/foo`, so our path is pointing at // `target/$target`. Here we conditionally pop the `$target` name. - if path.file_name().and_then(|s| s.to_str()) != Some("target") { + if path.file_name().and_then(OsStr::to_str) != Some("target") { path.pop(); } diff --git a/tests/tests_old.rs b/tests/tests_old.rs deleted file mode 100644 index ea81eb73cf4..00000000000 --- a/tests/tests_old.rs +++ /dev/null @@ -1,1604 +0,0 @@ -#![allow(clippy::cyclomatic_complexity)] - -use jsonrpc_core; -use rls::actions::{notifications, requests}; -use rls::config::{Config, Inferrable}; -use rls::server::{self as ls_server, Notification, Request, RequestId, ShutdownRequest}; -use rls_analysis::{AnalysisHost, Target}; -use rls_vfs::Vfs; -use serde_json::Value; - -use self::support::harness::{ - compare_json, expect_message, expect_series, src, Environment, ExpectedMessage, RecordOutput, -}; - -use lsp_types::*; - -use env_logger; -use serde_json; -use std::marker::PhantomData; -use std::path::Path; -use std::sync::{Arc, Mutex}; -use std::time::Instant; -use url::Url; - -#[allow(dead_code)] -mod support; - -fn initialize(id: usize, root_path: Option) -> Request { - initialize_with_opts(id, root_path, None) -} - -fn initialize_with_opts( - id: usize, - root_path: Option, - initialization_options: Option, -) -> Request { - let params = InitializeParams { - process_id: None, - root_path, - root_uri: None, - initialization_options, - capabilities: ClientCapabilities { - workspace: None, - text_document: None, - experimental: None, - }, - trace: Some(TraceOption::Off), - workspace_folders: None, - }; - Request { - id: RequestId::Num(id as u64), - params, - received: Instant::now(), - _action: PhantomData, - } -} - -fn blocking_request( - id: usize, - params: T::Params, -) -> Request { - Request { - id: RequestId::Num(id as u64), - params, - received: Instant::now(), - _action: PhantomData, - } -} - -fn request(id: usize, params: T::Params) -> Request { - Request { - id: RequestId::Num(id as u64), - params, - received: Instant::now(), - _action: PhantomData, - } -} - -fn notification(params: A::Params) -> Notification { - Notification { params, _action: PhantomData } -} - -#[test] -fn test_shutdown() { - let mut env = Environment::generate_from_fixture("common"); - - let root_path = env.cache.abs_path(Path::new(".")); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - blocking_request::(1, ()).to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]);; - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message(&mut server, results, &ExpectedMessage::new(Some(1))); -} - -#[test] -fn test_goto_def() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 11, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 13, "world")), - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]);; - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - // TODO structural checking of result, rather than looking for a string - src(&source_file_path, 12, "world") - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(11)).expect_contains(r#""start":{"line":11,"character":8}"#), - ); -} - -#[test] -fn test_hover() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 11, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 13, "world")), - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]);; - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(11)) - .expect_contains(r#"[{"language":"rust","value":"&str"},{"language":"rust","value":"let world = \"world\";"}]"#) - ); -} - -/// Test hover continues to work after the source has moved line -#[test] -fn test_hover_after_src_line_change() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let world_src_pos = env.cache.mk_ls_position(src(&source_file_path, 12, "world")); - let world_src_pos_after = Position { line: world_src_pos.line + 1, ..world_src_pos }; - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 11, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url.clone()), - position: world_src_pos, - }, - ) - .to_string(), - notification::(DidChangeTextDocumentParams { - text_document: VersionedTextDocumentIdentifier { uri: url.clone(), version: Some(2) }, - content_changes: vec![TextDocumentContentChangeEvent { - range: Some(Range { - start: Position { line: 10, character: 15 }, - end: Position { line: 10, character: 15 }, - }), - range_length: Some(0), - text: "\n ".into(), - }], - }) - .to_string(), - request::( - 13, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url), - position: world_src_pos_after, - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - // first hover over unmodified - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(11)).expect_contains(r#"[{"language":"rust","value":"&str"}]"#), - ); - - // handle didChange notification and wait for rebuild - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - // hover after line change should work at the new line - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains(r#"[{"language":"rust","value":"&str"}]"#), - ); -} - -#[test] -fn test_workspace_symbol() { - let mut env = Environment::generate_from_fixture("workspace_symbol"); - - let root_path = env.cache.abs_path(Path::new(".")); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - WorkspaceSymbolParams { query: "nemo".to_owned() }, - ) - .to_string(), - ]; - - env.with_config(|c| c.cfg_test = true); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains(r#""id":42"#) - // in main.rs - .expect_contains(r#"main.rs"#) - .expect_contains(r#""name":"nemo""#) - .expect_contains(r#""kind":12"#) - .expect_contains( - r#""range":{"start":{"line":1,"character":11},"end":{"line":1,"character":15}}"#, - ) - .expect_contains(r#""containerName":"x""#) - // in foo.rs - .expect_contains(r#"foo.rs"#) - .expect_contains(r#""name":"nemo""#) - .expect_contains(r#""kind":2"#) - .expect_contains( - r#""range":{"start":{"line":0,"character":4},"end":{"line":0,"character":8}}"#, - ) - .expect_contains(r#""containerName":"foo""#), - ); -} - -#[test] -fn test_workspace_symbol_duplicates() { - let mut env = Environment::generate_from_fixture("workspace_symbol_duplicates"); - - let root_path = env.cache.abs_path(Path::new(".")); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - WorkspaceSymbolParams { query: "Frobnicator".to_owned() }, - ) - .to_string(), - ]; - - env.with_config(|c| c.cfg_test = true); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - server.wait_for_concurrent_jobs(); - let result: serde_json::Value = - serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap(); - let mut result = result.get("result").unwrap().clone(); - *result.pointer_mut("/0/location/uri").unwrap() = "shared.rs".into(); - compare_json( - &result, - r#"[{ - "containerName": "a", - "kind": 23, - "location": { - "range": { - "end": { "line": 1, "character": 18 }, - "start": { "line": 1, "character": 7 } - }, - "uri": "shared.rs" - }, - "name": "Frobnicator" - }]"#, - ) -} - -#[test] -fn test_find_all_refs() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - ReferenceParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 1, "Bar")), - context: ReferenceContext { include_declaration: true }, - }, - ) - .to_string(), - ]; - - env.with_config(|c| c.cfg_test = true); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains( - r#"{"start":{"line":0,"character":7},"end":{"line":0,"character":10}}"#, - ) - .expect_contains( - r#"{"start":{"line":6,"character":14},"end":{"line":6,"character":17}}"#, - ) - .expect_contains( - r#"{"start":{"line":14,"character":15},"end":{"line":14,"character":18}}"#, - ), - ); -} - -#[test] -fn test_find_all_refs_no_cfg_test() { - let mut env = Environment::generate_from_fixture("find_all_refs_no_cfg_test"); - env.with_config(|c| c.all_targets = false); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - ReferenceParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 1, "Bar")), - context: ReferenceContext { include_declaration: true }, - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains( - r#"{"start":{"line":0,"character":7},"end":{"line":0,"character":10}}"#, - ) - .expect_contains( - r#"{"start":{"line":13,"character":15},"end":{"line":13,"character":18}}"#, - ), - ); -} - -#[test] -fn test_borrow_error() { - let mut env = Environment::generate_from_fixture("borrow_error"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None) - .expect_contains(r#""message":"cannot borrow `x` as mutable more than once at a time"#), - ); - - expect_series(&mut server, results, vec!["progress"]); -} - -#[test] -fn test_highlight() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 13, "world")), - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains( - r#"{"start":{"line":11,"character":8},"end":{"line":11,"character":13}}"#, - ) - .expect_contains( - r#"{"start":{"line":12,"character":27},"end":{"line":12,"character":32}}"#, - ), - ); -} - -#[test] -fn test_rename() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - let text_doc = TextDocumentIdentifier::new(url); - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - RenameParams { - text_document: text_doc, - position: env.cache.mk_ls_position(src(&source_file_path, 13, "world")), - new_name: "foo".to_owned(), - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains( - r#"{"start":{"line":11,"character":8},"end":{"line":11,"character":13}}"#, - ) - .expect_contains( - r#"{"start":{"line":12,"character":27},"end":{"line":12,"character":32}}"#, - ) - .expect_contains(r#"{"changes""#), - ); -} - -#[test] -fn test_reformat() { - let mut env = Environment::generate_from_fixture("reformat"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - let text_doc = TextDocumentIdentifier::new(url); - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - DocumentFormattingParams { - text_document: text_doc, - options: FormattingOptions { - tab_size: 4, - insert_spaces: true, - properties: ::std::collections::HashMap::new(), - }, - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains(r#"{"start":{"line":0,"character":0},"end":{"line":12,"character":0}}"#) - .expect_contains(r#"newText":"// Copyright 2017 The Rust Project Developers. See the COPYRIGHT\n// file at the top-level directory of this distribution and at\n// http://rust-lang.org/COPYRIGHT.\n//\n// Licensed under the Apache License, Version 2.0 or the MIT license\n// , at your\n// option. This file may not be copied, modified, or distributed\n// except according to those terms.\n\npub mod foo;\npub fn main() {\n let world = \"world\";\n println!(\"Hello, {}!\", world);\n}"#) - ); -} - -#[test] -fn test_reformat_with_range() { - let mut env = Environment::generate_from_fixture("reformat_with_range"); - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - let text_doc = TextDocumentIdentifier::new(url); - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 42, - DocumentRangeFormattingParams { - text_document: text_doc, - range: Range { - start: Position { line: 12, character: 0 }, - end: Position { line: 13, character: 0 }, - }, - options: FormattingOptions { - tab_size: 4, - insert_spaces: true, - properties: ::std::collections::HashMap::new(), - }, - }, - ) - .to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - let newline = if cfg!(windows) { r#"\r\n"# } else { r#"\n"# }; - let formatted = r#"newText":"// Copyright 2017 The Rust Project Developers. See the COPYRIGHT\n// file at the top-level directory of this distribution and at\n// http://rust-lang.org/COPYRIGHT.\n//\n// Licensed under the Apache License, Version 2.0 or the MIT license\n// , at your\n// option. This file may not be copied, modified, or distributed\n// except according to those terms.\n\npub fn main() {\n let world1 = \"world\";\n println!(\"Hello, {}!\", world1);\n let world2 = \"world\";\n println!(\"Hello, {}!\", world2);\n let world3 = \"world\";\n println!(\"Hello, {}!\", world3);\n}\n"#; - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(42)) - .expect_contains( - r#"{"start":{"line":0,"character":0},"end":{"line":15,"character":5}}"#, - ) - .expect_contains(&formatted.replace(r#"\n"#, newline)), - ); -} - -#[test] -fn test_multiple_binaries() { - let mut env = Environment::generate_from_fixture("multiple_bins"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - env.with_config(|c| c.build_bin = Inferrable::Specified(Some("bin2".to_owned()))); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - // These messages should be about bin_name1 and bin_name2, but the order is - // not deterministic FIXME(#606) - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None).expect_contains("unused variable: `bin_name"), - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None).expect_contains("unused variable: `bin_name"), - ); - expect_series(&mut server, results, vec!["progress"]); -} - -// FIXME Requires rust-src component, which would break Rust CI -// #[test] -// fn test_completion() { -// let mut env = Environment::generate_from_fixture("common"); - -// let source_file_path = Path::new("src").join("main.rs"); - -// let root_path = env.cache.abs_path(Path::new(".")); -// let url = Url::from_file_path(env.cache.abs_path(&source_file_path)).expect("couldn't convert file path to URL"); -// let text_doc = TextDocumentIdentifier::new(url); - -// let messages = vec![ -// initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), -// request::(11, TextDocumentPositionParams { -// text_document: text_doc.clone(), -// position: env.cache.mk_ls_position(src(&source_file_path, 22, "rld")) -// }).to_string(), -// request::(22, TextDocumentPositionParams { -// text_document: text_doc.clone(), -// position: env.cache.mk_ls_position(src(&source_file_path, 25, "x)")) -// }).to_string(), -// ]; - -// let (mut server, results, ..) = env.mock_server(messages); -// // Initialize and build. -// assert_eq!(ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue); -// expect_message(results.clone(), &[ExpectedMessage::new(Some(0)).expect_contains("capabilities"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Indexing""#), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#)]); - -// assert_eq!(ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue); -// expect_message(results.clone(), &[ExpectedMessage::new(Some(11)).expect_contains(r#"[{"label":"world","kind":6,"detail":"let world = \"world\";"}]"#)]); - -// assert_eq!(ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue); -// expect_message(results.clone(), &[ExpectedMessage::new(Some(22)).expect_contains(r#"{"label":"x","kind":5,"detail":"u64"#)]); -// } - -#[test] -fn test_bin_lib_project() { - let mut env = Environment::generate_from_fixture("bin_lib"); - - let root_path = env.cache.abs_path(Path::new(".")); - - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - env.with_config(|c| { - c.cfg_test = true; - c.build_bin = Inferrable::Specified(Some("bin_lib".into())); - }); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None) - .expect_contains(r#"bin_lib/tests/tests.rs"#) - .expect_contains(r#"unused variable: `unused_var`"#), - ); - - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -// FIXME(#524) timing issues when run concurrently with `test_bin_lib_project` -// #[test] -// fn test_bin_lib_project_no_cfg_test() { -// let mut env = Environment::generate_from_fixture("bin_lib"); - -// let root_path = env.cache.abs_path(Path::new(".")); - -// let messages = vec![ -// initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), -// ]; - -// env.with_config(|c| { -// c.build_lib = Inferrable::Specified(false); -// c.build_bin = Inferrable::Specified(Some("bin_lib".into())); -// }); -// let (mut server, results, ..) = env.mock_server(messages); -// // Initialize and build. -// assert_eq!(ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue); -// expect_message(results.clone(), &[ExpectedMessage::new(Some(0)).expect_contains("capabilities"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Indexing""#), -// ExpectedMessage::new(None).expect_contains("cannot find struct, variant or union type `LibCfgTestStruct` in module `bin_lib`"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#)]); -// } - -// FIXME(#455) reinstate this test -// #[test] -// fn test_simple_workspace() { -// let mut env = Environment::generate_from_fixture("simple_workspace"); - -// let root_path = env.cache.abs_path(Path::new(".")); - -// let messages = vec![ -// initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), -// ]; - -// let (mut server, results, ..) = env.mock_server(messages); -// // Initialize and build. -// assert_eq!(ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue); -// expect_message(results.clone(), &[ExpectedMessage::new(Some(0)).expect_contains("capabilities"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Indexing""#), -// // TODO: Ideally we should check for message contents for different crates/targets, -// // however order of received messages is non-deterministic and this -// // would require implementing something like `or_expect_contains` -// ExpectedMessage::new(None).expect_contains("publishIndexing"), -// ExpectedMessage::new(None).expect_contains("publishIndexing"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#)]); -// } - -#[test] -fn test_infer_lib() { - let mut env = Environment::generate_from_fixture("infer_lib"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None).expect_contains("struct is never constructed: `UnusedLib`"), - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -#[test] -fn test_infer_bin() { - let mut env = Environment::generate_from_fixture("infer_bin"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None).expect_contains("struct is never constructed: `UnusedBin`"), - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -#[test] -fn test_infer_custom_bin() { - let mut env = Environment::generate_from_fixture("infer_custom_bin"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None) - .expect_contains("struct is never constructed: `UnusedCustomBin`"), - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -#[test] -fn test_omit_init_build() { - use serde_json::json; - - let mut env = Environment::generate_from_fixture("common"); - - let root_path = env.cache.abs_path(Path::new(".")); - let root_path = root_path.as_os_str().to_str().map(|x| x.to_owned()); - let init_options = json!({ - "omitInitBuild": true, - "cmdRun": true - }); - let initialize = initialize_with_opts(0, root_path, Some(init_options)); - - let (mut server, results, ..) = env.mock_server(vec![initialize.to_string()]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); -} - -pub trait ConvertStringCase { - fn convert_string_case(s: &str) -> String; -} - -struct CamelCaseConverter; -impl ConvertStringCase for CamelCaseConverter { - fn convert_string_case(s: &str) -> String { - use heck::CamelCase; - s.to_camel_case().to_string() - } -} - -struct KebabCaseConverter; -impl ConvertStringCase for KebabCaseConverter { - fn convert_string_case(s: &str) -> String { - use heck::KebabCase; - s.to_kebab_case().to_string() - } -} - -struct SnakeCaseConverter; -impl ConvertStringCase for SnakeCaseConverter { - fn convert_string_case(s: &str) -> String { - use heck::SnakeCase; - s.to_snake_case().to_string() - } -} - -fn test_init_impl() { - use serde_json::json; - - let mut env = Environment::generate_from_fixture("common"); - - let root_path = env.cache.abs_path(Path::new(".")); - let root_path = root_path.as_os_str().to_str().map(|x| x.to_owned()); - let init_options = json!({ - "settings": { - "rust": { - T::convert_string_case("features"): ["some_feature"], - T::convert_string_case("all_targets"): false - } - } - }); - - let initialize = initialize_with_opts(0, root_path, Some(init_options)); - - let (mut server, results, config) = env.mock_server(vec![initialize.to_string()]); - - assert!( - &config.lock().unwrap().features.is_empty(), - "Default config should have no explicit features enabled" - ); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - assert_eq!(&config.lock().unwrap().features, &["some_feature"]); - assert_eq!(config.lock().unwrap().all_targets, false); -} - -#[test] -fn test_init_with_configuration_camel_case() { - test_init_impl::(); -} - -#[test] -fn test_init_with_configuration_kebab_case() { - test_init_impl::(); -} - -#[test] -fn test_init_with_configuration_snake_case() { - test_init_impl::(); -} - -#[test] -fn test_parse_error_on_malformed_input() { - let _ = env_logger::try_init(); - struct NoneMsgReader; - - impl ls_server::MessageReader for NoneMsgReader { - fn read_message(&self) -> Option { - None - } - } - - let analysis = Arc::new(AnalysisHost::new(Target::Debug)); - let vfs = Arc::new(Vfs::new()); - let reader = Box::new(NoneMsgReader); - let output = RecordOutput::new(); - let results = output.output.clone(); - let mut server = ls_server::LsService::new( - analysis, - vfs, - Arc::new(Mutex::new(Config::default())), - reader, - output, - ); - - let result = ls_server::LsService::handle_message(&mut server); - assert_eq!(result, ls_server::ServerStateChange::Break { exit_code: 101 }); - - let error = results.lock().unwrap().pop().expect("no error response"); - - let failure: jsonrpc_core::Failure = - serde_json::from_str(&error).expect("Couldn't parse json failure response"); - - assert!(failure.error.code == jsonrpc_core::ErrorCode::ParseError); -} - -#[test] -fn test_find_impls() { - let mut env = Environment::generate_from_fixture("find_impls"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - // This test contains code for testing implementations of `Eq`. However, `rust-analysis` is not - // installed on Travis making rls-analysis fail why retrieving the typeid. Installing - // `rust-analysis` is also not an option, because this makes other test timeout. - // e.g., https://travis-ci.org/rust-lang/rls/jobs/265339002 - - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - request::( - 1, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url.clone()), - position: env.cache.mk_ls_position(src(&source_file_path, 13, "Bar")), - }, - ) - .to_string(), - request::( - 2, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 16, "Super")), - }, - ) - .to_string(), - // FIXME Does not work on Travis - // request::( - // 3, - // TextDocumentPositionParams { - // text_document: TextDocumentIdentifier::new(url), - // position: env.cache.mk_ls_position(src(&source_file_path, 20, "Eq")), - // }, - // ).to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - // TODO structural checking of result, rather than looking for a string - src(&source_file_path, 12, "world") - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(1)) - .expect_contains( - r#""range":{"start":{"line":18,"character":15},"end":{"line":18,"character":18}}"#, - ) - .expect_contains( - r#""range":{"start":{"line":19,"character":12},"end":{"line":19,"character":15}}"#, - ), - ); - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(2)) - .expect_contains( - r#""range":{"start":{"line":18,"character":15},"end":{"line":18,"character":18}}"#, - ) - .expect_contains( - r#""range":{"start":{"line":22,"character":15},"end":{"line":22,"character":18}}"#, - ), - ); - // FIXME Does not work on Travis - // assert_eq!(ls_server::LsService::handle_message(&mut server), - // ls_server::ServerStateChange::Continue); - // expect_message(results.clone(), &[ - // // TODO assert that only one position is returned - // ExpectedMessage::new(Some(3)) - // .expect_contains(r#""range":{"start":{"line":19,"character":12},"end":{"line":19,"character":15}}"#) - // ]); -} - -#[test] -fn test_features() { - let mut env = Environment::generate_from_fixture("features"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - env.with_config(|c| c.features = vec!["foo".to_owned()]); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None).expect_contains( - r#""message":"cannot find struct, variant or union type `Bar` in this scope"#, - ), - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -#[test] -fn test_all_features() { - let mut env = Environment::generate_from_fixture("features"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - env.with_config(|c| c.all_features = true); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results, vec!["progress"]); -} - -#[test] -fn test_no_default_features() { - let mut env = Environment::generate_from_fixture("features"); - - let root_path = env.cache.abs_path(Path::new(".")); - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - env.with_config(|c| { - c.no_default_features = true; - c.features = vec!["foo".to_owned(), "bar".to_owned()] - }); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None).expect_contains( - r#""message":"cannot find struct, variant or union type `Baz` in this scope"#, - ), - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -// #[test] -// fn test_handle_utf8_directory() { -// let mut env = Environment::generate_from_fixture("unicødë"); -// -// let root_path = env.cache.abs_path(Path::new(".")); -// let root_url = Url::from_directory_path(&root_path).unwrap(); -// let messages = vec![ -// initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string() -// ]; -// -// let (mut server, results, ..) = env.mock_server(messages); -// // Initialize and build. -// assert_eq!(ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue); -// expect_message(results.clone(), &[ExpectedMessage::new(Some(0)).expect_contains("capabilities"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Indexing""#), -// ExpectedMessage::new(None) -// .expect_contains(root_url.path()) -// .expect_contains("struct is never used: `Unused`"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#)]); -// } - -#[test] -fn test_all_targets() { - let mut env = Environment::generate_from_fixture("bin_lib"); - - let root_path = env.cache.abs_path(Path::new(".")); - - let messages = - vec![initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string()]; - - env.with_config(|c| { - c.all_targets = true; - c.cfg_test = true; - }); - let (mut server, results, ..) = env.mock_server(messages); - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results.clone(), vec!["progress"]); - - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(None) - .expect_contains(r#"bin_lib/tests/tests.rs"#) - .expect_contains(r#"unused variable: `unused_var`"#), - ); - expect_message( - &mut server, - results, - ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), - ); -} - -/// Handle receiving a notification before the `initialize` request by ignoring and -/// continuing to run -#[test] -fn ignore_uninitialized_notification() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - notification::(DidChangeTextDocumentParams { - text_document: VersionedTextDocumentIdentifier { uri: url, version: Some(2) }, - content_changes: vec![TextDocumentContentChangeEvent { - range: Some(Range { - start: Position { line: 19, character: 15 }, - end: Position { line: 19, character: 15 }, - }), - range_length: Some(0), - text: "\n ".into(), - }], - }) - .to_string(), - initialize(1, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - - // Ignore notification - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - - // Initialize and build - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(1)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results, vec!["progress"]); -} - -/// Handle receiving requests before the `initialize` request by returning an error response -/// and continuing to run -#[test] -fn fail_uninitialized_request() { - let mut env = Environment::generate_from_fixture("common"); - - let source_file_path = Path::new("src").join("main.rs"); - let root_path = env.cache.abs_path(Path::new(".")); - let url = Url::from_file_path(env.cache.abs_path(&source_file_path)) - .expect("couldn't convert file path to URL"); - - let messages = vec![ - request::( - 0, - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(url), - position: env.cache.mk_ls_position(src(&source_file_path, 13, "world")), - }, - ) - .to_string(), - initialize(1, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - ]; - - let (mut server, results, ..) = env.mock_server(messages); - - // Return error response to pre `initialize` request, keep running. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - { - server.wait_for_concurrent_jobs(); - let response: Value = serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap(); - assert_eq!(response["id"], 0); - assert_eq!(response["error"]["code"], -32002); - let message = response["error"]["message"].as_str().unwrap(); - assert!( - message.to_lowercase().contains("initialize"), - "Unexpected error.message `{}`", - message, - ); - } - - // Initialize and build. - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(1)).expect_contains("capabilities"), - ); - - expect_series(&mut server, results, vec!["progress"]); -} - -// FIXME disabled since it is failing in the Rust repo. -// #[test] -// fn test_dep_fail() { -// let mut env = Environment::generate_from_fixture("dep_fail"); - -// let root_path = env.cache.abs_path(Path::new(".")); - -// let messages = vec![ -// initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), -// ]; - -// let (mut server, results, ..) = env.mock_server(messages); -// // Initialize and build. -// assert_eq!( -// ls_server::LsService::handle_message(&mut server), -// ls_server::ServerStateChange::Continue -// ); -// expect_message( -// results.clone(), -// &[ -// ExpectedMessage::new(Some(0)).expect_contains("capabilities"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Building""#), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Building""#), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#""done":true"#), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Indexing""#), -// ExpectedMessage::new(None).expect_contains("message").expect_contains("Cargo failed: Error compiling dependent crate"), -// ExpectedMessage::new(None).expect_contains("progress").expect_contains(r#"title":"Indexing""#), -// ], -// ); -// }