mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-25 12:33:33 +00:00
Add an integration test
This commit is contained in:
parent
e8515fecd7
commit
541170420b
9 changed files with 266 additions and 16 deletions
|
@ -15,7 +15,7 @@ mod stdio;
|
|||
use crossbeam_channel::{Sender, Receiver};
|
||||
use languageserver_types::{
|
||||
ServerCapabilities, InitializeResult,
|
||||
request::{Initialize},
|
||||
request::{Initialize, Shutdown},
|
||||
notification::{Initialized, Exit},
|
||||
};
|
||||
|
||||
|
@ -48,6 +48,17 @@ pub fn run_server(
|
|||
Ok(())
|
||||
}
|
||||
|
||||
pub fn handle_shutdown(req: RawRequest, sender: &Sender<RawMessage>) -> Option<RawRequest> {
|
||||
match req.cast::<Shutdown>() {
|
||||
Ok((id, ())) => {
|
||||
let resp = RawResponse::ok::<Shutdown>(id, ());
|
||||
sender.send(RawMessage::Response(resp));
|
||||
None
|
||||
}
|
||||
Err(req) => Some(req),
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize(
|
||||
receiver: &mut Receiver<RawMessage>,
|
||||
sender: &mut Sender<RawMessage>,
|
||||
|
@ -61,7 +72,7 @@ fn initialize(
|
|||
msg =>
|
||||
bail!("expected initialize request, got {:?}", msg),
|
||||
};
|
||||
let resp = RawResponse::ok(id, InitializeResult { capabilities: caps });
|
||||
let resp = RawResponse::ok::<Initialize>(id, InitializeResult { capabilities: caps });
|
||||
sender.send(RawMessage::Response(resp));
|
||||
match receiver.recv() {
|
||||
Some(RawMessage::Notification(n)) => {
|
||||
|
|
|
@ -87,6 +87,17 @@ impl RawMessage {
|
|||
}
|
||||
|
||||
impl RawRequest {
|
||||
pub fn new<R>(id: u64, params: R::Params) -> RawRequest
|
||||
where
|
||||
R: Request,
|
||||
R::Params: Serialize,
|
||||
{
|
||||
RawRequest {
|
||||
id: id,
|
||||
method: R::METHOD.to_string(),
|
||||
params: to_value(¶ms).unwrap(),
|
||||
}
|
||||
}
|
||||
pub fn cast<R>(self) -> ::std::result::Result<(u64, R::Params), RawRequest>
|
||||
where
|
||||
R: Request,
|
||||
|
@ -102,7 +113,10 @@ impl RawRequest {
|
|||
}
|
||||
|
||||
impl RawResponse {
|
||||
pub fn ok(id: u64, result: impl Serialize) -> RawResponse {
|
||||
pub fn ok<R>(id: u64, result: R::Result) -> RawResponse
|
||||
where R: Request,
|
||||
R::Result: Serialize,
|
||||
{
|
||||
RawResponse {
|
||||
id,
|
||||
result: Some(to_value(&result).unwrap()),
|
||||
|
|
|
@ -24,3 +24,6 @@ libsyntax2 = { path = "../libsyntax2" }
|
|||
libeditor = { path = "../libeditor" }
|
||||
libanalysis = { path = "../libanalysis" }
|
||||
gen_lsp_server = { path = "../gen_lsp_server" }
|
||||
|
||||
[dev-dependencies]
|
||||
tempdir = "0.3.7"
|
||||
|
|
|
@ -21,7 +21,7 @@ extern crate im;
|
|||
extern crate relative_path;
|
||||
|
||||
mod caps;
|
||||
mod req;
|
||||
pub mod req;
|
||||
mod conv;
|
||||
mod main_loop;
|
||||
mod vfs;
|
||||
|
|
|
@ -11,7 +11,10 @@ use serde::{Serialize, de::DeserializeOwned};
|
|||
use crossbeam_channel::{bounded, Sender, Receiver};
|
||||
use languageserver_types::{NumberOrString};
|
||||
use libanalysis::{FileId, JobHandle, JobToken};
|
||||
use gen_lsp_server::{RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode};
|
||||
use gen_lsp_server::{
|
||||
RawRequest, RawNotification, RawMessage, RawResponse, ErrorCode,
|
||||
handle_shutdown,
|
||||
};
|
||||
|
||||
use {
|
||||
req,
|
||||
|
@ -21,6 +24,7 @@ use {
|
|||
main_loop::subscriptions::{Subscriptions},
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Task {
|
||||
Respond(RawResponse),
|
||||
Notify(RawNotification),
|
||||
|
@ -40,7 +44,7 @@ pub fn main_loop(
|
|||
|
||||
let mut pending_requests = HashMap::new();
|
||||
let mut subs = Subscriptions::new();
|
||||
main_loop_inner(
|
||||
let res = main_loop_inner(
|
||||
&pool,
|
||||
msg_receriver,
|
||||
msg_sender,
|
||||
|
@ -50,17 +54,19 @@ pub fn main_loop(
|
|||
&mut state,
|
||||
&mut pending_requests,
|
||||
&mut subs,
|
||||
)?;
|
||||
);
|
||||
|
||||
info!("waiting for background jobs to finish...");
|
||||
info!("waiting for tasks to finish...");
|
||||
task_receiver.for_each(|task| on_task(task, msg_sender, &mut pending_requests));
|
||||
info!("...tasks have finished");
|
||||
info!("joining threadpool...");
|
||||
pool.join();
|
||||
info!("...background jobs have finished");
|
||||
info!("...threadpool has finished");
|
||||
|
||||
info!("waiting for file watcher to finish...");
|
||||
watcher.stop()?;
|
||||
info!("...file watcher has finished");
|
||||
Ok(())
|
||||
res
|
||||
}
|
||||
|
||||
fn main_loop_inner(
|
||||
|
@ -73,15 +79,17 @@ fn main_loop_inner(
|
|||
state: &mut ServerWorldState,
|
||||
pending_requests: &mut HashMap<u64, JobHandle>,
|
||||
subs: &mut Subscriptions,
|
||||
) -> Result<u64> {
|
||||
) -> Result<()> {
|
||||
let mut fs_receiver = Some(fs_receiver);
|
||||
loop {
|
||||
#[derive(Debug)]
|
||||
enum Event {
|
||||
Msg(RawMessage),
|
||||
Task(Task),
|
||||
Fs(Vec<FileEvent>),
|
||||
FsWatcherDead,
|
||||
}
|
||||
trace!("selecting");
|
||||
let event = select! {
|
||||
recv(msg_receiver, msg) => match msg {
|
||||
Some(msg) => Event::Msg(msg),
|
||||
|
@ -93,6 +101,7 @@ fn main_loop_inner(
|
|||
None => Event::FsWatcherDead,
|
||||
}
|
||||
};
|
||||
trace!("selected {:?}", event);
|
||||
let mut state_changed = false;
|
||||
match event {
|
||||
Event::FsWatcherDead => fs_receiver = None,
|
||||
|
@ -105,9 +114,9 @@ fn main_loop_inner(
|
|||
Event::Msg(msg) => {
|
||||
match msg {
|
||||
RawMessage::Request(req) => {
|
||||
let req = match req.cast::<req::Shutdown>() {
|
||||
Ok((id, _params)) => return Ok(id),
|
||||
Err(req) => req,
|
||||
let req = match handle_shutdown(req, msg_sender) {
|
||||
Some(req) => req,
|
||||
None => return Ok(()),
|
||||
};
|
||||
match on_request(state, pending_requests, pool, &task_sender, req)? {
|
||||
None => (),
|
||||
|
@ -290,7 +299,7 @@ impl<'a> PoolDispatcher<'a> {
|
|||
let sender = self.sender.clone();
|
||||
self.pool.execute(move || {
|
||||
let resp = match f(world, params, token) {
|
||||
Ok(resp) => RawResponse::ok(id, resp),
|
||||
Ok(resp) => RawResponse::ok::<R>(id, resp),
|
||||
Err(e) => RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string()),
|
||||
};
|
||||
let task = Task::Respond(resp);
|
||||
|
|
|
@ -127,7 +127,7 @@ impl Request for Runnables {
|
|||
const METHOD: &'static str = "m/runnables";
|
||||
}
|
||||
|
||||
#[derive(Deserialize, Debug)]
|
||||
#[derive(Serialize, Deserialize, Debug)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct RunnablesParams {
|
||||
pub text_document: TextDocumentIdentifier,
|
||||
|
|
|
@ -11,11 +11,13 @@ use walkdir::WalkDir;
|
|||
use Result;
|
||||
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct FileEvent {
|
||||
pub path: PathBuf,
|
||||
pub kind: FileEventKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum FileEventKind {
|
||||
Add(String),
|
||||
#[allow(unused)]
|
||||
|
|
42
crates/server/tests/heavy_tests/main.rs
Normal file
42
crates/server/tests/heavy_tests/main.rs
Normal file
|
@ -0,0 +1,42 @@
|
|||
extern crate tempdir;
|
||||
extern crate crossbeam_channel;
|
||||
extern crate languageserver_types;
|
||||
extern crate serde;
|
||||
extern crate serde_json;
|
||||
extern crate gen_lsp_server;
|
||||
extern crate flexi_logger;
|
||||
extern crate m;
|
||||
|
||||
mod support;
|
||||
|
||||
use m::req::{Runnables, RunnablesParams};
|
||||
|
||||
use support::project;
|
||||
|
||||
#[test]
|
||||
fn test_runnables() {
|
||||
let server = project(r"
|
||||
//- lib.rs
|
||||
#[test]
|
||||
fn foo() {
|
||||
}
|
||||
");
|
||||
server.request::<Runnables>(
|
||||
RunnablesParams {
|
||||
text_document: server.doc_id("lib.rs"),
|
||||
position: None,
|
||||
},
|
||||
r#"[
|
||||
{
|
||||
"args": [ "test", "--", "foo", "--nocapture" ],
|
||||
"bin": "cargo",
|
||||
"env": { "RUST_BACKTRACE": "short" },
|
||||
"label": "test foo",
|
||||
"range": {
|
||||
"end": { "character": 1, "line": 2 },
|
||||
"start": { "character": 0, "line": 0 }
|
||||
}
|
||||
}
|
||||
]"#
|
||||
);
|
||||
}
|
169
crates/server/tests/heavy_tests/support.rs
Normal file
169
crates/server/tests/heavy_tests/support.rs
Normal file
|
@ -0,0 +1,169 @@
|
|||
use std::{
|
||||
fs,
|
||||
thread,
|
||||
cell::Cell,
|
||||
path::PathBuf,
|
||||
};
|
||||
|
||||
use tempdir::TempDir;
|
||||
use crossbeam_channel::{bounded, Sender, Receiver};
|
||||
use flexi_logger::Logger;
|
||||
use languageserver_types::{
|
||||
Url,
|
||||
TextDocumentIdentifier,
|
||||
request::{Request, Shutdown},
|
||||
notification::DidOpenTextDocument,
|
||||
DidOpenTextDocumentParams,
|
||||
TextDocumentItem,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::{Value, from_str, to_string_pretty};
|
||||
use gen_lsp_server::{RawMessage, RawRequest, RawNotification};
|
||||
|
||||
use m::{Result, main_loop};
|
||||
|
||||
pub fn project(fixture: &str) -> Server {
|
||||
Logger::with_env_or_str("").start().unwrap();
|
||||
|
||||
let tmp_dir = TempDir::new("test-project")
|
||||
.unwrap();
|
||||
let mut buf = String::new();
|
||||
let mut file_name = None;
|
||||
let mut paths = vec![];
|
||||
macro_rules! flush {
|
||||
() => {
|
||||
if let Some(file_name) = file_name {
|
||||
let path = tmp_dir.path().join(file_name);
|
||||
fs::write(path.as_path(), buf.as_bytes()).unwrap();
|
||||
paths.push((path, buf.clone()));
|
||||
}
|
||||
}
|
||||
};
|
||||
for line in fixture.lines() {
|
||||
if line.starts_with("//-") {
|
||||
flush!();
|
||||
buf.clear();
|
||||
file_name = Some(line["//-".len()..].trim());
|
||||
continue;
|
||||
}
|
||||
buf.push_str(line);
|
||||
buf.push('\n');
|
||||
}
|
||||
flush!();
|
||||
|
||||
Server::new(tmp_dir, paths)
|
||||
}
|
||||
|
||||
pub struct Server {
|
||||
req_id: Cell<u64>,
|
||||
dir: TempDir,
|
||||
sender: Option<Sender<RawMessage>>,
|
||||
receiver: Receiver<RawMessage>,
|
||||
server: Option<thread::JoinHandle<Result<()>>>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
fn new(dir: TempDir, files: Vec<(PathBuf, String)>) -> Server {
|
||||
let path = dir.path().to_path_buf();
|
||||
let (client_sender, mut server_receiver) = bounded(1);
|
||||
let (mut server_sender, client_receiver) = bounded(1);
|
||||
let server = thread::spawn(move || main_loop(path, &mut server_receiver, &mut server_sender));
|
||||
let res = Server {
|
||||
req_id: Cell::new(1),
|
||||
dir,
|
||||
sender: Some(client_sender),
|
||||
receiver: client_receiver,
|
||||
server: Some(server),
|
||||
};
|
||||
for (path, text) in files {
|
||||
res.send_notification(RawNotification::new::<DidOpenTextDocument>(
|
||||
DidOpenTextDocumentParams {
|
||||
text_document: TextDocumentItem {
|
||||
uri: Url::from_file_path(path).unwrap(),
|
||||
language_id: "rust".to_string(),
|
||||
version: 0,
|
||||
text,
|
||||
}
|
||||
}
|
||||
))
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
pub fn doc_id(&self, rel_path: &str) -> TextDocumentIdentifier {
|
||||
let path = self.dir.path().join(rel_path);
|
||||
TextDocumentIdentifier {
|
||||
uri: Url::from_file_path(path).unwrap(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn request<R>(
|
||||
&self,
|
||||
params: R::Params,
|
||||
expected_resp: &str,
|
||||
)
|
||||
where
|
||||
R: Request,
|
||||
R::Params: Serialize,
|
||||
{
|
||||
let id = self.req_id.get();
|
||||
self.req_id.set(id + 1);
|
||||
let expected_resp: Value = from_str(expected_resp).unwrap();
|
||||
let actual = self.send_request::<R>(id, params);
|
||||
assert_eq!(
|
||||
expected_resp, actual,
|
||||
"Expected:\n{}\n\
|
||||
Actual:\n{}\n",
|
||||
to_string_pretty(&expected_resp).unwrap(),
|
||||
to_string_pretty(&actual).unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
fn send_request<R>(&self, id: u64, params: R::Params) -> Value
|
||||
where
|
||||
R: Request,
|
||||
R::Params: Serialize,
|
||||
{
|
||||
let r = RawRequest::new::<R>(id, params);
|
||||
self.sender.as_ref()
|
||||
.unwrap()
|
||||
.send(RawMessage::Request(r));
|
||||
|
||||
while let Some(msg) = self.receiver.recv() {
|
||||
match msg {
|
||||
RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
|
||||
RawMessage::Notification(_) => (),
|
||||
RawMessage::Response(res) => {
|
||||
assert_eq!(res.id, id);
|
||||
if let Some(err) = res.error {
|
||||
panic!("error response: {:#?}", err);
|
||||
}
|
||||
return res.result.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
panic!("no response");
|
||||
}
|
||||
fn send_notification(&self, not: RawNotification) {
|
||||
|
||||
self.sender.as_ref()
|
||||
.unwrap()
|
||||
.send(RawMessage::Notification(not));
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for Server {
|
||||
fn drop(&mut self) {
|
||||
{
|
||||
self.send_request::<Shutdown>(666, ());
|
||||
drop(self.sender.take().unwrap());
|
||||
while let Some(msg) = self.receiver.recv() {
|
||||
drop(msg);
|
||||
}
|
||||
}
|
||||
eprintln!("joining server");
|
||||
self.server.take()
|
||||
.unwrap()
|
||||
.join().unwrap().unwrap();
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue