mirror of
https://github.com/mas-cli/mas
synced 2025-02-16 12:38:30 +00:00
💚 Fix stdout interception
This commit is contained in:
parent
66563b8083
commit
7636343540
2 changed files with 55 additions and 39 deletions
|
@ -60,11 +60,11 @@ class InfoCommandSpec: QuickSpec {
|
|||
let result = cmd.run(InfoCommand.Options(appId: result.trackId.description))
|
||||
|
||||
expect(result).to(beSuccess())
|
||||
waitUntil { done in
|
||||
print(output.contents)
|
||||
expect(output.contents) == expectedOutput
|
||||
done()
|
||||
}
|
||||
// output is async so need to wait for contents to be updated
|
||||
expect(output.contents).toNotEventually(beEmpty())
|
||||
expect(output.contents) == expectedOutput
|
||||
|
||||
output.closeConsolePipe()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -6,60 +6,76 @@
|
|||
// Copyright © 2019 mas-cli. All rights reserved.
|
||||
//
|
||||
|
||||
import XCTest
|
||||
import Foundation
|
||||
|
||||
/// Test helper for monitoring strings written to stdout. Modified from:
|
||||
/// https://medium.com/@thesaadismail/eavesdropping-on-swifts-print-statements-57f0215efb42
|
||||
class OutputListener {
|
||||
// open a new Pipe to consume the messages on STDOUT and STDERR
|
||||
/// consumes the messages on STDOUT
|
||||
let inputPipe = Pipe()
|
||||
|
||||
// open another Pipe to output messages back to STDOUT
|
||||
/// outputs messages back to STDOUT
|
||||
let outputPipe = Pipe()
|
||||
|
||||
/// Buffers strings written to stdout
|
||||
var contents = ""
|
||||
|
||||
/// 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() {
|
||||
let pipeReadHandle = inputPipe.fileHandleForReading
|
||||
// Set up a read handler which fires when data is written to our inputPipe
|
||||
inputPipe.fileHandleForReading.readabilityHandler = { [weak self] fileHandle in
|
||||
guard let strongSelf = self else { return }
|
||||
|
||||
//from documentation
|
||||
//dup2() makes newfd (new file descriptor) be the copy of oldfd (old file descriptor), closing newfd first if necessary.
|
||||
let data = fileHandle.availableData
|
||||
if let string = String(data: data, encoding: String.Encoding.utf8) {
|
||||
strongSelf.contents += string
|
||||
}
|
||||
|
||||
//here we are copying the STDOUT file descriptor into our output pipe's file descriptor
|
||||
//this is so we can write the strings back to STDOUT, so it can show up on the xcode console
|
||||
dup2(STDOUT_FILENO, outputPipe.fileHandleForWriting.fileDescriptor)
|
||||
// Write input back to stdout
|
||||
strongSelf.outputPipe.fileHandleForWriting.write(data)
|
||||
}
|
||||
|
||||
//In this case, the newFileDescriptor is the pipe's file descriptor and the old file descriptor is STDOUT_FILENO and STDERR_FILENO
|
||||
var dupStatus: Int32
|
||||
|
||||
dup2(inputPipe.fileHandleForWriting.fileDescriptor, STDOUT_FILENO)
|
||||
dup2(inputPipe.fileHandleForWriting.fileDescriptor, STDERR_FILENO)
|
||||
// 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)
|
||||
|
||||
//listen in to the readHandle notification
|
||||
NotificationCenter.default.addObserver(self, selector: #selector(self.handlePipeNotification), name: FileHandle.readCompletionNotification, object: pipeReadHandle)
|
||||
// Intercept STDOUT with inputPipe
|
||||
// newFileDescriptor is the pipe's file descriptor and the old file descriptor is STDOUT_FILENO and STDERR_FILENO
|
||||
dupStatus = dup2(inputPipe.fileHandleForWriting.fileDescriptor, stdoutFileDescriptor)
|
||||
// Status should equal newfd
|
||||
assert(dupStatus == stdoutFileDescriptor)
|
||||
|
||||
//state that you want to be notified of any data coming across the pipe
|
||||
pipeReadHandle.readInBackgroundAndNotify()
|
||||
// Don't have any tests on stderr yet
|
||||
// dup2(inputPipe.fileHandleForWriting.fileDescriptor, stderr)
|
||||
}
|
||||
|
||||
@objc func handlePipeNotification(notification: Notification) {
|
||||
//note you have to continuously call this when you get a message
|
||||
//see this from documentation:
|
||||
//Note that this method does not cause a continuous stream of notifications to be sent. If you wish to keep getting notified, you’ll also need to call readInBackgroundAndNotify() in your observer method.
|
||||
inputPipe.fileHandleForReading.readInBackgroundAndNotify()
|
||||
/// Tears down the "tee" of piped output.
|
||||
func closeConsolePipe() {
|
||||
// Restore stdout
|
||||
freopen("/dev/stdout", "a", stdout)
|
||||
|
||||
if let userInfo = notification.userInfo,
|
||||
let data = userInfo[NSFileHandleNotificationDataItem] as? Data,
|
||||
let str = String(data: data, encoding: String.Encoding.ascii) {
|
||||
|
||||
contents += str
|
||||
|
||||
//write the data back into the output pipe. the output pipe's write file descriptor points to STDOUT. this allows the logs to show up on the xcode console
|
||||
outputPipe.fileHandleForWriting.write(data)
|
||||
|
||||
// `str` here is the log/contents of the print statement
|
||||
//if you would like to route your print statements to the UI: make
|
||||
//sure to subscribe to this notification in your VC and update the UITextView.
|
||||
//Or if you wanted to send your print statements to the server, then
|
||||
//you could do this in your notification handler in the app delegate.
|
||||
[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
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue