9 use crossbeam_channel::{after, select, Receiver};
10 use lsp_server::{Connection, Message, Notification, Request};
12 notification::{DidOpenTextDocument, Exit},
14 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
15 TextDocumentIdentifier, TextDocumentItem, Url,
18 use serde_json::{to_string_pretty, Value};
19 use tempfile::TempDir;
20 use test_utils::{find_mismatch, parse_fixture};
22 use rust_analyzer::{main_loop, req, ServerConfig};
24 pub struct Project<'a> {
27 tmp_dir: Option<TempDir>,
31 impl<'a> Project<'a> {
32 pub fn with_fixture(fixture: &str) -> Project {
33 Project { fixture, tmp_dir: None, roots: vec![], with_sysroot: false }
36 pub fn tmp_dir(mut self, tmp_dir: TempDir) -> Project<'a> {
37 self.tmp_dir = Some(tmp_dir);
41 pub fn root(mut self, path: &str) -> Project<'a> {
42 self.roots.push(path.into());
46 pub fn with_sysroot(mut self, sysroot: bool) -> Project<'a> {
47 self.with_sysroot = sysroot;
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();
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()
59 ra_prof::Filter::from_spec(&crate::PROFILE)
63 let mut paths = vec![];
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));
72 let roots = self.roots.into_iter().map(|root| tmp_dir.path().join(root)).collect();
74 Server::new(tmp_dir, self.with_sysroot, roots, paths)
78 pub fn project(fixture: &str) -> Server {
79 Project::with_fixture(fixture).server()
84 messages: RefCell<Vec<Message>>,
86 _thread: jod_thread::JoinHandle<()>,
95 files: Vec<(PathBuf, String)>,
97 let path = dir.path().to_path_buf();
99 let roots = if roots.is_empty() { vec![path] } else { roots };
100 let (connection, client) = Connection::memory();
102 let _thread = jod_thread::Builder::new()
103 .name("test server".to_string())
109 text_document: Some(TextDocumentClientCapabilities {
110 definition: Some(GotoCapability {
111 dynamic_registration: None,
112 link_support: Some(true),
119 ServerConfig { with_sysroot, ..ServerConfig::default() },
124 .expect("failed to spawn a thread");
127 Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread };
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(),
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() }
147 pub fn notification<N>(&self, params: N::Params)
149 N: lsp_types::notification::Notification,
150 N::Params: Serialize,
152 let r = Notification::new(N::METHOD.to_string(), params);
153 self.send_notification(r)
156 pub fn request<R>(&self, params: R::Params, expected_resp: Value)
158 R: lsp_types::request::Request,
159 R::Params: Serialize,
161 let actual = self.send_request::<R>(params);
162 if let Some((expected_part, actual_part)) = find_mismatch(&expected_resp, &actual) {
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(),
173 pub fn send_request<R>(&self, params: R::Params) -> Value
175 R: lsp_types::request::Request,
176 R::Params: Serialize,
178 let id = self.req_id.get();
179 self.req_id.set(id + 1);
181 let r = Request::new(id.into(), R::METHOD.to_string(), params);
182 self.send_request_(r)
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() {
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);
196 return res.result.unwrap();
200 panic!("no response");
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" => {
206 n.clone().extract::<req::ShowMessageParams>("window/showMessage").unwrap();
207 msg.message.starts_with("workspace loaded")
212 fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&Message) -> bool) {
214 for msg in self.messages.borrow().iter() {
220 let msg = self.recv().expect("no response");
226 fn recv(&self) -> Option<Message> {
227 recv_timeout(&self.client.receiver).map(|msg| {
228 self.messages.borrow_mut().push(msg.clone());
232 fn send_notification(&self, not: Notification) {
233 self.client.sender.send(Message::Notification(not)).unwrap();
236 pub fn path(&self) -> &Path {
241 impl Drop for Server {
243 self.request::<Shutdown>((), Value::Null);
244 self.notification::<Exit>(());
248 fn recv_timeout(receiver: &Receiver<Message>) -> Option<Message> {
249 let timeout = Duration::from_secs(120);
251 recv(receiver) -> msg => msg.ok(),
252 recv(after(timeout)) -> _ => panic!("timed out"),