Search code examples
swiftxctestnspipe

How to test that no data is written to an NSPipe


I've been working on a logging framework as of recent, and I'm allowing the user to provide an NSPipe for the logs to go down if they don't want the log messages to go to the console.

The Logger can be enabled or disabled, and when disabled, should not send any data down the pipe.

It's easy to check to see whether or not data has been sent down the pipe when the logger is enabled, as availableData immediately contains the data, and the NSFileHandleDataAvailableNotification/readabilityHandler block is triggered.

However, if you were to test that data was not sent down the pipe, and tried applying the same idea that availableData would not contain any data, the tests will timeout as availableData only returns once data has been received.

I came up with one solution which would be to listen out for the notification, and if it hasn't been received after X seconds, fulfil an expectation that the notification hasn't been received, with the premise that the expectation will be fulfilled twice (causing the test to fail) if a notification is received.

The problem with this approach is that if, for some reason, the pipe does receive some data, once the expectation is fulfilled in the readabilityHandler block, the test succeeds immediately, and the expectation is not fulfilled for a second time:

func testDisabledLoggerDoesntLog() {
    let logger = Logger()
    let pipe = NSPipe()

    logger.pipe = pipe
    logger.enabled = false

    let expectation = expectationWithDescription("handler not triggered")
    logger.pipe!.fileHandleForReading.readabilityHandler = { handler in
        expectation.fulfill()
    }

    logger.debug("Test message")

    fulfillAfter(expectation, time: 2)

    waitForExpectationsWithTimeout(3, handler: nil)
}

func fulfillAfter(expectation: XCTestExpectation, time: Double = 4) {
    let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(time * Double(NSEC_PER_SEC)))
    dispatch_after(delayTime, dispatch_get_main_queue()) {
        expectation.fulfill()
    }
}

What would a better approach be that will lead to a failing test if the pipe receives data, and a passing test if it doesn't?


Solution

  • As outlined in the comments, I've found a separate solution which writes arbitrary data to the pipe, executes the log (which may or may not result in more data being written to the pipe), and then retrieves the data from the pipe's availableData.

    If the availableData's length is equal to that of the arbitrary data, then it is clear that the log has not been written to the pipe. Otherwise if the length is greater than the data that the test has written, the log has indeed been executed and written its data to the pipe.

    let logger = Logger()
    let pipe = NSPipe()
    
    logger.pipe = pipe
    logger.enabled = false
    logger.useCurrentThread = true // The logs generally use a background thread to prevent interrupting the main queue. In this test, the current thread must be used in order for it to be synchronous.
    
    let writtenData = "written data".dataUsingEncoding(NSUTF8StringEncoding)!
    logger.pipe!.fileHandleForWriting.writeData(writtenData)
    
    logger.debug("Test message")
    
    XCTAssertEqual(logger.pipe!.fileHandleForReading.availableData.length, writtenData.length)