Skip to content

Commit 8ac958f

Browse files
committed
Test stream finalization retry behavior
1 parent f804f23 commit 8ac958f

2 files changed

Lines changed: 59 additions & 0 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@
4141
distinguish EOF or terminal padding from fully drained buffered output.
4242
- Expanded the no-alloc portability smoke harness to cover custom alphabets,
4343
checked profiles, recoverable length helpers, and stack-buffer state helpers.
44+
- Added stream finalization failure regressions proving failed `try_finish()`
45+
calls do not mark adapters finalized and can be retried.
4446

4547
## 0.8.0 - 2026-05-16
4648

tests/rfc4648.rs

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,33 @@ impl Read for FramedChunkedReader<'_> {
6262
}
6363
}
6464

65+
#[cfg(feature = "stream")]
66+
#[derive(Default)]
67+
struct FailOnceWriter {
68+
output: Vec<u8>,
69+
fail_next: bool,
70+
}
71+
72+
#[cfg(feature = "stream")]
73+
impl Write for FailOnceWriter {
74+
fn write(&mut self, input: &[u8]) -> std::io::Result<usize> {
75+
if self.fail_next {
76+
self.fail_next = false;
77+
return Err(std::io::Error::new(
78+
std::io::ErrorKind::BrokenPipe,
79+
"injected writer failure",
80+
));
81+
}
82+
83+
self.output.extend_from_slice(input);
84+
Ok(input.len())
85+
}
86+
87+
fn flush(&mut self) -> std::io::Result<()> {
88+
Ok(())
89+
}
90+
}
91+
6592
struct ReverseAlphabet;
6693

6794
impl Alphabet for ReverseAlphabet {
@@ -2642,6 +2669,28 @@ fn stream_encoder_try_finish_keeps_adapter_available() {
26422669
assert_eq!(inner, b"aGU=");
26432670
}
26442671

2672+
#[cfg(feature = "stream")]
2673+
#[test]
2674+
fn stream_encoder_try_finish_failure_does_not_finalize() {
2675+
let writer = FailOnceWriter {
2676+
output: Vec::new(),
2677+
fail_next: true,
2678+
};
2679+
let mut encoder = Encoder::new(writer, STANDARD);
2680+
encoder.write_all(b"he").unwrap();
2681+
2682+
let err = encoder.try_finish().unwrap_err();
2683+
assert_eq!(err.kind(), std::io::ErrorKind::BrokenPipe);
2684+
assert!(!encoder.is_finalized());
2685+
assert_eq!(encoder.pending_len(), 2);
2686+
assert!(encoder.has_pending_input());
2687+
2688+
encoder.try_finish().unwrap();
2689+
assert!(encoder.is_finalized());
2690+
assert_eq!(encoder.pending_len(), 0);
2691+
assert_eq!(encoder.get_ref().output, b"aGU=");
2692+
}
2693+
26452694
#[cfg(feature = "stream")]
26462695
#[test]
26472696
fn stream_encoder_into_inner_still_returns_writer() {
@@ -2868,7 +2917,15 @@ fn stream_decoder_try_finish_reports_bad_final_pending_input() {
28682917

28692918
let err = decoder.try_finish().unwrap_err();
28702919
assert_eq!(err.kind(), std::io::ErrorKind::InvalidInput);
2920+
assert!(!decoder.is_finalized());
2921+
assert_eq!(decoder.pending_len(), 1);
2922+
assert!(decoder.has_pending_input());
28712923
assert!(decoder.get_ref().is_empty());
2924+
2925+
decoder.write_all(b"Gk=").unwrap();
2926+
decoder.try_finish().unwrap();
2927+
assert!(decoder.is_finalized());
2928+
assert_eq!(decoder.get_ref(), b"hi");
28722929
}
28732930

28742931
#[cfg(feature = "stream")]

0 commit comments

Comments
 (0)