From fcc65db151751e9405f233da62616274d7d30502 Mon Sep 17 00:00:00 2001 From: David Hart Date: Sun, 6 Jan 2019 16:57:10 +0100 Subject: [PATCH] Update the build animation for each Swift compiler task --- Sources/Build/BuildDelegate.swift | 255 +++++++++++++++--- Sources/Build/BuildPlan.swift | 1 + Sources/Build/SwiftCompilerOutputParser.swift | 33 ++- Sources/Commands/SwiftTool.swift | 47 ++-- Sources/SPMUtility/ProgressAnimation.swift | 19 +- .../SwiftCompilerOutputParserTests.swift | 32 ++- Tests/CommandsTests/RunToolTests.swift | 2 +- .../FunctionalTests/MiscellaneousTests.swift | 23 +- 8 files changed, 311 insertions(+), 101 deletions(-) diff --git a/Sources/Build/BuildDelegate.swift b/Sources/Build/BuildDelegate.swift index 0ab5bbdb574..91ba6c236be 100644 --- a/Sources/Build/BuildDelegate.swift +++ b/Sources/Build/BuildDelegate.swift @@ -12,6 +12,8 @@ import Basic import SPMUtility import SPMLLBuild import Dispatch +import Foundation +import POSIX /// Diagnostic error when a llbuild command encounters an error. struct LLBuildCommandErrorDiagnostic: DiagnosticData { @@ -160,6 +162,21 @@ struct LLBuildCommandError: DiagnosticData { let message: String } +/// Swift Compiler output parsing error +struct SwiftCompilerOutputParsingError: DiagnosticData { + static let id = DiagnosticID( + type: SwiftCompilerOutputParsingError.self, + name: "org.swift.diags.swift-compiler-output-parsing-error", + defaultBehavior: .error, + description: { + $0 <<< "failed parsing the Swift compiler output: " + $0 <<< { $0.message } + } + ) + + let message: String +} + extension SPMLLBuild.Diagnostic: DiagnosticDataConvertible { public var diagnosticData: DiagnosticData { switch kind { @@ -171,44 +188,20 @@ extension SPMLLBuild.Diagnostic: DiagnosticDataConvertible { } private let newLineByte: UInt8 = 10 -public final class BuildDelegate: BuildSystemDelegate { - // Track counts of commands based on their CommandStatusKind - private struct CommandCounter { - var scanningCount = 0 - var upToDateCount = 0 - var completedCount = 0 - var startedCount = 0 - - var estimatedMaximum: Int { - return completedCount + scanningCount - upToDateCount - } - - mutating func update(command: SPMLLBuild.Command, kind: CommandStatusKind) { - guard command.shouldShowStatus else { return } - - switch kind { - case .isScanning: - scanningCount += 1 - case .isUpToDate: - scanningCount -= 1 - upToDateCount += 1 - completedCount += 1 - case .isComplete: - scanningCount -= 1 - completedCount += 1 - } - } - } - +public final class BuildDelegate: BuildSystemDelegate, SwiftCompilerOutputParserDelegate { private let diagnostics: DiagnosticsEngine public var outputStream: ThreadSafeOutputByteStream public var progressAnimation: ProgressAnimationProtocol - public var isVerbose: Bool = false public var onCommmandFailure: (() -> Void)? - private var commandCounter = CommandCounter() + public var isVerbose: Bool = false private let queue = DispatchQueue(label: "org.swift.swiftpm.build-delegate") + private var taskTracker = CommandTaskTracker() + + /// Swift parsers keyed by llbuild command name. + private var swiftParsers: [String: SwiftCompilerOutputParser] = [:] public init( + plan: BuildPlan, diagnostics: DiagnosticsEngine, outputStream: OutputByteStream, progressAnimation: ProgressAnimationProtocol @@ -218,6 +211,12 @@ public final class BuildDelegate: BuildSystemDelegate { // https://forums.swift.org/t/allow-self-x-in-class-convenience-initializers/15924 self.outputStream = outputStream as? ThreadSafeOutputByteStream ?? ThreadSafeOutputByteStream(outputStream) self.progressAnimation = progressAnimation + + let buildConfig = plan.buildParameters.configuration.dirname + swiftParsers = Dictionary(uniqueKeysWithValues: plan.targetMap.compactMap({ (target, description) in + guard case .swift = description else { return nil } + return (target.getCommandName(config: buildConfig), SwiftCompilerOutputParser(delegate: self)) + })) } public var fs: SPMLLBuild.FileSystem? { @@ -237,9 +236,6 @@ public final class BuildDelegate: BuildSystemDelegate { } public func commandStatusChanged(_ command: SPMLLBuild.Command, kind: CommandStatusKind) { - queue.sync { - commandCounter.update(command: command, kind: kind) - } } public func commandPreparing(_ command: SPMLLBuild.Command) { @@ -249,16 +245,12 @@ public final class BuildDelegate: BuildSystemDelegate { guard command.shouldShowStatus else { return } queue.sync { - commandCounter.startedCount += 1 - if isVerbose { outputStream <<< command.verboseDescription <<< "\n" outputStream.flush() - } else { - progressAnimation.update( - step: commandCounter.startedCount, - total: commandCounter.estimatedMaximum, - text: command.description) + } else if !swiftParsers.keys.contains(command.name) { + taskTracker.commandStarted(command) + updateProgress() } } } @@ -268,6 +260,14 @@ public final class BuildDelegate: BuildSystemDelegate { } public func commandFinished(_ command: SPMLLBuild.Command, result: CommandResult) { + guard command.shouldShowStatus else { return } + guard !swiftParsers.keys.contains(command.name) else { return } + guard !isVerbose else { return } + + queue.sync { + taskTracker.commandFinished(command, result: result) + updateProgress() + } } public func commandHadError(_ command: SPMLLBuild.Command, message: String) { @@ -302,9 +302,15 @@ public final class BuildDelegate: BuildSystemDelegate { } public func commandProcessHadOutput(_ command: SPMLLBuild.Command, process: ProcessHandle, data: [UInt8]) { - progressAnimation.clear() - outputStream <<< data - outputStream.flush() + guard command.shouldShowStatus else { return } + + if let swiftParser = swiftParsers[command.name] { + swiftParser.parse(bytes: data) + } else { + progressAnimation.clear() + outputStream <<< data + outputStream.flush() + } } public func commandProcessFinished( @@ -321,4 +327,165 @@ public final class BuildDelegate: BuildSystemDelegate { public func shouldResolveCycle(rules: [BuildKey], candidate: BuildKey, action: CycleAction) -> Bool { return false } + + func swiftCompilerDidOutputMessage(_ message: SwiftCompilerMessage) { + queue.sync { + if isVerbose { + if let text = message.verboseProgressText { + outputStream <<< text <<< "\n" + outputStream.flush() + } + } else { + taskTracker.swiftCompilerDidOuputMessage(message) + updateProgress() + } + + if let output = message.standardOutput { + if !isVerbose { + progressAnimation.clear() + } + + outputStream <<< output + outputStream.flush() + } + } + } + + func swiftCompilerOutputParserDidFail(withError error: Error) { + let message = (error as? LocalizedError)?.errorDescription ?? error.localizedDescription + diagnostics.emit(data: SwiftCompilerOutputParsingError(message: message)) + onCommmandFailure?() + } + + private func updateProgress() { + if let progressText = taskTracker.latestRunningText { + progressAnimation.update( + step: taskTracker.finishedCount, + total: taskTracker.totalCount, + text: progressText) + } + } +} + +/// Tracks tasks based on command status and swift compiler output. +fileprivate struct CommandTaskTracker { + private struct Task { + let identifier: String + let text: String + } + + private var tasks: [Task] = [] + private(set) var finishedCount = 0 + private(set) var totalCount = 0 + + /// The last task text before the task list was emptied. + private var lastText: String? + + var latestRunningText: String? { + return tasks.last?.text ?? lastText + } + + mutating func commandStarted(_ command: SPMLLBuild.Command) { + addTask(identifier: command.name, text: command.description) + totalCount += 1 + } + + mutating func commandFinished(_ command: SPMLLBuild.Command, result: CommandResult) { + removeTask(identifier: command.name) + + switch result { + case .succeeded: + finishedCount += 1 + case .cancelled, .failed, .skipped: + break + } + } + + mutating func swiftCompilerDidOuputMessage(_ message: SwiftCompilerMessage) { + switch message.kind { + case .began(let info): + if let text = message.progressText { + addTask(identifier: info.pid.description, text: text) + } + + totalCount += 1 + case .finished(let info): + removeTask(identifier: info.pid.description) + finishedCount += 1 + case .signalled(let info): + removeTask(identifier: info.pid.description) + case .skipped: + break + } + } + + private mutating func addTask(identifier: String, text: String) { + tasks.append(Task(identifier: identifier, text: text)) + } + + private mutating func removeTask(identifier: String) { + if let index = tasks.index(where: { $0.identifier == identifier }) { + if tasks.count == 1 { + lastText = tasks[0].text + } + + tasks.remove(at: index) + } + } +} + +extension SwiftCompilerMessage { + fileprivate var progressText: String? { + if case .began(let info) = kind { + switch name { + case "compile": + if let sourceFile = info.inputs.first { + return generateProgressText(prefix: "Compiling", file: sourceFile) + } + case "link": + if let imageFile = info.outputs.first(where: { $0.type == "image" })?.path { + return generateProgressText(prefix: "Linking", file: imageFile) + } + case "merge-module": + if let moduleFile = info.outputs.first(where: { $0.type == "swiftmodule" })?.path { + return generateProgressText(prefix: "Merging module", file: moduleFile) + } + case "generate-dsym": + if let dSYMFile = info.outputs.first(where: { $0.type == "dSYM" })?.path { + return generateProgressText(prefix: "Generating dSYM", file: dSYMFile) + } + case "generate-pch": + if let pchFile = info.outputs.first(where: { $0.type == "pch" })?.path { + return generateProgressText(prefix: "Generating PCH", file: pchFile) + } + default: + break + } + } + + return nil + } + + fileprivate var verboseProgressText: String? { + if case .began(let info) = kind { + return ([info.commandExecutable] + info.commandArguments).joined(separator: " ") + } else { + return nil + } + } + + fileprivate var standardOutput: String? { + switch kind { + case .finished(let info), + .signalled(let info): + return info.output + default: + return nil + } + } + + private func generateProgressText(prefix: String, file: String) -> String { + let relativePath = AbsolutePath(file).relative(to: AbsolutePath(getcwd())) + return "\(prefix) \(relativePath)" + } } diff --git a/Sources/Build/BuildPlan.swift b/Sources/Build/BuildPlan.swift index 0622653fa27..739f730dc91 100644 --- a/Sources/Build/BuildPlan.swift +++ b/Sources/Build/BuildPlan.swift @@ -511,6 +511,7 @@ public final class SwiftTargetBuildDescription { args += additionalFlags args += moduleCacheArgs args += buildParameters.sanitizers.compileSwiftFlags() + args += ["-parseable-output"] // Add arguments needed for code coverage if it is enabled. if buildParameters.enableCodeCoverage { diff --git a/Sources/Build/SwiftCompilerOutputParser.swift b/Sources/Build/SwiftCompilerOutputParser.swift index 639ced47dbf..c3abc47be03 100644 --- a/Sources/Build/SwiftCompilerOutputParser.swift +++ b/Sources/Build/SwiftCompilerOutputParser.swift @@ -19,17 +19,26 @@ struct SwiftCompilerMessage { let path: String } - struct CommandInfo { + struct BeganInfo { + let pid: Int + let inputs: [String] + let outputs: [Output] + let commandExecutable: String + let commandArguments: [String] + } + + struct SkippedInfo { let inputs: [String] let outputs: [Output] } struct OutputInfo { + let pid: Int let output: String? } - case began(CommandInfo) - case skipped(CommandInfo) + case began(BeganInfo) + case skipped(SkippedInfo) case finished(OutputInfo) case signalled(OutputInfo) } @@ -58,7 +67,7 @@ final class SwiftCompilerOutputParser { } /// Delegate to notify of parsing events. - public var delegate: SwiftCompilerOutputParserDelegate + public weak var delegate: SwiftCompilerOutputParserDelegate? /// Buffer containing the bytes until a full message can be parsed. private var buffer: [UInt8] = [] @@ -70,11 +79,14 @@ final class SwiftCompilerOutputParser { private var hasFailed = false /// The JSON decoder to parse messages. - private let decoder = JSONDecoder() + private let decoder: JSONDecoder /// Initializes the parser with a delegate to notify of parsing events. init(delegate: SwiftCompilerOutputParserDelegate) { self.delegate = delegate + let decoder = JSONDecoder() + decoder.keyDecodingStrategy = .convertFromSnakeCase + self.decoder = decoder } /// Parse the next bytes of the Swift compiler JSON output. @@ -87,7 +99,7 @@ final class SwiftCompilerOutputParser { try parseImpl(bytes: bytes) } catch { hasFailed = true - delegate.swiftCompilerOutputParserDidFail(withError: error) + delegate?.swiftCompilerOutputParserDidFail(withError: error) } } } @@ -123,7 +135,7 @@ private extension SwiftCompilerOutputParser { buffer.append(contentsOf: bytes.prefix(remainingBytes)) let message = try parseMessage() - delegate.swiftCompilerDidOutputMessage(message) + delegate?.swiftCompilerDidOutputMessage(message) if case .signalled = message.kind { hasFailed = true @@ -194,9 +206,9 @@ extension SwiftCompilerMessage.Kind: Decodable, Equatable { let kind = try container.decode(String.self, forKey: .kind) switch kind { case "began": - self = try .began(CommandInfo(from: decoder)) + self = try .began(BeganInfo(from: decoder)) case "skipped": - self = try .skipped(CommandInfo(from: decoder)) + self = try .skipped(SkippedInfo(from: decoder)) case "finished": self = try .finished(OutputInfo(from: decoder)) case "signalled": @@ -208,7 +220,8 @@ extension SwiftCompilerMessage.Kind: Decodable, Equatable { } extension SwiftCompilerMessage.Kind.Output: Decodable, Equatable {} -extension SwiftCompilerMessage.Kind.CommandInfo: Decodable, Equatable {} +extension SwiftCompilerMessage.Kind.BeganInfo: Decodable, Equatable {} +extension SwiftCompilerMessage.Kind.SkippedInfo: Decodable, Equatable {} extension SwiftCompilerMessage.Kind.OutputInfo: Decodable, Equatable {} private let newline = UInt8(ascii: "\n") diff --git a/Sources/Commands/SwiftTool.swift b/Sources/Commands/SwiftTool.swift index f9fa083f49c..80399a134dc 100644 --- a/Sources/Commands/SwiftTool.swift +++ b/Sources/Commands/SwiftTool.swift @@ -207,12 +207,6 @@ public class SwiftTool { let diagnostics: DiagnosticsEngine = DiagnosticsEngine( handlers: [DiagnosticsEngineHandler.default.diagnosticsHandler]) - /// The llbuild Build System delegate. - private(set) var buildDelegate: BuildDelegate - - /// The building progress animation. - private(set) var buildingProgressAnimation: ProgressAnimationProtocol - /// The execution status of the tool. var executionStatus: ExecutionStatus = .success @@ -234,13 +228,6 @@ public class SwiftTool { } originalWorkingDirectory = cwd - // Setup the build delegate. - buildingProgressAnimation = NinjaProgressAnimation(stream: stdoutStream) - buildDelegate = BuildDelegate( - diagnostics: diagnostics, - outputStream: stdoutStream, - progressAnimation: buildingProgressAnimation) - // Create the parser. parser = ArgumentParser( commandName: "swift \(toolName)", @@ -528,9 +515,6 @@ public class SwiftTool { self.shouldRedirectStdoutToStderr = true self.stdoutStream = Basic.stderrStream DiagnosticsEngineHandler.default.stdoutStream = Basic.stderrStream - buildDelegate.outputStream = Basic.stderrStream - buildingProgressAnimation = NinjaProgressAnimation(stream: Basic.stderrStream) - buildDelegate.progressAnimation = buildingProgressAnimation } /// Resolve the dependencies. @@ -598,12 +582,12 @@ public class SwiftTool { return try subset.llbuildTargetName(for: loadPackageGraph(), diagnostics: diagnostics, config: buildParameters.configuration.dirname) } } - - func build(parameters: BuildParameters, subset: BuildSubset) throws { + + func build(plan: BuildPlan, parameters: BuildParameters, subset: BuildSubset) throws { guard let llbuildTargetName = try computeLLBuildTargetName(for: subset, buildParameters: parameters) else { return } - try runLLBuild(manifest: parameters.llbuildManifest, llbuildTarget: llbuildTargetName) + try runLLBuild(plan: plan, manifest: parameters.llbuildManifest, llbuildTarget: llbuildTargetName) } /// Build a subset of products and targets using swift-build-tool. @@ -619,7 +603,7 @@ public class SwiftTool { try llbuild.generateManifest(at: yaml) // Run llbuild. - try runLLBuild(manifest: yaml, llbuildTarget: llbuildTargetName) + try runLLBuild(plan: plan, manifest: yaml, llbuildTarget: llbuildTargetName) // Create backwards-compatibilty symlink to old build path. let oldBuildPath = buildPath.appending(component: options.configuration.dirname) @@ -629,23 +613,34 @@ public class SwiftTool { try createSymlink(oldBuildPath, pointingAt: plan.buildParameters.buildPath, relative: true) } - func runLLBuild(manifest: AbsolutePath, llbuildTarget: String) throws { + func runLLBuild(plan: BuildPlan, manifest: AbsolutePath, llbuildTarget: String) throws { assert(localFileSystem.isFile(manifest), "llbuild manifest not present: \(manifest)") if options.shouldEnableLLBuildLibrary { - try runLLBuildAsLibrary(manifest: manifest, llbuildTarget: llbuildTarget) + try runLLBuildAsLibrary(plan: plan, manifest: manifest, llbuildTarget: llbuildTarget) } else { try runLLBuildAsExecutable(manifest: manifest, llbuildTarget: llbuildTarget) } } - func runLLBuildAsLibrary(manifest: AbsolutePath, llbuildTarget: String) throws { + func runLLBuildAsLibrary(plan: BuildPlan, manifest: AbsolutePath, llbuildTarget: String) throws { + // Setup the build delegate. + let isVerbose = verbosity != .concise + let progressAnimation: ProgressAnimationProtocol = isVerbose ? + MultiLineNinjaProgressAnimation(stream: stdoutStream) : + NinjaProgressAnimation(stream: stdoutStream) + let buildDelegate = BuildDelegate( + plan: plan, + diagnostics: diagnostics, + outputStream: stdoutStream, + progressAnimation: progressAnimation) + buildDelegate.isVerbose = isVerbose + let databasePath = buildPath.appending(component: "build.db").pathString let buildSystem = BuildSystem(buildFile: manifest.pathString, databaseFile: databasePath, delegate: buildDelegate) - buildDelegate.isVerbose = verbosity != .concise - buildDelegate.onCommmandFailure = { [weak buildSystem] in buildSystem?.cancel() } + buildDelegate.onCommmandFailure = { buildSystem.cancel() } let success = buildSystem.build(target: llbuildTarget) - buildingProgressAnimation.complete(success: success) + progressAnimation.complete(success: success) guard success else { throw Diagnostics.fatalError } } diff --git a/Sources/SPMUtility/ProgressAnimation.swift b/Sources/SPMUtility/ProgressAnimation.swift index c861ed0899c..8d413b6cec4 100644 --- a/Sources/SPMUtility/ProgressAnimation.swift +++ b/Sources/SPMUtility/ProgressAnimation.swift @@ -71,18 +71,28 @@ public final class SingleLinePercentProgressAnimation: ProgressAnimationProtocol /// A multi-line ninja-like progress animation. public final class MultiLineNinjaProgressAnimation: ProgressAnimationProtocol { + private struct Info: Equatable { + let step: Int + let total: Int + let text: String + } + private let stream: OutputByteStream + private var lastDisplayedText: String? = nil - init(stream: OutputByteStream) { + public init(stream: OutputByteStream) { self.stream = stream } public func update(step: Int, total: Int, text: String) { assert(step <= total) + guard text != lastDisplayedText else { return } + stream <<< "[\(step)/\(total)] " <<< text stream <<< "\n" stream.flush() + lastDisplayedText = text } public func complete(success: Bool) { @@ -142,9 +152,15 @@ public final class NinjaProgressAnimation: DynamicProgressAnimation { /// A multi-line percent-based progress animation. public final class MultiLinePercentProgressAnimation: ProgressAnimationProtocol { + private struct Info: Equatable { + let percentage: Int + let text: String + } + private let stream: OutputByteStream private let header: String private var hasDisplayedHeader = false + private var lastDisplayedText: String? = nil init(stream: OutputByteStream, header: String) { self.stream = stream @@ -165,6 +181,7 @@ public final class MultiLinePercentProgressAnimation: ProgressAnimationProtocol stream <<< "\(percentage)%: " <<< text stream <<< "\n" stream.flush() + lastDisplayedText = text } public func complete(success: Bool) { diff --git a/Tests/BuildTests/SwiftCompilerOutputParserTests.swift b/Tests/BuildTests/SwiftCompilerOutputParserTests.swift index 2940be57e5d..e3539198e59 100644 --- a/Tests/BuildTests/SwiftCompilerOutputParserTests.swift +++ b/Tests/BuildTests/SwiftCompilerOutputParserTests.swift @@ -42,14 +42,14 @@ class SwiftCompilerOutputParserTests: XCTestCase { let delegate = MockSwiftCompilerOutputParserDelegate() let parser = SwiftCompilerOutputParser(delegate: delegate) - parser.parse(bytes: "22".utf8) + parser.parse(bytes: "33".utf8) delegate.assert(messages: [], errorDescription: nil) parser.parse(bytes: "".utf8) delegate.assert(messages: [], errorDescription: nil) parser.parse(bytes: """ - 9 + 8 { "kind": "began", "name": "compile", @@ -70,7 +70,9 @@ class SwiftCompilerOutputParserTests: XCTestCase { "path": "/var/folders/yc/rgflx8m11p5d71k1ydy0l_pr0000gn/T/test-77d991.o" } ], - "pid": 22698 + "pid": 22698, + "command_executable": "swift", + "command_arguments" : ["-frontend", "-c", "-primary-file", "test.swift"] } 117 @@ -79,10 +81,13 @@ class SwiftCompilerOutputParserTests: XCTestCase { SwiftCompilerMessage( name: "compile", kind: .began(.init( + pid: 22698, inputs: ["test.swift"], outputs: [.init( type: "object", - path: "/var/folders/yc/rgflx8m11p5d71k1ydy0l_pr0000gn/T/test-77d991.o")]))) + path: "/var/folders/yc/rgflx8m11p5d71k1ydy0l_pr0000gn/T/test-77d991.o")], + commandExecutable: "swift", + commandArguments: ["-frontend", "-c", "-primary-file", "test.swift"]))) ], errorDescription: nil) parser.parse(bytes: """ @@ -97,7 +102,9 @@ class SwiftCompilerOutputParserTests: XCTestCase { delegate.assert(messages: [ SwiftCompilerMessage( name: "compile", - kind: .finished(.init(output: "error: it failed :-("))) + kind: .finished(.init( + pid: 22698, + output: "error: it failed :-("))) ], errorDescription: nil) parser.parse(bytes: """ @@ -117,7 +124,7 @@ class SwiftCompilerOutputParserTests: XCTestCase { ], "pid": 58776 } - 219 + 299 { "kind": "began", "name": "link", @@ -130,7 +137,9 @@ class SwiftCompilerOutputParserTests: XCTestCase { "path": "test" } ], - "pid": 22699 + "pid": 22699, + "command_executable": "ld", + "command_arguments" : ["-o", "option", "test"] } 119 """.utf8) @@ -145,10 +154,13 @@ class SwiftCompilerOutputParserTests: XCTestCase { SwiftCompilerMessage( name: "link", kind: .began(.init( + pid: 22699, inputs: ["/var/folders/yc/rgflx8m11p5d71k1ydy0l_pr0000gn/T/test-77d991.o"], outputs: [.init( type: "image", - path: "test")]))) + path: "test")], + commandExecutable: "ld", + commandArguments: ["-o", "option", "test"]))) ], errorDescription: nil) parser.parse(bytes: """ @@ -165,7 +177,9 @@ class SwiftCompilerOutputParserTests: XCTestCase { delegate.assert(messages: [ SwiftCompilerMessage( name: "link", - kind: .signalled(.init(output: nil))) + kind: .signalled(.init( + pid: 22699, + output: nil))) ], errorDescription: nil) } diff --git a/Tests/CommandsTests/RunToolTests.swift b/Tests/CommandsTests/RunToolTests.swift index 061fa7a3f62..4829494f7b9 100644 --- a/Tests/CommandsTests/RunToolTests.swift +++ b/Tests/CommandsTests/RunToolTests.swift @@ -44,7 +44,7 @@ final class RunToolTests: XCTestCase { """)) // swift-build-tool output should go to stderr. - XCTAssertMatch(try result.utf8stderrOutput(), .regex("Compil(e|ing) Swift Module")) + XCTAssertMatch(try result.utf8stderrOutput(), .regex("Compiling")) XCTAssertMatch(try result.utf8stderrOutput(), .contains("Linking")) do { diff --git a/Tests/FunctionalTests/MiscellaneousTests.swift b/Tests/FunctionalTests/MiscellaneousTests.swift index 389e95e4c31..8e8aaa0debf 100644 --- a/Tests/FunctionalTests/MiscellaneousTests.swift +++ b/Tests/FunctionalTests/MiscellaneousTests.swift @@ -28,8 +28,12 @@ class MiscellaneousTestCase: XCTestCase { fixture(name: "DependencyResolution/External/Simple") { prefix in let output = try executeSwiftBuild(prefix.appending(component: "Bar")) - XCTAssertTrue(output.contains("Resolving")) - XCTAssertTrue(output.contains("at 1.2.3")) + XCTAssertMatch(output, .regex("Resolving .* at 1\\.2\\.3")) + XCTAssertMatch(output, .regex("Compiling .*Foo.swift")) + XCTAssertMatch(output, .regex("Merging module .*Foo\\.swiftmodule")) + XCTAssertMatch(output, .contains("Compiling main.swift")) + XCTAssertMatch(output, .regex("Merging module .*Bar\\.swiftmodule")) + XCTAssertMatch(output, .regex("Linking .*Bar")) } } @@ -81,23 +85,22 @@ class MiscellaneousTestCase: XCTestCase { func testCompileFailureExitsGracefully() { fixture(name: "Miscellaneous/CompileFails") { prefix in - var foo = false do { try executeSwiftBuild(prefix) - } catch SwiftPMProductError.executionFailure(let error, _, _) { - switch error { - case ProcessResult.Error.nonZeroExit(let result): + XCTFail() + } catch SwiftPMProductError.executionFailure(let error, let output, _) { + XCTAssertMatch(output, .contains("Compiling Foo.swift")) + XCTAssertMatch(output, .regex("error: .*\n.*compile_failure")) + + if case ProcessResult.Error.nonZeroExit(let result) = error { // if our code crashes we'll get an exit code of 256 XCTAssertEqual(result.exitStatus, .terminated(code: 1)) - foo = true - default: + } else { XCTFail() } } catch { XCTFail() } - - XCTAssertTrue(foo) } }