Merge pull request #21479 from Shourya742/2026-01-15-add-bidirectional-test-flow
Add bidirectional test flow
This commit is contained in:
commit
af21cbe926
5 changed files with 745 additions and 456 deletions
|
|
@ -23,7 +23,6 @@ use crate::{
|
|||
},
|
||||
process::ProcMacroServerProcess,
|
||||
transport::codec::postcard::PostcardProtocol,
|
||||
version,
|
||||
};
|
||||
|
||||
pub mod msg;
|
||||
|
|
@ -159,12 +158,7 @@ pub(crate) fn expand(
|
|||
macro_name: proc_macro.name.to_string(),
|
||||
attributes: attr
|
||||
.map(|subtree| FlatTree::from_subtree(subtree, version, &mut span_data_table)),
|
||||
has_global_spans: ExpnGlobals {
|
||||
serialize: version >= version::HAS_GLOBAL_SPANS,
|
||||
def_site,
|
||||
call_site,
|
||||
mixed_site,
|
||||
},
|
||||
has_global_spans: ExpnGlobals { def_site, call_site, mixed_site },
|
||||
span_data_table: if process.rust_analyzer_spans() {
|
||||
serialize_span_data_index_map(&span_data_table)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -84,29 +84,17 @@ pub struct ExpandMacroData {
|
|||
pub macro_body: FlatTree,
|
||||
pub macro_name: String,
|
||||
pub attributes: Option<FlatTree>,
|
||||
#[serde(skip_serializing_if = "ExpnGlobals::skip_serializing_if")]
|
||||
#[serde(default)]
|
||||
pub has_global_spans: ExpnGlobals,
|
||||
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
#[serde(default)]
|
||||
pub span_data_table: Vec<u32>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Default, Debug, Serialize, Deserialize)]
|
||||
pub struct ExpnGlobals {
|
||||
#[serde(skip_serializing)]
|
||||
#[serde(default)]
|
||||
pub serialize: bool,
|
||||
pub def_site: usize,
|
||||
pub call_site: usize,
|
||||
pub mixed_site: usize,
|
||||
}
|
||||
|
||||
impl ExpnGlobals {
|
||||
fn skip_serializing_if(&self) -> bool {
|
||||
!self.serialize
|
||||
}
|
||||
}
|
||||
|
||||
impl Message for BidirectionalMessage {}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,225 @@
|
|||
#![cfg(feature = "sysroot-abi")]
|
||||
|
||||
mod common {
|
||||
pub(crate) mod utils;
|
||||
}
|
||||
|
||||
use common::utils::{
|
||||
create_empty_token_tree, proc_macro_test_dylib_path, request_bidirectional, with_server,
|
||||
};
|
||||
use expect_test::expect;
|
||||
use proc_macro_api::{
|
||||
ProtocolFormat::BidirectionalPostcardPrototype,
|
||||
bidirectional_protocol::{
|
||||
msg::{ExpandMacro, ExpandMacroData, ExpnGlobals, Request, Response},
|
||||
reject_subrequests,
|
||||
},
|
||||
legacy_protocol::msg::{PanicMessage, ServerConfig, SpanDataIndexMap, SpanMode},
|
||||
version::CURRENT_API_VERSION,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_bidi_version_check_bidirectional() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let response =
|
||||
request_bidirectional(writer, reader, Request::ApiVersionCheck {}, reject_subrequests);
|
||||
|
||||
match response {
|
||||
Response::ApiVersionCheck(version) => {
|
||||
assert_eq!(version, CURRENT_API_VERSION);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_list_macros() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
let response = request_bidirectional(
|
||||
writer,
|
||||
reader,
|
||||
Request::ListMacros { dylib_path },
|
||||
&reject_subrequests,
|
||||
);
|
||||
|
||||
let Response::ListMacros(Ok(macros)) = response else {
|
||||
panic!("expected successful ListMacros response");
|
||||
};
|
||||
|
||||
let mut macro_list: Vec<_> =
|
||||
macros.iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect();
|
||||
macro_list.sort();
|
||||
let macro_list_str = macro_list.join("\n");
|
||||
|
||||
expect![[r#"
|
||||
DeriveEmpty [CustomDerive]
|
||||
DeriveError [CustomDerive]
|
||||
DerivePanic [CustomDerive]
|
||||
DeriveReemit [CustomDerive]
|
||||
attr_error [Attr]
|
||||
attr_noop [Attr]
|
||||
attr_panic [Attr]
|
||||
fn_like_clone_tokens [Bang]
|
||||
fn_like_error [Bang]
|
||||
fn_like_mk_idents [Bang]
|
||||
fn_like_mk_literals [Bang]
|
||||
fn_like_noop [Bang]
|
||||
fn_like_panic [Bang]
|
||||
fn_like_span_join [Bang]
|
||||
fn_like_span_line_column [Bang]
|
||||
fn_like_span_ops [Bang]"#]]
|
||||
.assert_eq(¯o_list_str);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_list_macros_invalid_path() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let response = request_bidirectional(
|
||||
writer,
|
||||
reader,
|
||||
Request::ListMacros { dylib_path: "/nonexistent/path/to/dylib.so".into() },
|
||||
reject_subrequests,
|
||||
);
|
||||
|
||||
match response {
|
||||
Response::ListMacros(Err(e)) => assert!(
|
||||
e.starts_with("Cannot create expander for /nonexistent/path/to/dylib.so"),
|
||||
"{e}"
|
||||
),
|
||||
other => panic!("expected error response, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_set_config() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let config = ServerConfig { span_mode: SpanMode::Id };
|
||||
let response =
|
||||
request_bidirectional(writer, reader, Request::SetConfig(config), reject_subrequests);
|
||||
|
||||
match response {
|
||||
Response::SetConfig(returned_config) => {
|
||||
assert_eq!(returned_config.span_mode, SpanMode::Id);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_set_config_rust_analyzer_mode() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let config = ServerConfig { span_mode: SpanMode::RustAnalyzer };
|
||||
let response =
|
||||
request_bidirectional(writer, reader, Request::SetConfig(config), reject_subrequests);
|
||||
|
||||
match response {
|
||||
Response::SetConfig(returned_config) => {
|
||||
assert_eq!(returned_config.span_mode, SpanMode::RustAnalyzer);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_expand_macro_panic() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let mut span_data_table = SpanDataIndexMap::default();
|
||||
let macro_body =
|
||||
common::utils::create_empty_token_tree(CURRENT_API_VERSION, &mut span_data_table);
|
||||
|
||||
let request1 = Request::ExpandMacro(Box::new(ExpandMacro {
|
||||
lib: dylib_path,
|
||||
env: vec![],
|
||||
current_dir: None,
|
||||
data: ExpandMacroData {
|
||||
macro_body,
|
||||
macro_name: "fn_like_panic".to_owned(),
|
||||
attributes: None,
|
||||
has_global_spans: ExpnGlobals { def_site: 0, call_site: 0, mixed_site: 0 },
|
||||
span_data_table: vec![],
|
||||
},
|
||||
}));
|
||||
|
||||
let response = request_bidirectional(writer, reader, request1, reject_subrequests);
|
||||
|
||||
match response {
|
||||
Response::ExpandMacro(Err(PanicMessage(msg))) => {
|
||||
assert!(msg.contains("fn_like_panic"), "panic message should mention macro name");
|
||||
}
|
||||
other => panic!("expected panic response, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_basic_call_flow() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let response1 =
|
||||
request_bidirectional(writer, reader, Request::ApiVersionCheck {}, reject_subrequests);
|
||||
assert!(matches!(response1, Response::ApiVersionCheck(_)));
|
||||
|
||||
let response2 = request_bidirectional(
|
||||
writer,
|
||||
reader,
|
||||
Request::SetConfig(ServerConfig { span_mode: SpanMode::Id }),
|
||||
reject_subrequests,
|
||||
);
|
||||
assert!(matches!(response2, Response::SetConfig(_)));
|
||||
|
||||
let response3 = request_bidirectional(
|
||||
writer,
|
||||
reader,
|
||||
Request::ListMacros { dylib_path: dylib_path.clone() },
|
||||
reject_subrequests,
|
||||
);
|
||||
assert!(matches!(response3, Response::ListMacros(Ok(_))));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bidi_expand_nonexistent_macro() {
|
||||
with_server(BidirectionalPostcardPrototype, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let version_response =
|
||||
request_bidirectional(writer, reader, Request::ApiVersionCheck {}, reject_subrequests);
|
||||
let Response::ApiVersionCheck(version) = version_response else {
|
||||
panic!("expected version check response");
|
||||
};
|
||||
|
||||
let mut span_data_table = SpanDataIndexMap::default();
|
||||
let macro_body = create_empty_token_tree(version, &mut span_data_table);
|
||||
|
||||
let expand_request = Request::ExpandMacro(Box::new(ExpandMacro {
|
||||
lib: dylib_path,
|
||||
env: vec![],
|
||||
current_dir: None,
|
||||
data: ExpandMacroData {
|
||||
macro_body,
|
||||
macro_name: "NonexistentMacro".to_owned(),
|
||||
attributes: None,
|
||||
has_global_spans: ExpnGlobals { def_site: 0, call_site: 0, mixed_site: 0 },
|
||||
span_data_table: vec![],
|
||||
},
|
||||
}));
|
||||
|
||||
let response = request_bidirectional(writer, reader, expand_request, reject_subrequests);
|
||||
|
||||
match response {
|
||||
Response::ExpandMacro(Err(PanicMessage(msg))) => {
|
||||
expect!["proc-macro `NonexistentMacro` is missing"].assert_eq(&msg)
|
||||
}
|
||||
other => panic!("expected error for nonexistent macro, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -1,213 +1,289 @@
|
|||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, BufRead, Read, Write},
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
use paths::Utf8PathBuf;
|
||||
use proc_macro_api::{
|
||||
legacy_protocol::msg::{FlatTree, Message, Request, Response, SpanDataIndexMap},
|
||||
transport::codec::json::JsonProtocol,
|
||||
};
|
||||
use span::{Edition, EditionedFileId, FileId, Span, SpanAnchor, SyntaxContext, TextRange};
|
||||
use tt::{Delimiter, DelimiterKind, TopSubtreeBuilder};
|
||||
|
||||
/// Shared state for an in-memory byte channel.
|
||||
#[derive(Default)]
|
||||
struct ChannelState {
|
||||
buffer: VecDeque<u8>,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
type InMemoryChannel = Arc<(Mutex<ChannelState>, Condvar)>;
|
||||
|
||||
/// Writer end of an in-memory channel.
|
||||
pub(crate) struct ChannelWriter {
|
||||
state: InMemoryChannel,
|
||||
}
|
||||
|
||||
impl Write for ChannelWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
if state.closed {
|
||||
return Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed"));
|
||||
}
|
||||
state.buffer.extend(buf);
|
||||
cvar.notify_all();
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ChannelWriter {
|
||||
fn drop(&mut self) {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
state.closed = true;
|
||||
cvar.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reader end of an in-memory channel.
|
||||
pub(crate) struct ChannelReader {
|
||||
state: InMemoryChannel,
|
||||
internal_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Read for ChannelReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
while state.buffer.is_empty() && !state.closed {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
|
||||
if state.buffer.is_empty() && state.closed {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let to_read = buf.len().min(state.buffer.len());
|
||||
for (dst, src) in buf.iter_mut().zip(state.buffer.drain(..to_read)) {
|
||||
*dst = src;
|
||||
}
|
||||
Ok(to_read)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufRead for ChannelReader {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
while state.buffer.is_empty() && !state.closed {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
|
||||
self.internal_buf.clear();
|
||||
self.internal_buf.extend(&state.buffer);
|
||||
Ok(&self.internal_buf)
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
let (lock, _) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
let to_drain = amt.min(state.buffer.len());
|
||||
drop(state.buffer.drain(..to_drain));
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a connected pair of channels for bidirectional communication.
|
||||
fn create_channel_pair() -> (ChannelWriter, ChannelReader, ChannelWriter, ChannelReader) {
|
||||
// Channel for client -> server communication
|
||||
let client_to_server = Arc::new((
|
||||
Mutex::new(ChannelState { buffer: VecDeque::new(), closed: false }),
|
||||
Condvar::new(),
|
||||
));
|
||||
let client_writer = ChannelWriter { state: client_to_server.clone() };
|
||||
let server_reader = ChannelReader { state: client_to_server, internal_buf: Vec::new() };
|
||||
|
||||
// Channel for server -> client communication
|
||||
let server_to_client = Arc::new((
|
||||
Mutex::new(ChannelState { buffer: VecDeque::new(), closed: false }),
|
||||
Condvar::new(),
|
||||
));
|
||||
|
||||
let server_writer = ChannelWriter { state: server_to_client.clone() };
|
||||
let client_reader = ChannelReader { state: server_to_client, internal_buf: Vec::new() };
|
||||
|
||||
(client_writer, client_reader, server_writer, server_reader)
|
||||
}
|
||||
|
||||
pub(crate) fn proc_macro_test_dylib_path() -> Utf8PathBuf {
|
||||
let path = proc_macro_test::PROC_MACRO_TEST_LOCATION;
|
||||
if path.is_empty() {
|
||||
panic!("proc-macro-test dylib not available (requires nightly toolchain)");
|
||||
}
|
||||
path.into()
|
||||
}
|
||||
|
||||
/// Runs a test with the server in a background thread.
|
||||
pub(crate) fn with_server<F, R>(test_fn: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut dyn Write, &mut dyn BufRead) -> R,
|
||||
{
|
||||
let (mut client_writer, mut client_reader, mut server_writer, mut server_reader) =
|
||||
create_channel_pair();
|
||||
|
||||
let server_handle = thread::spawn(move || {
|
||||
proc_macro_srv_cli::main_loop::run(
|
||||
&mut server_reader,
|
||||
&mut server_writer,
|
||||
proc_macro_api::ProtocolFormat::JsonLegacy,
|
||||
)
|
||||
});
|
||||
|
||||
let result = test_fn(&mut client_writer, &mut client_reader);
|
||||
|
||||
// Close the client writer to signal the server to stop
|
||||
drop(client_writer);
|
||||
|
||||
// Wait for server to finish
|
||||
match server_handle.join() {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(e)) => {
|
||||
// IO error from server is expected when client disconnects
|
||||
if matches!(
|
||||
e.kind(),
|
||||
io::ErrorKind::BrokenPipe
|
||||
| io::ErrorKind::UnexpectedEof
|
||||
| io::ErrorKind::InvalidData
|
||||
) {
|
||||
panic!("Server error: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => std::panic::resume_unwind(e),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
/// Sends a request and reads the response using JSON protocol.
|
||||
pub(crate) fn request(
|
||||
writer: &mut dyn Write,
|
||||
reader: &mut dyn BufRead,
|
||||
request: Request,
|
||||
) -> Response {
|
||||
request.write::<JsonProtocol>(writer).expect("failed to write request");
|
||||
|
||||
let mut buf = String::new();
|
||||
Response::read::<JsonProtocol>(reader, &mut buf)
|
||||
.expect("failed to read response")
|
||||
.expect("no response received")
|
||||
}
|
||||
|
||||
/// Creates a simple empty token tree suitable for testing.
|
||||
pub(crate) fn create_empty_token_tree(
|
||||
version: u32,
|
||||
span_data_table: &mut SpanDataIndexMap,
|
||||
) -> FlatTree {
|
||||
let anchor = SpanAnchor {
|
||||
file_id: EditionedFileId::new(FileId::from_raw(0), Edition::CURRENT),
|
||||
ast_id: span::ROOT_ERASED_FILE_AST_ID,
|
||||
};
|
||||
let span = Span {
|
||||
range: TextRange::empty(0.into()),
|
||||
anchor,
|
||||
ctx: SyntaxContext::root(Edition::CURRENT),
|
||||
};
|
||||
|
||||
let builder = TopSubtreeBuilder::new(Delimiter {
|
||||
open: span,
|
||||
close: span,
|
||||
kind: DelimiterKind::Invisible,
|
||||
});
|
||||
let tt = builder.build();
|
||||
|
||||
FlatTree::from_subtree(tt.view(), version, span_data_table)
|
||||
}
|
||||
use std::{
|
||||
collections::VecDeque,
|
||||
io::{self, BufRead, Read, Write},
|
||||
sync::{Arc, Condvar, Mutex},
|
||||
thread,
|
||||
};
|
||||
|
||||
use paths::Utf8PathBuf;
|
||||
use proc_macro_api::{
|
||||
ServerError,
|
||||
bidirectional_protocol::msg::{
|
||||
BidirectionalMessage, Request as BiRequest, Response as BiResponse, SubRequest, SubResponse,
|
||||
},
|
||||
legacy_protocol::msg::{FlatTree, Message, Request, Response, SpanDataIndexMap},
|
||||
transport::codec::{json::JsonProtocol, postcard::PostcardProtocol},
|
||||
};
|
||||
use span::{Edition, EditionedFileId, FileId, Span, SpanAnchor, SyntaxContext, TextRange};
|
||||
use tt::{Delimiter, DelimiterKind, TopSubtreeBuilder};
|
||||
|
||||
/// Shared state for an in-memory byte channel.
|
||||
#[derive(Default)]
|
||||
struct ChannelState {
|
||||
buffer: VecDeque<u8>,
|
||||
closed: bool,
|
||||
}
|
||||
|
||||
type InMemoryChannel = Arc<(Mutex<ChannelState>, Condvar)>;
|
||||
|
||||
/// Writer end of an in-memory channel.
|
||||
pub(crate) struct ChannelWriter {
|
||||
state: InMemoryChannel,
|
||||
}
|
||||
|
||||
impl Write for ChannelWriter {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
if state.closed {
|
||||
return Err(io::Error::new(io::ErrorKind::BrokenPipe, "channel closed"));
|
||||
}
|
||||
state.buffer.extend(buf);
|
||||
cvar.notify_all();
|
||||
Ok(buf.len())
|
||||
}
|
||||
|
||||
fn flush(&mut self) -> io::Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for ChannelWriter {
|
||||
fn drop(&mut self) {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
state.closed = true;
|
||||
cvar.notify_all();
|
||||
}
|
||||
}
|
||||
|
||||
/// Reader end of an in-memory channel.
|
||||
pub(crate) struct ChannelReader {
|
||||
state: InMemoryChannel,
|
||||
internal_buf: Vec<u8>,
|
||||
}
|
||||
|
||||
impl Read for ChannelReader {
|
||||
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
while state.buffer.is_empty() && !state.closed {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
|
||||
if state.buffer.is_empty() && state.closed {
|
||||
return Ok(0);
|
||||
}
|
||||
|
||||
let to_read = buf.len().min(state.buffer.len());
|
||||
for (dst, src) in buf.iter_mut().zip(state.buffer.drain(..to_read)) {
|
||||
*dst = src;
|
||||
}
|
||||
Ok(to_read)
|
||||
}
|
||||
}
|
||||
|
||||
impl BufRead for ChannelReader {
|
||||
fn fill_buf(&mut self) -> io::Result<&[u8]> {
|
||||
let (lock, cvar) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
|
||||
while state.buffer.is_empty() && !state.closed {
|
||||
state = cvar.wait(state).unwrap();
|
||||
}
|
||||
|
||||
self.internal_buf.clear();
|
||||
self.internal_buf.extend(&state.buffer);
|
||||
Ok(&self.internal_buf)
|
||||
}
|
||||
|
||||
fn consume(&mut self, amt: usize) {
|
||||
let (lock, _) = &*self.state;
|
||||
let mut state = lock.lock().unwrap();
|
||||
let to_drain = amt.min(state.buffer.len());
|
||||
drop(state.buffer.drain(..to_drain));
|
||||
}
|
||||
}
|
||||
|
||||
/// Creates a connected pair of channels for bidirectional communication.
|
||||
fn create_channel_pair() -> (ChannelWriter, ChannelReader, ChannelWriter, ChannelReader) {
|
||||
// Channel for client -> server communication
|
||||
let client_to_server = Arc::new((
|
||||
Mutex::new(ChannelState { buffer: VecDeque::new(), closed: false }),
|
||||
Condvar::new(),
|
||||
));
|
||||
let client_writer = ChannelWriter { state: client_to_server.clone() };
|
||||
let server_reader = ChannelReader { state: client_to_server, internal_buf: Vec::new() };
|
||||
|
||||
// Channel for server -> client communication
|
||||
let server_to_client = Arc::new((
|
||||
Mutex::new(ChannelState { buffer: VecDeque::new(), closed: false }),
|
||||
Condvar::new(),
|
||||
));
|
||||
|
||||
let server_writer = ChannelWriter { state: server_to_client.clone() };
|
||||
let client_reader = ChannelReader { state: server_to_client, internal_buf: Vec::new() };
|
||||
|
||||
(client_writer, client_reader, server_writer, server_reader)
|
||||
}
|
||||
|
||||
pub(crate) fn proc_macro_test_dylib_path() -> Utf8PathBuf {
|
||||
let path = proc_macro_test::PROC_MACRO_TEST_LOCATION;
|
||||
if path.is_empty() {
|
||||
panic!("proc-macro-test dylib not available (requires nightly toolchain)");
|
||||
}
|
||||
path.into()
|
||||
}
|
||||
|
||||
/// Creates a simple empty token tree suitable for testing.
|
||||
pub(crate) fn create_empty_token_tree(
|
||||
version: u32,
|
||||
span_data_table: &mut SpanDataIndexMap,
|
||||
) -> FlatTree {
|
||||
let anchor = SpanAnchor {
|
||||
file_id: EditionedFileId::new(FileId::from_raw(0), Edition::CURRENT),
|
||||
ast_id: span::ROOT_ERASED_FILE_AST_ID,
|
||||
};
|
||||
let span = Span {
|
||||
range: TextRange::empty(0.into()),
|
||||
anchor,
|
||||
ctx: SyntaxContext::root(Edition::CURRENT),
|
||||
};
|
||||
|
||||
let builder = TopSubtreeBuilder::new(Delimiter {
|
||||
open: span,
|
||||
close: span,
|
||||
kind: DelimiterKind::Invisible,
|
||||
});
|
||||
let tt = builder.build();
|
||||
|
||||
FlatTree::from_subtree(tt.view(), version, span_data_table)
|
||||
}
|
||||
|
||||
pub(crate) fn with_server<F, R>(format: proc_macro_api::ProtocolFormat, test_fn: F) -> R
|
||||
where
|
||||
F: FnOnce(&mut dyn Write, &mut dyn BufRead) -> R,
|
||||
{
|
||||
let (mut client_writer, mut client_reader, mut server_writer, mut server_reader) =
|
||||
create_channel_pair();
|
||||
|
||||
let server_handle = thread::spawn(move || {
|
||||
proc_macro_srv_cli::main_loop::run(&mut server_reader, &mut server_writer, format)
|
||||
});
|
||||
|
||||
let result = test_fn(&mut client_writer, &mut client_reader);
|
||||
|
||||
drop(client_writer);
|
||||
|
||||
match server_handle.join() {
|
||||
Ok(Ok(())) => {}
|
||||
Ok(Err(e)) => {
|
||||
if !matches!(
|
||||
e.kind(),
|
||||
io::ErrorKind::BrokenPipe
|
||||
| io::ErrorKind::UnexpectedEof
|
||||
| io::ErrorKind::InvalidData
|
||||
) {
|
||||
panic!("Server error: {e}");
|
||||
}
|
||||
}
|
||||
Err(e) => std::panic::resume_unwind(e),
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
|
||||
trait TestProtocol {
|
||||
type Request;
|
||||
type Response;
|
||||
|
||||
fn request(&self, writer: &mut dyn Write, req: Self::Request);
|
||||
fn receive(&self, reader: &mut dyn BufRead, writer: &mut dyn Write) -> Self::Response;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct JsonLegacy;
|
||||
|
||||
impl TestProtocol for JsonLegacy {
|
||||
type Request = Request;
|
||||
type Response = Response;
|
||||
|
||||
fn request(&self, writer: &mut dyn Write, req: Request) {
|
||||
req.write::<JsonProtocol>(writer).expect("failed to write request");
|
||||
}
|
||||
|
||||
fn receive(&self, reader: &mut dyn BufRead, _writer: &mut dyn Write) -> Response {
|
||||
let mut buf = String::new();
|
||||
Response::read::<JsonProtocol>(reader, &mut buf)
|
||||
.expect("failed to read response")
|
||||
.expect("no response received")
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
struct PostcardBidirectional<F>
|
||||
where
|
||||
F: Fn(SubRequest) -> Result<SubResponse, ServerError>,
|
||||
{
|
||||
callback: F,
|
||||
}
|
||||
|
||||
impl<F> TestProtocol for PostcardBidirectional<F>
|
||||
where
|
||||
F: Fn(SubRequest) -> Result<SubResponse, ServerError>,
|
||||
{
|
||||
type Request = BiRequest;
|
||||
type Response = BiResponse;
|
||||
|
||||
fn request(&self, writer: &mut dyn Write, req: BiRequest) {
|
||||
let msg = BidirectionalMessage::Request(req);
|
||||
msg.write::<PostcardProtocol>(writer).expect("failed to write request");
|
||||
}
|
||||
|
||||
fn receive(&self, reader: &mut dyn BufRead, writer: &mut dyn Write) -> BiResponse {
|
||||
let mut buf = Vec::new();
|
||||
|
||||
loop {
|
||||
let msg = BidirectionalMessage::read::<PostcardProtocol>(reader, &mut buf)
|
||||
.expect("failed to read message")
|
||||
.expect("no message received");
|
||||
|
||||
match msg {
|
||||
BidirectionalMessage::Response(resp) => return resp,
|
||||
BidirectionalMessage::SubRequest(sr) => {
|
||||
let reply = (self.callback)(sr).expect("subrequest callback failed");
|
||||
let msg = BidirectionalMessage::SubResponse(reply);
|
||||
msg.write::<PostcardProtocol>(writer).expect("failed to write subresponse");
|
||||
}
|
||||
other => panic!("unexpected message: {other:?}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn request_legacy(
|
||||
writer: &mut dyn Write,
|
||||
reader: &mut dyn BufRead,
|
||||
request: Request,
|
||||
) -> Response {
|
||||
let protocol = JsonLegacy;
|
||||
protocol.request(writer, request);
|
||||
protocol.receive(reader, writer)
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn request_bidirectional<F>(
|
||||
writer: &mut dyn Write,
|
||||
reader: &mut dyn BufRead,
|
||||
request: BiRequest,
|
||||
callback: F,
|
||||
) -> BiResponse
|
||||
where
|
||||
F: Fn(SubRequest) -> Result<SubResponse, ServerError>,
|
||||
{
|
||||
let protocol = PostcardBidirectional { callback };
|
||||
protocol.request(writer, request);
|
||||
protocol.receive(reader, writer)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,224 +1,230 @@
|
|||
//! Integration tests for the proc-macro-srv-cli main loop.
|
||||
//!
|
||||
//! These tests exercise the full client-server RPC procedure using in-memory
|
||||
//! channels without needing to spawn the actual server and client processes.
|
||||
|
||||
#![cfg(feature = "sysroot-abi")]
|
||||
|
||||
mod common {
|
||||
pub(crate) mod utils;
|
||||
}
|
||||
|
||||
use common::utils::{create_empty_token_tree, proc_macro_test_dylib_path, request, with_server};
|
||||
use expect_test::expect;
|
||||
use proc_macro_api::{
|
||||
legacy_protocol::msg::{
|
||||
ExpandMacro, ExpandMacroData, ExpnGlobals, PanicMessage, Request, Response, ServerConfig,
|
||||
SpanDataIndexMap, SpanMode,
|
||||
},
|
||||
version::CURRENT_API_VERSION,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_version_check() {
|
||||
with_server(|writer, reader| {
|
||||
let response = request(writer, reader, Request::ApiVersionCheck {});
|
||||
|
||||
match response {
|
||||
Response::ApiVersionCheck(version) => {
|
||||
assert_eq!(version, CURRENT_API_VERSION);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_macros() {
|
||||
with_server(|writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
let response = request(writer, reader, Request::ListMacros { dylib_path });
|
||||
|
||||
let Response::ListMacros(Ok(macros)) = response else {
|
||||
panic!("expected successful ListMacros response");
|
||||
};
|
||||
|
||||
let mut macro_list: Vec<_> =
|
||||
macros.iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect();
|
||||
macro_list.sort();
|
||||
let macro_list_str = macro_list.join("\n");
|
||||
|
||||
expect![[r#"
|
||||
DeriveEmpty [CustomDerive]
|
||||
DeriveError [CustomDerive]
|
||||
DerivePanic [CustomDerive]
|
||||
DeriveReemit [CustomDerive]
|
||||
attr_error [Attr]
|
||||
attr_noop [Attr]
|
||||
attr_panic [Attr]
|
||||
fn_like_clone_tokens [Bang]
|
||||
fn_like_error [Bang]
|
||||
fn_like_mk_idents [Bang]
|
||||
fn_like_mk_literals [Bang]
|
||||
fn_like_noop [Bang]
|
||||
fn_like_panic [Bang]
|
||||
fn_like_span_join [Bang]
|
||||
fn_like_span_line_column [Bang]
|
||||
fn_like_span_ops [Bang]"#]]
|
||||
.assert_eq(¯o_list_str);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_macros_invalid_path() {
|
||||
with_server(|writer, reader| {
|
||||
let response = request(
|
||||
writer,
|
||||
reader,
|
||||
Request::ListMacros { dylib_path: "/nonexistent/path/to/dylib.so".into() },
|
||||
);
|
||||
|
||||
match response {
|
||||
Response::ListMacros(Err(e)) => assert!(
|
||||
e.starts_with("Cannot create expander for /nonexistent/path/to/dylib.so"),
|
||||
"{e}"
|
||||
),
|
||||
other => panic!("expected error response, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_config() {
|
||||
with_server(|writer, reader| {
|
||||
let config = ServerConfig { span_mode: SpanMode::Id };
|
||||
let response = request(writer, reader, Request::SetConfig(config));
|
||||
|
||||
match response {
|
||||
Response::SetConfig(returned_config) => {
|
||||
assert_eq!(returned_config.span_mode, SpanMode::Id);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_config_rust_analyzer_mode() {
|
||||
with_server(|writer, reader| {
|
||||
let config = ServerConfig { span_mode: SpanMode::RustAnalyzer };
|
||||
let response = request(writer, reader, Request::SetConfig(config));
|
||||
|
||||
match response {
|
||||
Response::SetConfig(returned_config) => {
|
||||
assert_eq!(returned_config.span_mode, SpanMode::RustAnalyzer);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_macro_panic() {
|
||||
with_server(|writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let version_response = request(writer, reader, Request::ApiVersionCheck {});
|
||||
let Response::ApiVersionCheck(version) = version_response else {
|
||||
panic!("expected version check response");
|
||||
};
|
||||
|
||||
let mut span_data_table = SpanDataIndexMap::default();
|
||||
let macro_body = create_empty_token_tree(version, &mut span_data_table);
|
||||
|
||||
let expand_request = Request::ExpandMacro(Box::new(ExpandMacro {
|
||||
lib: dylib_path,
|
||||
env: vec![],
|
||||
current_dir: None,
|
||||
data: ExpandMacroData {
|
||||
macro_body,
|
||||
macro_name: "fn_like_panic".to_owned(),
|
||||
attributes: None,
|
||||
has_global_spans: ExpnGlobals {
|
||||
serialize: version >= 3,
|
||||
def_site: 0,
|
||||
call_site: 0,
|
||||
mixed_site: 0,
|
||||
},
|
||||
span_data_table: vec![],
|
||||
},
|
||||
}));
|
||||
|
||||
let response = request(writer, reader, expand_request);
|
||||
|
||||
match response {
|
||||
Response::ExpandMacro(Err(PanicMessage(msg))) => {
|
||||
assert!(msg.contains("fn_like_panic"), "panic message should mention the macro");
|
||||
}
|
||||
Response::ExpandMacro(Ok(_)) => {
|
||||
panic!("expected panic, but macro succeeded");
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_call_flow() {
|
||||
with_server(|writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let response1 = request(writer, reader, Request::ApiVersionCheck {});
|
||||
assert!(matches!(response1, Response::ApiVersionCheck(_)));
|
||||
|
||||
let response2 =
|
||||
request(writer, reader, Request::SetConfig(ServerConfig { span_mode: SpanMode::Id }));
|
||||
assert!(matches!(response2, Response::SetConfig(_)));
|
||||
|
||||
let response3 =
|
||||
request(writer, reader, Request::ListMacros { dylib_path: dylib_path.clone() });
|
||||
assert!(matches!(response3, Response::ListMacros(Ok(_))));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_nonexistent_macro() {
|
||||
with_server(|writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let version_response = request(writer, reader, Request::ApiVersionCheck {});
|
||||
let Response::ApiVersionCheck(version) = version_response else {
|
||||
panic!("expected version check response");
|
||||
};
|
||||
|
||||
let mut span_data_table = SpanDataIndexMap::default();
|
||||
let macro_body = create_empty_token_tree(version, &mut span_data_table);
|
||||
|
||||
let expand_request = Request::ExpandMacro(Box::new(ExpandMacro {
|
||||
lib: dylib_path,
|
||||
env: vec![],
|
||||
current_dir: None,
|
||||
data: ExpandMacroData {
|
||||
macro_body,
|
||||
macro_name: "NonexistentMacro".to_owned(),
|
||||
attributes: None,
|
||||
has_global_spans: ExpnGlobals {
|
||||
serialize: version >= 3,
|
||||
def_site: 0,
|
||||
call_site: 0,
|
||||
mixed_site: 0,
|
||||
},
|
||||
span_data_table: vec![],
|
||||
},
|
||||
}));
|
||||
|
||||
let response = request(writer, reader, expand_request);
|
||||
|
||||
match response {
|
||||
Response::ExpandMacro(Err(PanicMessage(msg))) => {
|
||||
expect!["proc-macro `NonexistentMacro` is missing"].assert_eq(&msg)
|
||||
}
|
||||
other => panic!("expected error for nonexistent macro, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
//! Integration tests for the proc-macro-srv-cli main loop.
|
||||
//!
|
||||
//! These tests exercise the full client-server RPC procedure using in-memory
|
||||
//! channels without needing to spawn the actual server and client processes.
|
||||
|
||||
#![cfg(feature = "sysroot-abi")]
|
||||
|
||||
mod common {
|
||||
pub(crate) mod utils;
|
||||
}
|
||||
|
||||
use common::utils::{
|
||||
create_empty_token_tree, proc_macro_test_dylib_path, request_legacy, with_server,
|
||||
};
|
||||
use expect_test::expect;
|
||||
use proc_macro_api::{
|
||||
ProtocolFormat::JsonLegacy,
|
||||
legacy_protocol::msg::{
|
||||
ExpandMacro, ExpandMacroData, ExpnGlobals, PanicMessage, Request, Response, ServerConfig,
|
||||
SpanDataIndexMap, SpanMode,
|
||||
},
|
||||
version::CURRENT_API_VERSION,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn test_version_check() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let response = request_legacy(writer, reader, Request::ApiVersionCheck {});
|
||||
|
||||
match response {
|
||||
Response::ApiVersionCheck(version) => {
|
||||
assert_eq!(version, CURRENT_API_VERSION);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_macros() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
let response = request_legacy(writer, reader, Request::ListMacros { dylib_path });
|
||||
|
||||
let Response::ListMacros(Ok(macros)) = response else {
|
||||
panic!("expected successful ListMacros response");
|
||||
};
|
||||
|
||||
let mut macro_list: Vec<_> =
|
||||
macros.iter().map(|(name, kind)| format!("{name} [{kind:?}]")).collect();
|
||||
macro_list.sort();
|
||||
let macro_list_str = macro_list.join("\n");
|
||||
|
||||
expect![[r#"
|
||||
DeriveEmpty [CustomDerive]
|
||||
DeriveError [CustomDerive]
|
||||
DerivePanic [CustomDerive]
|
||||
DeriveReemit [CustomDerive]
|
||||
attr_error [Attr]
|
||||
attr_noop [Attr]
|
||||
attr_panic [Attr]
|
||||
fn_like_clone_tokens [Bang]
|
||||
fn_like_error [Bang]
|
||||
fn_like_mk_idents [Bang]
|
||||
fn_like_mk_literals [Bang]
|
||||
fn_like_noop [Bang]
|
||||
fn_like_panic [Bang]
|
||||
fn_like_span_join [Bang]
|
||||
fn_like_span_line_column [Bang]
|
||||
fn_like_span_ops [Bang]"#]]
|
||||
.assert_eq(¯o_list_str);
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_list_macros_invalid_path() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let response = request_legacy(
|
||||
writer,
|
||||
reader,
|
||||
Request::ListMacros { dylib_path: "/nonexistent/path/to/dylib.so".into() },
|
||||
);
|
||||
|
||||
match response {
|
||||
Response::ListMacros(Err(e)) => assert!(
|
||||
e.starts_with("Cannot create expander for /nonexistent/path/to/dylib.so"),
|
||||
"{e}"
|
||||
),
|
||||
other => panic!("expected error response, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_config() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let config = ServerConfig { span_mode: SpanMode::Id };
|
||||
let response = request_legacy(writer, reader, Request::SetConfig(config));
|
||||
|
||||
match response {
|
||||
Response::SetConfig(returned_config) => {
|
||||
assert_eq!(returned_config.span_mode, SpanMode::Id);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_set_config_rust_analyzer_mode() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let config = ServerConfig { span_mode: SpanMode::RustAnalyzer };
|
||||
let response = request_legacy(writer, reader, Request::SetConfig(config));
|
||||
|
||||
match response {
|
||||
Response::SetConfig(returned_config) => {
|
||||
assert_eq!(returned_config.span_mode, SpanMode::RustAnalyzer);
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_macro_panic() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let version_response = request_legacy(writer, reader, Request::ApiVersionCheck {});
|
||||
let Response::ApiVersionCheck(version) = version_response else {
|
||||
panic!("expected version check response");
|
||||
};
|
||||
|
||||
let mut span_data_table = SpanDataIndexMap::default();
|
||||
let macro_body = create_empty_token_tree(version, &mut span_data_table);
|
||||
|
||||
let expand_request = Request::ExpandMacro(Box::new(ExpandMacro {
|
||||
lib: dylib_path,
|
||||
env: vec![],
|
||||
current_dir: None,
|
||||
data: ExpandMacroData {
|
||||
macro_body,
|
||||
macro_name: "fn_like_panic".to_owned(),
|
||||
attributes: None,
|
||||
has_global_spans: ExpnGlobals {
|
||||
serialize: version >= 3,
|
||||
def_site: 0,
|
||||
call_site: 0,
|
||||
mixed_site: 0,
|
||||
},
|
||||
span_data_table: vec![],
|
||||
},
|
||||
}));
|
||||
|
||||
let response = request_legacy(writer, reader, expand_request);
|
||||
|
||||
match response {
|
||||
Response::ExpandMacro(Err(PanicMessage(msg))) => {
|
||||
assert!(msg.contains("fn_like_panic"), "panic message should mention the macro");
|
||||
}
|
||||
Response::ExpandMacro(Ok(_)) => {
|
||||
panic!("expected panic, but macro succeeded");
|
||||
}
|
||||
other => panic!("unexpected response: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_basic_call_flow() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let response1 = request_legacy(writer, reader, Request::ApiVersionCheck {});
|
||||
assert!(matches!(response1, Response::ApiVersionCheck(_)));
|
||||
|
||||
let response2 = request_legacy(
|
||||
writer,
|
||||
reader,
|
||||
Request::SetConfig(ServerConfig { span_mode: SpanMode::Id }),
|
||||
);
|
||||
assert!(matches!(response2, Response::SetConfig(_)));
|
||||
|
||||
let response3 =
|
||||
request_legacy(writer, reader, Request::ListMacros { dylib_path: dylib_path.clone() });
|
||||
assert!(matches!(response3, Response::ListMacros(Ok(_))));
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_expand_nonexistent_macro() {
|
||||
with_server(JsonLegacy, |writer, reader| {
|
||||
let dylib_path = proc_macro_test_dylib_path();
|
||||
|
||||
let version_response = request_legacy(writer, reader, Request::ApiVersionCheck {});
|
||||
let Response::ApiVersionCheck(version) = version_response else {
|
||||
panic!("expected version check response");
|
||||
};
|
||||
|
||||
let mut span_data_table = SpanDataIndexMap::default();
|
||||
let macro_body = create_empty_token_tree(version, &mut span_data_table);
|
||||
|
||||
let expand_request = Request::ExpandMacro(Box::new(ExpandMacro {
|
||||
lib: dylib_path,
|
||||
env: vec![],
|
||||
current_dir: None,
|
||||
data: ExpandMacroData {
|
||||
macro_body,
|
||||
macro_name: "NonexistentMacro".to_owned(),
|
||||
attributes: None,
|
||||
has_global_spans: ExpnGlobals {
|
||||
serialize: version >= 3,
|
||||
def_site: 0,
|
||||
call_site: 0,
|
||||
mixed_site: 0,
|
||||
},
|
||||
span_data_table: vec![],
|
||||
},
|
||||
}));
|
||||
|
||||
let response = request_legacy(writer, reader, expand_request);
|
||||
|
||||
match response {
|
||||
Response::ExpandMacro(Err(PanicMessage(msg))) => {
|
||||
expect!["proc-macro `NonexistentMacro` is missing"].assert_eq(&msg)
|
||||
}
|
||||
other => panic!("expected error for nonexistent macro, got: {other:?}"),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue