#![allow(dead_code)]
use std::io::{self, Write};
pub struct BuggyWrite<W> {
inner: W,
buf: Vec<u8>,
offset: usize,
}
impl<W: Write> BuggyWrite<W> {
pub fn new(inner: W) -> Self {
BuggyWrite {
inner,
buf: Vec::with_capacity(256),
offset: 0,
}
}
fn write_from_offset(&mut self) -> io::Result<()> {
while self.offset < self.buf.len() {
self.offset += self.inner.write(&self.buf[self.offset..])?;
}
Ok(())
}
fn reset_buffer(&mut self) {
unsafe {
self.buf.set_len(0);
}
self.offset = 0;
}
pub fn into_inner(self) -> W {
self.inner
}
}
impl<W: Write> Write for BuggyWrite<W> {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
if self.offset < self.buf.len() {
self.write_from_offset()?;
}
self.reset_buffer();
self.buf.extend_from_slice(buf);
self.write_from_offset()?;
Ok(self.buf.len())
}
fn flush(&mut self) -> io::Result<()> {
while self.offset < self.buf.len() {
self.write_from_offset()?;
}
self.reset_buffer();
self.inner.flush()
}
}
fn main() {
check::check_write_is_buggy();
}
mod check {
use super::*;
use once_cell::sync::Lazy;
use partial_io::{PartialOp, PartialWrite};
pub(crate) static HELLO_STR: Lazy<Vec<u8>> = Lazy::new(|| "Hello".repeat(50).into_bytes());
pub(crate) static WORLD_STR: Lazy<Vec<u8>> = Lazy::new(|| "World".repeat(40).into_bytes());
pub fn check_write_is_buggy() {
let partial = vec![
PartialOp::Err(io::ErrorKind::Interrupted),
PartialOp::Unlimited,
];
let (hello_res, world_res, flush_res, inner) = buggy_write_internal(partial);
assert_eq!(hello_res.unwrap_err().kind(), io::ErrorKind::Interrupted);
assert_eq!(world_res.unwrap(), 5 * 40);
flush_res.unwrap();
let mut expected = Vec::new();
expected.extend_from_slice(&HELLO_STR);
expected.extend_from_slice(&WORLD_STR);
assert_eq!(inner, expected);
}
pub(crate) fn buggy_write_internal<I>(
partial_iter: I,
) -> (
io::Result<usize>,
io::Result<usize>,
io::Result<()>,
Vec<u8>,
)
where
I: IntoIterator<Item = PartialOp> + 'static,
I::IntoIter: Send,
{
let inner = Vec::new();
let partial_writer = PartialWrite::new(inner, partial_iter);
let mut buggy_write = BuggyWrite::new(partial_writer);
let hello_res = buggy_write.write(&HELLO_STR);
let world_res = buggy_write.write(&WORLD_STR);
let flush_res = buggy_write.flush();
let inner = buggy_write.into_inner().into_inner();
(hello_res, world_res, flush_res, inner)
}
}
#[cfg(test)]
mod tests {
use super::*;
use partial_io::{
proptest_types::{interrupted_strategy, partial_op_strategy},
quickcheck_types::{GenInterrupted, PartialWithErrors},
};
use proptest::{collection::vec, prelude::*};
use quickcheck::{quickcheck, TestResult};
#[test]
fn test_check_write_is_buggy() {
check::check_write_is_buggy();
}
#[test]
#[ignore]
fn test_quickcheck_buggy_write() {
quickcheck(quickcheck_buggy_write2 as fn(PartialWithErrors<GenInterrupted>) -> TestResult);
}
fn quickcheck_buggy_write2(partial: PartialWithErrors<GenInterrupted>) -> TestResult {
let (hello_res, world_res, flush_res, inner) = check::buggy_write_internal(partial);
if flush_res.is_err() {
return TestResult::discard();
}
let mut expected = Vec::new();
if hello_res.is_ok() {
expected.extend_from_slice(&check::HELLO_STR);
}
if world_res.is_ok() {
expected.extend_from_slice(&check::WORLD_STR);
}
assert_eq!(inner, expected);
TestResult::passed()
}
proptest! {
#[test]
#[ignore]
fn test_proptest_buggy_write(ops in vec(partial_op_strategy(interrupted_strategy(), 128), 0..128)) {
let (hello_res, world_res, flush_res, inner) = check::buggy_write_internal(ops);
prop_assume!(flush_res.is_ok(), "if flush failed, we don't know what was written");
let mut expected = Vec::new();
if hello_res.is_ok() {
expected.extend_from_slice(&check::HELLO_STR);
}
if world_res.is_ok() {
expected.extend_from_slice(&check::WORLD_STR);
}
prop_assert_eq!(inner, expected, "actual value matches expected");
}
}
}