Skip to content

Add bidirectional test flow#21479

Merged
Veykril merged 6 commits into
rust-lang:masterfrom
Shourya742:2026-01-15-add-bidirectional-test-flow
Jan 24, 2026
Merged

Add bidirectional test flow#21479
Veykril merged 6 commits into
rust-lang:masterfrom
Shourya742:2026-01-15-add-bidirectional-test-flow

Conversation

@Shourya742
Copy link
Copy Markdown
Member

This PR extends the proc-macro integration test suite to cover the bidirectional Postcard protocol flow.

@rustbot rustbot added the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 15, 2026
Comment thread crates/proc-macro-api/src/bidirectional_protocol/msg.rs Outdated
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why are the line endings changed in this file?

Comment on lines -160 to +182
// 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!(
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why this change?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thats sus... I am not sure why line change happened although locally on my editor I am seeing the diff clearly.
Screenshot from 2026-01-18 20-03-07

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Though the lines are the same(nothing changed at all). I recently enable auto formatting on save, but not sure if that's the culprit.

Comment thread crates/proc-macro-srv-cli/tests/common/utils.rs Outdated
Comment thread crates/proc-macro-srv-cli/tests/common/utils.rs Outdated
Comment thread crates/proc-macro-srv-cli/tests/common/utils.rs Outdated
@Shourya742 Shourya742 force-pushed the 2026-01-15-add-bidirectional-test-flow branch 9 times, most recently from fb4a580 to 97178c8 Compare January 18, 2026 14:53
@Shourya742 Shourya742 requested a review from Veykril January 18, 2026 15:19
@Shourya742 Shourya742 force-pushed the 2026-01-15-add-bidirectional-test-flow branch from 8adeaba to 06fb5a7 Compare January 19, 2026 07:04
@Veykril Veykril added this pull request to the merge queue Jan 24, 2026
Merged via the queue into rust-lang:master with commit 5dc1579 Jan 24, 2026
15 checks passed
@rustbot rustbot removed the S-waiting-on-review Status: Awaiting review from the assignee but also interested parties. label Jan 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants