From e91d0bda1e80072182615921e8fea827c2ec8118 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Mon, 17 Dec 2018 13:10:45 +0100 Subject: [PATCH 01/14] Split RLS into bin/lib --- src/actions/hover.rs | 4 ++-- src/lib.rs | 52 ++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 40 ++++++++-------------------------- 3 files changed, 63 insertions(+), 33 deletions(-) create mode 100644 src/lib.rs diff --git a/src/actions/hover.rs b/src/actions/hover.rs index ecffa0dbcbb..8311c8d25a9 100644 --- a/src/actions/hover.rs +++ b/src/actions/hover.rs @@ -541,7 +541,7 @@ fn create_tooltip( /// /// # Examples /// -/// ``` +/// ```ignore /// # use std::path::Path; /// /// let base_path = Path::new(".rustup/toolchains/nightly-x86_64-pc-windows-msvc/lib/rustlib/src/rust/src/liballoc/string.rs"); @@ -580,7 +580,7 @@ fn skip_path_components>( /// /// # Example /// -/// ``` +/// ```ignore /// # use std::path::PathBuf; /// /// let path = PathBuf::from("libstd/../liballoc/string.rs"); diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 00000000000..e8a35f8e394 --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,52 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +//! The Rust Language Server. +//! +//! The RLS provides a server that runs in the background, providing IDEs, +//! editors, and other tools with information about Rust programs. It supports +//! functionality such as 'goto definition', symbol search, reformatting, and +//! code completion, and enables renaming and refactorings. + +#![feature(rustc_private, integer_atomics, drain_filter)] +#![feature(crate_visibility_modifier)] // needed for edition 2018 +#![allow(unknown_lints)] +#![warn(clippy::all, rust_2018_idioms)] +#![allow( + clippy::cyclomatic_complexity, + clippy::needless_pass_by_value, + clippy::too_many_arguments +)] + +use log::warn; +use rustc_tools_util::*; + +pub use rls_analysis::{AnalysisHost, Target}; +pub use rls_vfs::Vfs; + +pub mod actions; +pub mod build; +pub mod cmd; +pub mod concurrency; +pub mod config; +pub mod lsp_data; +pub mod project_model; +pub mod server; + +#[cfg(test)] +mod test; + +type Span = rls_span::Span; + +pub const RUSTC_SHIM_ENV_VAR_NAME: &str = "RLS_RUSTC_SHIM"; + +pub fn version() -> String { + rustc_tools_util::get_version_info!().to_string() +} diff --git a/src/main.rs b/src/main.rs index 8f7456a32f3..6ddfa9cd207 100644 --- a/src/main.rs +++ b/src/main.rs @@ -37,34 +37,17 @@ )] extern "C" {} +use rls; + use log::warn; use env_logger; -use rustc_tools_util::*; +use rls_rustc as rustc_shim; use std::env; use std::sync::Arc; -use rls_analysis::{AnalysisHost, Target}; -use rls_rustc as rustc_shim; -use rls_vfs::Vfs; - -pub mod actions; -pub mod build; -pub mod cmd; -pub mod concurrency; -pub mod config; -pub mod lsp_data; -pub mod project_model; -pub mod server; - -#[cfg(test)] -mod test; - -const RUSTC_SHIM_ENV_VAR_NAME: &str = "RLS_RUSTC_SHIM"; const RUSTC_WRAPPER_ENV_VAR: &str = "RUSTC_WRAPPER"; -type Span = rls_span::Span; - /// The main entry point to the RLS. Parses CLI arguments and then runs the /// server. pub fn main() { @@ -88,7 +71,7 @@ fn main_inner() -> i32 { env::remove_var(RUSTC_WRAPPER_ENV_VAR); } - if env::var(RUSTC_SHIM_ENV_VAR_NAME) + if env::var(rls::RUSTC_SHIM_ENV_VAR_NAME) .map(|v| v != "0") .unwrap_or(false) { @@ -99,7 +82,7 @@ fn main_inner() -> i32 { if let Some(first_arg) = ::std::env::args().nth(1) { return match first_arg.as_str() { "--version" | "-V" => { - println!("{}", version()); + println!("{}", rls::version()); 0 } "--help" | "-h" => { @@ -107,7 +90,7 @@ fn main_inner() -> i32 { 0 } "--cli" => { - cmd::run(); + rls::cmd::run(); 0 } unknown => { @@ -121,15 +104,10 @@ fn main_inner() -> i32 { }; } - let analysis = Arc::new(AnalysisHost::new(Target::Debug)); - let vfs = Arc::new(Vfs::new()); - - server::run_server(analysis, vfs) -} + let analysis = Arc::new(rls::AnalysisHost::new(rls::Target::Debug)); + let vfs = Arc::new(rls::Vfs::new()); -fn version() -> String { - let version = rustc_tools_util::get_version_info!(); - version.to_string() + rls::server::run_server(analysis, vfs) } fn help() -> &'static str { From 8ef81dab26e58513815d545c5bc8212a12fb8812 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Tue, 18 Dec 2018 16:25:42 +0100 Subject: [PATCH 02/14] Move src/test/harness to tests/support/harness --- tests/support/harness.rs | 394 +++++++++++++++++++++++++++++++++++++++ tests/support/mod.rs | 1 + 2 files changed, 395 insertions(+) create mode 100644 tests/support/harness.rs diff --git a/tests/support/harness.rs b/tests/support/harness.rs new file mode 100644 index 00000000000..356f93017d5 --- /dev/null +++ b/tests/support/harness.rs @@ -0,0 +1,394 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +#![allow(clippy::expect_fun_call)] + +use std::collections::HashMap; +use std::env; +use std::fs::File; +use std::io::{self, BufRead, BufReader}; +use std::path::{Path, PathBuf}; +use std::sync::atomic::{AtomicUsize, Ordering}; +use std::sync::{Arc, Mutex}; + +use rls::config::{Config, Inferrable}; +use rls::server as ls_server; +use env_logger; +use languageserver_types as ls_types; +use lazy_static::lazy_static; +use rls_analysis::{AnalysisHost, Target}; +use rls_vfs::Vfs; +use serde_json; +use walkdir::WalkDir; + +lazy_static! { + static ref MANIFEST_DIR: &'static Path = Path::new(env!("CARGO_MANIFEST_DIR")); + pub static ref FIXTURES_DIR: PathBuf = MANIFEST_DIR.join("tests").join("fixtures"); +} + +pub(crate) struct Environment { + pub(crate) config: Option, + pub(crate) cache: Cache, + pub(crate) target_path: PathBuf, +} + +impl Environment { + pub(crate) fn generate_from_fixture(fixture_dir: impl AsRef) -> Self { + let _ = env_logger::try_init(); + if env::var("RUSTC").is_err() { + env::set_var("RUSTC", "rustc"); + } + + let fixture_dir = FIXTURES_DIR.join(fixture_dir.as_ref()); + let scratchpad_dir = build_scratchpad_from_fixture(fixture_dir) + .expect("Can't copy fixture files to scratchpad"); + + let target_dir = scratchpad_dir.join("target"); + + let mut config = Config::default(); + config.target_dir = Inferrable::Specified(Some(target_dir.clone())); + config.unstable_features = true; + + let cache = Cache::new(scratchpad_dir); + + Self { + config: Some(config), + cache, + target_path: target_dir, + } + } +} + +impl Environment { + pub(crate) fn with_config(&mut self, f: F) + where + F: FnOnce(&mut Config), + { + let config = self.config.as_mut().unwrap(); + f(config); + } + + // Initialize and run the internals of an LS protocol RLS server. + pub(crate) fn mock_server( + &mut self, + messages: Vec, + ) -> (ls_server::LsService, LsResultList, Arc>) { + let analysis = Arc::new(AnalysisHost::new(Target::Debug)); + let vfs = Arc::new(Vfs::new()); + let config = Arc::new(Mutex::new(self.config.take().unwrap())); + let reader = Box::new(MockMsgReader::new(messages)); + let output = RecordOutput::new(); + let results = output.output.clone(); + ( + ls_server::LsService::new(analysis, vfs, Arc::clone(&config), reader, output), + results, + config, + ) + } +} + +impl Drop for Environment { + fn drop(&mut self) { + use std::fs; + + if fs::metadata(&self.target_path).is_ok() { + fs::remove_dir_all(&self.target_path).expect("failed to tidy up"); + } + } +} + +pub fn build_scratchpad_from_fixture(fixture_dir: impl AsRef) -> io::Result { + let fixture_dir = fixture_dir.as_ref(); + + let dirname = fixture_dir.file_name() + .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No filename"))?; + + // FIXME: For now persist the path; ideally we should clean up after every test + let genroot = tempfile::tempdir()?.into_path().join(dirname); + // Recursively copy read-only fixture files to freshly generated scratchpad + for entry in WalkDir::new(fixture_dir).into_iter() { + let entry = entry?; + let src = entry.path(); + + let relative = src.strip_prefix(fixture_dir).unwrap(); + let dst = genroot.join(relative); + + if std::fs::metadata(src)?.is_dir() { + std::fs::create_dir(dst)?; + } else { + std::fs::copy(src, dst)?; + } + } + + Ok(genroot) +} + +struct MockMsgReader { + messages: Vec, + cur: AtomicUsize, +} + +impl MockMsgReader { + fn new(messages: Vec) -> MockMsgReader { + MockMsgReader { + messages, + cur: AtomicUsize::new(0), + } + } +} + +impl ls_server::MessageReader for MockMsgReader { + fn read_message(&self) -> Option { + // Note that we hold this lock until the end of the function, thus meaning + // that we must finish processing one message before processing the next. + let index = self.cur.fetch_add(1, Ordering::SeqCst); + if index >= self.messages.len() { + return None; + } + + let message = &self.messages[index]; + + Some(message.to_owned()) + } +} + +type LsResultList = Arc>>; + +#[derive(Clone)] +pub(crate) struct RecordOutput { + pub(crate) output: LsResultList, + output_id: Arc>, +} + +impl RecordOutput { + pub(crate) fn new() -> RecordOutput { + RecordOutput { + output: Arc::new(Mutex::new(vec![])), + // use some distinguishable value + output_id: Arc::new(Mutex::new(0x0100_0000)), + } + } +} + +impl ls_server::Output for RecordOutput { + fn response(&self, output: String) { + let mut records = self.output.lock().unwrap(); + records.push(output); + } + + fn provide_id(&self) -> ls_server::RequestId { + let mut id = self.output_id.lock().unwrap(); + *id += 1; + ls_server::RequestId::Num(*id) + } +} + +#[derive(Clone, Debug)] +pub(crate) struct ExpectedMessage { + id: Option, + contains: Vec, +} + +impl ExpectedMessage { + pub(crate) fn new(id: Option) -> ExpectedMessage { + ExpectedMessage { + id, + contains: vec![], + } + } + + pub(crate) fn expect_contains(&mut self, s: &str) -> &mut ExpectedMessage { + self.contains.push(s.to_owned()); + self + } +} + +/// This function checks for messages with a series of constraints (expecrations) +/// to appear in the buffer, removing valid messages and returning when encountering +/// some that didn't meet the expectation +pub(crate) fn expect_series( + server: &mut ls_server::LsService, + results: LsResultList, + contains: Vec<&str>, +) { + let mut expected = ExpectedMessage::new(None); + for c in contains { + expected.expect_contains(c); + } + while try_expect_message(server, results.clone(), &expected).is_ok() {} +} + +/// Expect a single message +/// +/// It panics if the message wasn't valid and removes it from the buffer +/// if it was +pub(crate) fn expect_message( + server: &mut ls_server::LsService, + results: LsResultList, + expected: &ExpectedMessage, +) { + if let Err(e) = try_expect_message(server, results, expected) { + panic!("Assert failed: {}", e); + } +} + +/// Check a single message without panicking +/// +/// A valid message is removed from the buffer while invalid messages +/// are left in place +fn try_expect_message( + server: &mut ls_server::LsService, + results: LsResultList, + expected: &ExpectedMessage, +) -> Result<(), String> { + server.wait_for_concurrent_jobs(); + let mut results = results.lock().unwrap(); + + let found = match results.get(0) { + Some(s) => s, + None => return Err("No message found!".into()), + }; + + let values: serde_json::Value = serde_json::from_str(&found).unwrap(); + if values + .get("jsonrpc") + .expect("Missing jsonrpc field") + .as_str() + .unwrap() + != "2.0" + { + return Err("Bad jsonrpc field".into()); + } + + if let Some(id) = expected.id { + if values + .get("id") + .expect("Missing id field") + .as_u64() + .unwrap() + != id + { + return Err("Unexpected id".into()); + } + } + + for c in &expected.contains { + if found.find(c).is_none() { + return Err(format!("Could not find `{}` in `{}`", c, found)); + } + } + + results.remove(0); + Ok(()) +} + +pub(crate) fn compare_json(actual: &serde_json::Value, expected: &str) { + let expected: serde_json::Value = serde_json::from_str(expected).unwrap(); + if actual != &expected { + panic!( + "JSON differs\nExpected:\n{}\nActual:\n{}\n", + serde_json::to_string_pretty(&expected).unwrap(), + serde_json::to_string_pretty(actual).unwrap(), + ); + } +} + +#[derive(Clone, Copy, Debug)] +pub(crate) struct Src<'a> { + pub(crate) file_name: &'a Path, + // 1 indexed + pub(crate) line: usize, + pub(crate) name: &'a str, +} + +pub(crate) fn src<'a>(file_name: &'a Path, line: usize, name: &'a str) -> Src<'a> { + Src { + file_name, + line, + name, + } +} + +pub(crate) struct Cache { + base_path: PathBuf, + files: HashMap>, +} + +impl Cache { + fn new(base_path: PathBuf) -> Cache { + Cache { + base_path, + files: HashMap::new(), + } + } + + pub(crate) fn mk_ls_position(&mut self, src: Src<'_>) -> ls_types::Position { + let line = self.get_line(src); + let col = line + .find(src.name) + .expect(&format!("Line does not contain name {}", src.name)); + ls_types::Position::new((src.line - 1) as u64, char_of_byte_index(&line, col) as u64) + } + + /// Create a range covering the initial position on the line + /// + /// The line number uses a 0-based index. + pub(crate) fn mk_ls_range_from_line(&mut self, line: u64) -> ls_types::Range { + ls_types::Range::new( + ls_types::Position::new(line, 0), + ls_types::Position::new(line, 0), + ) + } + + pub(crate) fn abs_path(&self, file_name: &Path) -> PathBuf { + let result = self + .base_path + .join(file_name) + .canonicalize() + .expect("Couldn't canonicalise path"); + if cfg!(windows) { + // FIXME: If the \\?\ prefix is not stripped from the canonical path, the HTTP server tests fail. Why? + let result_string = result.to_str().expect("Path contains non-utf8 characters."); + PathBuf::from(&result_string[r"\\?\".len()..]) + } else { + result + } + } + + fn get_line(&mut self, src: Src<'_>) -> String { + let base_path = &self.base_path; + let lines = self + .files + .entry(src.file_name.to_owned()) + .or_insert_with(|| { + let file_name = &base_path.join(src.file_name); + let file = + File::open(file_name).expect(&format!("Couldn't find file: {:?}", file_name)); + let lines = BufReader::new(file).lines(); + lines.collect::, _>>().unwrap() + }); + + if src.line > lines.len() { + panic!("Line {} not in file, found {} lines", src.line, lines.len()); + } + + lines[src.line - 1].to_owned() + } +} + +fn char_of_byte_index(s: &str, byte: usize) -> usize { + for (c, (b, _)) in s.char_indices().enumerate() { + if b == byte { + return c; + } + } + + panic!("Couldn't find byte {} in {:?}", byte, s); +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 74907ce1c2e..7bd022903cf 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -23,6 +23,7 @@ use std::time::{Duration, Instant}; pub mod project_builder; pub mod paths; +pub mod harness; /// Parse valid LSP stdout into a list of json messages pub fn parse_messages(stdout: &str) -> Vec { From fc0969199a2571f129e8b23317195048c5f63889 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Tue, 18 Dec 2018 22:41:34 +0100 Subject: [PATCH 03/14] WIP: Move tests --- tests/lens/mod.rs | 83 ++ tests/translated.rs | 1823 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 1906 insertions(+) create mode 100644 tests/lens/mod.rs create mode 100644 tests/translated.rs diff --git a/tests/lens/mod.rs b/tests/lens/mod.rs new file mode 100644 index 00000000000..4e36b7d07ff --- /dev/null +++ b/tests/lens/mod.rs @@ -0,0 +1,83 @@ +use std::path::Path; + +use languageserver_types::{CodeLensParams, TextDocumentIdentifier}; +use serde_json; +use url::Url; + +use crate::{ + actions::requests, + server as ls_server, + test::{ + harness::{compare_json, expect_message, expect_series, Environment, ExpectedMessage}, + initialize_with_opts, request, + }, +}; + +#[test] +fn test_lens_run() { + use serde_json::json; + + let mut env = Environment::generate_from_fixture("lens_run"); + + let source_file_path = Path::new("src").join("main.rs"); + + 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 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_with_opts( + 0, + root_path, + Some(json!({ "cmdRun": true })), + ).to_string(), + request::( + 100, + CodeLensParams { + text_document: text_doc, + }, + ).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(r#""codeLensProvider":{"resolveProvider":false}"#), + ); + + 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(); + compare_json( + result.get("result").unwrap(), + r#"[{ + "command": { + "command": "rls.run", + "title": "Run test", + "arguments": [{ + "args": [ "test", "--", "--nocapture", "test_foo" ], + "binary": "cargo", + "env": { "RUST_BACKTRACE": "short" } + }] + }, + "range": { + "start": { "character": 3, "line": 14 }, + "end": { "character": 11, "line": 14 } + } + }]"#, + ) +} diff --git a/tests/translated.rs b/tests/translated.rs new file mode 100644 index 00000000000..b924d11ca6e --- /dev/null +++ b/tests/translated.rs @@ -0,0 +1,1823 @@ +// Copyright 2016 The Rust Project Developers. See the COPYRIGHT +// file at the top-level directory of this distribution and at +// http://rust-lang.org/COPYRIGHT. +// +// Licensed under the Apache License, Version 2.0 or the MIT license +// , at your +// option. This file may not be copied, modified, or distributed +// except according to those terms. + +use rls::actions::{notifications, requests}; +use rls::config::{Config, Inferrable}; +use rls::server::{self as ls_server, Notification, Request, RequestId, ShutdownRequest}; +use jsonrpc_core; +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 languageserver_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; + +pub 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, 22, "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":20,"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, 22, "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, 21, "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: 19, + character: 15, + }, + end: Position { + line: 19, + 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":11,"character":11},"end":{"line":11,"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": 11, "character": 18 }, + "start": { "line": 11, "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, 10, "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":9,"character":7},"end":{"line":9,"character":10}}"#, + ).expect_contains( + r#"{"start":{"line":15,"character":14},"end":{"line":15,"character":17}}"#, + ).expect_contains( + r#"{"start":{"line":23,"character":15},"end":{"line":23,"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, 10, "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":9,"character":7},"end":{"line":9,"character":10}}"#, + ).expect_contains( + r#"{"start":{"line":22,"character":15},"end":{"line":22,"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, 22, "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":20,"character":8},"end":{"line":20,"character":13}}"#, + ).expect_contains( + r#"{"start":{"line":21,"character":27},"end":{"line":21,"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, 22, "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":20,"character":8},"end":{"line":20,"character":13}}"#, + ).expect_contains( + r#"{"start":{"line":21,"character":27},"end":{"line":21,"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"), + ); +} + +#[test] +fn test_init_with_configuration() { + 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": { + "features": ["some_feature"], + "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_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_deglob() { + let mut env = Environment::generate_from_fixture("deglob"); + + 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.clone()); + let messages = vec![ + initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), + // request deglob for single wildcard + request::( + 100, + CodeActionParams { + text_document: text_doc.clone(), + range: env.cache.mk_ls_range_from_line(12), + context: CodeActionContext { + diagnostics: vec![], + only: None, + }, + }, + ).to_string(), + // deglob single + request::( + 200, + ExecuteCommandParams { + command: format!("rls.deglobImports-{}", ::std::process::id()), + arguments: vec![ + serde_json::to_value(&requests::DeglobResult { + location: Location { + uri: url.clone(), + range: Range::new(Position::new(12, 13), Position::new(12, 14)), + }, + new_text: "{Stdout, Stdin}".into(), + }).unwrap(), + ], + }, + ).to_string(), + // request deglob for double wildcard + request::( + 1100, + CodeActionParams { + text_document: text_doc, + range: env.cache.mk_ls_range_from_line(15), + context: CodeActionContext { + diagnostics: vec![], + only: None, + }, + }, + ).to_string(), + // deglob two wildcards + request::( + 1200, + ExecuteCommandParams { + command: format!("rls.deglobImports-{}", ::std::process::id()), + arguments: vec![ + serde_json::to_value(&requests::DeglobResult { + location: Location { + uri: url.clone(), + range: Range::new(Position::new(15, 14), Position::new(15, 15)), + }, + new_text: "size_of".into(), + }).unwrap(), + serde_json::to_value(&requests::DeglobResult { + location: Location { + uri: url, + range: Range::new(Position::new(15, 31), Position::new(15, 32)), + }, + new_text: "max".into(), + }).unwrap(), + ], + }, + ).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("rls.deglobImports-"), + ); + + 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 response: Value = serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap(); + assert_eq!(response["id"], 100); + assert_eq!(response["result"][0]["title"], "Deglob import"); + assert_eq!( + response["result"][0]["command"], + &*format!("rls.deglobImports-{}", ::std::process::id()) + ); + let deglob = &response["result"][0]["arguments"][0]; + assert!( + deglob["location"]["uri"] + .as_str() + .unwrap() + .ends_with("deglob/src/main.rs") + ); + let deglob_loc = °lob["location"]["range"]; + assert_eq!(deglob_loc["start"]["line"], 12); + assert_eq!(deglob_loc["start"]["character"], 13); + assert_eq!(deglob_loc["end"]["line"], 12); + assert_eq!(deglob_loc["end"]["character"], 14); + let mut imports: Vec<_> = deglob["new_text"] + .as_str() + .unwrap() + .trim_matches('{') + .trim_matches('}') + .split(", ") + .collect(); + imports.sort(); + assert_eq!(imports, vec!["Stdin", "Stdout"]); + } + + 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"], 0x0100_0001); + assert_eq!(response["method"], "workspace/applyEdit"); + let (key, changes) = response["params"]["edit"]["changes"] + .as_object() + .unwrap() + .iter() + .next() + .unwrap(); + assert!(key.ends_with("deglob/src/main.rs")); + let change = &changes[0]; + assert_eq!(change["range"]["start"]["line"], 12); + assert_eq!(change["range"]["start"]["character"], 13); + assert_eq!(change["range"]["end"]["line"], 12); + assert_eq!(change["range"]["end"]["character"], 14); + let mut imports: Vec<_> = change["newText"] + .as_str() + .expect("newText missing") + .trim_matches('{') + .trim_matches('}') + .split(", ") + .collect(); + imports.sort(); + assert_eq!(imports, vec!["Stdin", "Stdout"]); + + let response: Value = serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap(); + assert_eq!(response["id"], 200); + assert!(response["result"].is_null()); + } + + assert_eq!( + ls_server::LsService::handle_message(&mut server), + ls_server::ServerStateChange::Continue + ); + expect_message( + &mut server, + results.clone(), + ExpectedMessage::new(Some(1100)) + .expect_contains(r#""title":"Deglob imports""#) + .expect_contains(r#""command":"rls.deglobImports-"#) + .expect_contains(r#"{"location":{"range":{"end":{"character":15,"line":15},"start":{"character":14,"line":15}},"uri":"#) + .expect_contains(r#"deglob/src/main.rs"}"#) + .expect_contains(r#""new_text":"size_of""#) + .expect_contains(r#"{"location":{"range":{"end":{"character":32,"line":15},"start":{"character":31,"line":15}},"uri":"#) + .expect_contains(r#"deglob/src/main.rs"}"#) + .expect_contains(r#""new_text":"max""#) + ); + + 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"], 0x0100_0002); + assert_eq!(response["method"], "workspace/applyEdit"); + let (key, changes) = response["params"]["edit"]["changes"] + .as_object() + .unwrap() + .iter() + .next() + .unwrap(); + assert!(key.ends_with("deglob/src/main.rs")); + let change = &changes[0]; + assert_eq!(change["range"]["start"]["line"], 15); + assert_eq!(change["range"]["start"]["character"], 14); + assert_eq!(change["range"]["end"]["line"], 15); + assert_eq!(change["range"]["end"]["character"], 15); + assert_eq!(change["newText"], "size_of"); + let change = &changes[1]; + assert_eq!(change["range"]["start"]["line"], 15); + assert_eq!(change["range"]["start"]["character"], 31); + assert_eq!(change["range"]["end"]["line"], 15); + assert_eq!(change["range"]["end"]["character"], 32); + assert_eq!(change["newText"], "max"); + } + + expect_message( + &mut server, + results, + ExpectedMessage::new(Some(1200)).expect_contains(r#"null"#), + ); +} + +#[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, 22, "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""#), +// ], +// ); +// } From 501c7cbec636b60debcfd445639745cffb9e0fc6 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Fri, 21 Dec 2018 21:12:44 +0100 Subject: [PATCH 04/14] Beautify main.rs and lib.rs --- src/lib.rs | 6 ++---- src/main.rs | 21 +++------------------ 2 files changed, 5 insertions(+), 22 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index e8a35f8e394..45c4377c493 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -21,13 +21,9 @@ #![warn(clippy::all, rust_2018_idioms)] #![allow( clippy::cyclomatic_complexity, - clippy::needless_pass_by_value, clippy::too_many_arguments )] -use log::warn; -use rustc_tools_util::*; - pub use rls_analysis::{AnalysisHost, Target}; pub use rls_vfs::Vfs; @@ -48,5 +44,7 @@ type Span = rls_span::Span; pub const RUSTC_SHIM_ENV_VAR_NAME: &str = "RLS_RUSTC_SHIM"; pub fn version() -> String { + use rustc_tools_util::VersionInfo; + rustc_tools_util::get_version_info!().to_string() } diff --git a/src/main.rs b/src/main.rs index 6ddfa9cd207..a054615618d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -15,15 +15,6 @@ //! functionality such as 'goto definition', symbol search, reformatting, and //! code completion, and enables renaming and refactorings. -#![feature(rustc_private, integer_atomics, drain_filter)] -#![feature(crate_visibility_modifier)] // needed for edition 2018 -#![allow(unknown_lints)] -#![warn(clippy::all, rust_2018_idioms)] -#![allow( - clippy::cyclomatic_complexity, - clippy::needless_pass_by_value, - clippy::too_many_arguments -)] // See rustc/rustc.rs in rust repo for explanation of stack adjustments. #![feature(link_args)] #[allow(unused_attributes)] @@ -37,10 +28,7 @@ )] extern "C" {} -use rls; - use log::warn; -use env_logger; use rls_rustc as rustc_shim; use std::env; @@ -52,7 +40,7 @@ const RUSTC_WRAPPER_ENV_VAR: &str = "RUSTC_WRAPPER"; /// server. pub fn main() { let exit_code = main_inner(); - ::std::process::exit(exit_code); + std::process::exit(exit_code); } fn main_inner() -> i32 { @@ -71,15 +59,12 @@ fn main_inner() -> i32 { env::remove_var(RUSTC_WRAPPER_ENV_VAR); } - if env::var(rls::RUSTC_SHIM_ENV_VAR_NAME) - .map(|v| v != "0") - .unwrap_or(false) - { + if env::var(rls::RUSTC_SHIM_ENV_VAR_NAME).ok().map_or(false, |v| v != "0") { rustc_shim::run(); return 0; } - if let Some(first_arg) = ::std::env::args().nth(1) { + if let Some(first_arg) = env::args().nth(1) { return match first_arg.as_str() { "--version" | "-V" => { println!("{}", rls::version()); From 8de90e09206e971158a6c80120d6f633596bd679 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Fri, 21 Dec 2018 21:53:45 +0100 Subject: [PATCH 05/14] Suppress unused warnings in tests/* --- tests/tests.rs | 1 + tests/translated.rs | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/tests.rs b/tests/tests.rs index a7d1dbe4e32..2a02d4f284c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -16,6 +16,7 @@ use std::time::Duration; use self::support::{basic_bin_manifest, RlsStdout}; use self::support::project_builder::project; +#[allow(dead_code)] mod support; /// Returns a timeout for waiting for rls stdout messages diff --git a/tests/translated.rs b/tests/translated.rs index b924d11ca6e..3b268de6bfb 100644 --- a/tests/translated.rs +++ b/tests/translated.rs @@ -30,7 +30,8 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use url::Url; -pub mod support; +#[allow(dead_code)] +mod support; fn initialize(id: usize, root_path: Option) -> Request { initialize_with_opts(id, root_path, None) From 8ccf1fdb5550b46de6dbc52df86017e304150fc2 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Fri, 21 Dec 2018 22:46:56 +0100 Subject: [PATCH 06/14] Move lens test to tests/ --- src/test/lens.rs | 83 -------------------------------------------- src/test/mod.rs | 1 - tests/lens.rs | 74 +++++++++++++++++++++++++++++++++++++++ tests/lens/mod.rs | 83 -------------------------------------------- tests/support/mod.rs | 11 ++++++ tests/tests.rs | 13 +------ 6 files changed, 86 insertions(+), 179 deletions(-) delete mode 100644 src/test/lens.rs create mode 100644 tests/lens.rs delete mode 100644 tests/lens/mod.rs diff --git a/src/test/lens.rs b/src/test/lens.rs deleted file mode 100644 index 4e36b7d07ff..00000000000 --- a/src/test/lens.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::path::Path; - -use languageserver_types::{CodeLensParams, TextDocumentIdentifier}; -use serde_json; -use url::Url; - -use crate::{ - actions::requests, - server as ls_server, - test::{ - harness::{compare_json, expect_message, expect_series, Environment, ExpectedMessage}, - initialize_with_opts, request, - }, -}; - -#[test] -fn test_lens_run() { - use serde_json::json; - - let mut env = Environment::generate_from_fixture("lens_run"); - - let source_file_path = Path::new("src").join("main.rs"); - - 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 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_with_opts( - 0, - root_path, - Some(json!({ "cmdRun": true })), - ).to_string(), - request::( - 100, - CodeLensParams { - text_document: text_doc, - }, - ).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(r#""codeLensProvider":{"resolveProvider":false}"#), - ); - - 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(); - compare_json( - result.get("result").unwrap(), - r#"[{ - "command": { - "command": "rls.run", - "title": "Run test", - "arguments": [{ - "args": [ "test", "--", "--nocapture", "test_foo" ], - "binary": "cargo", - "env": { "RUST_BACKTRACE": "short" } - }] - }, - "range": { - "start": { "character": 3, "line": 14 }, - "end": { "character": 11, "line": 14 } - } - }]"#, - ) -} diff --git a/src/test/mod.rs b/src/test/mod.rs index f5e745bfce8..0dcd4a48443 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -9,7 +9,6 @@ // except according to those terms. #[macro_use] mod harness; -mod lens; use crate::actions::{notifications, requests}; use crate::config::{Config, Inferrable}; diff --git a/tests/lens.rs b/tests/lens.rs new file mode 100644 index 00000000000..2ec051d441b --- /dev/null +++ b/tests/lens.rs @@ -0,0 +1,74 @@ +use rls::actions::requests; + +use languageserver_types::request::Request as _; + +use self::support::rls_timeout; +use self::support::harness::{compare_json, FIXTURES_DIR}; +use self::support::project_builder::ProjectBuilder; +use serde_json; +use serde_json::json; + +#[allow(dead_code)] +mod support; + +#[test] +fn cmd_lens_run() { + let p = ProjectBuilder::try_from_fixture(FIXTURES_DIR.join("lens_run")) + .unwrap() + .build(); + let root_path = p.root(); + let mut rls = p.spawn_rls(); + + rls.request( + 0, + "initialize", + Some(json!({ + "rootPath": root_path, + "capabilities": {}, + "initializationOptions": { "cmdRun": true } + })), + ) + .unwrap(); + + let json: Vec<_> = rls + .wait_until_done_indexing(rls_timeout()) + .to_json_messages() + .collect(); + assert!(json.len() >= 7); + + let request_id = 1; + rls.request( + request_id, + requests::CodeLensRequest::METHOD, + Some(json!({ + "textDocument": { + "uri": format!("file://{}/src/main.rs", root_path.display()), + "version": 1 + } + })), + ) + .unwrap(); + + let json = rls.wait_until_json_id(request_id, rls_timeout()); + + compare_json( + &json["result"], + r#"[{ + "command": { + "command": "rls.run", + "title": "Run test", + "arguments": [{ + "args": [ "test", "--", "--nocapture", "test_foo" ], + "binary": "cargo", + "env": { "RUST_BACKTRACE": "short" } + }] + }, + "range": { + "start": { "character": 3, "line": 14 }, + "end": { "character": 11, "line": 14 } + } + }]"#, + ); + + rls.shutdown(rls_timeout()); +} diff --git a/tests/lens/mod.rs b/tests/lens/mod.rs deleted file mode 100644 index 4e36b7d07ff..00000000000 --- a/tests/lens/mod.rs +++ /dev/null @@ -1,83 +0,0 @@ -use std::path::Path; - -use languageserver_types::{CodeLensParams, TextDocumentIdentifier}; -use serde_json; -use url::Url; - -use crate::{ - actions::requests, - server as ls_server, - test::{ - harness::{compare_json, expect_message, expect_series, Environment, ExpectedMessage}, - initialize_with_opts, request, - }, -}; - -#[test] -fn test_lens_run() { - use serde_json::json; - - let mut env = Environment::generate_from_fixture("lens_run"); - - let source_file_path = Path::new("src").join("main.rs"); - - 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 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_with_opts( - 0, - root_path, - Some(json!({ "cmdRun": true })), - ).to_string(), - request::( - 100, - CodeLensParams { - text_document: text_doc, - }, - ).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(r#""codeLensProvider":{"resolveProvider":false}"#), - ); - - 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(); - compare_json( - result.get("result").unwrap(), - r#"[{ - "command": { - "command": "rls.run", - "title": "Run test", - "arguments": [{ - "args": [ "test", "--", "--nocapture", "test_foo" ], - "binary": "cargo", - "env": { "RUST_BACKTRACE": "short" } - }] - }, - "range": { - "start": { "character": 3, "line": 14 }, - "end": { "character": 11, "line": 14 } - } - }]"#, - ) -} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 7bd022903cf..4898e24a4c9 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -25,6 +25,17 @@ pub mod project_builder; pub mod paths; pub mod harness; +/// Returns a timeout for waiting for rls stdout messages +/// +/// Env var `RLS_TEST_WAIT_FOR_AGES` allows super long waiting for CI +pub fn rls_timeout() -> Duration { + Duration::from_secs(if std::env::var("RLS_TEST_WAIT_FOR_AGES").is_ok() { + 300 + } else { + 15 + }) +} + /// Parse valid LSP stdout into a list of json messages pub fn parse_messages(stdout: &str) -> Vec { let mut messages = vec![]; diff --git a/tests/tests.rs b/tests/tests.rs index 2a02d4f284c..cf35a3a524e 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -13,23 +13,12 @@ use serde_json::{self, json}; use std::io::Write; use std::time::Duration; -use self::support::{basic_bin_manifest, RlsStdout}; +use self::support::{basic_bin_manifest, RlsStdout, rls_timeout}; use self::support::project_builder::project; #[allow(dead_code)] mod support; -/// Returns a timeout for waiting for rls stdout messages -/// -/// Env var `RLS_TEST_WAIT_FOR_AGES` allows super long waiting for CI -fn rls_timeout() -> Duration { - Duration::from_secs(if std::env::var("RLS_TEST_WAIT_FOR_AGES").is_ok() { - 300 - } else { - 15 - }) -} - #[test] fn cmd_test_infer_bin() { let p = project("simple_workspace") From 9e5cb5331a7a9284c98f59ec3e8302668cd1fb66 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Fri, 21 Dec 2018 22:54:13 +0100 Subject: [PATCH 07/14] Centralise FIXTURES_DIR across unit and integration tests --- src/test/harness.rs | 6 +----- src/test/mod.rs | 7 +++++-- tests/support/fixtures.rs | 8 ++++++++ 3 files changed, 14 insertions(+), 7 deletions(-) create mode 100644 tests/support/fixtures.rs diff --git a/src/test/harness.rs b/src/test/harness.rs index d8170ee8441..c89b4853199 100644 --- a/src/test/harness.rs +++ b/src/test/harness.rs @@ -22,16 +22,12 @@ use crate::config::{Config, Inferrable}; use crate::server as ls_server; use env_logger; use languageserver_types as ls_types; -use lazy_static::lazy_static; use rls_analysis::{AnalysisHost, Target}; use rls_vfs::Vfs; use serde_json; use walkdir::WalkDir; -lazy_static! { - static ref MANIFEST_DIR: &'static Path = Path::new(env!("CARGO_MANIFEST_DIR")); - pub static ref FIXTURES_DIR: PathBuf = MANIFEST_DIR.join("tests").join("fixtures"); -} +use super::FIXTURES_DIR; crate struct Environment { crate config: Option, diff --git a/src/test/mod.rs b/src/test/mod.rs index 0dcd4a48443..e561891df10 100644 --- a/src/test/mod.rs +++ b/src/test/mod.rs @@ -10,6 +10,11 @@ #[macro_use] mod harness; +#[path = "../../tests/support/fixtures.rs"] +mod fixtures; + +pub use self::fixtures::FIXTURES_DIR; + use crate::actions::{notifications, requests}; use crate::config::{Config, Inferrable}; use crate::server::{self as ls_server, Notification, Request, RequestId, ShutdownRequest}; @@ -32,8 +37,6 @@ use std::sync::{Arc, Mutex}; use std::time::Instant; use url::Url; -pub use self::harness::FIXTURES_DIR; - fn initialize(id: usize, root_path: Option) -> Request { initialize_with_opts(id, root_path, None) } diff --git a/tests/support/fixtures.rs b/tests/support/fixtures.rs new file mode 100644 index 00000000000..a1a49049464 --- /dev/null +++ b/tests/support/fixtures.rs @@ -0,0 +1,8 @@ +use std::path::{Path, PathBuf}; + +use lazy_static::lazy_static; + +lazy_static! { + static ref MANIFEST_DIR: &'static Path = Path::new(env!("CARGO_MANIFEST_DIR")); + pub static ref FIXTURES_DIR: PathBuf = MANIFEST_DIR.join("tests/fixtures"); +} \ No newline at end of file From 881ab943f62cc041039f2ca0932d3dc36592c538 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Fri, 21 Dec 2018 23:27:46 +0100 Subject: [PATCH 08/14] Remove src/test/mod.rs --- build.rs | 7 + src/actions/diagnostics.rs | 9 +- src/actions/hover.rs | 33 +- src/lib.rs | 3 - src/test/harness.rs | 390 -------- src/test/mod.rs | 1828 ------------------------------------ 6 files changed, 31 insertions(+), 2239 deletions(-) delete mode 100644 src/test/harness.rs delete mode 100644 src/test/mod.rs diff --git a/build.rs b/build.rs index 72ee7376fe8..095258c7ed5 100644 --- a/build.rs +++ b/build.rs @@ -8,6 +8,9 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use std::path::Path; +use std::env; + fn main() { println!("cargo:rerun-if-changed=build.rs"); println!("cargo:rerun-if-env-changed=CFG_RELEASE_CHANNEL"); @@ -20,4 +23,8 @@ fn main() { "cargo:rustc-env=COMMIT_DATE={}", rustc_tools_util::get_commit_date().unwrap_or_default() ); + println!( + "cargo:rustc-env=FIXTURES_DIR={}", + Path::new(&env::var("CARGO_MANIFEST_DIR").unwrap()).join("tests/fixtures").display() + ); } diff --git a/src/actions/diagnostics.rs b/src/actions/diagnostics.rs index 8e5c82903a4..e16246f2658 100644 --- a/src/actions/diagnostics.rs +++ b/src/actions/diagnostics.rs @@ -344,15 +344,18 @@ impl IsWithin for Range { /// Tests for formatted messages from the compilers json output /// run cargo with `--message-format=json` to generate the json for new tests and add .json -/// message files to '$(crate::test::FIXTURES_DIR)/compiler_message/' +/// message files to '$FIXTURES_DIR/compiler_message/' #[cfg(test)] mod diagnostic_message_test { use super::*; use languageserver_types::Position; - pub(super) use crate::test::FIXTURES_DIR; + + pub(super) fn fixtures_dir() -> &'static Path { + Path::new(env!("FIXTURES_DIR")) + } pub(super) fn read_fixture(path: impl AsRef) -> String { - std::fs::read_to_string(FIXTURES_DIR.join(path.as_ref())).unwrap() + std::fs::read_to_string(fixtures_dir().join(path.as_ref())).unwrap() } pub(super) fn parse_compiler_message( diff --git a/src/actions/hover.rs b/src/actions/hover.rs index 8311c8d25a9..07c888b623d 100644 --- a/src/actions/hover.rs +++ b/src/actions/hover.rs @@ -979,7 +979,6 @@ pub mod test { use crate::lsp_data::{ClientCapabilities, InitializationOptions}; use crate::lsp_data::{Position, TextDocumentIdentifier, TextDocumentPositionParams}; use crate::server::{Output, RequestId}; - use crate::test::FIXTURES_DIR; use rls_analysis as analysis; use serde_derive::{Deserialize, Serialize}; use serde_json as json; @@ -992,9 +991,13 @@ pub mod test { use std::sync::{Arc, Mutex}; use std::fmt; + pub fn fixtures_dir() -> &'static Path { + Path::new(env!("FIXTURES_DIR")) + } + #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] pub struct Test { - /// Relative to the project _source_ dir (e.g. relative to FIXTURES_DIR/hover/src) + /// Relative to the project _source_ dir (e.g. relative to $FIXTURES_DIR/hover/src) pub file: String, /// One-based line number pub line: u64, @@ -1658,7 +1661,7 @@ pub mod test { #[test] fn test_extract_decl() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_decl.rs"); + let file = fixtures_dir().join("hover/src/test_extract_decl.rs"); let expected = "pub fn foo() -> Foo"; let row_start = Row::new_zero_indexed(10); @@ -1823,7 +1826,7 @@ pub mod test { #[test] fn test_extract_decl_multiline_empty_function() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_decl_multiline_empty_function.rs"); + let file = fixtures_dir().join("hover/src/test_extract_decl_multiline_empty_function.rs"); let expected = noindent( " @@ -1845,7 +1848,7 @@ pub mod test { #[test] fn test_extract_docs_module_docs_with_attribute() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_module_docs_with_attribute.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_module_docs_with_attribute.rs"); let row_start = Row::new_zero_indexed(0); let actual = extract_docs(&vfs, &file, row_start) .expect(&format!("failed to extract docs: {:?}", file)) @@ -1869,7 +1872,7 @@ pub mod test { #[test] fn test_extract_docs_module_docs_no_copyright() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_module_docs_no_copyright.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_module_docs_no_copyright.rs"); let row_start = Row::new_zero_indexed(0); let actual = extract_docs(&vfs, &file, row_start) .expect(&format!("failed to extract docs: {:?}", file)) @@ -1893,7 +1896,7 @@ pub mod test { #[test] fn test_extract_docs_comment_block() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_comment_block.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_comment_block.rs"); let row_start = Row::new_zero_indexed(21); let actual = extract_docs(&vfs, &file, row_start) .expect(&format!("failed to extract docs: {:?}", file)) @@ -1917,7 +1920,7 @@ pub mod test { #[test] fn test_extract_docs_empty_line_before_decl() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_empty_line_before_decl.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_empty_line_before_decl.rs"); let row_start = Row::new_zero_indexed(18); let actual = extract_docs(&vfs, &file, row_start) .expect(&format!("failed to extract docs: {:?}", file)) @@ -1941,7 +1944,7 @@ pub mod test { #[test] fn test_extract_docs_module_docs() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_module_docs.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_module_docs.rs"); let row_start = Row::new_zero_indexed(0); let actual = extract_docs(&vfs, &file, row_start) @@ -1981,7 +1984,7 @@ pub mod test { #[test] fn test_extract_docs_attributes() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_attributes.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_attributes.rs"); let row_start = Row::new_zero_indexed(21); let actual = extract_docs(&vfs, &file, row_start) @@ -2025,7 +2028,7 @@ pub mod test { #[test] fn test_extract_docs_comment_first_line() { let vfs = Vfs::new(); - let file = FIXTURES_DIR.join("hover/src/test_extract_docs_comment_first_line.rs"); + let file = fixtures_dir().join("hover/src/test_extract_docs_comment_first_line.rs"); let row_start = Row::new_zero_indexed(1); let actual = extract_docs(&vfs, &file, row_start) @@ -2133,7 +2136,7 @@ pub mod test { Test::new("test_tooltip_mod_use.rs", 13, 28), ]; - run_tooltip_tests(&tests, FIXTURES_DIR.join("hover"), RacerFallback::No) + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::No) } #[test] @@ -2148,7 +2151,7 @@ pub mod test { Test::new("test_tooltip_mod_use_external.rs", 12, 12), ]; - run_tooltip_tests(&tests, FIXTURES_DIR.join("hover"), RacerFallback::Yes) + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::Yes) } /// Note: This test is ignored as it doesn't work in the rust-lang/rust repo. @@ -2175,7 +2178,7 @@ pub mod test { Test::new("test_tooltip_std.rs", 25, 25), ]; - run_tooltip_tests(&tests, FIXTURES_DIR.join("hover"), RacerFallback::No) + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::No) } /// Note: This test is ignored as it doesn't work in the rust-lang/rust repo. @@ -2192,6 +2195,6 @@ pub mod test { Test::new("test_tooltip_mod_use_external.rs", 15, 12), ]; - run_tooltip_tests(&tests, FIXTURES_DIR.join("hover"), RacerFallback::Yes) + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::Yes) } } diff --git a/src/lib.rs b/src/lib.rs index 45c4377c493..615f178c219 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -36,9 +36,6 @@ pub mod lsp_data; pub mod project_model; pub mod server; -#[cfg(test)] -mod test; - type Span = rls_span::Span; pub const RUSTC_SHIM_ENV_VAR_NAME: &str = "RLS_RUSTC_SHIM"; diff --git a/src/test/harness.rs b/src/test/harness.rs deleted file mode 100644 index c89b4853199..00000000000 --- a/src/test/harness.rs +++ /dev/null @@ -1,390 +0,0 @@ -// Copyright 2016 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. - -#![allow(clippy::expect_fun_call)] - -use std::collections::HashMap; -use std::env; -use std::fs::File; -use std::io::{self, BufRead, BufReader}; -use std::path::{Path, PathBuf}; -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::{Arc, Mutex}; - -use crate::config::{Config, Inferrable}; -use crate::server as ls_server; -use env_logger; -use languageserver_types as ls_types; -use rls_analysis::{AnalysisHost, Target}; -use rls_vfs::Vfs; -use serde_json; -use walkdir::WalkDir; - -use super::FIXTURES_DIR; - -crate struct Environment { - crate config: Option, - crate cache: Cache, - crate target_path: PathBuf, -} - -impl Environment { - crate fn generate_from_fixture(fixture_dir: impl AsRef) -> Self { - let _ = env_logger::try_init(); - if env::var("RUSTC").is_err() { - env::set_var("RUSTC", "rustc"); - } - - let fixture_dir = FIXTURES_DIR.join(fixture_dir.as_ref()); - let scratchpad_dir = build_scratchpad_from_fixture(fixture_dir) - .expect("Can't copy fixture files to scratchpad"); - - let target_dir = scratchpad_dir.join("target"); - - let mut config = Config::default(); - config.target_dir = Inferrable::Specified(Some(target_dir.clone())); - config.unstable_features = true; - - let cache = Cache::new(scratchpad_dir); - - Self { - config: Some(config), - cache, - target_path: target_dir, - } - } -} - -impl Environment { - crate fn with_config(&mut self, f: F) - where - F: FnOnce(&mut Config), - { - let config = self.config.as_mut().unwrap(); - f(config); - } - - // Initialize and run the internals of an LS protocol RLS server. - crate fn mock_server( - &mut self, - messages: Vec, - ) -> (ls_server::LsService, LsResultList, Arc>) { - let analysis = Arc::new(AnalysisHost::new(Target::Debug)); - let vfs = Arc::new(Vfs::new()); - let config = Arc::new(Mutex::new(self.config.take().unwrap())); - let reader = Box::new(MockMsgReader::new(messages)); - let output = RecordOutput::new(); - let results = output.output.clone(); - ( - ls_server::LsService::new(analysis, vfs, Arc::clone(&config), reader, output), - results, - config, - ) - } -} - -impl Drop for Environment { - fn drop(&mut self) { - use std::fs; - - if fs::metadata(&self.target_path).is_ok() { - fs::remove_dir_all(&self.target_path).expect("failed to tidy up"); - } - } -} - -pub fn build_scratchpad_from_fixture(fixture_dir: impl AsRef) -> io::Result { - let fixture_dir = fixture_dir.as_ref(); - - let dirname = fixture_dir.file_name() - .ok_or_else(|| io::Error::new(io::ErrorKind::NotFound, "No filename"))?; - - // FIXME: For now persist the path; ideally we should clean up after every test - let genroot = tempfile::tempdir()?.into_path().join(dirname); - // Recursively copy read-only fixture files to freshly generated scratchpad - for entry in WalkDir::new(fixture_dir).into_iter() { - let entry = entry?; - let src = entry.path(); - - let relative = src.strip_prefix(fixture_dir).unwrap(); - let dst = genroot.join(relative); - - if std::fs::metadata(src)?.is_dir() { - std::fs::create_dir(dst)?; - } else { - std::fs::copy(src, dst)?; - } - } - - Ok(genroot) -} - -struct MockMsgReader { - messages: Vec, - cur: AtomicUsize, -} - -impl MockMsgReader { - fn new(messages: Vec) -> MockMsgReader { - MockMsgReader { - messages, - cur: AtomicUsize::new(0), - } - } -} - -impl ls_server::MessageReader for MockMsgReader { - fn read_message(&self) -> Option { - // Note that we hold this lock until the end of the function, thus meaning - // that we must finish processing one message before processing the next. - let index = self.cur.fetch_add(1, Ordering::SeqCst); - if index >= self.messages.len() { - return None; - } - - let message = &self.messages[index]; - - Some(message.to_owned()) - } -} - -type LsResultList = Arc>>; - -#[derive(Clone)] -crate struct RecordOutput { - crate output: LsResultList, - output_id: Arc>, -} - -impl RecordOutput { - crate fn new() -> RecordOutput { - RecordOutput { - output: Arc::new(Mutex::new(vec![])), - // use some distinguishable value - output_id: Arc::new(Mutex::new(0x0100_0000)), - } - } -} - -impl ls_server::Output for RecordOutput { - fn response(&self, output: String) { - let mut records = self.output.lock().unwrap(); - records.push(output); - } - - fn provide_id(&self) -> ls_server::RequestId { - let mut id = self.output_id.lock().unwrap(); - *id += 1; - ls_server::RequestId::Num(*id) - } -} - -#[derive(Clone, Debug)] -crate struct ExpectedMessage { - id: Option, - contains: Vec, -} - -impl ExpectedMessage { - crate fn new(id: Option) -> ExpectedMessage { - ExpectedMessage { - id, - contains: vec![], - } - } - - crate fn expect_contains(&mut self, s: &str) -> &mut ExpectedMessage { - self.contains.push(s.to_owned()); - self - } -} - -/// This function checks for messages with a series of constraints (expecrations) -/// to appear in the buffer, removing valid messages and returning when encountering -/// some that didn't meet the expectation -crate fn expect_series( - server: &mut ls_server::LsService, - results: LsResultList, - contains: Vec<&str>, -) { - let mut expected = ExpectedMessage::new(None); - for c in contains { - expected.expect_contains(c); - } - while try_expect_message(server, results.clone(), &expected).is_ok() {} -} - -/// Expect a single message -/// -/// It panics if the message wasn't valid and removes it from the buffer -/// if it was -crate fn expect_message( - server: &mut ls_server::LsService, - results: LsResultList, - expected: &ExpectedMessage, -) { - if let Err(e) = try_expect_message(server, results, expected) { - panic!("Assert failed: {}", e); - } -} - -/// Check a single message without panicking -/// -/// A valid message is removed from the buffer while invalid messages -/// are left in place -fn try_expect_message( - server: &mut ls_server::LsService, - results: LsResultList, - expected: &ExpectedMessage, -) -> Result<(), String> { - server.wait_for_concurrent_jobs(); - let mut results = results.lock().unwrap(); - - let found = match results.get(0) { - Some(s) => s, - None => return Err("No message found!".into()), - }; - - let values: serde_json::Value = serde_json::from_str(&found).unwrap(); - if values - .get("jsonrpc") - .expect("Missing jsonrpc field") - .as_str() - .unwrap() - != "2.0" - { - return Err("Bad jsonrpc field".into()); - } - - if let Some(id) = expected.id { - if values - .get("id") - .expect("Missing id field") - .as_u64() - .unwrap() - != id - { - return Err("Unexpected id".into()); - } - } - - for c in &expected.contains { - if found.find(c).is_none() { - return Err(format!("Could not find `{}` in `{}`", c, found)); - } - } - - results.remove(0); - Ok(()) -} - -crate fn compare_json(actual: &serde_json::Value, expected: &str) { - let expected: serde_json::Value = serde_json::from_str(expected).unwrap(); - if actual != &expected { - panic!( - "JSON differs\nExpected:\n{}\nActual:\n{}\n", - serde_json::to_string_pretty(&expected).unwrap(), - serde_json::to_string_pretty(actual).unwrap(), - ); - } -} - -#[derive(Clone, Copy, Debug)] -crate struct Src<'a> { - crate file_name: &'a Path, - // 1 indexed - crate line: usize, - crate name: &'a str, -} - -crate fn src<'a>(file_name: &'a Path, line: usize, name: &'a str) -> Src<'a> { - Src { - file_name, - line, - name, - } -} - -crate struct Cache { - base_path: PathBuf, - files: HashMap>, -} - -impl Cache { - fn new(base_path: PathBuf) -> Cache { - Cache { - base_path, - files: HashMap::new(), - } - } - - crate fn mk_ls_position(&mut self, src: Src<'_>) -> ls_types::Position { - let line = self.get_line(src); - let col = line - .find(src.name) - .expect(&format!("Line does not contain name {}", src.name)); - ls_types::Position::new((src.line - 1) as u64, char_of_byte_index(&line, col) as u64) - } - - /// Create a range covering the initial position on the line - /// - /// The line number uses a 0-based index. - crate fn mk_ls_range_from_line(&mut self, line: u64) -> ls_types::Range { - ls_types::Range::new( - ls_types::Position::new(line, 0), - ls_types::Position::new(line, 0), - ) - } - - crate fn abs_path(&self, file_name: &Path) -> PathBuf { - let result = self - .base_path - .join(file_name) - .canonicalize() - .expect("Couldn't canonicalise path"); - if cfg!(windows) { - // FIXME: If the \\?\ prefix is not stripped from the canonical path, the HTTP server tests fail. Why? - let result_string = result.to_str().expect("Path contains non-utf8 characters."); - PathBuf::from(&result_string[r"\\?\".len()..]) - } else { - result - } - } - - fn get_line(&mut self, src: Src<'_>) -> String { - let base_path = &self.base_path; - let lines = self - .files - .entry(src.file_name.to_owned()) - .or_insert_with(|| { - let file_name = &base_path.join(src.file_name); - let file = - File::open(file_name).expect(&format!("Couldn't find file: {:?}", file_name)); - let lines = BufReader::new(file).lines(); - lines.collect::, _>>().unwrap() - }); - - if src.line > lines.len() { - panic!("Line {} not in file, found {} lines", src.line, lines.len()); - } - - lines[src.line - 1].to_owned() - } -} - -fn char_of_byte_index(s: &str, byte: usize) -> usize { - for (c, (b, _)) in s.char_indices().enumerate() { - if b == byte { - return c; - } - } - - panic!("Couldn't find byte {} in {:?}", byte, s); -} diff --git a/src/test/mod.rs b/src/test/mod.rs deleted file mode 100644 index e561891df10..00000000000 --- a/src/test/mod.rs +++ /dev/null @@ -1,1828 +0,0 @@ -// Copyright 2016 The Rust Project Developers. See the COPYRIGHT -// file at the top-level directory of this distribution and at -// http://rust-lang.org/COPYRIGHT. -// -// Licensed under the Apache License, Version 2.0 or the MIT license -// , at your -// option. This file may not be copied, modified, or distributed -// except according to those terms. -#[macro_use] -mod harness; - -#[path = "../../tests/support/fixtures.rs"] -mod fixtures; - -pub use self::fixtures::FIXTURES_DIR; - -use crate::actions::{notifications, requests}; -use crate::config::{Config, Inferrable}; -use crate::server::{self as ls_server, Notification, Request, RequestId, ShutdownRequest}; -use jsonrpc_core; -use rls_analysis::{AnalysisHost, Target}; -use rls_vfs::Vfs; -use serde_json::Value; - -use self::harness::{ - compare_json, expect_message, expect_series, src, Environment, ExpectedMessage, RecordOutput, -}; - -use languageserver_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; - -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, 22, "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":20,"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, 22, "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, 21, "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: 19, - character: 15, - }, - end: Position { - line: 19, - 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":11,"character":11},"end":{"line":11,"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": 11, "character": 18 }, - "start": { "line": 11, "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, 10, "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":9,"character":7},"end":{"line":9,"character":10}}"#, - ).expect_contains( - r#"{"start":{"line":15,"character":14},"end":{"line":15,"character":17}}"#, - ).expect_contains( - r#"{"start":{"line":23,"character":15},"end":{"line":23,"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, 10, "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":9,"character":7},"end":{"line":9,"character":10}}"#, - ).expect_contains( - r#"{"start":{"line":22,"character":15},"end":{"line":22,"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, 22, "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":20,"character":8},"end":{"line":20,"character":13}}"#, - ).expect_contains( - r#"{"start":{"line":21,"character":27},"end":{"line":21,"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, 22, "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":20,"character":8},"end":{"line":20,"character":13}}"#, - ).expect_contains( - r#"{"start":{"line":21,"character":27},"end":{"line":21,"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"), - ); -} - -#[test] -fn test_init_with_configuration() { - 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": { - "features": ["some_feature"], - "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_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_deglob() { - let mut env = Environment::generate_from_fixture("deglob"); - - 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.clone()); - let messages = vec![ - initialize(0, root_path.as_os_str().to_str().map(|x| x.to_owned())).to_string(), - // request deglob for single wildcard - request::( - 100, - CodeActionParams { - text_document: text_doc.clone(), - range: env.cache.mk_ls_range_from_line(12), - context: CodeActionContext { - diagnostics: vec![], - only: None, - }, - }, - ).to_string(), - // deglob single - request::( - 200, - ExecuteCommandParams { - command: format!("rls.deglobImports-{}", ::std::process::id()), - arguments: vec![ - serde_json::to_value(&requests::DeglobResult { - location: Location { - uri: url.clone(), - range: Range::new(Position::new(12, 13), Position::new(12, 14)), - }, - new_text: "{Stdout, Stdin}".into(), - }).unwrap(), - ], - }, - ).to_string(), - // request deglob for double wildcard - request::( - 1100, - CodeActionParams { - text_document: text_doc, - range: env.cache.mk_ls_range_from_line(15), - context: CodeActionContext { - diagnostics: vec![], - only: None, - }, - }, - ).to_string(), - // deglob two wildcards - request::( - 1200, - ExecuteCommandParams { - command: format!("rls.deglobImports-{}", ::std::process::id()), - arguments: vec![ - serde_json::to_value(&requests::DeglobResult { - location: Location { - uri: url.clone(), - range: Range::new(Position::new(15, 14), Position::new(15, 15)), - }, - new_text: "size_of".into(), - }).unwrap(), - serde_json::to_value(&requests::DeglobResult { - location: Location { - uri: url, - range: Range::new(Position::new(15, 31), Position::new(15, 32)), - }, - new_text: "max".into(), - }).unwrap(), - ], - }, - ).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("rls.deglobImports-"), - ); - - 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 response: Value = serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap(); - assert_eq!(response["id"], 100); - assert_eq!(response["result"][0]["title"], "Deglob import"); - assert_eq!( - response["result"][0]["command"], - &*format!("rls.deglobImports-{}", ::std::process::id()) - ); - let deglob = &response["result"][0]["arguments"][0]; - assert!( - deglob["location"]["uri"] - .as_str() - .unwrap() - .ends_with("deglob/src/main.rs") - ); - let deglob_loc = °lob["location"]["range"]; - assert_eq!(deglob_loc["start"]["line"], 12); - assert_eq!(deglob_loc["start"]["character"], 13); - assert_eq!(deglob_loc["end"]["line"], 12); - assert_eq!(deglob_loc["end"]["character"], 14); - let mut imports: Vec<_> = deglob["new_text"] - .as_str() - .unwrap() - .trim_matches('{') - .trim_matches('}') - .split(", ") - .collect(); - imports.sort(); - assert_eq!(imports, vec!["Stdin", "Stdout"]); - } - - 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"], 0x0100_0001); - assert_eq!(response["method"], "workspace/applyEdit"); - let (key, changes) = response["params"]["edit"]["changes"] - .as_object() - .unwrap() - .iter() - .next() - .unwrap(); - assert!(key.ends_with("deglob/src/main.rs")); - let change = &changes[0]; - assert_eq!(change["range"]["start"]["line"], 12); - assert_eq!(change["range"]["start"]["character"], 13); - assert_eq!(change["range"]["end"]["line"], 12); - assert_eq!(change["range"]["end"]["character"], 14); - let mut imports: Vec<_> = change["newText"] - .as_str() - .expect("newText missing") - .trim_matches('{') - .trim_matches('}') - .split(", ") - .collect(); - imports.sort(); - assert_eq!(imports, vec!["Stdin", "Stdout"]); - - let response: Value = serde_json::from_str(&results.lock().unwrap().remove(0)).unwrap(); - assert_eq!(response["id"], 200); - assert!(response["result"].is_null()); - } - - assert_eq!( - ls_server::LsService::handle_message(&mut server), - ls_server::ServerStateChange::Continue - ); - expect_message( - &mut server, - results.clone(), - ExpectedMessage::new(Some(1100)) - .expect_contains(r#""title":"Deglob imports""#) - .expect_contains(r#""command":"rls.deglobImports-"#) - .expect_contains(r#"{"location":{"range":{"end":{"character":15,"line":15},"start":{"character":14,"line":15}},"uri":"#) - .expect_contains(r#"deglob/src/main.rs"}"#) - .expect_contains(r#""new_text":"size_of""#) - .expect_contains(r#"{"location":{"range":{"end":{"character":32,"line":15},"start":{"character":31,"line":15}},"uri":"#) - .expect_contains(r#"deglob/src/main.rs"}"#) - .expect_contains(r#""new_text":"max""#) - ); - - 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"], 0x0100_0002); - assert_eq!(response["method"], "workspace/applyEdit"); - let (key, changes) = response["params"]["edit"]["changes"] - .as_object() - .unwrap() - .iter() - .next() - .unwrap(); - assert!(key.ends_with("deglob/src/main.rs")); - let change = &changes[0]; - assert_eq!(change["range"]["start"]["line"], 15); - assert_eq!(change["range"]["start"]["character"], 14); - assert_eq!(change["range"]["end"]["line"], 15); - assert_eq!(change["range"]["end"]["character"], 15); - assert_eq!(change["newText"], "size_of"); - let change = &changes[1]; - assert_eq!(change["range"]["start"]["line"], 15); - assert_eq!(change["range"]["start"]["character"], 31); - assert_eq!(change["range"]["end"]["line"], 15); - assert_eq!(change["range"]["end"]["character"], 32); - assert_eq!(change["newText"], "max"); - } - - expect_message( - &mut server, - results, - ExpectedMessage::new(Some(1200)).expect_contains(r#"null"#), - ); -} - -#[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, 22, "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""#), -// ], -// ); -// } From 718e022a7d2880fa045a2f18eb29fcbbe391cbba Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sat, 22 Dec 2018 00:13:36 +0100 Subject: [PATCH 09/14] Only use FIXTURES_DIR to determine fixtures --- tests/lens.rs | 6 +++--- tests/support/fixtures.rs | 8 -------- tests/support/harness.rs | 8 ++------ tests/support/mod.rs | 7 ++++++- 4 files changed, 11 insertions(+), 18 deletions(-) delete mode 100644 tests/support/fixtures.rs diff --git a/tests/lens.rs b/tests/lens.rs index 2ec051d441b..e71bfb35b9c 100644 --- a/tests/lens.rs +++ b/tests/lens.rs @@ -2,8 +2,8 @@ use rls::actions::requests; use languageserver_types::request::Request as _; -use self::support::rls_timeout; -use self::support::harness::{compare_json, FIXTURES_DIR}; +use self::support::{fixtures_dir, rls_timeout}; +use self::support::harness::compare_json; use self::support::project_builder::ProjectBuilder; use serde_json; use serde_json::json; @@ -13,7 +13,7 @@ mod support; #[test] fn cmd_lens_run() { - let p = ProjectBuilder::try_from_fixture(FIXTURES_DIR.join("lens_run")) + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("lens_run")) .unwrap() .build(); let root_path = p.root(); diff --git a/tests/support/fixtures.rs b/tests/support/fixtures.rs deleted file mode 100644 index a1a49049464..00000000000 --- a/tests/support/fixtures.rs +++ /dev/null @@ -1,8 +0,0 @@ -use std::path::{Path, PathBuf}; - -use lazy_static::lazy_static; - -lazy_static! { - static ref MANIFEST_DIR: &'static Path = Path::new(env!("CARGO_MANIFEST_DIR")); - pub static ref FIXTURES_DIR: PathBuf = MANIFEST_DIR.join("tests/fixtures"); -} \ No newline at end of file diff --git a/tests/support/harness.rs b/tests/support/harness.rs index 356f93017d5..d8f4bcc2e32 100644 --- a/tests/support/harness.rs +++ b/tests/support/harness.rs @@ -22,16 +22,12 @@ use rls::config::{Config, Inferrable}; use rls::server as ls_server; use env_logger; use languageserver_types as ls_types; -use lazy_static::lazy_static; use rls_analysis::{AnalysisHost, Target}; use rls_vfs::Vfs; use serde_json; use walkdir::WalkDir; -lazy_static! { - static ref MANIFEST_DIR: &'static Path = Path::new(env!("CARGO_MANIFEST_DIR")); - pub static ref FIXTURES_DIR: PathBuf = MANIFEST_DIR.join("tests").join("fixtures"); -} +use super::fixtures_dir; pub(crate) struct Environment { pub(crate) config: Option, @@ -46,7 +42,7 @@ impl Environment { env::set_var("RUSTC", "rustc"); } - let fixture_dir = FIXTURES_DIR.join(fixture_dir.as_ref()); + let fixture_dir = fixtures_dir().join(fixture_dir.as_ref()); let scratchpad_dir = build_scratchpad_from_fixture(fixture_dir) .expect("Can't copy fixture files to scratchpad"); diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 4898e24a4c9..4d3b3d4aef4 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -14,7 +14,7 @@ use std::env; use std::io::{self, Read, Write}; use std::mem; use std::panic; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::process::{Child, ChildStdin, Command, Stdio}; use std::str; use std::sync::{Arc, Mutex}; @@ -25,6 +25,11 @@ pub mod project_builder; pub mod paths; pub mod harness; +/// Returns a path to directory containing test fixtures. +pub fn fixtures_dir() -> &'static Path { + Path::new(env!("FIXTURES_DIR")) +} + /// Returns a timeout for waiting for rls stdout messages /// /// Env var `RLS_TEST_WAIT_FOR_AGES` allows super long waiting for CI From 86d92a4a2ad5b231a8e1fbd356556b2b512fa86e Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sat, 22 Dec 2018 10:39:19 +0100 Subject: [PATCH 10/14] Simplify tooltip test harness --- src/actions/hover.rs | 73 +++++++++++++++++--------------------------- 1 file changed, 28 insertions(+), 45 deletions(-) diff --git a/src/actions/hover.rs b/src/actions/hover.rs index 07c888b623d..cb31487315e 100644 --- a/src/actions/hover.rs +++ b/src/actions/hover.rs @@ -974,7 +974,6 @@ pub mod test { use super::*; use crate::actions::format::Rustfmt; - use crate::build::BuildPriority; use crate::config; use crate::lsp_data::{ClientCapabilities, InitializationOptions}; use crate::lsp_data::{Position, TextDocumentIdentifier, TextDocumentPositionParams}; @@ -1035,7 +1034,7 @@ pub mod test { path, e ) })?; - fs::write(path.clone(), data) + fs::write(&path, data) .map_err(|e| format!("failed to save hover test result: {:?} ({:?})", path, e)) } @@ -1154,7 +1153,7 @@ pub mod test { pub struct TooltipTestHarness { ctx: InitActionContext, project_dir: PathBuf, - working_dir: PathBuf, + _working_dir: tempfile::TempDir, } impl TooltipTestHarness { @@ -1165,7 +1164,6 @@ pub mod test { output: &O, racer_fallback_completion: bool, ) -> TooltipTestHarness { - use env_logger; let _ = env_logger::try_init(); // Prevent the hover test project build from trying to use the rls test @@ -1174,20 +1172,23 @@ pub mod test { env::set_var("RUSTC", "rustc"); } - let pid = process::id(); let client_caps = ClientCapabilities { code_completion_has_snippet_support: true, related_information_support: true, }; - let mut config = config::Config::default(); - let temp_dir = tempfile::tempdir().unwrap().into_path(); - config.target_dir = config::Inferrable::Specified(Some(temp_dir.clone())); - config.racer_completion = racer_fallback_completion; - // FIXME(#1195): This led to spurious failures on macOS; possibly - // because regular build and #[cfg(test)] did race or rls-analysis - // didn't lower them properly? - config.all_targets = false; + let _working_dir = tempfile::tempdir().expect("Couldn't create tempdir"); + let target_dir = _working_dir.path().to_owned(); + + let config = config::Config { + target_dir: config::Inferrable::Specified(Some(target_dir)), + racer_completion: racer_fallback_completion, + // FIXME(#1195): This led to spurious failures on macOS. + // Possibly because regular build and #[cfg(test)] did race or + // rls-analysis didn't lower them properly? + all_targets: false, + ..Default::default() + }; let config = Arc::new(Mutex::new(config)); let analysis = Arc::new(analysis::AnalysisHost::new(analysis::Target::Debug)); @@ -1199,17 +1200,17 @@ pub mod test { config, client_caps, project_dir.clone(), - pid, + process::id(), true, ); ctx.init(InitializationOptions::default(), output); - ctx.build(&project_dir, BuildPriority::Immediate, output); + ctx.block_on_build(); TooltipTestHarness { ctx, project_dir, - working_dir: temp_dir + _working_dir, } } @@ -1237,7 +1238,6 @@ pub mod test { save_dir, e ) })?; - self.ctx.block_on_build(); let results: Vec = tests .iter() @@ -1263,30 +1263,17 @@ pub mod test { } Err(e) => Some((Err(e), actual_result)), } - }).filter(|failed_result| failed_result.is_some()) - .map(|failed_result| failed_result.unwrap()) - .map(|failed_result| match failed_result { - (Ok(expect_result), actual_result) => { - let load_file = actual_result.test.path(&load_dir); - let save_file = actual_result.test.path(&save_dir); - TestFailure { - test: actual_result.test, - expect_data: Ok(expect_result.data), - expect_file: load_file, - actual_data: Ok(actual_result.data), - actual_file: save_file, - } - } - (Err(e), actual_result) => { - let load_file = actual_result.test.path(&load_dir); - let save_file = actual_result.test.path(&save_dir); - TestFailure { - test: actual_result.test, - expect_data: Err(e), - expect_file: load_file, - actual_data: Ok(actual_result.data), - actual_file: save_file, - } + }).filter_map(|failed_result| failed_result) + .map(|(result, actual_result)| { + let load_file = actual_result.test.path(&load_dir); + let save_file = actual_result.test.path(&save_dir); + + TestFailure { + test: actual_result.test, + expect_data: result.map(|x| x.data), + expect_file: load_file, + actual_data: Ok(actual_result.data), + actual_file: save_file, } }).collect(); @@ -1299,10 +1286,6 @@ pub mod test { if let Ok(mut jobs) = self.ctx.jobs.lock() { jobs.wait_for_all(); } - - if fs::metadata(&self.working_dir).is_ok() { - fs::remove_dir_all(&self.working_dir).expect("failed to tidy up"); - } } } From 1ad5ede977fd1f644b32dde77851a41aed68396f Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sat, 22 Dec 2018 11:38:44 +0100 Subject: [PATCH 11/14] Move tooltip tests to integration tests They basically spin up the entire server so it's almost end-to-end, after all. --- src/actions/hover.rs | 469 ------------------------------------------ src/actions/mod.rs | 2 +- tests/tooltip.rs | 476 +++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 477 insertions(+), 470 deletions(-) create mode 100644 tests/tooltip.rs diff --git a/src/actions/hover.rs b/src/actions/hover.rs index cb31487315e..f1b3b1b2dcc 100644 --- a/src/actions/hover.rs +++ b/src/actions/hover.rs @@ -972,323 +972,12 @@ pub fn tooltip( #[allow(clippy::expect_fun_call)] pub mod test { use super::*; - use crate::actions::format::Rustfmt; - use crate::config; - use crate::lsp_data::{ClientCapabilities, InitializationOptions}; - use crate::lsp_data::{Position, TextDocumentIdentifier, TextDocumentPositionParams}; - use crate::server::{Output, RequestId}; - use rls_analysis as analysis; - use serde_derive::{Deserialize, Serialize}; - use serde_json as json; - use url::Url; - - use std::env; - use std::fs; - use std::path::PathBuf; - use std::process; - use std::sync::{Arc, Mutex}; - use std::fmt; pub fn fixtures_dir() -> &'static Path { Path::new(env!("FIXTURES_DIR")) } - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] - pub struct Test { - /// Relative to the project _source_ dir (e.g. relative to $FIXTURES_DIR/hover/src) - pub file: String, - /// One-based line number - pub line: u64, - /// One-based column number - pub col: u64, - } - - impl Test { - fn load_result(&self, dir: &Path) -> Result { - let path = self.path(dir); - let file = fs::File::open(path.clone()) - .map_err(|e| format!("failed to open hover test result: {:?} ({:?})", path, e))?; - let result: Result = json::from_reader(file).map_err(|e| { - format!( - "failed to deserialize hover test result: {:?} ({:?})", - path, e - ) - }); - result - } - } - - #[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] - struct TestResult { - test: Test, - data: Result, String>, - } - - impl TestResult { - fn save(&self, result_dir: &Path) -> Result<(), String> { - let path = self.test.path(result_dir); - let data = json::to_string_pretty(&self).map_err(|e| { - format!( - "failed to serialize hover test result: {:?} ({:?})", - path, e - ) - })?; - fs::write(&path, data) - .map_err(|e| format!("failed to save hover test result: {:?} ({:?})", path, e)) - } - - /// Returns true if data is equal to `other` relaxed so that - /// `MarkedString::String` in `other` need only start with self's. - fn has_same_data_start(&self, other: &Self) -> bool { - match (&self.data, &other.data) { - (Ok(data), Ok(them)) if data.len() == them.len() => data - .iter() - .zip(them.iter()) - .map(|(us, them)| match (us, them) { - (MarkedString::String(us), MarkedString::String(them)) => { - them.starts_with(us) - } - _ => us == them, - }) - .all(|r| r), - _ => false, - } - } - } - - impl Test { - pub fn new(file: &str, line: u64, col: u64) -> Test { - Test { - file: file.into(), - line, - col, - } - } - - fn path(&self, result_dir: &Path) -> PathBuf { - result_dir.join(format!( - "{}.{:04}_{:03}.json", - self.file, self.line, self.col - )) - } - - fn run(&self, project_dir: &Path, ctx: &InitActionContext) -> TestResult { - let url = - Url::from_file_path(project_dir.join("src").join(&self.file)).expect(&self.file); - let doc_id = TextDocumentIdentifier::new(url); - let position = Position::new(self.line - 1u64, self.col - 1u64); - let params = TextDocumentPositionParams::new(doc_id, position); - let result = tooltip(&ctx, ¶ms).map_err(|e| format!("tooltip error: {:?}", e)); - - TestResult { - test: self.clone(), - data: result, - } - } - } - - #[derive(PartialEq, Eq)] - pub struct TestFailure { - /// The test case, indicating file, line, and column - pub test: Test, - /// The location of the loaded result input. - pub expect_file: PathBuf, - /// The location of the saved result output. - pub actual_file: PathBuf, - /// The expected outcome. The outer `Result` relates to errors while - /// loading saved data. The inner `Result` is the saved output from - /// `hover::tooltip`. - pub expect_data: Result, String>, String>, - /// The current output from `hover::tooltip`. The inner `Result` - /// is the output from `hover::tooltip`. - pub actual_data: Result, String>, ()>, - } - - impl fmt::Debug for TestFailure { - fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { - fmt.debug_struct("TestFailure") - .field("test", &self.test) - .field("expect_file", &self.expect_file) - .field("actual_file", &self.actual_file) - .field("expect_data", &self.expect_data) - .field("actual_data", &self.actual_data) - .finish()?; - - let expected = format!("{:#?}", self.expect_data); - let actual = format!("{:#?}", self.actual_data); - write!(fmt, "-diff: {}", difference::Changeset::new(&expected, &actual, "")) - } - } - - - #[derive(Clone, Default)] - pub struct LineOutput { - req_id: Arc>, - lines: Arc>>, - } - - impl LineOutput { - /// Clears and returns the recorded output lines - pub fn reset(&self) -> Vec { - let mut lines = self.lines.lock().unwrap(); - let mut swapped = Vec::new(); - ::std::mem::swap(&mut *lines, &mut swapped); - swapped - } - } - - impl Output for LineOutput { - fn response(&self, output: String) { - self.lines.lock().unwrap().push(output); - } - - fn provide_id(&self) -> RequestId { - let mut id = self.req_id.lock().unwrap(); - *id += 1; - RequestId::Num(*id as u64) - } - } - - pub struct TooltipTestHarness { - ctx: InitActionContext, - project_dir: PathBuf, - _working_dir: tempfile::TempDir, - } - - impl TooltipTestHarness { - /// Creates a new `TooltipTestHarness`. The `project_dir` must contain - /// a valid rust project with a `Cargo.toml`. - pub fn new( - project_dir: PathBuf, - output: &O, - racer_fallback_completion: bool, - ) -> TooltipTestHarness { - let _ = env_logger::try_init(); - - // Prevent the hover test project build from trying to use the rls test - // binary as a rustc shim. See RlsExecutor::exec for more information. - if env::var("RUSTC").is_err() { - env::set_var("RUSTC", "rustc"); - } - - let client_caps = ClientCapabilities { - code_completion_has_snippet_support: true, - related_information_support: true, - }; - - let _working_dir = tempfile::tempdir().expect("Couldn't create tempdir"); - let target_dir = _working_dir.path().to_owned(); - - let config = config::Config { - target_dir: config::Inferrable::Specified(Some(target_dir)), - racer_completion: racer_fallback_completion, - // FIXME(#1195): This led to spurious failures on macOS. - // Possibly because regular build and #[cfg(test)] did race or - // rls-analysis didn't lower them properly? - all_targets: false, - ..Default::default() - }; - - let config = Arc::new(Mutex::new(config)); - let analysis = Arc::new(analysis::AnalysisHost::new(analysis::Target::Debug)); - let vfs = Arc::new(Vfs::new()); - - let ctx = InitActionContext::new( - analysis, - vfs, - config, - client_caps, - project_dir.clone(), - process::id(), - true, - ); - - ctx.init(InitializationOptions::default(), output); - ctx.block_on_build(); - - TooltipTestHarness { - ctx, - project_dir, - _working_dir, - } - } - - /// Execute a series of tooltip tests. The test results will be saved in `save_dir`. - /// Each test will attempt to load a previous result from the `load_dir` and compare - /// the results. If a matching file can't be found or the compared data mismatches, - /// the test case fails. The output file names are derived from the source filename, - /// line number, and column. The execution will return an `Err` if either the save or - /// load directories do not exist nor could be created. - pub fn run_tests( - &self, - tests: &[Test], - load_dir: PathBuf, - save_dir: PathBuf, - ) -> Result, String> { - fs::create_dir_all(&load_dir).map_err(|e| { - format!( - "load_dir does not exist and could not be created: {:?} ({:?})", - load_dir, e - ) - })?; - fs::create_dir_all(&save_dir).map_err(|e| { - format!( - "save_dir does not exist and could not be created: {:?} ({:?})", - save_dir, e - ) - })?; - - let results: Vec = tests - .iter() - .map(|test| { - let result = test.run(&self.project_dir, &self.ctx); - result.save(&save_dir).unwrap(); - result - }).collect(); - - let failures: Vec = results - .into_iter() - .map(|actual_result| { - match actual_result.test.load_result(&load_dir) { - Ok(expect_result) => { - if actual_result.test != expect_result.test { - let e = format!("Mismatched test: {:?}", expect_result.test); - Some((Err(e), actual_result)) - } else if expect_result.has_same_data_start(&actual_result) { - None - } else { - Some((Ok(expect_result), actual_result)) - } - } - Err(e) => Some((Err(e), actual_result)), - } - }).filter_map(|failed_result| failed_result) - .map(|(result, actual_result)| { - let load_file = actual_result.test.path(&load_dir); - let save_file = actual_result.test.path(&save_dir); - - TestFailure { - test: actual_result.test, - expect_data: result.map(|x| x.data), - expect_file: load_file, - actual_data: Ok(actual_result.data), - actual_file: save_file, - } - }).collect(); - - Ok(failures) - } - } - - impl Drop for TooltipTestHarness { - fn drop(&mut self) { - if let Ok(mut jobs) = self.ctx.jobs.lock() { - jobs.wait_for_all(); - } - } - } - /// Strips indentation from string literals by examining /// the indent of the first non-empty line. Preceding /// and trailing whitespace is also removed. @@ -2022,162 +1711,4 @@ pub mod test { assert_eq!(expected, actual); } - - enum RacerFallback { - Yes, - No, - } - - impl From for bool { - fn from(arg: RacerFallback) -> bool { - match arg { - RacerFallback::Yes => true, - RacerFallback::No => false, - } - } - } - - // Common logic used in `test_tooltip_*` tests below - fn run_tooltip_tests( - tests: &[Test], - proj_dir: PathBuf, - racer_completion: RacerFallback, - ) -> Result<(), Box> { - let out = LineOutput::default(); - - let save_dir_guard = tempfile::tempdir().unwrap(); - let save_dir = save_dir_guard.path().to_owned(); - let load_dir = proj_dir.join("save_data"); - - let harness = TooltipTestHarness::new(proj_dir, &out, racer_completion.into()); - - out.reset(); - - let failures = harness.run_tests(tests, load_dir, save_dir)?; - - if failures.is_empty() { - Ok(()) - } else { - eprintln!("{}\n\n", out.reset().join("\n")); - eprintln!( - "Failures (\x1b[91mexpected\x1b[92mactual\x1b[0m): {:#?}\n\n", - failures - ); - Err(format!("{} of {} tooltip tests failed", failures.len(), tests.len()).into()) - } - } - - #[test] - fn test_tooltip() -> Result<(), Box> { - let _ = env_logger::try_init(); - - let tests = vec![ - Test::new("test_tooltip_01.rs", 13, 11), - Test::new("test_tooltip_01.rs", 15, 7), - Test::new("test_tooltip_01.rs", 17, 7), - Test::new("test_tooltip_01.rs", 21, 13), - Test::new("test_tooltip_01.rs", 23, 9), - Test::new("test_tooltip_01.rs", 23, 16), - Test::new("test_tooltip_01.rs", 25, 8), - Test::new("test_tooltip_01.rs", 27, 8), - Test::new("test_tooltip_01.rs", 27, 8), - Test::new("test_tooltip_01.rs", 30, 11), - Test::new("test_tooltip_01.rs", 32, 10), - Test::new("test_tooltip_01.rs", 32, 19), - Test::new("test_tooltip_01.rs", 32, 26), - Test::new("test_tooltip_01.rs", 32, 35), - Test::new("test_tooltip_01.rs", 32, 49), - Test::new("test_tooltip_01.rs", 33, 11), - Test::new("test_tooltip_01.rs", 34, 16), - Test::new("test_tooltip_01.rs", 34, 23), - Test::new("test_tooltip_01.rs", 35, 16), - Test::new("test_tooltip_01.rs", 35, 23), - Test::new("test_tooltip_01.rs", 36, 16), - Test::new("test_tooltip_01.rs", 36, 23), - Test::new("test_tooltip_01.rs", 42, 15), - Test::new("test_tooltip_01.rs", 56, 6), - Test::new("test_tooltip_01.rs", 66, 6), - Test::new("test_tooltip_01.rs", 67, 30), - Test::new("test_tooltip_01.rs", 68, 11), - Test::new("test_tooltip_01.rs", 68, 26), - Test::new("test_tooltip_01.rs", 75, 10), - Test::new("test_tooltip_01.rs", 85, 14), - Test::new("test_tooltip_01.rs", 85, 50), - Test::new("test_tooltip_01.rs", 85, 54), - Test::new("test_tooltip_01.rs", 86, 7), - Test::new("test_tooltip_01.rs", 86, 10), - Test::new("test_tooltip_01.rs", 87, 20), - Test::new("test_tooltip_01.rs", 88, 18), - Test::new("test_tooltip_01.rs", 93, 11), - Test::new("test_tooltip_01.rs", 95, 25), - Test::new("test_tooltip_01.rs", 109, 21), - Test::new("test_tooltip_01.rs", 113, 21), - Test::new("test_tooltip_mod.rs", 22, 14), - Test::new("test_tooltip_mod_use.rs", 11, 14), - Test::new("test_tooltip_mod_use.rs", 12, 14), - Test::new("test_tooltip_mod_use.rs", 12, 25), - Test::new("test_tooltip_mod_use.rs", 13, 28), - ]; - - run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::No) - } - - #[test] - fn test_tooltip_racer() -> Result<(), Box> { - let _ = env_logger::try_init(); - - let tests = vec![ - Test::new("test_tooltip_01.rs", 80, 11), - Test::new("test_tooltip_01.rs", 93, 18), - Test::new("test_tooltip_mod_use_external.rs", 11, 7), - Test::new("test_tooltip_mod_use_external.rs", 12, 7), - Test::new("test_tooltip_mod_use_external.rs", 12, 12), - ]; - - run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::Yes) - } - - /// Note: This test is ignored as it doesn't work in the rust-lang/rust repo. - /// It is enabled on CI. - /// Run with `cargo test test_tooltip_std -- --ignored` - #[test] - #[ignore] - fn test_tooltip_std() -> Result<(), Box> { - let _ = env_logger::try_init(); - - let tests = vec![ - Test::new("test_tooltip_std.rs", 18, 15), - Test::new("test_tooltip_std.rs", 18, 27), - Test::new("test_tooltip_std.rs", 19, 7), - Test::new("test_tooltip_std.rs", 19, 12), - Test::new("test_tooltip_std.rs", 20, 12), - Test::new("test_tooltip_std.rs", 20, 20), - Test::new("test_tooltip_std.rs", 21, 25), - Test::new("test_tooltip_std.rs", 22, 33), - Test::new("test_tooltip_std.rs", 23, 11), - Test::new("test_tooltip_std.rs", 23, 18), - Test::new("test_tooltip_std.rs", 24, 24), - Test::new("test_tooltip_std.rs", 25, 17), - Test::new("test_tooltip_std.rs", 25, 25), - ]; - - run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::No) - } - - /// Note: This test is ignored as it doesn't work in the rust-lang/rust repo. - /// It is enabled on CI. - /// Run with `cargo test test_tooltip_std -- --ignored` - #[test] - #[ignore] - fn test_tooltip_std_racer() -> Result<(), Box> { - let _ = env_logger::try_init(); - - let tests = vec![ - // these test std stuff - Test::new("test_tooltip_mod_use_external.rs", 14, 12), - Test::new("test_tooltip_mod_use_external.rs", 15, 12), - ]; - - run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::Yes) - } } diff --git a/src/actions/mod.rs b/src/actions/mod.rs index 2ee9f5aa1c2..2c3150ecb63 100644 --- a/src/actions/mod.rs +++ b/src/actions/mod.rs @@ -381,7 +381,7 @@ impl InitActionContext { } /// Block until any builds and analysis tasks are complete. - fn block_on_build(&self) { + pub fn block_on_build(&self) { self.build_queue.block_on_build(); } diff --git a/tests/tooltip.rs b/tests/tooltip.rs new file mode 100644 index 00000000000..0b66dc631ab --- /dev/null +++ b/tests/tooltip.rs @@ -0,0 +1,476 @@ +use rls::actions::hover::tooltip; +use rls::actions::{ActionContext, InitActionContext}; +use rls::config; +use rls::lsp_data::{ClientCapabilities, InitializationOptions}; +use rls::lsp_data::MarkedString; +use rls::lsp_data::{Position, TextDocumentIdentifier, TextDocumentPositionParams}; +use rls::server::{Output, RequestId}; +use rls_analysis as analysis; +use rls_vfs::Vfs; +use serde_derive::{Deserialize, Serialize}; +use serde_json as json; +use url::Url; + +use std::env; +use std::fmt; +use std::fs; +use std::path::{Path, PathBuf}; +use std::sync::{Arc, Mutex}; + +pub fn fixtures_dir() -> &'static Path { + Path::new(env!("FIXTURES_DIR")) +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone)] +pub struct Test { + /// Relative to the project _source_ dir (e.g. relative to $FIXTURES_DIR/hover/src) + pub file: String, + /// One-based line number + pub line: u64, + /// One-based column number + pub col: u64, +} + +impl Test { + fn load_result(&self, dir: &Path) -> Result { + let path = self.path(dir); + let file = fs::File::open(path.clone()) + .map_err(|e| format!("failed to open hover test result: {:?} ({:?})", path, e))?; + let result: Result = json::from_reader(file).map_err(|e| { + format!( + "failed to deserialize hover test result: {:?} ({:?})", + path, e + ) + }); + result + } +} + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +struct TestResult { + test: Test, + data: Result, String>, +} + +impl TestResult { + fn save(&self, result_dir: &Path) -> Result<(), String> { + let path = self.test.path(result_dir); + let data = json::to_string_pretty(&self).map_err(|e| { + format!( + "failed to serialize hover test result: {:?} ({:?})", + path, e + ) + })?; + fs::write(&path, data) + .map_err(|e| format!("failed to save hover test result: {:?} ({:?})", path, e)) + } + + /// Returns true if data is equal to `other` relaxed so that + /// `MarkedString::String` in `other` need only start with self's. + fn has_same_data_start(&self, other: &Self) -> bool { + match (&self.data, &other.data) { + (Ok(data), Ok(them)) if data.len() == them.len() => data + .iter() + .zip(them.iter()) + .map(|(us, them)| match (us, them) { + (MarkedString::String(us), MarkedString::String(them)) => them.starts_with(us), + _ => us == them, + }) + .all(|r| r), + _ => false, + } + } +} + +impl Test { + pub fn new(file: &str, line: u64, col: u64) -> Test { + Test { + file: file.into(), + line, + col, + } + } + + fn path(&self, result_dir: &Path) -> PathBuf { + result_dir.join(format!( + "{}.{:04}_{:03}.json", + self.file, self.line, self.col + )) + } + + fn run(&self, project_dir: &Path, ctx: &InitActionContext) -> TestResult { + let url = Url::from_file_path(project_dir.join("src").join(&self.file)).expect(&self.file); + let doc_id = TextDocumentIdentifier::new(url); + let position = Position::new(self.line - 1u64, self.col - 1u64); + let params = TextDocumentPositionParams::new(doc_id, position); + let result = tooltip(&ctx, ¶ms).map_err(|e| format!("tooltip error: {:?}", e)); + + TestResult { + test: self.clone(), + data: result, + } + } +} + +#[derive(PartialEq, Eq)] +pub struct TestFailure { + /// The test case, indicating file, line, and column + pub test: Test, + /// The location of the loaded result input. + pub expect_file: PathBuf, + /// The location of the saved result output. + pub actual_file: PathBuf, + /// The expected outcome. The outer `Result` relates to errors while + /// loading saved data. The inner `Result` is the saved output from + /// `hover::tooltip`. + pub expect_data: Result, String>, String>, + /// The current output from `hover::tooltip`. The inner `Result` + /// is the output from `hover::tooltip`. + pub actual_data: Result, String>, ()>, +} + +impl fmt::Debug for TestFailure { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { + fmt.debug_struct("TestFailure") + .field("test", &self.test) + .field("expect_file", &self.expect_file) + .field("actual_file", &self.actual_file) + .field("expect_data", &self.expect_data) + .field("actual_data", &self.actual_data) + .finish()?; + + let expected = format!("{:#?}", self.expect_data); + let actual = format!("{:#?}", self.actual_data); + write!( + fmt, + "-diff: {}", + difference::Changeset::new(&expected, &actual, "") + ) + } +} + +#[derive(Clone, Default)] +pub struct LineOutput { + req_id: Arc>, + lines: Arc>>, +} + +impl LineOutput { + /// Clears and returns the recorded output lines + pub fn reset(&self) -> Vec { + let mut lines = self.lines.lock().unwrap(); + let mut swapped = Vec::new(); + ::std::mem::swap(&mut *lines, &mut swapped); + swapped + } +} + +impl Output for LineOutput { + fn response(&self, output: String) { + self.lines.lock().unwrap().push(output); + } + + fn provide_id(&self) -> RequestId { + let mut id = self.req_id.lock().unwrap(); + *id += 1; + RequestId::Num(*id as u64) + } +} + +pub struct TooltipTestHarness { + ctx: InitActionContext, + project_dir: PathBuf, + _working_dir: tempfile::TempDir, +} + +impl TooltipTestHarness { + /// Creates a new `TooltipTestHarness`. The `project_dir` must contain + /// a valid rust project with a `Cargo.toml`. + pub fn new( + project_dir: PathBuf, + output: &O, + racer_fallback_completion: bool, + ) -> TooltipTestHarness { + let _ = env_logger::try_init(); + + // Prevent the hover test project build from trying to use the rls test + // binary as a rustc shim. See RlsExecutor::exec for more information. + if env::var("RUSTC").is_err() { + env::set_var("RUSTC", "rustc"); + } + + let client_caps = ClientCapabilities { + code_completion_has_snippet_support: true, + related_information_support: true, + }; + + let _working_dir = tempfile::tempdir().expect("Couldn't create tempdir"); + let target_dir = _working_dir.path().to_owned(); + + let config = config::Config { + target_dir: config::Inferrable::Specified(Some(target_dir)), + racer_completion: racer_fallback_completion, + // FIXME(#1195): This led to spurious failures on macOS. + // Possibly because regular build and #[cfg(test)] did race or + // rls-analysis didn't lower them properly? + all_targets: false, + ..Default::default() + }; + + let config = Arc::new(Mutex::new(config)); + let analysis = Arc::new(analysis::AnalysisHost::new(analysis::Target::Debug)); + let vfs = Arc::new(Vfs::new()); + + let ctx = { + let mut ctx = ActionContext::new(analysis, vfs, config); + ctx.init( + project_dir.clone(), + InitializationOptions::default(), + client_caps, + output, + ) + .unwrap(); + ctx.inited().unwrap() + }; + + ctx.block_on_build(); + + TooltipTestHarness { + ctx, + project_dir, + _working_dir, + } + } + + /// Execute a series of tooltip tests. The test results will be saved in `save_dir`. + /// Each test will attempt to load a previous result from the `load_dir` and compare + /// the results. If a matching file can't be found or the compared data mismatches, + /// the test case fails. The output file names are derived from the source filename, + /// line number, and column. The execution will return an `Err` if either the save or + /// load directories do not exist nor could be created. + pub fn run_tests( + &self, + tests: &[Test], + load_dir: PathBuf, + save_dir: PathBuf, + ) -> Result, String> { + fs::create_dir_all(&load_dir).map_err(|e| { + format!( + "load_dir does not exist and could not be created: {:?} ({:?})", + load_dir, e + ) + })?; + fs::create_dir_all(&save_dir).map_err(|e| { + format!( + "save_dir does not exist and could not be created: {:?} ({:?})", + save_dir, e + ) + })?; + + let results: Vec = tests + .iter() + .map(|test| { + let result = test.run(&self.project_dir, &self.ctx); + result.save(&save_dir).unwrap(); + result + }) + .collect(); + + let failures: Vec = results + .into_iter() + .map( + |actual_result: TestResult| match actual_result.test.load_result(&load_dir) { + Ok(expect_result) => { + if actual_result.test != expect_result.test { + let e = format!("Mismatched test: {:?}", expect_result.test); + Some((Err(e), actual_result)) + } else if expect_result.has_same_data_start(&actual_result) { + None + } else { + Some((Ok(expect_result), actual_result)) + } + } + Err(e) => Some((Err(e), actual_result)), + }, + ) + .filter_map(|failed_result| failed_result) + .map(|(result, actual_result)| { + let load_file = actual_result.test.path(&load_dir); + let save_file = actual_result.test.path(&save_dir); + + TestFailure { + test: actual_result.test, + expect_data: result.map(|x| x.data), + expect_file: load_file, + actual_data: Ok(actual_result.data), + actual_file: save_file, + } + }) + .collect(); + + Ok(failures) + } +} + +impl Drop for TooltipTestHarness { + fn drop(&mut self) { + self.ctx.wait_for_concurrent_jobs(); + } +} + +enum RacerFallback { + Yes, + No, +} + +impl From for bool { + fn from(arg: RacerFallback) -> bool { + match arg { + RacerFallback::Yes => true, + RacerFallback::No => false, + } + } +} + +fn run_tooltip_tests( + tests: &[Test], + proj_dir: PathBuf, + racer_completion: RacerFallback, +) -> Result<(), Box> { + let out = LineOutput::default(); + + let save_dir_guard = tempfile::tempdir().unwrap(); + let save_dir = save_dir_guard.path().to_owned(); + let load_dir = proj_dir.join("save_data"); + + let harness = TooltipTestHarness::new(proj_dir, &out, racer_completion.into()); + + out.reset(); + + let failures = harness.run_tests(tests, load_dir, save_dir)?; + + if failures.is_empty() { + Ok(()) + } else { + eprintln!("{}\n\n", out.reset().join("\n")); + eprintln!( + "Failures (\x1b[91mexpected\x1b[92mactual\x1b[0m): {:#?}\n\n", + failures + ); + Err(format!("{} of {} tooltip tests failed", failures.len(), tests.len()).into()) + } +} + +#[test] +fn test_tooltip() -> Result<(), Box> { + let _ = env_logger::try_init(); + + let tests = vec![ + Test::new("test_tooltip_01.rs", 13, 11), + Test::new("test_tooltip_01.rs", 15, 7), + Test::new("test_tooltip_01.rs", 17, 7), + Test::new("test_tooltip_01.rs", 21, 13), + Test::new("test_tooltip_01.rs", 23, 9), + Test::new("test_tooltip_01.rs", 23, 16), + Test::new("test_tooltip_01.rs", 25, 8), + Test::new("test_tooltip_01.rs", 27, 8), + Test::new("test_tooltip_01.rs", 27, 8), + Test::new("test_tooltip_01.rs", 30, 11), + Test::new("test_tooltip_01.rs", 32, 10), + Test::new("test_tooltip_01.rs", 32, 19), + Test::new("test_tooltip_01.rs", 32, 26), + Test::new("test_tooltip_01.rs", 32, 35), + Test::new("test_tooltip_01.rs", 32, 49), + Test::new("test_tooltip_01.rs", 33, 11), + Test::new("test_tooltip_01.rs", 34, 16), + Test::new("test_tooltip_01.rs", 34, 23), + Test::new("test_tooltip_01.rs", 35, 16), + Test::new("test_tooltip_01.rs", 35, 23), + Test::new("test_tooltip_01.rs", 36, 16), + Test::new("test_tooltip_01.rs", 36, 23), + Test::new("test_tooltip_01.rs", 42, 15), + Test::new("test_tooltip_01.rs", 56, 6), + Test::new("test_tooltip_01.rs", 66, 6), + Test::new("test_tooltip_01.rs", 67, 30), + Test::new("test_tooltip_01.rs", 68, 11), + Test::new("test_tooltip_01.rs", 68, 26), + Test::new("test_tooltip_01.rs", 75, 10), + Test::new("test_tooltip_01.rs", 85, 14), + Test::new("test_tooltip_01.rs", 85, 50), + Test::new("test_tooltip_01.rs", 85, 54), + Test::new("test_tooltip_01.rs", 86, 7), + Test::new("test_tooltip_01.rs", 86, 10), + Test::new("test_tooltip_01.rs", 87, 20), + Test::new("test_tooltip_01.rs", 88, 18), + Test::new("test_tooltip_01.rs", 93, 11), + Test::new("test_tooltip_01.rs", 95, 25), + Test::new("test_tooltip_01.rs", 109, 21), + Test::new("test_tooltip_01.rs", 113, 21), + Test::new("test_tooltip_mod.rs", 22, 14), + Test::new("test_tooltip_mod_use.rs", 11, 14), + Test::new("test_tooltip_mod_use.rs", 12, 14), + Test::new("test_tooltip_mod_use.rs", 12, 25), + Test::new("test_tooltip_mod_use.rs", 13, 28), + ]; + + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::No) +} + +#[test] +fn test_tooltip_racer() -> Result<(), Box> { + let _ = env_logger::try_init(); + + let tests = vec![ + Test::new("test_tooltip_01.rs", 80, 11), + Test::new("test_tooltip_01.rs", 93, 18), + Test::new("test_tooltip_mod_use_external.rs", 11, 7), + Test::new("test_tooltip_mod_use_external.rs", 12, 7), + Test::new("test_tooltip_mod_use_external.rs", 12, 12), + ]; + + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::Yes) +} + +/// Note: This test is ignored as it doesn't work in the rust-lang/rust repo. +/// It is enabled on CI. +/// Run with `cargo test test_tooltip_std -- --ignored` +#[test] +#[ignore] +fn test_tooltip_std() -> Result<(), Box> { + let _ = env_logger::try_init(); + + let tests = vec![ + Test::new("test_tooltip_std.rs", 18, 15), + Test::new("test_tooltip_std.rs", 18, 27), + Test::new("test_tooltip_std.rs", 19, 7), + Test::new("test_tooltip_std.rs", 19, 12), + Test::new("test_tooltip_std.rs", 20, 12), + Test::new("test_tooltip_std.rs", 20, 20), + Test::new("test_tooltip_std.rs", 21, 25), + Test::new("test_tooltip_std.rs", 22, 33), + Test::new("test_tooltip_std.rs", 23, 11), + Test::new("test_tooltip_std.rs", 23, 18), + Test::new("test_tooltip_std.rs", 24, 24), + Test::new("test_tooltip_std.rs", 25, 17), + Test::new("test_tooltip_std.rs", 25, 25), + ]; + + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::No) +} + +/// Note: This test is ignored as it doesn't work in the rust-lang/rust repo. +/// It is enabled on CI. +/// Run with `cargo test test_tooltip_std -- --ignored` +#[test] +#[ignore] +fn test_tooltip_std_racer() -> Result<(), Box> { + let _ = env_logger::try_init(); + + let tests = vec![ + // these test std stuff + Test::new("test_tooltip_mod_use_external.rs", 14, 12), + Test::new("test_tooltip_mod_use_external.rs", 15, 12), + ]; + + run_tooltip_tests(&tests, fixtures_dir().join("hover"), RacerFallback::Yes) +} From 71cce6423f28708eb57236a7423daae1c52cb33b Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sat, 22 Dec 2018 11:46:01 +0100 Subject: [PATCH 12/14] Don't test RLS binary target directly And don't doc-test RLS library while we're at it. For now, it doesn't make sense to do so, since because of how RLS is architectured, every test would have to potentially always include a lengthy initialization code. This would be too much noise and of dubious benefit. --- Cargo.toml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/Cargo.toml b/Cargo.toml index 4d803c76873..32ed961cbee 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,6 +10,14 @@ categories = ["development-tools"] build = "build.rs" +[lib] +name = "rls" +doctest = false + +[[bin]] +name = "rls" +test = false + [dependencies] cargo = { git = "https://github.com/rust-lang/cargo", rev = "2a9f16da7ffc4877aacf7547bac705f0d82de2d6" } cargo_metadata = "0.6" From 406e709c115b6765f81c353837b8a7beb4c297f9 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sat, 22 Dec 2018 12:04:05 +0100 Subject: [PATCH 13/14] Reorganize some tests --- tests/lens.rs | 74 --------------------------- tests/tests.rs | 70 ++++++++++++++++++++++++- tests/{translated.rs => tests_old.rs} | 0 3 files changed, 68 insertions(+), 76 deletions(-) delete mode 100644 tests/lens.rs rename tests/{translated.rs => tests_old.rs} (100%) diff --git a/tests/lens.rs b/tests/lens.rs deleted file mode 100644 index e71bfb35b9c..00000000000 --- a/tests/lens.rs +++ /dev/null @@ -1,74 +0,0 @@ -use rls::actions::requests; - -use languageserver_types::request::Request as _; - -use self::support::{fixtures_dir, rls_timeout}; -use self::support::harness::compare_json; -use self::support::project_builder::ProjectBuilder; -use serde_json; -use serde_json::json; - -#[allow(dead_code)] -mod support; - -#[test] -fn cmd_lens_run() { - let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("lens_run")) - .unwrap() - .build(); - let root_path = p.root(); - let mut rls = p.spawn_rls(); - - rls.request( - 0, - "initialize", - Some(json!({ - "rootPath": root_path, - "capabilities": {}, - "initializationOptions": { "cmdRun": true } - })), - ) - .unwrap(); - - let json: Vec<_> = rls - .wait_until_done_indexing(rls_timeout()) - .to_json_messages() - .collect(); - assert!(json.len() >= 7); - - let request_id = 1; - rls.request( - request_id, - requests::CodeLensRequest::METHOD, - Some(json!({ - "textDocument": { - "uri": format!("file://{}/src/main.rs", root_path.display()), - "version": 1 - } - })), - ) - .unwrap(); - - let json = rls.wait_until_json_id(request_id, rls_timeout()); - - compare_json( - &json["result"], - r#"[{ - "command": { - "command": "rls.run", - "title": "Run test", - "arguments": [{ - "args": [ "test", "--", "--nocapture", "test_foo" ], - "binary": "cargo", - "env": { "RUST_BACKTRACE": "short" } - }] - }, - "range": { - "start": { "character": 3, "line": 14 }, - "end": { "character": 11, "line": 14 } - } - }]"#, - ); - - rls.shutdown(rls_timeout()); -} diff --git a/tests/tests.rs b/tests/tests.rs index cf35a3a524e..50f3303c4ca 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -13,8 +13,12 @@ use serde_json::{self, json}; use std::io::Write; use std::time::Duration; -use self::support::{basic_bin_manifest, RlsStdout, rls_timeout}; -use self::support::project_builder::project; +use rls::actions::requests; +use rls::lsp_data::request::Request as _; + +use self::support::harness::compare_json; +use self::support::project_builder::{project, ProjectBuilder}; +use self::support::{basic_bin_manifest, fixtures_dir, rls_timeout, RlsStdout}; #[allow(dead_code)] mod support; @@ -1298,3 +1302,65 @@ fn cmd_format_utf16_range() { rls.shutdown(rls_timeout()); } + +#[test] +fn cmd_lens_run() { + let p = ProjectBuilder::try_from_fixture(fixtures_dir().join("lens_run")) + .unwrap() + .build(); + let root_path = p.root(); + let mut rls = p.spawn_rls(); + + rls.request( + 0, + "initialize", + Some(json!({ + "rootPath": root_path, + "capabilities": {}, + "initializationOptions": { "cmdRun": true } + })), + ) + .unwrap(); + + let json: Vec<_> = rls + .wait_until_done_indexing(rls_timeout()) + .to_json_messages() + .collect(); + assert!(json.len() >= 7); + + let request_id = 1; + rls.request( + request_id, + requests::CodeLensRequest::METHOD, + Some(json!({ + "textDocument": { + "uri": format!("file://{}/src/main.rs", root_path.display()), + "version": 1 + } + })), + ) + .unwrap(); + + let json = rls.wait_until_json_id(request_id, rls_timeout()); + + compare_json( + &json["result"], + r#"[{ + "command": { + "command": "rls.run", + "title": "Run test", + "arguments": [{ + "args": [ "test", "--", "--nocapture", "test_foo" ], + "binary": "cargo", + "env": { "RUST_BACKTRACE": "short" } + }] + }, + "range": { + "start": { "character": 3, "line": 14 }, + "end": { "character": 11, "line": 14 } + } + }]"#, + ); + + rls.shutdown(rls_timeout()); +} diff --git a/tests/translated.rs b/tests/tests_old.rs similarity index 100% rename from tests/translated.rs rename to tests/tests_old.rs From 9f116ceabc5bbdf74ab935fc19be6bcb11f67c76 Mon Sep 17 00:00:00 2001 From: Igor Matuszewski Date: Sat, 22 Dec 2018 12:29:49 +0100 Subject: [PATCH 14/14] Remove old test_data entry in .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index b2f26765566..665657d11ba 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,3 @@ .idea target -test_data/Cargo.lock