mas/MasKitTests/OutputListener.swift
2021-03-28 14:21:37 -07:00

85 lines
2.8 KiB
Swift

//
// OutputListener.swift
// MasKitTests
//
// Created by Ben Chatelain on 1/7/19.
// Copyright © 2019 mas-cli. All rights reserved.
//
import Foundation
/// Test helper for monitoring strings written to stdout. Modified from:
/// https://medium.com/@thesaadismail/eavesdropping-on-swifts-print-statements-57f0215efb42
class OutputListener {
/// consumes the messages on STDOUT
let inputPipe = Pipe()
/// outputs messages back to STDOUT
let outputPipe = Pipe()
/// Buffers strings written to stdout
var contents = ""
init() {
// Set up a read handler which fires when data is written to our inputPipe
inputPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
strongify(self) { context in
let data = fileHandle.availableData
if let string = String(data: data, encoding: String.Encoding.utf8) {
context.contents += string
}
// Write input back to stdout
context.outputPipe.fileHandleForWriting.write(data)
}
}
}
}
extension OutputListener {
/// Sets up the "tee" of piped output, intercepting stdout then passing it through.
///
/// ## [dup2 documentation](https://linux.die.net/man/2/dup2)
/// `int dup2(int oldfd, int newfd);`
/// `dup2()` makes `newfd` be the copy of `oldfd`, closing `newfd` first if necessary.
func openConsolePipe() {
var dupStatus: Int32
// Copy STDOUT file descriptor to outputPipe for writing strings back to STDOUT
dupStatus = dup2(stdoutFileDescriptor, outputPipe.fileHandleForWriting.fileDescriptor)
// Status should equal newfd
assert(dupStatus == outputPipe.fileHandleForWriting.fileDescriptor)
// Intercept STDOUT with inputPipe
// newFileDescriptor is the pipe's file descriptor and the old file descriptor is STDOUT_FILENO
dupStatus = dup2(inputPipe.fileHandleForWriting.fileDescriptor, stdoutFileDescriptor)
// Status should equal newfd
assert(dupStatus == stdoutFileDescriptor)
// Don't have any tests on stderr yet
// dup2(inputPipe.fileHandleForWriting.fileDescriptor, stderr)
}
/// Tears down the "tee" of piped output.
func closeConsolePipe() {
// Restore stdout
freopen("/dev/stdout", "a", stdout)
[inputPipe.fileHandleForReading, outputPipe.fileHandleForWriting]
.forEach { file in
file.closeFile()
}
}
}
extension OutputListener {
/// File descriptor for stdout (aka STDOUT_FILENO)
var stdoutFileDescriptor: Int32 {
FileHandle.standardOutput.fileDescriptor
}
/// File descriptor for stderr (aka STDERR_FILENO)
var stderrFileDescriptor: Int32 {
FileHandle.standardError.fileDescriptor
}
}