Search code examples
objective-cunit-testingocunit

How do I unit test for EXC_BAD_ACCESS?


I know how to resolve EXC_BAD_ACCESS issues, but I'm not sure how to unit test for it. Is there a way to capture EXC_BAD_ACCESS in code instead of simply crashing?

Here's why I ask: I have written a library that heavily uses blocks, like this:

- (void)doSomething:(void (^)())myBlock;

In my implementation of doSomething: I'm going to eventually run the block, like this:

myBlock();

If a caller passes nil for the block, then it will crash with EXC_BAD_ACCESS, so the solution is to check that the block exists, like this:

if (myBlock) {
    myBlock();
}

This nil check is pretty easy to forget, so I'd like a way to write a unit test that fails when the crash occurs. I suppose a crash could be considered a test failure, but I think it would be nicer for others trying to run the tests to see a nice failure message rather than a crash. Any ideas?


Solution

  • I think you'll need to run the test in a subprocess; then you can let the subprocess crash, check for that crash, and fail the test neatly if it occurs.

    Working from Peter Hosey's singleton test code.

    - (void) runTestInSubprocess:(SEL)testCmd {
            pid_t pid = fork();
            // The return value of fork is 0 in the child process, and it is
            // the id of the child process in the parent process.
            if (pid == 0) {
                // Child process: run test
                // isInSubprocess is an ivar of your test case class
                isInSubprocess = YES;
                [self performSelector:testCmd];
                exit(0);
            } else {
                // Parent process: wait for child process to end, check 
                // its status
                int status;
                waitpid(pid, &status, /*options*/ 0);
                // This was a crash; fail the test
                STAssertFalse(WIFSIGNALED(status), @"Test %@ crashed due to signal %d", NSStringFromSelector(testCmd), WTERMSIG(status));
            }
    }
    

    Each test will then run itself in a subprocess like so:

    - (void) testSomething {
        if (!isInSubprocess) {
                // Hand off this test's selector to be run in a subprocess
                [self runTestInSubprocess:_cmd];
                return;
        }
    
        // Put actual test code here
        STAssertEquals(1, 1, @"Something wrong with the universe.");
    
    }
    

    You may need to tweak this; I haven't tested it.