diff options
-rw-r--r-- | crates/gen_lsp_server/src/lib.rs | 15 | ||||
-rw-r--r-- | crates/gen_lsp_server/src/msg.rs | 16 | ||||
-rw-r--r-- | crates/server/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/server/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 31 | ||||
-rw-r--r-- | crates/server/src/req.rs | 2 | ||||
-rw-r--r-- | crates/server/src/vfs.rs | 2 | ||||
-rw-r--r-- | crates/server/tests/heavy_tests/main.rs | 42 | ||||
-rw-r--r-- | crates/server/tests/heavy_tests/support.rs | 169 |
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; | |||
15 | use crossbeam_channel::{Sender, Receiver}; | 15 | use crossbeam_channel::{Sender, Receiver}; |
16 | use languageserver_types::{ | 16 | use 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 | ||
51 | pub 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 | |||
51 | fn initialize( | 62 | fn 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 | ||
89 | impl RawRequest { | 89 | impl 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(¶ms).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 | ||
104 | impl RawResponse { | 115 | impl 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" } | |||
24 | libeditor = { path = "../libeditor" } | 24 | libeditor = { path = "../libeditor" } |
25 | libanalysis = { path = "../libanalysis" } | 25 | libanalysis = { path = "../libanalysis" } |
26 | gen_lsp_server = { path = "../gen_lsp_server" } | 26 | gen_lsp_server = { path = "../gen_lsp_server" } |
27 | |||
28 | [dev-dependencies] | ||
29 | tempdir = "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; | |||
21 | extern crate relative_path; | 21 | extern crate relative_path; |
22 | 22 | ||
23 | mod caps; | 23 | mod caps; |
24 | mod req; | 24 | pub mod req; |
25 | mod conv; | 25 | mod conv; |
26 | mod main_loop; | 26 | mod main_loop; |
27 | mod vfs; | 27 | mod 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}; | |||
11 | use crossbeam_channel::{bounded, Sender, Receiver}; | 11 | use crossbeam_channel::{bounded, Sender, Receiver}; |
12 | use languageserver_types::{NumberOrString}; | 12 | use languageserver_types::{NumberOrString}; |
13 | use libanalysis::{FileId, JobHandle, JobToken}; | 13 | use libanalysis::{FileId, JobHandle, JobToken}; |
14 | use gen_lsp_server::{RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode}; | 14 | use gen_lsp_server::{ |
15 | RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode, | ||
16 | handle_shutdown, | ||
17 | }; | ||
15 | 18 | ||
16 | use { | 19 | use { |
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)] | ||
24 | enum Task { | 28 | enum 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 | ||
66 | fn main_loop_inner( | 72 | fn 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")] |
132 | pub struct RunnablesParams { | 132 | pub 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; | |||
11 | use Result; | 11 | use Result; |
12 | 12 | ||
13 | 13 | ||
14 | #[derive(Debug)] | ||
14 | pub struct FileEvent { | 15 | pub 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)] | ||
19 | pub enum FileEventKind { | 21 | pub 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 @@ | |||
1 | extern crate tempdir; | ||
2 | extern crate crossbeam_channel; | ||
3 | extern crate languageserver_types; | ||
4 | extern crate serde; | ||
5 | extern crate serde_json; | ||
6 | extern crate gen_lsp_server; | ||
7 | extern crate flexi_logger; | ||
8 | extern crate m; | ||
9 | |||
10 | mod support; | ||
11 | |||
12 | use m::req::{Runnables, RunnablesParams}; | ||
13 | |||
14 | use support::project; | ||
15 | |||
16 | #[test] | ||
17 | fn test_runnables() { | ||
18 | let server = project(r" | ||
19 | //- lib.rs | ||
20 | #[test] | ||
21 | fn 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 @@ | |||
1 | use std::{ | ||
2 | fs, | ||
3 | thread, | ||
4 | cell::Cell, | ||
5 | path::PathBuf, | ||
6 | }; | ||
7 | |||
8 | use tempdir::TempDir; | ||
9 | use crossbeam_channel::{bounded, Sender, Receiver}; | ||
10 | use flexi_logger::Logger; | ||
11 | use languageserver_types::{ | ||
12 | Url, | ||
13 | TextDocumentIdentifier, | ||
14 | request::{Request, Shutdown}, | ||
15 | notification::DidOpenTextDocument, | ||
16 | DidOpenTextDocumentParams, | ||
17 | TextDocumentItem, | ||
18 | }; | ||
19 | use serde::Serialize; | ||
20 | use serde_json::{Value, from_str, to_string_pretty}; | ||
21 | use gen_lsp_server::{RawMessage, RawRequest, RawNotification}; | ||
22 | |||
23 | use m::{Result, main_loop}; | ||
24 | |||
25 | pub 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 | |||
57 | pub 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 | |||
65 | impl 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 | |||
155 | impl 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 | } | ||