From 4b8265dc778d85686f70c9cbf88254dc6eb209ad Mon Sep 17 00:00:00 2001 From: Brian Vincent Date: Fri, 1 Dec 2017 01:50:17 -0600 Subject: [PATCH 1/4] Added integration tests, added back test_simple_workspace --- tests/support/harness.rs | 164 ++++++++++++++++++++++++++++++++++++++ tests/support/mod.rs | 167 +++++++++++++++++++++++++++++++++++++++ tests/support/paths.rs | 117 +++++++++++++++++++++++++++ tests/tests.rs | 131 ++++++++++++++++++++++++++++++ 4 files changed, 579 insertions(+) create mode 100644 tests/support/harness.rs create mode 100644 tests/support/mod.rs create mode 100644 tests/support/paths.rs create mode 100644 tests/tests.rs diff --git a/tests/support/harness.rs b/tests/support/harness.rs new file mode 100644 index 00000000000..a8b04987f1f --- /dev/null +++ b/tests/support/harness.rs @@ -0,0 +1,164 @@ +// 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 std::io::{self, BufRead, BufReader, Read, Write}; +use std::mem; +use std::process::{Child, ChildStdin, ChildStdout}; + +use serde_json; + +#[derive(Clone, Debug)] +pub struct ExpectedMessage { + id: Option, + contains: Vec, +} + +impl ExpectedMessage { + pub fn new(id: Option) -> ExpectedMessage { + ExpectedMessage { + id: id, + contains: vec![], + } + } + + pub fn expect_contains(&mut self, s: &str) -> &mut ExpectedMessage { + self.contains.push(s.to_owned()); + self + } +} + +pub fn read_message(reader: &mut BufReader) -> io::Result { + let mut content_length = None; + // Read the headers + loop { + let mut header = String::new(); + reader.read_line(&mut header)?; + if header.len() == 0 { + panic!("eof") + } + if header == "\r\n" { + // This is the end of the headers + break; + } + let parts: Vec<&str> = header.splitn(2, ": ").collect(); + if parts[0] == "Content-Length" { + content_length = Some(parts[1].trim().parse::().unwrap()) + } + } + + // Read the actual message + let content_length = content_length.expect("did not receive Content-Length header"); + let mut msg = vec![0; content_length]; + reader.read_exact(&mut msg)?; + let result = String::from_utf8_lossy(&msg).into_owned(); + Ok(result) +} + +pub fn expect_messages(reader: &mut BufReader, expected: &[&ExpectedMessage]) { + let mut results: Vec = Vec::new(); + while results.len() < expected.len() { + results.push(read_message(reader).unwrap()); + } + + println!( + "expect_messages:\n results: {:#?},\n expected: {:#?}", + results, + expected + ); + assert_eq!(results.len(), expected.len()); + for (found, expected) in results.iter().zip(expected.iter()) { + let values: serde_json::Value = serde_json::from_str(found).unwrap(); + assert!( + values + .get("jsonrpc") + .expect("Missing jsonrpc field") + .as_str() + .unwrap() == "2.0", + "Bad jsonrpc field" + ); + if let Some(id) = expected.id { + assert_eq!( + values + .get("id") + .expect("Missing id field") + .as_u64() + .unwrap(), + id, + "Unexpected id" + ); + } + for c in expected.contains.iter() { + found + .find(c) + .expect(&format!("Could not find `{}` in `{}`", c, found)); + } + } +} + +pub struct RlsHandle { + child: Child, + stdin: ChildStdin, + stdout: BufReader, +} + +impl RlsHandle { + pub fn new(mut child: Child) -> RlsHandle { + let stdin = mem::replace(&mut child.stdin, None).unwrap(); + let stdout = mem::replace(&mut child.stdout, None).unwrap(); + let stdout = BufReader::new(stdout); + + RlsHandle { + child, + stdin, + stdout, + } + } + + pub fn send_string(&mut self, s: &str) -> io::Result { + let full_msg = format!("Content-Length: {}\r\n\r\n{}", s.len(), s); + self.stdin.write(full_msg.as_bytes()) + } + pub fn send(&mut self, j: serde_json::Value) -> io::Result { + self.send_string(&j.to_string()) + } + pub fn notify(&mut self, method: &str, params: serde_json::Value) -> io::Result { + self.send(json!({ + "jsonrpc": "2.0", + "method": method, + "params": params, + })) + } + pub fn request(&mut self, id: u64, method: &str, params: serde_json::Value) -> io::Result { + self.send(json!({ + "jsonrpc": "2.0", + "id": id, + "method": method, + "params": params, + })) + } + pub fn shutdown_exit(&mut self) { + self.request(99999, "shutdown", json!({})).unwrap(); + + self.expect_messages(&[ + &ExpectedMessage::new(Some(99999)), + ]); + + self.notify("exit", json!({})).unwrap(); + + let ecode = self.child.wait() + .expect("failed to wait on child rls process"); + + assert!(ecode.success()); + } + + pub fn expect_messages(&mut self, expected: &[&ExpectedMessage]) { + expect_messages(&mut self.stdout, expected); + } +} \ No newline at end of file diff --git a/tests/support/mod.rs b/tests/support/mod.rs new file mode 100644 index 00000000000..df14b7c3931 --- /dev/null +++ b/tests/support/mod.rs @@ -0,0 +1,167 @@ +// 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 std::env; +use std::fs; +use std::io::prelude::*; +use std::path::{Path, PathBuf}; +use std::process::{Command, Stdio}; +use std::str; + +use support::paths::TestPathExt; + +pub mod harness; +pub mod paths; + +#[derive(PartialEq,Clone)] +struct FileBuilder { + path: PathBuf, + body: String +} + +impl FileBuilder { + pub fn new(path: PathBuf, body: &str) -> FileBuilder { + FileBuilder { path: path, body: body.to_string() } + } + + fn mk(&self) { + self.dirname().mkdir_p(); + + let mut file = fs::File::create(&self.path).unwrap_or_else(|e| { + panic!("could not create file {}: {}", self.path.display(), e) + }); + + file.write_all(self.body.as_bytes()).unwrap(); + } + + fn dirname(&self) -> &Path { + self.path.parent().unwrap() + } +} + +#[derive(PartialEq,Clone)] +pub struct Project{ + root: PathBuf, +} + +#[must_use] +#[derive(PartialEq,Clone)] +pub struct ProjectBuilder { + name: String, + root: Project, + files: Vec, +} + +impl ProjectBuilder { + pub fn new(name: &str, root: PathBuf) -> ProjectBuilder { + ProjectBuilder { + name: name.to_string(), + root: Project{ root }, + files: vec![], + } + } + + pub fn file>(mut self, path: B, + body: &str) -> Self { + self._file(path.as_ref(), body); + self + } + + fn _file(&mut self, path: &Path, body: &str) { + self.files.push(FileBuilder::new(self.root.root.join(path), body)); + } + + pub fn build(self) -> Project { + // First, clean the directory if it already exists + self.rm_root(); + + // Create the empty directory + self.root.root.mkdir_p(); + + for file in self.files.iter() { + file.mk(); + } + + self.root + } + + fn rm_root(&self) { + self.root.root.rm_rf() + } +} + +impl Project { + pub fn root(&self) -> PathBuf { + self.root.clone() + } + + pub fn rls(&self) -> Command { + let mut cmd = Command::new(rls_exe()); + cmd.stdin(Stdio::piped()) + .stdout(Stdio::piped()) + .current_dir(self.root()); + cmd + } +} + +// Generates a project layout +pub fn project(name: &str) -> ProjectBuilder { + ProjectBuilder::new(name, paths::root().join(name)) +} + +// Path to cargo executables +pub fn target_conf_dir() -> PathBuf { + let mut path = env::current_exe().unwrap(); + path.pop(); + if path.ends_with("deps") { + path.pop(); + } + path +} + +pub fn rls_exe() -> PathBuf { + target_conf_dir().join(format!("rls{}", env::consts::EXE_SUFFIX)) +} + +pub fn main_file(println: &str, deps: &[&str]) -> String { + let mut buf = String::new(); + + for dep in deps.iter() { + buf.push_str(&format!("extern crate {};\n", dep)); + } + + buf.push_str("fn main() { println!("); + buf.push_str(&println); + buf.push_str("); }\n"); + + buf.to_string() +} + +pub fn basic_bin_manifest(name: &str) -> String { + format!(r#" + [package] + name = "{}" + version = "0.5.0" + authors = ["wycats@example.com"] + [[bin]] + name = "{}" + "#, name, name) +} + +pub fn basic_lib_manifest(name: &str) -> String { + format!(r#" + [package] + name = "{}" + version = "0.5.0" + authors = ["wycats@example.com"] + [lib] + name = "{}" + "#, name, name) +} \ No newline at end of file diff --git a/tests/support/paths.rs b/tests/support/paths.rs new file mode 100644 index 00000000000..4b834521f88 --- /dev/null +++ b/tests/support/paths.rs @@ -0,0 +1,117 @@ +// 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 std::env; +use std::cell::Cell; +use std::fs; +use std::io::{self, ErrorKind}; +use std::path::{Path, PathBuf}; +use std::sync::{Once, ONCE_INIT}; +use std::sync::atomic::{AtomicUsize, ATOMIC_USIZE_INIT, Ordering}; + +static RLS_INTEGRATION_TEST_DIR : &'static str = "rlsit"; +static NEXT_ID: AtomicUsize = ATOMIC_USIZE_INIT; + +thread_local!(static TASK_ID: usize = NEXT_ID.fetch_add(1, Ordering::SeqCst)); + +fn init() { + static GLOBAL_INIT: Once = ONCE_INIT; + thread_local!(static LOCAL_INIT: Cell = Cell::new(false)); + GLOBAL_INIT.call_once(|| { + global_root().mkdir_p(); + }); + LOCAL_INIT.with(|i| { + if i.get() { + return + } + i.set(true); + root().rm_rf(); + }) +} + +fn global_root() -> PathBuf { + let mut path = env::current_exe().unwrap(); + path.pop(); // chop off exe name + path.pop(); // chop off 'debug' + + // If `cargo test` is run manually then our path looks like + // `target/debug/foo`, in which case our `path` is already pointing at + // `target`. If, however, `cargo test --target $target` is used then the + // output is `target/$target/debug/foo`, so our path is pointing at + // `target/$target`. Here we conditionally pop the `$target` name. + if path.file_name().and_then(|s| s.to_str()) != Some("target") { + path.pop(); + } + + path.join(RLS_INTEGRATION_TEST_DIR) +} + +pub fn root() -> PathBuf { + init(); + global_root().join(&TASK_ID.with(|my_id| format!("t{}", my_id))) +} + + +pub trait TestPathExt { + fn rm_rf(&self); + fn mkdir_p(&self); +} + +impl TestPathExt for Path { + /* Technically there is a potential race condition, but we don't + * care all that much for our tests + */ + fn rm_rf(&self) { + if !self.exists() { + return + } + + for file in fs::read_dir(self).unwrap() { + let file = file.unwrap().path(); + + if file.is_dir() { + file.rm_rf(); + } else { + // On windows we can't remove a readonly file, and git will + // often clone files as readonly. As a result, we have some + // special logic to remove readonly files on windows. + do_op(&file, "remove file", |p| fs::remove_file(p)); + } + } + do_op(self, "remove dir", |p| fs::remove_dir(p)); + } + + fn mkdir_p(&self) { + fs::create_dir_all(self).unwrap_or_else(|e| { + panic!("failed to mkdir_p {}: {}", self.display(), e) + }) + } + +} + +fn do_op(path: &Path, desc: &str, mut f: F) + where F: FnMut(&Path) -> io::Result<()> +{ + match f(path) { + Ok(()) => {} + Err(ref e) if cfg!(windows) && + e.kind() == ErrorKind::PermissionDenied => { + let mut p = path.metadata().unwrap().permissions(); + p.set_readonly(false); + fs::set_permissions(path, p).unwrap(); + f(path).unwrap_or_else(|e| { + panic!("failed to {} {}: {}", desc, path.display(), e); + }) + } + Err(e) => { + panic!("failed to {} {}: {}", desc, path.display(), e); + } + } +} \ No newline at end of file diff --git a/tests/tests.rs b/tests/tests.rs new file mode 100644 index 00000000000..10c16ca3ce1 --- /dev/null +++ b/tests/tests.rs @@ -0,0 +1,131 @@ + +// 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. + +extern crate cargo; +#[macro_use] +extern crate serde_json; + +mod support; +use support::{basic_bin_manifest, project}; +use support::harness::{ExpectedMessage, RlsHandle}; + +#[test] +fn test_infer_bin() { + let p = project("simple_workspace") + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/main.rs", r#" + struct UnusedBin; + fn main() { + println!("Hello world!"); + } + "#) + .build(); + + let root_path = p.root(); + let rls_child = p.rls().spawn().unwrap(); + let mut rls = RlsHandle::new(rls_child); + + rls.request(0, "initialize", json!({ + "rootPath": root_path, + "capabilities": {} + })).unwrap(); + + rls.expect_messages(&[ + ExpectedMessage::new(Some(0)).expect_contains("capabilities"), + ExpectedMessage::new(None).expect_contains("beginBuild"), + ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), + ExpectedMessage::new(None).expect_contains("struct is never used: `UnusedBin`"), + ExpectedMessage::new(None).expect_contains("diagnosticsEnd") + ]); + + rls.shutdown_exit(); +} + +#[test] +fn test_simple_workspace() { + let p = project("simple_workspace") + .file("Cargo.toml", r#" + [workspace] + members = [ + "member_lib", + "member_bin", + ] + "#) + .file("Cargo.lock", r#" + [root] + name = "member_lib" + version = "0.1.0" + + [[package]] + name = "member_bin" + version = "0.1.0" + dependencies = [ + "member_lib 0.1.0", + ] + "#) + .file("member_bin/Cargo.toml", r#" + [package] + name = "member_bin" + version = "0.1.0" + authors = ["Igor Matuszewski "] + + [dependencies] + member_lib = { path = "../member_lib" } + "#) + .file("member_bin/src/main.rs", r#" + extern crate member_lib; + + fn main() { + let a = member_lib::MemberLibStruct; + } + "#) + .file("member_lib/Cargo.toml", r#" + [package] + name = "member_lib" + version = "0.1.0" + authors = ["Igor Matuszewski "] + + [dependencies] + "#) + .file("member_lib/src/lib.rs", r#" + pub struct MemberLibStruct; + + struct Unused; + + #[cfg(test)] + mod tests { + #[test] + fn it_works() { + } + } + "#) + .build(); + + let root_path = p.root(); + let rls_child = p.rls().spawn().unwrap(); + let mut rls = RlsHandle::new(rls_child); + + rls.request(0, "initialize", json!({ + "rootPath": root_path, + "capabilities": {} + })).unwrap(); + + rls.expect_messages(&[ + ExpectedMessage::new(Some(0)).expect_contains("capabilities"), + ExpectedMessage::new(None).expect_contains("beginBuild"), + ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), + ExpectedMessage::new(None).expect_contains("publishDiagnostics"), + ExpectedMessage::new(None).expect_contains("publishDiagnostics"), + ExpectedMessage::new(None).expect_contains("diagnosticsEnd") + ]); + + rls.shutdown_exit(); +} \ No newline at end of file From 98e9bda4485ce0c207dfedbce2f730513864d804 Mon Sep 17 00:00:00 2001 From: Brian Vincent Date: Wed, 6 Dec 2017 22:46:07 -0600 Subject: [PATCH 2/4] Added a timeout to the integration tests --- tests/support/harness.rs | 5 +- tests/tests.rs | 245 ++++++++++++++++++++++----------------- 2 files changed, 143 insertions(+), 107 deletions(-) diff --git a/tests/support/harness.rs b/tests/support/harness.rs index a8b04987f1f..7932029b4c4 100644 --- a/tests/support/harness.rs +++ b/tests/support/harness.rs @@ -64,7 +64,8 @@ pub fn read_message(reader: &mut BufReader) -> io::Result { pub fn expect_messages(reader: &mut BufReader, expected: &[&ExpectedMessage]) { let mut results: Vec = Vec::new(); while results.len() < expected.len() { - results.push(read_message(reader).unwrap()); + let msg = read_message(reader).unwrap(); + results.push(msg); } println!( @@ -161,4 +162,4 @@ impl RlsHandle { pub fn expect_messages(&mut self, expected: &[&ExpectedMessage]) { expect_messages(&mut self.stdout, expected); } -} \ No newline at end of file +} diff --git a/tests/tests.rs b/tests/tests.rs index 10c16ca3ce1..40a6ef22d1b 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -13,119 +13,154 @@ extern crate cargo; #[macro_use] extern crate serde_json; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread; +use std::time::Duration; + mod support; use support::{basic_bin_manifest, project}; use support::harness::{ExpectedMessage, RlsHandle}; +fn timeout(dur: Duration, func: F) + where F: FnOnce() + Send + 'static { + let pair = Arc::new((Mutex::new(false), Condvar::new())); + let pair2 = pair.clone(); + + thread::spawn(move|| { + let &(ref lock, ref cvar) = &*pair2; + func(); + let mut finished = lock.lock().unwrap(); + *finished = true; + // We notify the condvar that the value has changed. + cvar.notify_one(); + }); + + // Wait for the test to finish. + let &(ref lock, ref cvar) = &*pair; + let mut finished = lock.lock().unwrap(); + // As long as the value inside the `Mutex` is false, we wait. + while !*finished { + let result = cvar.wait_timeout(finished, dur).unwrap(); + if result.1.timed_out() { + panic!("Timed out") + } + finished = result.0 + } +} + #[test] fn test_infer_bin() { - let p = project("simple_workspace") - .file("Cargo.toml", &basic_bin_manifest("foo")) - .file("src/main.rs", r#" - struct UnusedBin; - fn main() { - println!("Hello world!"); - } - "#) - .build(); - - let root_path = p.root(); - let rls_child = p.rls().spawn().unwrap(); - let mut rls = RlsHandle::new(rls_child); - - rls.request(0, "initialize", json!({ - "rootPath": root_path, - "capabilities": {} - })).unwrap(); - - rls.expect_messages(&[ - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ExpectedMessage::new(None).expect_contains("beginBuild"), - ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), - ExpectedMessage::new(None).expect_contains("struct is never used: `UnusedBin`"), - ExpectedMessage::new(None).expect_contains("diagnosticsEnd") - ]); - - rls.shutdown_exit(); + timeout(Duration::from_secs(300), ||{ + let p = project("simple_workspace") + .file("Cargo.toml", &basic_bin_manifest("foo")) + .file("src/main.rs", r#" + struct UnusedBin; + fn main() { + println!("Hello world!"); + } + "#) + .build(); + + let root_path = p.root(); + let rls_child = p.rls().spawn().unwrap(); + let mut rls = RlsHandle::new(rls_child); + + rls.request(0, "initialize", json!({ + "rootPath": root_path, + "capabilities": {} + })).unwrap(); + + rls.expect_messages(&[ + ExpectedMessage::new(Some(0)).expect_contains("capabilities"), + ExpectedMessage::new(None).expect_contains("beginBuild"), + ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), + ExpectedMessage::new(None).expect_contains("struct is never used: `UnusedBin`"), + ExpectedMessage::new(None).expect_contains("diagnosticsEnd") + ]); + + rls.shutdown_exit(); + }); } #[test] fn test_simple_workspace() { - let p = project("simple_workspace") - .file("Cargo.toml", r#" - [workspace] - members = [ - "member_lib", - "member_bin", - ] - "#) - .file("Cargo.lock", r#" - [root] - name = "member_lib" - version = "0.1.0" - - [[package]] - name = "member_bin" - version = "0.1.0" - dependencies = [ - "member_lib 0.1.0", - ] - "#) - .file("member_bin/Cargo.toml", r#" - [package] - name = "member_bin" - version = "0.1.0" - authors = ["Igor Matuszewski "] - - [dependencies] - member_lib = { path = "../member_lib" } - "#) - .file("member_bin/src/main.rs", r#" - extern crate member_lib; - - fn main() { - let a = member_lib::MemberLibStruct; - } - "#) - .file("member_lib/Cargo.toml", r#" - [package] - name = "member_lib" - version = "0.1.0" - authors = ["Igor Matuszewski "] - - [dependencies] - "#) - .file("member_lib/src/lib.rs", r#" - pub struct MemberLibStruct; - - struct Unused; - - #[cfg(test)] - mod tests { - #[test] - fn it_works() { + timeout(Duration::from_secs(300), ||{ + let p = project("simple_workspace") + .file("Cargo.toml", r#" + [workspace] + members = [ + "member_lib", + "member_bin", + ] + "#) + .file("Cargo.lock", r#" + [root] + name = "member_lib" + version = "0.1.0" + + [[package]] + name = "member_bin" + version = "0.1.0" + dependencies = [ + "member_lib 0.1.0", + ] + "#) + .file("member_bin/Cargo.toml", r#" + [package] + name = "member_bin" + version = "0.1.0" + authors = ["Igor Matuszewski "] + + [dependencies] + member_lib = { path = "../member_lib" } + "#) + .file("member_bin/src/main.rs", r#" + extern crate member_lib; + + fn main() { + let a = member_lib::MemberLibStruct; + } + "#) + .file("member_lib/Cargo.toml", r#" + [package] + name = "member_lib" + version = "0.1.0" + authors = ["Igor Matuszewski "] + + [dependencies] + "#) + .file("member_lib/src/lib.rs", r#" + pub struct MemberLibStruct; + + struct Unused; + + #[cfg(test)] + mod tests { + #[test] + fn it_works() { + } } - } - "#) - .build(); - - let root_path = p.root(); - let rls_child = p.rls().spawn().unwrap(); - let mut rls = RlsHandle::new(rls_child); - - rls.request(0, "initialize", json!({ - "rootPath": root_path, - "capabilities": {} - })).unwrap(); - - rls.expect_messages(&[ - ExpectedMessage::new(Some(0)).expect_contains("capabilities"), - ExpectedMessage::new(None).expect_contains("beginBuild"), - ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), - ExpectedMessage::new(None).expect_contains("publishDiagnostics"), - ExpectedMessage::new(None).expect_contains("publishDiagnostics"), - ExpectedMessage::new(None).expect_contains("diagnosticsEnd") - ]); - - rls.shutdown_exit(); -} \ No newline at end of file + "#) + .build(); + + let root_path = p.root(); + let rls_child = p.rls().spawn().unwrap(); + let mut rls = RlsHandle::new(rls_child); + + rls.request(0, "initialize", json!({ + "rootPath": root_path, + "capabilities": {} + })).unwrap(); + + rls.expect_messages(&[ + ExpectedMessage::new(Some(0)).expect_contains("capabilities"), + ExpectedMessage::new(None).expect_contains("beginBuild"), + ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), + ExpectedMessage::new(None).expect_contains("publishDiagnostics"), + ExpectedMessage::new(None).expect_contains("publishDiagnostics"), + ExpectedMessage::new(None).expect_contains("diagnosticsEnd") + ]); + + rls.shutdown_exit(); + }); +} From 8a2bcca2a818804d9bc82542ebeecae9feed097d Mon Sep 17 00:00:00 2001 From: Brian Vincent Date: Wed, 6 Dec 2017 23:43:38 -0600 Subject: [PATCH 3/4] Fixed simple_workspace_test for workspace_mode being off --- tests/tests.rs | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/tests.rs b/tests/tests.rs index 40a6ef22d1b..a4e2d7f6b61 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -152,10 +152,37 @@ fn test_simple_workspace() { "capabilities": {} })).unwrap(); + // This is the expected behavior is workspace_mode is on by default + // rls.expect_messages(&[ + // ExpectedMessage::new(Some(0)).expect_contains("capabilities"), + // ExpectedMessage::new(None).expect_contains("beginBuild"), + // ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), + // ExpectedMessage::new(None).expect_contains("publishDiagnostics"), + // ExpectedMessage::new(None).expect_contains("publishDiagnostics"), + // ExpectedMessage::new(None).expect_contains("diagnosticsEnd") + // ]); + + // This is the expected behavior is workspace_mode is off by default rls.expect_messages(&[ ExpectedMessage::new(Some(0)).expect_contains("capabilities"), ExpectedMessage::new(None).expect_contains("beginBuild"), ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), + ExpectedMessage::new(None).expect_contains("diagnosticsEnd") + ]); + + // Turn on workspace mode + rls.notify("workspace/didChangeConfiguration", json!({ + "settings": { + "rust": { + "workspace_mode": true + } + } + })).unwrap(); + + rls.expect_messages(&[ + ExpectedMessage::new(None).expect_contains("beginBuild"), + ExpectedMessage::new(Some(1)).expect_contains("unregisterCapability"), + ExpectedMessage::new(None).expect_contains("diagnosticsBegin"), ExpectedMessage::new(None).expect_contains("publishDiagnostics"), ExpectedMessage::new(None).expect_contains("publishDiagnostics"), ExpectedMessage::new(None).expect_contains("diagnosticsEnd") From 5223549b1e6d2a5a3d678507b1adbf33336dd5c4 Mon Sep 17 00:00:00 2001 From: Brian Vincent Date: Wed, 13 Dec 2017 01:08:20 -0600 Subject: [PATCH 4/4] Refactored integration tests --- tests/support/harness.rs | 165 ---------------------------------- tests/support/mod.rs | 189 ++++++++++++++++++++++++++++++++++++++- tests/tests.rs | 34 +------ 3 files changed, 189 insertions(+), 199 deletions(-) delete mode 100644 tests/support/harness.rs diff --git a/tests/support/harness.rs b/tests/support/harness.rs deleted file mode 100644 index 7932029b4c4..00000000000 --- a/tests/support/harness.rs +++ /dev/null @@ -1,165 +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. - -use std::io::{self, BufRead, BufReader, Read, Write}; -use std::mem; -use std::process::{Child, ChildStdin, ChildStdout}; - -use serde_json; - -#[derive(Clone, Debug)] -pub struct ExpectedMessage { - id: Option, - contains: Vec, -} - -impl ExpectedMessage { - pub fn new(id: Option) -> ExpectedMessage { - ExpectedMessage { - id: id, - contains: vec![], - } - } - - pub fn expect_contains(&mut self, s: &str) -> &mut ExpectedMessage { - self.contains.push(s.to_owned()); - self - } -} - -pub fn read_message(reader: &mut BufReader) -> io::Result { - let mut content_length = None; - // Read the headers - loop { - let mut header = String::new(); - reader.read_line(&mut header)?; - if header.len() == 0 { - panic!("eof") - } - if header == "\r\n" { - // This is the end of the headers - break; - } - let parts: Vec<&str> = header.splitn(2, ": ").collect(); - if parts[0] == "Content-Length" { - content_length = Some(parts[1].trim().parse::().unwrap()) - } - } - - // Read the actual message - let content_length = content_length.expect("did not receive Content-Length header"); - let mut msg = vec![0; content_length]; - reader.read_exact(&mut msg)?; - let result = String::from_utf8_lossy(&msg).into_owned(); - Ok(result) -} - -pub fn expect_messages(reader: &mut BufReader, expected: &[&ExpectedMessage]) { - let mut results: Vec = Vec::new(); - while results.len() < expected.len() { - let msg = read_message(reader).unwrap(); - results.push(msg); - } - - println!( - "expect_messages:\n results: {:#?},\n expected: {:#?}", - results, - expected - ); - assert_eq!(results.len(), expected.len()); - for (found, expected) in results.iter().zip(expected.iter()) { - let values: serde_json::Value = serde_json::from_str(found).unwrap(); - assert!( - values - .get("jsonrpc") - .expect("Missing jsonrpc field") - .as_str() - .unwrap() == "2.0", - "Bad jsonrpc field" - ); - if let Some(id) = expected.id { - assert_eq!( - values - .get("id") - .expect("Missing id field") - .as_u64() - .unwrap(), - id, - "Unexpected id" - ); - } - for c in expected.contains.iter() { - found - .find(c) - .expect(&format!("Could not find `{}` in `{}`", c, found)); - } - } -} - -pub struct RlsHandle { - child: Child, - stdin: ChildStdin, - stdout: BufReader, -} - -impl RlsHandle { - pub fn new(mut child: Child) -> RlsHandle { - let stdin = mem::replace(&mut child.stdin, None).unwrap(); - let stdout = mem::replace(&mut child.stdout, None).unwrap(); - let stdout = BufReader::new(stdout); - - RlsHandle { - child, - stdin, - stdout, - } - } - - pub fn send_string(&mut self, s: &str) -> io::Result { - let full_msg = format!("Content-Length: {}\r\n\r\n{}", s.len(), s); - self.stdin.write(full_msg.as_bytes()) - } - pub fn send(&mut self, j: serde_json::Value) -> io::Result { - self.send_string(&j.to_string()) - } - pub fn notify(&mut self, method: &str, params: serde_json::Value) -> io::Result { - self.send(json!({ - "jsonrpc": "2.0", - "method": method, - "params": params, - })) - } - pub fn request(&mut self, id: u64, method: &str, params: serde_json::Value) -> io::Result { - self.send(json!({ - "jsonrpc": "2.0", - "id": id, - "method": method, - "params": params, - })) - } - pub fn shutdown_exit(&mut self) { - self.request(99999, "shutdown", json!({})).unwrap(); - - self.expect_messages(&[ - &ExpectedMessage::new(Some(99999)), - ]); - - self.notify("exit", json!({})).unwrap(); - - let ecode = self.child.wait() - .expect("failed to wait on child rls process"); - - assert!(ecode.success()); - } - - pub fn expect_messages(&mut self, expected: &[&ExpectedMessage]) { - expect_messages(&mut self.stdout, expected); - } -} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index df14b7c3931..c216cad9c8b 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -8,18 +8,201 @@ // option. This file may not be copied, modified, or distributed // except according to those terms. +use serde_json; use std::env; use std::fs; -use std::io::prelude::*; +use std::io::{self, BufRead, BufReader, Read, Write}; +use std::mem; use std::path::{Path, PathBuf}; -use std::process::{Command, Stdio}; +use std::process::{Child, ChildStdin, ChildStdout, Command, Stdio}; use std::str; +use std::sync::{Arc, Condvar, Mutex}; +use std::thread; +use std::time::Duration; + use support::paths::TestPathExt; -pub mod harness; pub mod paths; +/// Executes `func` and panics if it takes longer than `dur`. +pub fn timeout(dur: Duration, func: F) + where F: FnOnce() + Send + 'static { + let pair = Arc::new((Mutex::new(false), Condvar::new())); + let pair2 = pair.clone(); + + thread::spawn(move|| { + let &(ref lock, ref cvar) = &*pair2; + func(); + let mut finished = lock.lock().unwrap(); + *finished = true; + // We notify the condvar that the value has changed. + cvar.notify_one(); + }); + + // Wait for the test to finish. + let &(ref lock, ref cvar) = &*pair; + let mut finished = lock.lock().unwrap(); + // As long as the value inside the `Mutex` is false, we wait. + while !*finished { + let result = cvar.wait_timeout(finished, dur).unwrap(); + if result.1.timed_out() { + panic!("Timed out") + } + finished = result.0 + } +} + +#[derive(Clone, Debug)] +pub struct ExpectedMessage { + id: Option, + contains: Vec, +} + +impl ExpectedMessage { + pub fn new(id: Option) -> ExpectedMessage { + ExpectedMessage { + id: id, + contains: vec![], + } + } + + pub fn expect_contains(&mut self, s: &str) -> &mut ExpectedMessage { + self.contains.push(s.to_owned()); + self + } +} + +pub fn read_message(reader: &mut BufReader) -> io::Result { + let mut content_length = None; + // Read the headers + loop { + let mut header = String::new(); + reader.read_line(&mut header)?; + if header.len() == 0 { + panic!("eof") + } + if header == "\r\n" { + // This is the end of the headers + break; + } + let parts: Vec<&str> = header.splitn(2, ": ").collect(); + if parts[0] == "Content-Length" { + content_length = Some(parts[1].trim().parse::().unwrap()) + } + } + + // Read the actual message + let content_length = content_length.expect("did not receive Content-Length header"); + let mut msg = vec![0; content_length]; + reader.read_exact(&mut msg)?; + let result = String::from_utf8_lossy(&msg).into_owned(); + Ok(result) +} + +pub fn expect_messages(reader: &mut BufReader, expected: &[&ExpectedMessage]) { + let mut results: Vec = Vec::new(); + while results.len() < expected.len() { + let msg = read_message(reader).unwrap(); + results.push(msg); + } + + println!( + "expect_messages:\n results: {:#?},\n expected: {:#?}", + results, + expected + ); + assert_eq!(results.len(), expected.len()); + for (found, expected) in results.iter().zip(expected.iter()) { + let values: serde_json::Value = serde_json::from_str(found).unwrap(); + assert!( + values + .get("jsonrpc") + .expect("Missing jsonrpc field") + .as_str() + .unwrap() == "2.0", + "Bad jsonrpc field" + ); + if let Some(id) = expected.id { + assert_eq!( + values + .get("id") + .expect("Missing id field") + .as_u64() + .unwrap(), + id, + "Unexpected id" + ); + } + for c in expected.contains.iter() { + found + .find(c) + .expect(&format!("Could not find `{}` in `{}`", c, found)); + } + } +} + +pub struct RlsHandle { + child: Child, + stdin: ChildStdin, + stdout: BufReader, +} + +impl RlsHandle { + pub fn new(mut child: Child) -> RlsHandle { + let stdin = mem::replace(&mut child.stdin, None).unwrap(); + let stdout = mem::replace(&mut child.stdout, None).unwrap(); + let stdout = BufReader::new(stdout); + + RlsHandle { + child, + stdin, + stdout, + } + } + + pub fn send_string(&mut self, s: &str) -> io::Result { + let full_msg = format!("Content-Length: {}\r\n\r\n{}", s.len(), s); + self.stdin.write(full_msg.as_bytes()) + } + pub fn send(&mut self, j: serde_json::Value) -> io::Result { + self.send_string(&j.to_string()) + } + pub fn notify(&mut self, method: &str, params: serde_json::Value) -> io::Result { + self.send(json!({ + "jsonrpc": "2.0", + "method": method, + "params": params, + })) + } + pub fn request(&mut self, id: u64, method: &str, params: serde_json::Value) -> io::Result { + self.send(json!({ + "jsonrpc": "2.0", + "id": id, + "method": method, + "params": params, + })) + } + pub fn shutdown_exit(&mut self) { + self.request(99999, "shutdown", json!({})).unwrap(); + + self.expect_messages(&[ + &ExpectedMessage::new(Some(99999)), + ]); + + self.notify("exit", json!({})).unwrap(); + + let ecode = self.child.wait() + .expect("failed to wait on child rls process"); + + assert!(ecode.success()); + } + + pub fn expect_messages(&mut self, expected: &[&ExpectedMessage]) { + expect_messages(&mut self.stdout, expected); + } +} + #[derive(PartialEq,Clone)] struct FileBuilder { path: PathBuf, diff --git a/tests/tests.rs b/tests/tests.rs index a4e2d7f6b61..781db02051c 100644 --- a/tests/tests.rs +++ b/tests/tests.rs @@ -13,44 +13,16 @@ extern crate cargo; #[macro_use] extern crate serde_json; -use std::sync::{Arc, Condvar, Mutex}; -use std::thread; use std::time::Duration; mod support; -use support::{basic_bin_manifest, project}; -use support::harness::{ExpectedMessage, RlsHandle}; - -fn timeout(dur: Duration, func: F) - where F: FnOnce() + Send + 'static { - let pair = Arc::new((Mutex::new(false), Condvar::new())); - let pair2 = pair.clone(); - - thread::spawn(move|| { - let &(ref lock, ref cvar) = &*pair2; - func(); - let mut finished = lock.lock().unwrap(); - *finished = true; - // We notify the condvar that the value has changed. - cvar.notify_one(); - }); +use support::{ExpectedMessage, RlsHandle, basic_bin_manifest, project, timeout}; - // Wait for the test to finish. - let &(ref lock, ref cvar) = &*pair; - let mut finished = lock.lock().unwrap(); - // As long as the value inside the `Mutex` is false, we wait. - while !*finished { - let result = cvar.wait_timeout(finished, dur).unwrap(); - if result.1.timed_out() { - panic!("Timed out") - } - finished = result.0 - } -} +const TIME_LIMIT_SECS: u64 = 300; #[test] fn test_infer_bin() { - timeout(Duration::from_secs(300), ||{ + timeout(Duration::from_secs(TIME_LIMIT_SECS), ||{ let p = project("simple_workspace") .file("Cargo.toml", &basic_bin_manifest("foo")) .file("src/main.rs", r#"