Test runner fail on PaaS

Code attached below. Eventually the whole thing fails because it expects a tab somewhere in the tests

use std::io::{Read, Result, Write};

// the PhantomData instances in this file are just to stop compiler complaints
// about missing generics; feel free to remove them

pub struct ReadStats<R>{
    reads: usize,
    bytes: usize,
    data: R,
}

impl<R: Read> ReadStats<R> {
    // _wrapped is ignored because R is not bounded on Debug or Display and therefore
    // can't be passed through format!(). For actual implementation you will likely
    // wish to remove the leading underscore so the variable is not ignored.
    pub fn new(data: R) -> Self {
        ReadStats {
            reads: 0,
            bytes: 0,
            data,
        }
    }

    pub fn get_ref(&self) -> &R {
        todo!()
    }

    pub fn bytes_through(&self) -> usize {
        todo!() 
    }
    pub fn reads(&self) -> usize {
        todo!()
    }
}

impl<R: Read> Read for ReadStats<R> {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
        self.reads += 1;
        let bytes = self.data.read(buf)?;
        self.bytes += bytes;
        Ok(bytes)
        //todo!("Collect statistics about this call reading {buf:?}")
    }
}

pub struct WriteStats<W> {
    reads: usize,
    bytes: usize,
    data: W,
}

impl<W: Write> WriteStats<W> {
    // _wrapped is ignored because W is not bounded on Debug or Display and therefore
    // can't be passed through format!(). For actual implementation you will likely
    // wish to remove the leading underscore so the variable is not ignored.
    pub fn new(_wrapped: W) -> Self {
        todo!()
    }

    pub fn get_ref(&self) -> &W {
        todo!()
    }

    pub fn bytes_through(&self) -> usize {
        todo!()
    }

    pub fn writes(&self) -> usize {
        todo!()
    }
}

impl<W: Write> Write for WriteStats<W> {
    fn write(&mut self, buf: &[u8]) -> Result<usize> {
        todo!("Collect statistics about this call writing {buf:?}")
    }

    fn flush(&mut self) -> Result<()> {
        todo!()
    }
}

It doesn’t look like you finished the exercise. Can you post the error message? The title is “Test runner fail”, does it fail locally as well?

The code that currently errors is

use std::io::{Read, Result, Write};

// the PhantomData instances in this file are just to stop compiler complaints
// about missing generics; feel free to remove them

pub struct ReadStats<R>{
    reads: usize,
    bytes: usize,
    data: R,
}

impl<R: Read> ReadStats<R> {
    // _wrapped is ignored because R is not bounded on Debug or Display and therefore
    // can't be passed through format!(). For actual implementation you will likely
    // wish to remove the leading underscore so the variable is not ignored.
    pub fn new(data: R) -> Self {
        ReadStats {
            reads: 0,
            bytes: 0,
            data,
        }
    }

    pub fn get_ref(&self) -> &R {
        todo!()
    }

    pub fn bytes_through(&self) -> usize {
        todo!() 
    }
    pub fn reads(&self) -> usize {
        todo!()
    }
}

impl<R: Read> Read for ReadStats<R> {
    fn read(&mut self, buf: &mut [u8]) -> Result<usize> {
        self.reads += 1;
        let bytes = self.data.read(buf)?;
        self.bytes += bytes;
        Ok(bytes)
        //todo!("Collect statistics about this call reading {buf:?}")
    }
}

pub struct WriteStats<W> {
    reads: usize,
    bytes: usize,
    data: W,
}

impl<W: Write> WriteStats<W> {
    // _wrapped is ignored because W is not bounded on Debug or Display and therefore
    // can't be passed through format!(). For actual implementation you will likely
    // wish to remove the leading underscore so the variable is not ignored.
    pub fn new(data: W) -> Self {
        WriteStats {
            reads: 0,
            bytes: 0,
            data,
        }
    }

    pub fn get_ref(&self) -> &W {
        todo!()
    }

    pub fn bytes_through(&self) -> usize {
        todo!()
    }

    pub fn writes(&self) -> usize {
        todo!()
    }
}

impl<W: Write> Write for WriteStats<W> {
    fn write(&mut self, buf: &[u8]) -> Result<usize> {
        todo!("Collect statistics about this call writing {buf:?}")
    }

    fn flush(&mut self) -> Result<()> {
        todo!()
    }
}

test event misparse at idx 29: expected ident at line 31 column 2

I have not tried it locally.

I can reproduce this in the test runner and locally. When running locally with the same command the test runner is using, I get a non-unwinding panic. This cannot be caught, so cargo cannot wrap it in a nice JSON-formatted event. That’s why the test runner output is “event misparse”.

The panicking test is using a BufWriter, which flushes the buffer when dropped. Flushing the buffer calls .write() on the inner writer, which in the exercise template panics due to the todo!() macro.

I’m not yet sure what the best way is to improve this. We could:

  • Improve the test runner to detect such non-json, non-unwinding panic messages.
  • Change the template of PaaS IO to not cause such non-unwinding panics in the first place.
  • Stop testing with BufWriter.

Leaking the BufWriter by wrapping it in ManuallyDrop in the tests seems to work. That seems like a decent option.

The standard library tries to avoid this by tracking internally in the BufWriter if the inner writer panicked. However, we are accessing the inner writer directly in the tests using .get_ref(). If a panic occurs in the inner writer at that point, the BufWriter cannot observe that and tries to flush in its destructor anyway.

Edit, because no more than three consecutive replies are allowed:

I opened a PR with what I consider to be a good fix:

let mut inner_writer = WriteStats::new(io::sink());
let mut writer = std::mem::ManuallyDrop::new(BufWriter::new(&mut inner_writer));

That way, the inner writer will be dropped correctly. The BufWriter will not be dropped, which only skips a final flush. We don’t care about that in the tests.