// // 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 { return FileHandle.standardOutput.fileDescriptor } /// File descriptor for stderr (aka STDERR_FILENO) var stderrFileDescriptor: Int32 { return FileHandle.standardError.fileDescriptor } }