views:

1610

answers:

7

In short, how do you unit test an error condition such as EINTR on a system call.

One particular example I'm working on, which could be a case all by itself, is whether it's necessary to call fclose again when it returns EOF with (errno==EINTR). The behavior depends on the implementation of fclose:

// Given an open FILE *fp
while (fclose(fp)==EOF && errno==EINTR) {
    errno = 0;
}

This call can be unsafe if fp freed when EINTR occurs. How can I test the error handling for when (errno==EINTR)?

+2  A: 

I don't think there is an easy way to actually test this at will.

EINTR will be generated if the fclose operation is interrupted by a signal. This implies that fclose was in a background thread which received a signal as it was processing the close request.

Good luck trying to reproduce that set of circumstances.

Benoit
+2  A: 

Use a struct of function pointers to abstract out the system functions. Replace the call to flcose(fp) with something like sys->fclose(fp). In your unit test, create an implementation of fclose that always returns EINTR then set sys->fclose to that version.

Essentially you are talking about mocking out the system calls which is the right thing to do in this case.
tvanfosson
Unfortunately, you'd be mocking out the meat of what he wants to test. The question is: what is the status of fp when the *real* system call is interrupted during close.
bmdhacks
+3  A: 

In this particular case, it's not safe to call fclose() again, as the C standard says the stream is disassociated from the file (and becomes indeterminate) even if the call fails.

fizzer
+2  A: 

Looking at the linux kernel source code, I'm unable to find any drivers that even return EINTR upon file close.

If you absosmurfly had to reproduce this case, you could write your own driver in Linux to return -EINTR on the .release method. Take a look at the example code from O'Reilly's Linux Device Drivers book. The scull project is one of the simplest ones. You'd change it to be like this:

int scull_release(struct inode *inode, struct file *filp)
{
    return -EINTR;
}

Again though, grepping through the linux source tree, I can't find any driver that would return EINTR on close.

EDIT - ok it looks like fuse - the userspace filesystem might be able to. This is used for things like sshfs. Still an edge case though.

bmdhacks
A: 

As Kris suggests, temporarily replace calls to the "real" fclose() with your own implementation defined in your unit test code. Your implementation could do something as simple as:

int my_fclose(FILE *fp)
{
  errno = EINTR;
  return EOF;
}

But, as fizzer points out, you shouldn't call fclose() again as the behavior is undefined, so you needn't bother even checking for the condition.

The other question to ask is whether you really need to worry about this; if your application code were to block all possible signals (except SIGKILL and SIGSTOP) during your fclose() then you wouldn't get a EINTR and wouldn't need to worry at all.

mhawke
A: 

Here is how I would test it, just for the sake of testing since fizzer reminded us calling fclose() twice is unsafe.

It is possible to redefine fclose() (or any other function of libc) in your program with your own behavior. On Unix-like systems, the linker does not complain - never tried on Windows but with cygwin. Of course this prevents your other tests to use the real fclose(), therefore such a test has to be put in a separate test executable.

Here is an all-in-one example with minunit.

#include <errno.h>
#include <stdio.h>
#include <stdbool.h>

/* from minunit.h : http://www.jera.com/techinfo/jtns/jtn002.html */
 #define mu_assert(message, test) do { if (!(test)) return message; } while (0)
 #define mu_run_test(test) do { char *message = test(); tests_run++; \
                                if (message) return message; } while (0)

int tests_run = 0;
bool fclose_shall_fail_on_EINTR = false;

//--- our implemention of fclose()
int fclose(FILE *fp) {
    if (fclose_shall_fail_on_EINTR) {
        errno = EINTR;
        fclose_shall_fail_on_EINTR = false;   //--- reset for next call 
        return EOF;
    } else { return 0; }
}

//--- this is the "production" function to be tested 
void uninterruptible_close(FILE *fp) {
    // Given an open FILE *fp
     while (fclose(fp)==EOF && errno==EINTR) {
        errno = 0;
     }
}

char *test_non_interrupted_fclose(void) {
    FILE *theHandle = NULL;   //--- don't care here
    uninterruptible_close(theHandle);
    mu_assert("test fclose wo/ interruption", 0 == errno);
    return 0;
}

char *test_interrupted_fclose(void) {
    FILE *theHandle = NULL;   //--- don't care here
    fclose_shall_fail_on_EINTR = true;

    uninterruptible_close(theHandle);
    mu_assert("test fclose wo/ interruption", 0 == errno);
    return 0;
}

char *test_suite(void)
{
    mu_run_test(test_non_interrupted_fclose);
    mu_run_test(test_interrupted_fclose);
    return 0;
}

int main(int ac, char **av)
{
  char *result = test_suite();

  printf("number of tests run: %d\n", tests_run);
  if (result) { printf("FAIL: %s\n", result); } 

  return 0 != result;
}
philippe
A: 

Um, I think all of you are ignoring something pretty important.

fclose() cannot return EINTR. Neither can fopen(), fwrite(), fread(), or any of the standard C I/O functions.

It is only when you dabble in the low-level I/O calls like open(2), write(2), and select(2) that you need to handle EINTR.

Read the [funny] man pages