How to simulate I/O errors when writing integration tests for a binary crate?

⚓ Rust    📅 2026-05-09    👤 surdeus    👁️ 1      

surdeus

I'm writing integration tests for my binary crate (CLI application) and I'm trying to achieve full code coverage. Currently, I'm at 95% code coverage, which I think is quite good already, but the only remaining code paths that are not currently reached by any tests all are code paths that are related to error handling — specifically, they are related to handling various kinds of I/O errors.

How can I "simulate" I/O errors in the integration tests? :thinking:


Here is what I have got, so far:

  • Simulating a failure of File::open() is trivial. Just pass a path to a non-existing file.

  • Simulating a failure of File.read() is not so trivial. I have found that, on Linux, I can pass special path /proc/self/mem as input file, because this file opens fine; meanwhile any read() attempt is going to fail reliably. But this is a Linux-only solution. And I'm not even sure whether Linux gurantees this behavior for all distrubutions and for all future kernel versions. It seems a bit brittle. Is there a more general, platform-independant solution?

    I thought about writing a custom Linux device driver (kernel module) that creates a virtual file /proc/fail, which always opens successfully — the open() implementation would be just a NOP — and that fails on any read or write attempt — the read() and write() implementations would simply return EIO right away. But this doesn't really solve the platform-specific issue. And it makes the test setup even more complex.

  • Simulating a failure of ReadDir.next() seems to be very hard. I have not found a way to simulate the case that a directory can be opened successfully but then fails to iterate the dir entries! Maybe this could be simulated with a custom device driver, but it seems to go beyond what procfs can do, and therefore I don't really have an idea to implement it.

  • Simulating a failure of write!() to the stdout stream is easy again. I can simply create a pipe, close the "read" handle of the pipe immediately, and set up the remaining "write" handle of the half-closed pipe as the Command::stdout() of my child process. This causes any write!() to stdout inside the child process to fail reliably, with a "broken pipe" error.

  • Simulating a failure of read!() from the stdin stream is difficult. Setting the Command::stdin() of the child process to the "read" handle of a pipe whose "write" handle has already been closed, does not seem to trigger a failure of read!() from the stdin inside the child process. I have not found a way to make read!() from stdin fail!


So, is there a simpler way to simulate these errors?

Or, is there at least some way to simulate the error cases that I currently could not simulate?

Regards.

13 posts - 6 participants

Read full topic

🏷️ Rust_feed