Skip to content

Commit

Permalink
Add stdout & stderr to PMKError.execution
Browse files Browse the repository at this point in the history
* Add check to fix compilation on Swift versions earlier than 4.1
* Update unit test for the changes recently made to Process+Promises.swift
* Second attempt at fixing unit tests for change to PMKError.execution
* Third attempt at fixing unit tests for change to PMKError.execution (This time for sure!)
  • Loading branch information
CharlesJS authored and mxcl committed Oct 10, 2018
1 parent c74a46d commit 9e1d760
Show file tree
Hide file tree
Showing 2 changed files with 51 additions and 9 deletions.
50 changes: 47 additions & 3 deletions Sources/Process+Promise.swift
Original file line number Diff line number Diff line change
Expand Up @@ -72,20 +72,64 @@ extension Process {
self.waitUntilExit()

guard self.terminationReason == .exit, self.terminationStatus == 0 else {
return seal.reject(PMKError.execution(self))
let stdoutData = try? self.readDataFromPipe(stdout)
let stderrData = try? self.readDataFromPipe(stderr)

let stdoutString = stdoutData.flatMap { (data: Data) -> String? in String(data: data, encoding: .utf8) }
let stderrString = stderrData.flatMap { (data: Data) -> String? in String(data: data, encoding: .utf8) }

return seal.reject(PMKError.execution(process: self, standardOutput: stdoutString, standardError: stderrString))
}
seal.fulfill((stdout, stderr))
}
}
}

private func readDataFromPipe(_ pipe: Pipe) throws -> Data {
let handle = pipe.fileHandleForReading
defer { handle.closeFile() }

// Someday, NSFileHandle will probably be updated with throwing equivalents to its read and write methods,
// as NSTask has, to avoid raising exceptions and crashing the app.
// Unfortunately that day has not yet come, so use the underlying BSD calls for now.

let fd = handle.fileDescriptor

let bufsize = 1024 * 8
let buf = UnsafeMutablePointer<UInt8>.allocate(capacity: bufsize)

#if swift(>=4.1)
defer { buf.deallocate() }
#else
defer { buf.deallocate(capacity: bufsize) }
#endif

var data = Data()

while true {
let bytesRead = read(fd, buf, bufsize)

if bytesRead == 0 {
break
}

if bytesRead < 0 {
throw POSIXError.Code(rawValue: errno).map { POSIXError($0) } ?? CocoaError(.fileReadUnknown)
}

data.append(buf, count: bytesRead)
}

return data
}

/**
The error generated by PromiseKit’s `Process` extension
*/
public enum PMKError {
/// NOT AVAILABLE ON 10.13 and above because Apple provide this error handling themselves
case notExecutable(String?)
case execution(Process)
case execution(process: Process, standardOutput: String?, standardError: String?)
}
}

Expand All @@ -97,7 +141,7 @@ extension Process.PMKError: LocalizedError {
return "File not executable: \(path)"
case .notExecutable(nil):
return "No launch path specified"
case .execution(let task):
case .execution(process: let task, standardOutput: _, standardError: _):
return "Failed executing: `\(task)` (\(task.terminationStatus))."
}
}
Expand Down
10 changes: 4 additions & 6 deletions Tests/TestNSTask.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,12 @@ class NSTaskTests: XCTestCase {
}.catch { err in
do {
throw err
} catch Process.PMKError.execution(let proc) {
let expectedStderrData = "ls: \(dir): No such file or directory\n".data(using: .utf8, allowLossyConversion: false)!
let stdout = (proc.standardOutput as! Pipe).fileHandleForReading.readDataToEndOfFile()
let stderr = (proc.standardError as! Pipe).fileHandleForReading.readDataToEndOfFile()
} catch Process.PMKError.execution(let proc, let stdout, let stderr) {
let expectedStderr = "ls: \(dir): No such file or directory\n"

XCTAssertEqual(stderr, expectedStderrData)
XCTAssertEqual(stderr, expectedStderr)
XCTAssertEqual(proc.terminationStatus, 1)
XCTAssertEqual(stdout.count, 0)
XCTAssertEqual(stdout?.count ?? 0, 0)
} catch {
XCTFail()
}
Expand Down

0 comments on commit 9e1d760

Please sign in to comment.