]> git.lizzy.rs Git - rust.git/blob - crates/rust-analyzer/tests/heavy_tests/support.rs
Merge #3221
[rust.git] / crates / rust-analyzer / tests / heavy_tests / support.rs
1 use std::{
2     cell::{Cell, RefCell},
3     fs,
4     path::{Path, PathBuf},
5     sync::Once,
6     time::Duration,
7 };
8
9 use crossbeam_channel::{after, select, Receiver};
10 use lsp_server::{Connection, Message, Notification, Request};
11 use lsp_types::{
12     notification::{DidOpenTextDocument, Exit},
13     request::Shutdown,
14     ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
15     TextDocumentIdentifier, TextDocumentItem, Url,
16 };
17 use serde::Serialize;
18 use serde_json::{to_string_pretty, Value};
19 use tempfile::TempDir;
20 use test_utils::{find_mismatch, parse_fixture};
21
22 use rust_analyzer::{main_loop, req, ServerConfig};
23
24 pub struct Project<'a> {
25     fixture: &'a str,
26     with_sysroot: bool,
27     tmp_dir: Option<TempDir>,
28     roots: Vec<PathBuf>,
29 }
30
31 impl<'a> Project<'a> {
32     pub fn with_fixture(fixture: &str) -> Project {
33         Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false }
34     }
35
36     pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> {
37         self.tmp_dir = Some(tmp_dir);
38         self
39     }
40
41     pub fn root(mut self, path: &str) -> Project<'a> {
42         self.roots.push(path.into());
43         self
44     }
45
46     pub fn with_sysroot(mut self, sysroot: bool) -> Project<'a> {
47         self.with_sysroot = sysroot;
48         self
49     }
50
51     pub fn server(self) -> Server {
52         let tmp_dir = self.tmp_dir.unwrap_or_else(|| TempDir::new().unwrap());
53         static INIT: Once = Once::new();
54         INIT.call_once(|| {
55             env_logger::builder().is_test(true).try_init().unwrap();
56             ra_prof::set_filter(if crate::PROFILE.is_empty() {
57                 ra_prof::Filter::disabled()
58             } else {
59                 ra_prof::Filter::from_spec(&crate::PROFILE)
60             });
61         });
62
63         let mut paths = vec![];
64
65         for entry in parse_fixture(self.fixture) {
66             let path = tmp_dir.path().join(entry.meta);
67             fs::create_dir_all(path.parent().unwrap()).unwrap();
68             fs::write(path.as_path(), entry.text.as_bytes()).unwrap();
69             paths.push((path, entry.text));
70         }
71
72         let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
73
74         Server::new(tmp_dir, self.with_sysroot, roots, paths)
75     }
76 }
77
78 pub fn project(fixture: &str) -> Server {
79     Project::with_fixture(fixture).server()
80 }
81
82 pub struct Server {
83     req_id: Cell<u64>,
84     messages: RefCell<Vec<Message>>,
85     dir: TempDir,
86     _thread: jod_thread::JoinHandle<()>,
87     client: Connection,
88 }
89
90 impl Server {
91     fn new(
92         dir: TempDir,
93         with_sysroot: bool,
94         roots: Vec<PathBuf>,
95         files: Vec<(PathBuf, String)>,
96     ) -> Server {
97         let path = dir.path().to_path_buf();
98
99         let roots = if roots.is_empty() { vec![path] } else { roots };
100         let (connection, client) = Connection::memory();
101
102         let _thread = jod_thread::Builder::new()
103             .name("test server".to_string())
104             .spawn(move || {
105                 main_loop(
106                     roots,
107                     ClientCapabilities {
108                         workspace: None,
109                         text_document: Some(TextDocumentClientCapabilities {
110                             definition: Some(GotoCapability {
111                                 dynamic_registration: None,
112                                 link_support: Some(true),
113                             }),
114                             ..Default::default()
115                         }),
116                         window: None,
117                         experimental: None,
118                     },
119                     ServerConfig { with_sysroot, ..ServerConfig::default() },
120                     connection,
121                 )
122                 .unwrap()
123             })
124             .expect("failed to spawn a thread");
125
126         let res =
127             Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread };
128
129         for (path, text) in files {
130             res.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
131                 text_document: TextDocumentItem {
132                     uri: Url::from_file_path(path).unwrap(),
133                     language_id: "rust".to_string(),
134                     version: 0,
135                     text,
136                 },
137             })
138         }
139         res
140     }
141
142     pub fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
143         let path = self.dir.path().join(rel_path);
144         TextDocumentIdentifier { uri: Url::from_file_path(path).unwrap() }
145     }
146
147     pub fn notification<N>(&self, params: N::Params)
148     where
149         N: lsp_types::notification::Notification,
150         N::Params: Serialize,
151     {
152         let r = Notification::new(N::METHOD.to_string(), params);
153         self.send_notification(r)
154     }
155
156     pub fn request<R>(&self, params: R::Params, expected_resp: Value)
157     where
158         R: lsp_types::request::Request,
159         R::Params: Serialize,
160     {
161         let actual = self.send_request::<R>(params);
162         if let Some((expected_part, actual_part)) = find_mismatch(&expected_resp, &actual) {
163             panic!(
164                 "JSON mismatch\nExpected:\n{}\nWas:\n{}\nExpected part:\n{}\nActual part:\n{}\n",
165                 to_string_pretty(&expected_resp).unwrap(),
166                 to_string_pretty(&actual).unwrap(),
167                 to_string_pretty(expected_part).unwrap(),
168                 to_string_pretty(actual_part).unwrap(),
169             );
170         }
171     }
172
173     pub fn send_request<R>(&self, params: R::Params) -> Value
174     where
175         R: lsp_types::request::Request,
176         R::Params: Serialize,
177     {
178         let id = self.req_id.get();
179         self.req_id.set(id + 1);
180
181         let r = Request::new(id.into(), R::METHOD.to_string(), params);
182         self.send_request_(r)
183     }
184     fn send_request_(&self, r: Request) -> Value {
185         let id = r.id.clone();
186         self.client.sender.send(r.into()).unwrap();
187         while let Some(msg) = self.recv() {
188             match msg {
189                 Message::Request(req) => panic!("unexpected request: {:?}", req),
190                 Message::Notification(_) => (),
191                 Message::Response(res) => {
192                     assert_eq!(res.id, id);
193                     if let Some(err) = res.error {
194                         panic!("error response: {:#?}", err);
195                     }
196                     return res.result.unwrap();
197                 }
198             }
199         }
200         panic!("no response");
201     }
202     pub fn wait_until_workspace_is_loaded(&self) {
203         self.wait_for_message_cond(1, &|msg: &Message| match msg {
204             Message::Notification(n) if n.method == "window/showMessage" => {
205                 let msg =
206                     n.clone().extract::<req::ShowMessageParams>("window/showMessage").unwrap();
207                 msg.message.starts_with("workspace loaded")
208             }
209             _ => false,
210         })
211     }
212     fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&Message) -> bool) {
213         let mut total = 0;
214         for msg in self.messages.borrow().iter() {
215             if cond(msg) {
216                 total += 1
217             }
218         }
219         while total < n {
220             let msg = self.recv().expect("no response");
221             if cond(&msg) {
222                 total += 1;
223             }
224         }
225     }
226     fn recv(&self) -> Option<Message> {
227         recv_timeout(&self.client.receiver).map(|msg| {
228             self.messages.borrow_mut().push(msg.clone());
229             msg
230         })
231     }
232     fn send_notification(&self, not: Notification) {
233         self.client.sender.send(Message::Notification(not)).unwrap();
234     }
235
236     pub fn path(&self) -> &Path {
237         self.dir.path()
238     }
239 }
240
241 impl Drop for Server {
242     fn drop(&mut self) {
243         self.request::<Shutdown>((), Value::Null);
244         self.notification::<Exit>(());
245     }
246 }
247
248 fn recv_timeout(receiver: &Receiver<Message>) -> Option<Message> {
249     let timeout = Duration::from_secs(120);
250     select! {
251         recv(receiver) -> msg => msg.ok(),
252         recv(after(timeout)) -> _ => panic!("timed out"),
253     }
254 }