aboutsummaryrefslogtreecommitdiff
path: root/crates/rust-analyzer/tests/heavy_tests/support.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/rust-analyzer/tests/heavy_tests/support.rs')
-rw-r--r--crates/rust-analyzer/tests/heavy_tests/support.rs254
1 files changed, 254 insertions, 0 deletions
diff --git a/crates/rust-analyzer/tests/heavy_tests/support.rs b/crates/rust-analyzer/tests/heavy_tests/support.rs
new file mode 100644
index 000000000..5b90b3218
--- /dev/null
+++ b/crates/rust-analyzer/tests/heavy_tests/support.rs
@@ -0,0 +1,254 @@
1use std::{
2 cell::{Cell, RefCell},
3 fs,
4 path::{Path, PathBuf},
5 sync::Once,
6 time::Duration,
7};
8
9use crossbeam_channel::{after, select, Receiver};
10use lsp_server::{Connection, Message, Notification, Request};
11use lsp_types::{
12 notification::{DidOpenTextDocument, Exit},
13 request::Shutdown,
14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
15 TextDocumentIdentifier, TextDocumentItem, Url,
16};
17use serde::Serialize;
18use serde_json::{to_string_pretty, Value};
19use tempfile::TempDir;
20use test_utils::{find_mismatch, parse_fixture};
21
22use rust_analyzer::{main_loop, req, ServerConfig};
23
24pub struct Project<'a> {
25 fixture: &'a str,
26 with_sysroot: bool,
27 tmp_dir: Option<TempDir>,
28 roots: Vec<PathBuf>,
29}
30
31impl<'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 let _ = 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
78pub fn project(fixture: &str) -> Server {
79 Project::with_fixture(fixture).server()
80}
81
82pub 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
90impl 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
241impl Drop for Server {
242 fn drop(&mut self) {
243 self.request::<Shutdown>((), Value::Null);
244 self.notification::<Exit>(());
245 }
246}
247
248fn 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}