Skip to content

Commit 840e70a

Browse files
karthiknadigCopilot
andcommitted
test: stabilize output polling coverage (Fixes #564)
Exercise pending and ready states deterministically in the real output-test polling helpers, without changing production behavior or coverage budgets. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 8320c0f commit 840e70a

1 file changed

Lines changed: 93 additions & 9 deletions

File tree

‎crates/pet-jsonrpc/src/output.rs‎

Lines changed: 93 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -500,11 +500,40 @@ mod tests {
500500
use super::*;
501501
use serde::Serializer;
502502
use std::sync::atomic::{AtomicUsize, Ordering};
503-
use std::sync::mpsc::{self, Receiver, Sender};
503+
use std::sync::mpsc::{self, Receiver, Sender, SyncSender};
504504
use std::time::{Duration, Instant};
505505

506506
const TIMEOUT: Duration = Duration::from_secs(5);
507507

508+
struct WaitBoundary {
509+
observed: SyncSender<()>,
510+
resume: Receiver<()>,
511+
}
512+
513+
impl WaitBoundary {
514+
fn observe(self) {
515+
self.observed
516+
.send(())
517+
.expect("wait observation receiver dropped");
518+
self.resume
519+
.recv_timeout(TIMEOUT)
520+
.expect("timed out resuming wait helper");
521+
}
522+
}
523+
524+
fn wait_boundary() -> (WaitBoundary, Receiver<()>, SyncSender<()>) {
525+
let (observed_tx, observed_rx) = mpsc::sync_channel(1);
526+
let (resume_tx, resume_rx) = mpsc::sync_channel(1);
527+
(
528+
WaitBoundary {
529+
observed: observed_tx,
530+
resume: resume_rx,
531+
},
532+
observed_rx,
533+
resume_tx,
534+
)
535+
}
536+
508537
#[derive(Clone, Default)]
509538
struct CapturedWriter {
510539
bytes: Arc<Mutex<Vec<u8>>>,
@@ -618,28 +647,83 @@ mod tests {
618647
}
619648
}
620649

621-
fn wait_for_error(output: &Output) -> io::Error {
650+
fn wait_for_error(output: &Output, boundary: Option<WaitBoundary>) -> io::Error {
622651
let deadline = Instant::now() + TIMEOUT;
652+
let mut boundary = boundary;
623653
loop {
624-
if let Some(error) = output.error() {
654+
let error = output.error();
655+
if let Some(boundary) = boundary.take() {
656+
boundary.observe();
657+
}
658+
if let Some(error) = error {
625659
return error;
626660
}
627661
assert!(Instant::now() < deadline, "timed out waiting for error");
628662
thread::yield_now();
629663
}
630664
}
631665

632-
fn wait_for_len(bytes: &Arc<Mutex<Vec<u8>>>, expected: usize) {
666+
fn wait_for_len(bytes: &Arc<Mutex<Vec<u8>>>, expected: usize, boundary: Option<WaitBoundary>) {
633667
let deadline = Instant::now() + TIMEOUT;
668+
let mut boundary = boundary;
634669
loop {
635-
if bytes.lock().expect("captured output lock poisoned").len() >= expected {
670+
let len = bytes.lock().expect("captured output lock poisoned").len();
671+
if let Some(boundary) = boundary.take() {
672+
boundary.observe();
673+
}
674+
if len >= expected {
636675
return;
637676
}
638677
assert!(Instant::now() < deadline, "timed out waiting for output");
639678
thread::yield_now();
640679
}
641680
}
642681

682+
#[test]
683+
fn wait_helpers_complete_after_observed_pending_state() {
684+
let bytes = Arc::new(Mutex::new(Vec::new()));
685+
let (boundary, observed, resume) = wait_boundary();
686+
let waiting_bytes = Arc::clone(&bytes);
687+
let length_waiter = thread::spawn(move || wait_for_len(&waiting_bytes, 1, Some(boundary)));
688+
689+
observed
690+
.recv_timeout(TIMEOUT)
691+
.expect("length wait was not observed");
692+
bytes.lock().expect("captured output lock poisoned").push(1);
693+
resume.send(()).expect("length wait helper dropped");
694+
length_waiter.join().expect("length wait helper panicked");
695+
696+
let output = Output::new(CapturedWriter::default()).expect("failed to create test output");
697+
let (boundary, observed, resume) = wait_boundary();
698+
let waiting_output = output.clone();
699+
let error_waiter = thread::spawn(move || wait_for_error(&waiting_output, Some(boundary)));
700+
701+
observed
702+
.recv_timeout(TIMEOUT)
703+
.expect("error wait was not observed");
704+
output.fail(io::Error::new(
705+
io::ErrorKind::BrokenPipe,
706+
"observed failure",
707+
));
708+
resume.send(()).expect("error wait helper dropped");
709+
let error = error_waiter.join().expect("error wait helper panicked");
710+
assert_eq!(error.kind(), io::ErrorKind::BrokenPipe);
711+
}
712+
713+
#[test]
714+
fn wait_helpers_complete_from_initially_ready_state() {
715+
let bytes = Arc::new(Mutex::new(vec![1]));
716+
wait_for_len(&bytes, 1, None);
717+
718+
let output = Output::new(CapturedWriter::default()).expect("failed to create test output");
719+
output.fail(io::Error::new(
720+
io::ErrorKind::ConnectionReset,
721+
"initial failure",
722+
));
723+
let error = wait_for_error(&output, None);
724+
assert_eq!(error.kind(), io::ErrorKind::ConnectionReset);
725+
}
726+
643727
fn frames(bytes: &[u8]) -> Vec<&[u8]> {
644728
let mut remaining = bytes;
645729
let mut result = Vec::new();
@@ -719,7 +803,7 @@ mod tests {
719803
+ 2 * (HEADER_PREFIX.len() + HEADER_SUFFIX.len())
720804
+ expected_first.len().to_string().len()
721805
+ expected_second.len().to_string().len();
722-
wait_for_len(&bytes, expected_len);
806+
wait_for_len(&bytes, expected_len, None);
723807
output.close();
724808

725809
let bytes = bytes.lock().expect("captured output lock poisoned");
@@ -896,7 +980,7 @@ mod tests {
896980
.expect("failed to create output");
897981

898982
output.send(&"message");
899-
let first = wait_for_error(&output);
983+
let first = wait_for_error(&output, None);
900984
assert_eq!(first.kind(), io::ErrorKind::BrokenPipe);
901985
assert!(first.to_string().contains("deliberate write failure"));
902986

@@ -919,7 +1003,7 @@ mod tests {
9191003
.expect("failed to create output");
9201004

9211005
output.send(&"message");
922-
let error = wait_for_error(&output);
1006+
let error = wait_for_error(&output, None);
9231007
assert_eq!(error.kind(), io::ErrorKind::ConnectionAborted);
9241008
assert!(error.to_string().contains("failed to flush JSONRPC output"));
9251009
assert!(error.to_string().contains("deliberate flush failure"));
@@ -1092,7 +1176,7 @@ mod tests {
10921176
.sum();
10931177
let minimum_len =
10941178
expected_payload_len + messages * (HEADER_PREFIX.len() + HEADER_SUFFIX.len() + 1);
1095-
wait_for_len(&bytes, minimum_len);
1179+
wait_for_len(&bytes, minimum_len, None);
10961180
output.close();
10971181

10981182
let bytes = bytes.lock().expect("captured output lock poisoned");

0 commit comments

Comments
 (0)