blob: 163dc31c722d6460c8abce5ece42266934c82002 [file] [log] [blame]
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2015 - 2016 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
import Foundation
import XCTest
#else
import SwiftFoundation
import SwiftXCTest
#endif
import CoreFoundation
class TestProcess : XCTestCase {
static var allTests: [(String, (TestProcess) -> () throws -> Void)] {
return [
("test_exit0" , test_exit0),
("test_exit1" , test_exit1),
("test_exit100" , test_exit100),
("test_sleep2", test_sleep2),
("test_sleep2_exit1", test_sleep2_exit1),
("test_terminationReason_uncaughtSignal", test_terminationReason_uncaughtSignal),
("test_pipe_stdin", test_pipe_stdin),
("test_pipe_stdout", test_pipe_stdout),
("test_pipe_stderr", test_pipe_stderr),
("test_current_working_directory", test_current_working_directory),
// disabled for now
// ("test_pipe_stdout_and_stderr_same_pipe", test_pipe_stdout_and_stderr_same_pipe),
("test_file_stdout", test_file_stdout),
// disabled for now
// ("test_passthrough_environment", test_passthrough_environment),
// ("test_no_environment", test_no_environment),
// ("test_custom_environment", test_custom_environment),
]
}
func test_exit0() {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", "exit 0"]
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
XCTAssertEqual(process.terminationReason, .exit)
}
func test_exit1() {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", "exit 1"]
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 1)
XCTAssertEqual(process.terminationReason, .exit)
}
func test_exit100() {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", "exit 100"]
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 100)
XCTAssertEqual(process.terminationReason, .exit)
}
func test_sleep2() {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", "sleep 2"]
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
XCTAssertEqual(process.terminationReason, .exit)
}
func test_sleep2_exit1() {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", "sleep 2; exit 1"]
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 1)
XCTAssertEqual(process.terminationReason, .exit)
}
func test_terminationReason_uncaughtSignal() {
let process = Process()
process.launchPath = "/bin/bash"
process.arguments = ["-c", "kill -TERM $$"]
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 15)
XCTAssertEqual(process.terminationReason, .uncaughtSignal)
}
func test_pipe_stdin() {
let process = Process()
process.launchPath = "/bin/cat"
let outputPipe = Pipe()
process.standardOutput = outputPipe
let inputPipe = Pipe()
process.standardInput = inputPipe
process.launch()
inputPipe.fileHandleForWriting.write("Hello, 🐶.\n".data(using: .utf8)!)
// Close the input pipe to send EOF to cat.
inputPipe.fileHandleForWriting.closeFile()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
let data = outputPipe.fileHandleForReading.availableData
guard let string = String(data: data, encoding: .utf8) else {
XCTFail("Could not read stdout")
return
}
XCTAssertEqual(string, "Hello, 🐶.\n")
}
func test_pipe_stdout() {
let process = Process()
process.launchPath = "/usr/bin/which"
process.arguments = ["which"]
let pipe = Pipe()
process.standardOutput = pipe
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
let data = pipe.fileHandleForReading.availableData
guard let string = String(data: data, encoding: .ascii) else {
XCTFail("Could not read stdout")
return
}
XCTAssertTrue(string.hasSuffix("/which\n"))
}
func test_pipe_stderr() {
let process = Process()
process.launchPath = "/bin/cat"
process.arguments = ["invalid_file_name"]
let errorPipe = Pipe()
process.standardError = errorPipe
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 1)
let data = errorPipe.fileHandleForReading.availableData
guard let _ = String(data: data, encoding: .ascii) else {
XCTFail("Could not read stdout")
return
}
// testing the return value of an external process is does not port well, and may change.
// XCTAssertEqual(string, "/bin/cat: invalid_file_name: No such file or directory\n")
}
func test_pipe_stdout_and_stderr_same_pipe() {
let process = Process()
process.launchPath = "/bin/cat"
process.arguments = ["invalid_file_name"]
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 1)
let data = pipe.fileHandleForReading.availableData
guard let string = String(data: data, encoding: .ascii) else {
XCTFail("Could not read stdout")
return
}
XCTAssertEqual(string, "/bin/cat: invalid_file_name: No such file or directory\n")
}
func test_file_stdout() {
let process = Process()
process.launchPath = "/usr/bin/which"
process.arguments = ["which"]
mkstemp(template: "TestProcess.XXXXXX") { handle in
process.standardOutput = handle
process.launch()
process.waitUntilExit()
XCTAssertEqual(process.terminationStatus, 0)
handle.seek(toFileOffset: 0)
let data = handle.readDataToEndOfFile()
guard let string = String(data: data, encoding: .ascii) else {
XCTFail("Could not read stdout")
return
}
XCTAssertTrue(string.hasSuffix("/which\n"))
}
}
func test_passthrough_environment() {
do {
let output = try runTask(["/usr/bin/env"], environment: nil)
let env = try parseEnv(output)
XCTAssertGreaterThan(env.count, 0)
} catch let error {
XCTFail("Test failed: \(error)")
}
}
func test_no_environment() {
do {
let output = try runTask(["/usr/bin/env"], environment: [:])
let env = try parseEnv(output)
XCTAssertEqual(env.count, 0)
} catch let error {
XCTFail("Test failed: \(error)")
}
}
func test_custom_environment() {
do {
let input = ["HELLO": "WORLD", "HOME": "CUPERTINO"]
let output = try runTask(["/usr/bin/env"], environment: input)
let env = try parseEnv(output)
XCTAssertEqual(env, input)
} catch let error {
XCTFail("Test failed: \(error)")
}
}
func test_current_working_directory() {
do {
let previousWorkingDirectory = FileManager.default.currentDirectoryPath
// `bash` will not be found if the current working directory is not set correctly.
let _ = try runTask(["bash", "-c", "exit 0"], currentDirectoryPath: "/bin")
XCTAssertEqual(previousWorkingDirectory, FileManager.default.currentDirectoryPath)
} catch let error {
XCTFail("Test failed: \(error)")
}
}
}
private func mkstemp(template: String, body: (FileHandle) throws -> Void) rethrows {
let url = URL(fileURLWithPath: NSTemporaryDirectory()).appendingPathComponent("TestProcess.XXXXXX")
try url.withUnsafeFileSystemRepresentation {
switch mkstemp(UnsafeMutablePointer(mutating: $0!)) {
case -1: XCTFail("Could not create temporary file")
case let fd:
defer { url.withUnsafeFileSystemRepresentation { _ = unlink($0!) } }
try body(FileHandle(fileDescriptor: fd, closeOnDealloc: true))
}
}
}
private enum Error: Swift.Error {
case TerminationStatus(Int32)
case UnicodeDecodingError(Data)
case InvalidEnvironmentVariable(String)
}
private func runTask(_ arguments: [String], environment: [String: String]? = nil, currentDirectoryPath: String? = nil) throws -> String {
let process = Process()
var arguments = arguments
process.launchPath = arguments.removeFirst()
process.arguments = arguments
process.environment = environment
if let directoryPath = currentDirectoryPath {
process.currentDirectoryPath = directoryPath
}
let pipe = Pipe()
process.standardOutput = pipe
process.standardError = pipe
process.launch()
process.waitUntilExit()
guard process.terminationStatus == 0 else {
throw Error.TerminationStatus(process.terminationStatus)
}
let data = pipe.fileHandleForReading.availableData
guard let output = String(data: data, encoding: .utf8) else {
throw Error.UnicodeDecodingError(data)
}
return output
}
private func parseEnv(_ env: String) throws -> [String: String] {
var result = [String: String]()
for line in env.components(separatedBy: "\n") where line != "" {
guard let range = line.range(of: "=") else {
throw Error.InvalidEnvironmentVariable(line)
}
result[line.substring(to: range.lowerBound)] = line.substring(from: range.upperBound)
}
return result
}