Skip to content

Instantly share code, notes, and snippets.

@jkereako
Created July 19, 2024 15:47
Show Gist options
  • Save jkereako/c864537636747912ab00ea72e06094e5 to your computer and use it in GitHub Desktop.
Save jkereako/c864537636747912ab00ea72e06094e5 to your computer and use it in GitHub Desktop.
import Foundation
import XCTest
final class STDOUTListener {
/// Consume messages sent to STDOUT
let input = Pipe()
/// Sends messages back to STDOUT
let output = Pipe()
/// Buffers strings written to stdout
private(set) var buffer = ""
/// Fulfilled when buffer is captured.
///
/// This is needed because writing to STDOUT is asynchronous
let expectation = XCTestExpectation(
description: "Fulfilled when buffer is captured"
)
init() {
// Set up a read handler which fires when data is written to our inputPipe
input.fileHandleForReading.readabilityHandler = { [weak self] handle in
guard let self else {
assertionFailure("`self` is nil")
return
}
let data = handle.availableData
if let string = String(data: data, encoding: String.Encoding.utf8) {
self.buffer += string
}
// Write input back to stdout
self.output.fileHandleForWriting.write(data)
expectation.fulfill()
}
}
/// Intercepts messages written to STDOUT
func open() {
// Copy the STDOUT file descriptor to `output`
dup2(STDOUT_FILENO, output.fileHandleForWriting.fileDescriptor)
// Intercept STDOUT with `inputPipe`
dup2(input.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
}
/// Removes the interceptor
func close() {
// Restore stdout
freopen("/dev/stdout", "a", stdout)
[input.fileHandleForReading, output.fileHandleForWriting].forEach {
$0.closeFile()
}
}
}
@jkereako
Copy link
Author

Description

Captures stdout messages for use in XCTests

final class MyTests: XCTestCase {
    func testSTDOUTListener() async throws {
      //-- Arrange
      let place = "world"
      let expectation = "Hello, world!\n" // print always appends a newline
      let listener = STDOUTListener()
      listener.open()
        
      //-- Act
      print("Hello, \(place)!")

      // Wait until the stdout buffer has captured text
      await fulfillment(of: [listener.expectation], timeout: 0.01)
        
      //-- Assert
      XCTAssertEqual(listener.buffer, expectation)
      listener.close()
    }
}

Credit

Lifted from the blog post Intercepting stdout in Swift

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment