TRAVIS_OS_NAME=osx <<<<<< ENV ./codecov.yml .github/CODEOWNERS Dockerfile LICENSE Makefile Package.swift Sources/System/ProcessResult.swift Sources/System/ProcessRunner.swift Sources/System/System.swift Tests/LinuxMain.swift Tests/SystemTests/CurrentDirectoryTests.swift Tests/SystemTests/ProcessRunnerTests.swift Tests/SystemTests/StandardErrorTests.swift Tests/SystemTests/StandardOutputTests.swift Tests/SystemTests/SuccessFailureTests.swift Tests/SystemTests/XCTestManifests.swift docs/PackageModules.dot <<<<<< network # path=System.framework.coverage.txt /Users/travis/build/eneko/System/Sources/System/ProcessResult.swift: 1| |import Foundation 2| | 3| |/// Process result, including captured output if required 4| |public struct ProcessResult { 5| | /// Process exit status code 6| | public let exitStatus: Int 7| | 8| | /// Standard output, if captured 9| | public let standardOutput: String 10| | 11| | /// Standard error output, if captured 12| | public let standardError: String 13| | 14| | /// Returns `true` if the process finalized with exit code 0. 15| 2| public var success: Bool { 16| 2| return exitStatus == 0 17| 2| } 18| |} /Users/travis/build/eneko/System/Sources/System/ProcessRunner.swift: 1| |import Foundation 2| | 3| |/// Process runner to execute system commands 4| |public class ProcessRunner { 5| | public let command: String 6| | public let arguments: [String] 7| | public let captureOutput: Bool 8| | public let currentDirectoryPath: String? 9| | 10| | /// Initialize process runner 11| | /// - Parameters: 12| | /// - command: Absolute path to the binary executable 13| | /// - arguments: List of arguments to pass to the executable 14| | /// - captureOutput: Capture stdout and stderr 15| | /// - currentDirectoryPath: Specify current directory for child process (optional) 16| 44| public init(command: String, arguments: [String], captureOutput: Bool, currentDirectoryPath: String? = nil) { 17| 44| self.command = command 18| 44| self.arguments = arguments 19| 44| self.captureOutput = captureOutput 20| 44| self.currentDirectoryPath = currentDirectoryPath 21| 44| } 22| | 23| | /// Execute the process 24| | /// - Throws: Throws error if process fails to run 25| | /// - Returns: Process execution result, including status code and captured output 26| 44| public func run() throws -> ProcessResult { 27| 44| let process = Process() 28| 44| process.executableURL = URL(fileURLWithPath: command) 29| 44| process.arguments = arguments 30| 44| 31| 44| if let path = currentDirectoryPath { 32| 3| process.currentDirectoryURL = URL(fileURLWithPath: path) 33| 44| } 34| 44| 35| 44| let outputPipe = Pipe() 36| 44| let errorPipe = Pipe() 37| 44| if captureOutput { 38| 41| process.standardOutput = outputPipe 39| 41| process.standardError = errorPipe 40| 44| } 41| 44| 42| 44| try process.run() 43| 44| process.waitUntilExit() 44| 44| 45| 44| let standardOutput = captureOutput ? outputPipe.string ?? "" : "" 46| 44| let standardError = captureOutput ? errorPipe.string ?? "" : "" 47| 44| return ProcessResult(exitStatus: Int(process.terminationStatus), 48| 44| standardOutput: standardOutput, standardError: standardError) 49| 44| } 50| |} 51| | 52| |extension Pipe { 53| 82| var string: String? { 54| 82| let data = fileHandleForReading.readDataToEndOfFile() 55| 82| return String(data: data, encoding: .utf8)? 56| 82| .trimmingCharacters(in: .whitespacesAndNewlines) 57| 82| } 58| |} /Users/travis/build/eneko/System/Sources/System/System.swift: 1| |import Foundation 2| | 3| |/// Executes command with parameters as a child process 4| |/// 5| |/// `system` will wait for the process to finish, blocking the current thread. 6| |/// 7| |/// - Parameters: 8| |/// - command: Command to execute (full binary path or name of executable in $PATH). 9| |/// - parameters: List of parameters to pass to the command. 10| |/// - captureOutput: If output is captured, both stdout and strerr will be available in 11| |/// the return object. Otherwise, process output will be forwarded to stdout and stderr. 12| |/// Defaults to `false`. 13| |/// - currentDirectoryPath: Specify current directory for child process (optional) 14| |/// - Returns: Process result data which is available after process termination. 15| |/// The `ProcessResult` object includes exit code or termination signal and any captured output. 16| |/// - Throws: `SystemError.waitpid` if process execution failed. 17| |@discardableResult 18| |public func system(command: String, parameters: [String], captureOutput: Bool = false, 19| 21| currentDirectoryPath: String? = nil) throws -> ProcessResult { 20| 21| let executablePath = /*command.hasPrefix("/") ? command :*/ try which(program: command) 21| 21| return try ProcessRunner(command: executablePath, arguments: parameters, 22| 21| captureOutput: captureOutput, 23| 21| currentDirectoryPath: currentDirectoryPath).run() 24| 21|} 25| | 26| |// MARK: Process helpers 27| | 28| |/// Executes command with parameters as a child process 29| |/// 30| |/// `system` will wait for the process to finish, blocking the current thread. 31| |/// 32| |/// - Parameters: 33| |/// - command: Command to execute (full binary path or name of executable in $PATH) and list of parameters, if any. 34| |/// - captureOutput: If output is captured, both stdout and strerr will be available in 35| |/// the return object. Otherwise, process output will be forwarded to stdout and stderr. 36| |/// Defaults to `false`. 37| |/// - currentDirectoryPath: Specify current directory for child process (optional) 38| |/// - Returns: Process result data which is available after process termination. 39| |/// The `ProcessResult` object includes exit code or termination signal and any captured output. 40| |/// - Throws: `SystemError.waitpid` if process execution failed. 41| |@discardableResult 42| |public func system(command: [String], captureOutput: Bool = false, 43| 11| currentDirectoryPath: String? = nil) throws -> ProcessResult { 44| 11| guard let executable = command.first else { 45| 0| throw SystemError.missingCommand 46| 11| } 47| 11| return try system(command: executable, parameters: Array(command[1...]), 48| 11| captureOutput: captureOutput, currentDirectoryPath: currentDirectoryPath) 49| 11|} 50| | 51| |/// Executes command with parameters as a child process 52| |/// 53| |/// `system` will wait for the process to finish, blocking the current thread. 54| |/// 55| |/// - Parameters: 56| |/// - command: Command to execute (full binary path or name of executable in $PATH) and list of parameters. 57| |/// Parameters will be split by spaces. 58| |/// - captureOutput: If output is captured, both stdout and strerr will be available in 59| |/// the return object. Otherwise, process output will be forwarded to stdout and stderr. 60| |/// Defaults to `false`. 61| |/// - currentDirectoryPath: Specify current directory for child process (optional) 62| |/// - Returns: Process result data which is available after process termination. 63| |/// The `ProcessResult` object includes exit code or termination signal and any captured output. 64| |/// - Throws: `SystemError.waitpid` if process execution failed. 65| |@discardableResult 66| |public func system(command: String, captureOutput: Bool = false, 67| 10| currentDirectoryPath: String? = nil) throws -> ProcessResult { 68| 10| return try system(command: command.split(separator: " ").map(String.init), 69| 10| captureOutput: captureOutput, currentDirectoryPath: currentDirectoryPath) 70| 10|} 71| | 72| |/// Executes command with parameters as a child process 73| |/// 74| |/// `system` will wait for the process to finish, blocking the current thread. 75| |/// 76| |/// - Parameters: 77| |/// - command: Command to execute (full binary path or name of executable in $PATH) and list of parameters, if any. 78| |/// - captureOutput: If output is captured, both stdout and strerr will be available in 79| |/// the return object. Otherwise, process output will be forwarded to stdout and stderr. 80| |/// Defaults to `false`. 81| |/// - currentDirectoryPath: Specify current directory for child process (optional) 82| |/// - Returns: Process result data which is available after process termination. 83| |/// The `ProcessResult` object includes exit code or termination signal and any captured output. 84| |/// - Throws: `SystemError.waitpid` if process execution failed. 85| |@discardableResult 86| |public func system(command: String..., captureOutput: Bool = false, 87| 1| currentDirectoryPath: String? = nil) throws -> ProcessResult { 88| 1| return try system(command: command, captureOutput: captureOutput, currentDirectoryPath: currentDirectoryPath) 89| 1|} 90| | 91| |// MARK: - Shell helpers 92| | 93| |/// Executes command with parameters in a subshell 94| |/// 95| |/// `system` will wait for the shell process to finish, blocking the current thread. 96| |/// 97| |/// - Parameters: 98| |/// - shell: Shell command with parameters to execute in a subshell with `sh -c`. 99| |/// - captureOutput: If output is captured, both stdout and strerr will be available in 100| |/// the return object. Otherwise, process output will be forwarded to stdout and stderr. 101| |/// Defaults to `false`. 102| |/// - currentDirectoryPath: Specify current directory for child process (optional) 103| |/// - Returns: Process result data which is available after process termination. 104| |/// The `ProcessResult` object includes exit code or termination signal and any captured output. 105| |/// - Throws: `SystemError.waitpid` if process execution failed. 106| |@discardableResult 107| |public func system(shell: String, captureOutput: Bool = false, 108| 7| currentDirectoryPath: String? = nil) throws -> ProcessResult { 109| 7| return try system(command: "/bin/sh", parameters: ["-c", shell], 110| 7| captureOutput: captureOutput, currentDirectoryPath: currentDirectoryPath) 111| 7|} 112| | 113| |// MARK: - Other helpers 114| | 115| |enum SystemError: Error { 116| | case missingCommand 117| |} 118| | 119| 21|func which(program: String) throws -> String { 120| 21| let result = try ProcessRunner(command: "/usr/bin/env", arguments: ["which", program], captureOutput: true).run() 121| 21| return result.standardOutput 122| 21|} <<<<<< EOF # path=SystemTests.xctest.coverage.txt /Users/travis/build/eneko/System/Tests/SystemTests/CurrentDirectoryTests.swift: 1| |import XCTest 2| |import System 3| | 4| |final class CurrentDirectoryTests: XCTestCase { 5| | 6| 1| func testCurrentDirectory() throws { 7| 1| XCTAssertEqual(try system(command: "pwd", captureOutput: true, currentDirectoryPath: "/").standardOutput, "/") 8| 1| } 9| | 10| 1| func testCurrentDirectoryCommand() throws { 11| 1| let content = UUID().uuidString 12| 1| try content.write(toFile: "/tmp/foo-command", atomically: true, encoding: .utf8) 13| 1| 14| 1| XCTAssertEqual(try system(command: "cat /tmp/foo-command", captureOutput: true).standardOutput, content) 15| 1| XCTAssertEqual(try system(command: "cat foo-command", captureOutput: true, 16| 1| currentDirectoryPath: "/tmp").standardOutput, content) 17| 1| } 18| | 19| 1| func testCurrentDirectoryShell() throws { 20| 1| let content = UUID().uuidString 21| 1| try content.write(toFile: "/tmp/foo-shell", atomically: true, encoding: .utf8) 22| 1| 23| 1| XCTAssertEqual(try system(shell: "cat /tmp/foo-shell", captureOutput: true).standardOutput, content) 24| 1| XCTAssertEqual(try system(shell: "cat foo-shell", captureOutput: true, 25| 1| currentDirectoryPath: "/tmp").standardOutput, content) 26| 1| } 27| | 28| |} /Users/travis/build/eneko/System/Tests/SystemTests/ProcessRunnerTests.swift: 1| |import XCTest 2| |import System 3| | 4| |final class ProcessRunnerTests: XCTestCase { 5| | 6| 1| func testWhich() throws { 7| 1| let result = try ProcessRunner(command: "/usr/bin/env", arguments: ["which", "which"], 8| 1| captureOutput: true).run() 9| 1| XCTAssertEqual(result.standardOutput, "/usr/bin/which") 10| 1| } 11| | 12| 1| func testShell() throws { 13| 1| let result = try ProcessRunner(command: "/usr/bin/env", arguments: ["which", "sh"], 14| 1| captureOutput: true).run() 15| 1| XCTAssertEqual(result.standardOutput, "/bin/sh") 16| 1| } 17| |} /Users/travis/build/eneko/System/Tests/SystemTests/StandardErrorTests.swift: 1| |import XCTest 2| |import System 3| | 4| |final class StandardErrorTests: XCTestCase { 5| | 6| 2| var expectedError: String { 7| 2| #if os(Linux) 8| 2| return "cat: unrecognized option" 9| 2| #else 10| 2| return "cat: illegal option" 11| 2| #endif 12| 2| } 13| | 14| 1| func testStandardError() throws { 15| 1| XCTAssertTrue(try system(command: "cat --foobar", captureOutput: true).standardError.contains(expectedError)) 16| 1| } 17| | 18| 1| func testStandardErrorShell() throws { 19| 1| XCTAssertTrue(try system(shell: "cat --foobar", captureOutput: true).standardError.contains(expectedError)) 20| 1| } 21| |} /Users/travis/build/eneko/System/Tests/SystemTests/StandardOutputTests.swift: 1| |import XCTest 2| |import System 3| | 4| |final class StandardOutputTests: XCTestCase { 5| | 6| 1| func testEchoCombined() throws { 7| 1| XCTAssertEqual(try system(command: "echo hello world", captureOutput: true).standardOutput, "hello world") 8| 1| } 9| | 10| 1| func testEchoSplit() throws { 11| 1| let output = try system(command: "echo", parameters: ["hello", "world"], captureOutput: true).standardOutput 12| 1| XCTAssertEqual(output, "hello world") 13| 1| } 14| | 15| 1| func testEchoEscaping() throws { 16| 1| let output = try system(command: "echo", parameters: ["hello world"], captureOutput: true).standardOutput 17| 1| XCTAssertEqual(output, "hello world") 18| 1| } 19| | 20| 1| func testPipe() throws { 21| 1| let output = try system(command: "echo hello cat | cat", captureOutput: true).standardOutput 22| 1| XCTAssertEqual(output, "hello cat | cat") 23| 1| } 24| | 25| 1| func testRedirect() throws { 26| 1| let output = try system(command: "echo hello cat > cat && cat cat", captureOutput: true).standardOutput 27| 1| XCTAssertEqual(output, "hello cat > cat && cat cat") 28| 1| } 29| | 30| 1| func testShell() throws { 31| 1| let path = try system(command: "sh -c pwd", captureOutput: true).standardOutput 32| 1| let parent = try system(command: "sh", parameters: ["-c", "cd .. && pwd"], captureOutput: true).standardOutput 33| 1| XCTAssertNotEqual(path, parent) 34| 1| XCTAssertTrue(path.hasPrefix(parent)) 35| 1| } 36| | 37| 1| func testShellPipe() throws { 38| 1| let output = try system(command: "sh", "-c", "echo foo bar | awk '{print $2}'", 39| 1| captureOutput: true).standardOutput 40| 1| XCTAssertEqual(output, "bar") 41| 1| } 42| | 43| 1| func testShellRedirect() throws { 44| 1| let result = try system(shell: "echo hello cat > cat && cat cat && rm cat", captureOutput: true) 45| 1| XCTAssertEqual(result.standardOutput, "hello cat") 46| 1| } 47| | 48| 1| func testShellRedirectPipe() throws { 49| 1| let output = try system(shell: "echo hello cat > cat && cat cat | awk '{print $2}' && rm cat", 50| 1| captureOutput: true).standardOutput 51| 1| XCTAssertEqual(output, "cat") 52| 1| } 53| | 54| 1| func testNoStandardOutput() throws { 55| 1| XCTAssertEqual(try system(command: "cat --foobar", captureOutput: true).standardOutput, "") 56| 1| } 57| |} /Users/travis/build/eneko/System/Tests/SystemTests/SuccessFailureTests.swift: 1| |import XCTest 2| |import System 3| | 4| |final class SuccessFailureTests: XCTestCase { 5| | 6| 1| func testSuccess() throws { 7| 1| XCTAssertTrue(try system(shell: "echo foo bar").success) 8| 1| } 9| | 10| 1| func testFailure() throws { 11| 1| XCTAssertFalse(try system(shell: "echoooooo foo bar").success) 12| 1| } 13| | 14| 1| func testProcessNotFound() { 15| 1| XCTAssertThrowsError(try system(command: "echoooooo foo bar")) 16| 1| } 17| |} <<<<<< EOF # path=fixes ./Tests/LinuxMain.swift:2,4,7 ./Tests/SystemTests/SuccessFailureTests.swift:3,5,8,9,12,13,16,17 ./Tests/SystemTests/StandardErrorTests.swift:3,5,12,13,16,17,20,21 ./Tests/SystemTests/XCTestManifests.swift:3,12,13,22,23,40,41,51,52,60 ./Tests/SystemTests/ProcessRunnerTests.swift:3,5,10,11,16,17 ./Tests/SystemTests/CurrentDirectoryTests.swift:3,5,8,9,13,17,18,22,26,27,28 ./Tests/SystemTests/StandardOutputTests.swift:3,5,8,9,13,14,18,19,23,24,28,29,35,36,41,42,46,47,52,53,56,57 ./Package.swift:3 ./Sources/System/ProcessRunner.swift:2,9,21,22,30,33,34,40,41,44,49,50,51,57,58 ./Sources/System/ProcessResult.swift:2,7,10,13,17,18 ./Sources/System/System.swift:2,24,25,27,46,49,50,70,71,89,90,92,111,112,114,117,118,122 <<<<<< EOF