aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2018-09-01 18:21:11 +0100
committerAleksey Kladov <[email protected]>2018-09-01 18:21:11 +0100
commit541170420bb6f9a5c0e8d6f56865567fd8ae0f93 (patch)
treea58366d1c9412d3192fc636c7912dcb8514baab3
parente8515fecd7a42870f2979c7900c94b59d935901c (diff)
Add an integration test
-rw-r--r--crates/gen_lsp_server/src/lib.rs15
-rw-r--r--crates/gen_lsp_server/src/msg.rs16
-rw-r--r--crates/server/Cargo.toml3
-rw-r--r--crates/server/src/lib.rs2
-rw-r--r--crates/server/src/main_loop/mod.rs31
-rw-r--r--crates/server/src/req.rs2
-rw-r--r--crates/server/src/vfs.rs2
-rw-r--r--crates/server/tests/heavy_tests/main.rs42
-rw-r--r--crates/server/tests/heavy_tests/support.rs169
9 files changed, 266 insertions, 16 deletions
diff --git a/crates/gen_lsp_server/src/lib.rs b/crates/gen_lsp_server/src/lib.rs
index 476c12cc1..0dc24ffc1 100644
--- a/crates/gen_lsp_server/src/lib.rs
+++ b/crates/gen_lsp_server/src/lib.rs
@@ -15,7 +15,7 @@ mod stdio;
15use crossbeam_channel::{Sender, Receiver}; 15use crossbeam_channel::{Sender, Receiver};
16use languageserver_types::{ 16use languageserver_types::{
17 ServerCapabilities, InitializeResult, 17 ServerCapabilities, InitializeResult,
18 request::{Initialize}, 18 request::{Initialize, Shutdown},
19 notification::{Initialized, Exit}, 19 notification::{Initialized, Exit},
20}; 20};
21 21
@@ -48,6 +48,17 @@ pub fn run_server(
48 Ok(()) 48 Ok(())
49} 49}
50 50
51pub fn handle_shutdown(req: RawRequest, sender: &Sender<RawMessage>) -> Option<RawRequest> {
52 match req.cast::<Shutdown>() {
53 Ok((id, ())) => {
54 let resp = RawResponse::ok::<Shutdown>(id, ());
55 sender.send(RawMessage::Response(resp));
56 None
57 }
58 Err(req) => Some(req),
59 }
60}
61
51fn initialize( 62fn initialize(
52 receiver: &mut Receiver<RawMessage>, 63 receiver: &mut Receiver<RawMessage>,
53 sender: &mut Sender<RawMessage>, 64 sender: &mut Sender<RawMessage>,
@@ -61,7 +72,7 @@ fn initialize(
61 msg => 72 msg =>
62 bail!("expected initialize request, got {:?}", msg), 73 bail!("expected initialize request, got {:?}", msg),
63 }; 74 };
64 let resp = RawResponse::ok(id, InitializeResult { capabilities: caps }); 75 let resp = RawResponse::ok::<Initialize>(id, InitializeResult { capabilities: caps });
65 sender.send(RawMessage::Response(resp)); 76 sender.send(RawMessage::Response(resp));
66 match receiver.recv() { 77 match receiver.recv() {
67 Some(RawMessage::Notification(n)) => { 78 Some(RawMessage::Notification(n)) => {
diff --git a/crates/gen_lsp_server/src/msg.rs b/crates/gen_lsp_server/src/msg.rs
index 533d7da3e..d2ce20a11 100644
--- a/crates/gen_lsp_server/src/msg.rs
+++ b/crates/gen_lsp_server/src/msg.rs
@@ -87,6 +87,17 @@ impl RawMessage {
87} 87}
88 88
89impl RawRequest { 89impl RawRequest {
90 pub fn new<R>(id: u64, params: R::Params) -> RawRequest
91 where
92 R: Request,
93 R::Params: Serialize,
94 {
95 RawRequest {
96 id: id,
97 method: R::METHOD.to_string(),
98 params: to_value(&params).unwrap(),
99 }
100 }
90 pub fn cast<R>(self) -> ::std::result::Result<(u64, R::Params), RawRequest> 101 pub fn cast<R>(self) -> ::std::result::Result<(u64, R::Params), RawRequest>
91 where 102 where
92 R: Request, 103 R: Request,
@@ -102,7 +113,10 @@ impl RawRequest {
102} 113}
103 114
104impl RawResponse { 115impl RawResponse {
105 pub fn ok(id: u64, result: impl Serialize) -> RawResponse { 116 pub fn ok<R>(id: u64, result: R::Result) -> RawResponse
117 where R: Request,
118 R::Result: Serialize,
119 {
106 RawResponse { 120 RawResponse {
107 id, 121 id,
108 result: Some(to_value(&result).unwrap()), 122 result: Some(to_value(&result).unwrap()),
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml
index 32c1219e1..2a9374e98 100644
--- a/crates/server/Cargo.toml
+++ b/crates/server/Cargo.toml
@@ -24,3 +24,6 @@ libsyntax2 = { path = "../libsyntax2" }
24libeditor = { path = "../libeditor" } 24libeditor = { path = "../libeditor" }
25libanalysis = { path = "../libanalysis" } 25libanalysis = { path = "../libanalysis" }
26gen_lsp_server = { path = "../gen_lsp_server" } 26gen_lsp_server = { path = "../gen_lsp_server" }
27
28[dev-dependencies]
29tempdir = "0.3.7"
diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs
index 5bbd21044..bfa4bc41e 100644
--- a/crates/server/src/lib.rs
+++ b/crates/server/src/lib.rs
@@ -21,7 +21,7 @@ extern crate im;
21extern crate relative_path; 21extern crate relative_path;
22 22
23mod caps; 23mod caps;
24mod req; 24pub mod req;
25mod conv; 25mod conv;
26mod main_loop; 26mod main_loop;
27mod vfs; 27mod vfs;
diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs
index 610aa4264..ff267fcad 100644
--- a/crates/server/src/main_loop/mod.rs
+++ b/crates/server/src/main_loop/mod.rs
@@ -11,7 +11,10 @@ use serde::{Serialize, de::DeserializeOwned};
11use crossbeam_channel::{bounded, Sender, Receiver}; 11use crossbeam_channel::{bounded, Sender, Receiver};
12use languageserver_types::{NumberOrString}; 12use languageserver_types::{NumberOrString};
13use libanalysis::{FileId, JobHandle, JobToken}; 13use libanalysis::{FileId, JobHandle, JobToken};
14use gen_lsp_server::{RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode}; 14use gen_lsp_server::{
15 RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode,
16 handle_shutdown,
17};
15 18
16use { 19use {
17 req, 20 req,
@@ -21,6 +24,7 @@ use {
21 main_loop::subscriptions::{Subscriptions}, 24 main_loop::subscriptions::{Subscriptions},
22}; 25};
23 26
27#[derive(Debug)]
24enum Task { 28enum Task {
25 Respond(RawResponse), 29 Respond(RawResponse),
26 Notify(RawNotification), 30 Notify(RawNotification),
@@ -40,7 +44,7 @@ pub fn main_loop(
40 44
41 let mut pending_requests = HashMap::new(); 45 let mut pending_requests = HashMap::new();
42 let mut subs = Subscriptions::new(); 46 let mut subs = Subscriptions::new();
43 main_loop_inner( 47 let res = main_loop_inner(
44 &pool, 48 &pool,
45 msg_receriver, 49 msg_receriver,
46 msg_sender, 50 msg_sender,
@@ -50,17 +54,19 @@ pub fn main_loop(
50 &mut state, 54 &mut state,
51 &mut pending_requests, 55 &mut pending_requests,
52 &mut subs, 56 &mut subs,
53 )?; 57 );
54 58
55 info!("waiting for background jobs to finish..."); 59 info!("waiting for tasks to finish...");
56 task_receiver.for_each(|task| on_task(task, msg_sender, &mut pending_requests)); 60 task_receiver.for_each(|task| on_task(task, msg_sender, &mut pending_requests));
61 info!("...tasks have finished");
62 info!("joining threadpool...");
57 pool.join(); 63 pool.join();
58 info!("...background jobs have finished"); 64 info!("...threadpool has finished");
59 65
60 info!("waiting for file watcher to finish..."); 66 info!("waiting for file watcher to finish...");
61 watcher.stop()?; 67 watcher.stop()?;
62 info!("...file watcher has finished"); 68 info!("...file watcher has finished");
63 Ok(()) 69 res
64} 70}
65 71
66fn main_loop_inner( 72fn main_loop_inner(
@@ -73,15 +79,17 @@ fn main_loop_inner(
73 state: &mut ServerWorldState, 79 state: &mut ServerWorldState,
74 pending_requests: &mut HashMap<u64, JobHandle>, 80 pending_requests: &mut HashMap<u64, JobHandle>,
75 subs: &mut Subscriptions, 81 subs: &mut Subscriptions,
76) -> Result<u64> { 82) -> Result<()> {
77 let mut fs_receiver = Some(fs_receiver); 83 let mut fs_receiver = Some(fs_receiver);
78 loop { 84 loop {
85 #[derive(Debug)]
79 enum Event { 86 enum Event {
80 Msg(RawMessage), 87 Msg(RawMessage),
81 Task(Task), 88 Task(Task),
82 Fs(Vec<FileEvent>), 89 Fs(Vec<FileEvent>),
83 FsWatcherDead, 90 FsWatcherDead,
84 } 91 }
92 trace!("selecting");
85 let event = select! { 93 let event = select! {
86 recv(msg_receiver, msg) => match msg { 94 recv(msg_receiver, msg) => match msg {
87 Some(msg) => Event::Msg(msg), 95 Some(msg) => Event::Msg(msg),
@@ -93,6 +101,7 @@ fn main_loop_inner(
93 None => Event::FsWatcherDead, 101 None => Event::FsWatcherDead,
94 } 102 }
95 }; 103 };
104 trace!("selected {:?}", event);
96 let mut state_changed = false; 105 let mut state_changed = false;
97 match event { 106 match event {
98 Event::FsWatcherDead => fs_receiver = None, 107 Event::FsWatcherDead => fs_receiver = None,
@@ -105,9 +114,9 @@ fn main_loop_inner(
105 Event::Msg(msg) => { 114 Event::Msg(msg) => {
106 match msg { 115 match msg {
107 RawMessage::Request(req) => { 116 RawMessage::Request(req) => {
108 let req = match req.cast::<req::Shutdown>() { 117 let req = match handle_shutdown(req, msg_sender) {
109 Ok((id, _params)) => return Ok(id), 118 Some(req) => req,
110 Err(req) => req, 119 None => return Ok(()),
111 }; 120 };
112 match on_request(state, pending_requests, pool, &task_sender, req)? { 121 match on_request(state, pending_requests, pool, &task_sender, req)? {
113 None => (), 122 None => (),
@@ -290,7 +299,7 @@ impl<'a> PoolDispatcher<'a> {
290 let sender = self.sender.clone(); 299 let sender = self.sender.clone();
291 self.pool.execute(move || { 300 self.pool.execute(move || {
292 let resp = match f(world, params, token) { 301 let resp = match f(world, params, token) {
293 Ok(resp) => RawResponse::ok(id, resp), 302 Ok(resp) => RawResponse::ok::<R>(id, resp),
294 Err(e) => RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string()), 303 Err(e) => RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string()),
295 }; 304 };
296 let task = Task::Respond(resp); 305 let task = Task::Respond(resp);
diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs
index f52127271..893cbde81 100644
--- a/crates/server/src/req.rs
+++ b/crates/server/src/req.rs
@@ -127,7 +127,7 @@ impl Request for Runnables {
127 const METHOD: &'static str = "m/runnables"; 127 const METHOD: &'static str = "m/runnables";
128} 128}
129 129
130#[derive(Deserialize, Debug)] 130#[derive(Serialize, Deserialize, Debug)]
131#[serde(rename_all = "camelCase")] 131#[serde(rename_all = "camelCase")]
132pub struct RunnablesParams { 132pub struct RunnablesParams {
133 pub text_document: TextDocumentIdentifier, 133 pub text_document: TextDocumentIdentifier,
diff --git a/crates/server/src/vfs.rs b/crates/server/src/vfs.rs
index a5c367494..2e4319cdb 100644
--- a/crates/server/src/vfs.rs
+++ b/crates/server/src/vfs.rs
@@ -11,11 +11,13 @@ use walkdir::WalkDir;
11use Result; 11use Result;
12 12
13 13
14#[derive(Debug)]
14pub struct FileEvent { 15pub struct FileEvent {
15 pub path: PathBuf, 16 pub path: PathBuf,
16 pub kind: FileEventKind, 17 pub kind: FileEventKind,
17} 18}
18 19
20#[derive(Debug)]
19pub enum FileEventKind { 21pub enum FileEventKind {
20 Add(String), 22 Add(String),
21 #[allow(unused)] 23 #[allow(unused)]
diff --git a/crates/server/tests/heavy_tests/main.rs b/crates/server/tests/heavy_tests/main.rs
new file mode 100644
index 000000000..94c8243b0
--- /dev/null
+++ b/crates/server/tests/heavy_tests/main.rs
@@ -0,0 +1,42 @@
1extern crate tempdir;
2extern crate crossbeam_channel;
3extern crate languageserver_types;
4extern crate serde;
5extern crate serde_json;
6extern crate gen_lsp_server;
7extern crate flexi_logger;
8extern crate m;
9
10mod support;
11
12use m::req::{Runnables, RunnablesParams};
13
14use support::project;
15
16#[test]
17fn test_runnables() {
18 let server = project(r"
19//- lib.rs
20#[test]
21fn foo() {
22}
23");
24 server.request::<Runnables>(
25 RunnablesParams {
26 text_document: server.doc_id("lib.rs"),
27 position: None,
28 },
29 r#"[
30 {
31 "args": [ "test", "--", "foo", "--nocapture" ],
32 "bin": "cargo",
33 "env": { "RUST_BACKTRACE": "short" },
34 "label": "test foo",
35 "range": {
36 "end": { "character": 1, "line": 2 },
37 "start": { "character": 0, "line": 0 }
38 }
39 }
40 ]"#
41 );
42}
diff --git a/crates/server/tests/heavy_tests/support.rs b/crates/server/tests/heavy_tests/support.rs
new file mode 100644
index 000000000..113ef4c54
--- /dev/null
+++ b/crates/server/tests/heavy_tests/support.rs
@@ -0,0 +1,169 @@
1use std::{
2 fs,
3 thread,
4 cell::Cell,
5 path::PathBuf,
6};
7
8use tempdir::TempDir;
9use crossbeam_channel::{bounded, Sender, Receiver};
10use flexi_logger::Logger;
11use languageserver_types::{
12 Url,
13 TextDocumentIdentifier,
14 request::{Request, Shutdown},
15 notification::DidOpenTextDocument,
16 DidOpenTextDocumentParams,
17 TextDocumentItem,
18};
19use serde::Serialize;
20use serde_json::{Value, from_str, to_string_pretty};
21use gen_lsp_server::{RawMessage, RawRequest, RawNotification};
22
23use m::{Result, main_loop};
24
25pub fn project(fixture: &str) -> Server {
26 Logger::with_env_or_str("").start().unwrap();
27
28 let tmp_dir = TempDir::new("test-project")
29 .unwrap();
30 let mut buf = String::new();
31 let mut file_name = None;
32 let mut paths = vec![];
33 macro_rules! flush {
34 () => {
35 if let Some(file_name) = file_name {
36 let path = tmp_dir.path().join(file_name);
37 fs::write(path.as_path(), buf.as_bytes()).unwrap();
38 paths.push((path, buf.clone()));
39 }
40 }
41 };
42 for line in fixture.lines() {
43 if line.starts_with("//-") {
44 flush!();
45 buf.clear();
46 file_name = Some(line["//-".len()..].trim());
47 continue;
48 }
49 buf.push_str(line);
50 buf.push('\n');
51 }
52 flush!();
53
54 Server::new(tmp_dir, paths)
55}
56
57pub struct Server {
58 req_id: Cell<u64>,
59 dir: TempDir,
60 sender: Option<Sender<RawMessage>>,
61 receiver: Receiver<RawMessage>,
62 server: Option<thread::JoinHandle<Result<()>>>,
63}
64
65impl Server {
66 fn new(dir: TempDir, files: Vec<(PathBuf, String)>) -> Server {
67 let path = dir.path().to_path_buf();
68 let (client_sender, mut server_receiver) = bounded(1);
69 let (mut server_sender, client_receiver) = bounded(1);
70 let server = thread::spawn(move || main_loop(path, &mut server_receiver, &mut server_sender));
71 let res = Server {
72 req_id: Cell::new(1),
73 dir,
74 sender: Some(client_sender),
75 receiver: client_receiver,
76 server: Some(server),
77 };
78 for (path, text) in files {
79 res.send_notification(RawNotification::new::<DidOpenTextDocument>(
80 DidOpenTextDocumentParams {
81 text_document: TextDocumentItem {
82 uri: Url::from_file_path(path).unwrap(),
83 language_id: "rust".to_string(),
84 version: 0,
85 text,
86 }
87 }
88 ))
89 }
90 res
91 }
92
93 pub fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
94 let path = self.dir.path().join(rel_path);
95 TextDocumentIdentifier {
96 uri: Url::from_file_path(path).unwrap(),
97 }
98 }
99
100 pub fn request<R>(
101 &self,
102 params: R::Params,
103 expected_resp: &str,
104 )
105 where
106 R: Request,
107 R::Params: Serialize,
108 {
109 let id = self.req_id.get();
110 self.req_id.set(id + 1);
111 let expected_resp: Value = from_str(expected_resp).unwrap();
112 let actual = self.send_request::<R>(id, params);
113 assert_eq!(
114 expected_resp, actual,
115 "Expected:\n{}\n\
116 Actual:\n{}\n",
117 to_string_pretty(&expected_resp).unwrap(),
118 to_string_pretty(&actual).unwrap(),
119 );
120 }
121
122 fn send_request<R>(&self, id: u64, params: R::Params) -> Value
123 where
124 R: Request,
125 R::Params: Serialize,
126 {
127 let r = RawRequest::new::<R>(id, params);
128 self.sender.as_ref()
129 .unwrap()
130 .send(RawMessage::Request(r));
131
132 while let Some(msg) = self.receiver.recv() {
133 match msg {
134 RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
135 RawMessage::Notification(_) => (),
136 RawMessage::Response(res) => {
137 assert_eq!(res.id, id);
138 if let Some(err) = res.error {
139 panic!("error response: {:#?}", err);
140 }
141 return res.result.unwrap();
142 }
143 }
144 }
145 panic!("no response");
146 }
147 fn send_notification(&self, not: RawNotification) {
148
149 self.sender.as_ref()
150 .unwrap()
151 .send(RawMessage::Notification(not));
152 }
153}
154
155impl Drop for Server {
156 fn drop(&mut self) {
157 {
158 self.send_request::<Shutdown>(666, ());
159 drop(self.sender.take().unwrap());
160 while let Some(msg) = self.receiver.recv() {
161 drop(msg);
162 }
163 }
164 eprintln!("joining server");
165 self.server.take()
166 .unwrap()
167 .join().unwrap().unwrap();
168 }
169}