Skip to content

Commit

Permalink
Added runner for testing external executables
Browse files Browse the repository at this point in the history
  • Loading branch information
samdeane committed Mar 23, 2020
1 parent fb1df61 commit 82713eb
Show file tree
Hide file tree
Showing 3 changed files with 93 additions and 6 deletions.
6 changes: 0 additions & 6 deletions Package.swift
Original file line number Diff line number Diff line change
@@ -1,23 +1,17 @@
// swift-tools-version:5.1
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription

let package = Package(
name: "XCTestExtensions",
products: [
// Products define the executables and libraries produced by a package, and make them visible to other packages.
.library(
name: "XCTestExtensions",
targets: ["XCTestExtensions"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
],
targets: [
// Targets are the basic building blocks of a package. A target can define a module or a test suite.
// Targets can depend on other targets in this package, and on products in packages which this package depends on.
.target(
name: "XCTestExtensions",
dependencies: []),
Expand Down
11 changes: 11 additions & 0 deletions Sources/XCTestExtensions/XCTestExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,17 @@ extension XCTestCase {
return Bundle.main.bundleURL
#endif
}

#if os(macOS) || os(Linux)
/// Run an external executable in the same location as the test bundle, and
/// return its output.
@available(macOS 10.13, *) public func run(_ command: String, arguments: [String] = []) -> XCTestRunner.Result {
let runner = XCTestRunner(for: command)
let result = runner.run(with: arguments)
XCTAssertNotNil(result)
return result!
}
#endif
}

/// Assert that a result is a success.
Expand Down
82 changes: 82 additions & 0 deletions Sources/XCTestExtensions/XCTestRunner.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
// Created by Sam Deane on 23/03/2020.
// All code (c) 2020 - present day, Elegant Chaos Limited.
// -=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-

import Foundation

#if os(macOS) || os(Linux)
@available(macOS 10.13, *) public class XCTestRunner {
var environment: [String:String]
let executable: URL
public var cwd: URL?

public struct Result {
public let status: Int32
public let stdout: String
public let stderr: String
}

/**
Initialise with an explicit URL to the executable.
*/

public init(for executable: URL, cwd: URL? = nil, environment: [String:String] = ProcessInfo.processInfo.environment) {
self.executable = executable
self.environment = environment
self.cwd = cwd
}

/**
Initialise to run a command in the same built products directory as this test bundle.
*/

public convenience init(for command: String, cwd: URL? = nil, environment: [String:String] = ProcessInfo.processInfo.environment) {
let url = Bundle(for: XCTestRunner.self).bundleURL.deletingLastPathComponent()
self.init(for: url.appendingPathComponent(command), cwd: cwd, environment: environment)
}


/**
Invoke a command and some optional arguments synchronously.
Waits for the process to exit and returns the captured output plus the exit status.
*/

public func run(with arguments: [String] = []) -> Result? {
let stdout = Pipe()
let stderr = Pipe()
let process = Process()
if let cwd = cwd {
process.currentDirectoryURL = cwd
}
process.executableURL = executable
process.arguments = arguments
process.standardOutput = stdout
process.standardError = stderr
process.environment = environment
process.launch()
process.waitUntilExit()

return Result(status: process.terminationStatus, stdout: stdout.asString, stderr: stderr.asString)
}

/// Extract text from an output pipe
/// - Parameter source: the pipe
func captureString(from pipe: Any?) -> String {
if let pipe = pipe as? Pipe {
let data = pipe.fileHandleForReading.readDataToEndOfFile()
if let string = String(data: data, encoding: .utf8) {
return string
}
}
return ""
}
}

public extension Pipe {
var asString: String {
let data = fileHandleForReading.readDataToEndOfFile()
return String(data: data, encoding: .utf8) ?? ""
}
}
#endif

0 comments on commit 82713eb

Please sign in to comment.