diff --git a/Sources/SWBTestSupport/BuildOperationTester.swift b/Sources/SWBTestSupport/BuildOperationTester.swift index 735bdf07..b3ecd874 100644 --- a/Sources/SWBTestSupport/BuildOperationTester.swift +++ b/Sources/SWBTestSupport/BuildOperationTester.swift @@ -1414,12 +1414,16 @@ package final class BuildOperationTester { } /// Construct the tasks for the given build parameters, and test the result. - @discardableResult package func checkBuild(_ name: String? = nil, parameters: BuildParameters? = nil, runDestination: SWBProtocol.RunDestinationInfo?, buildRequest inputBuildRequest: BuildRequest? = nil, buildCommand: BuildCommand? = nil, schemeCommand: SchemeCommand? = .launch, persistent: Bool = false, serial: Bool = false, buildOutputMap: [String:String]? = nil, signableTargets: Set = [], signableTargetInputs: [String: ProvisioningTaskInputs] = [:], clientDelegate: (any ClientDelegate)? = nil, sourceLocation: SourceLocation = #_sourceLocation, body: (BuildResults) async throws -> T) async throws -> T { - try await checkBuild(name, parameters: parameters, runDestination: runDestination, buildRequest: inputBuildRequest, buildCommand: buildCommand, schemeCommand: schemeCommand, persistent: persistent, serial: serial, buildOutputMap: buildOutputMap, signableTargets: signableTargets, signableTargetInputs: signableTargetInputs, clientDelegate: clientDelegate, sourceLocation: sourceLocation, body: body, performBuild: { try await $0.buildWithTimeout() }) + /// + /// This variant of `checkBuild()` differs from the main versiion in these ways: + /// - Does not have a `operationBuildRequest` parameter. + /// - Has a default value of the `performBuild` parameter which performs a nortmal build. + @discardableResult package func checkBuild(_ name: String? = nil, parameters: BuildParameters? = nil, runDestination: SWBProtocol.RunDestinationInfo?, buildRequest inputBuildRequest: BuildRequest? = nil, buildCommand: BuildCommand? = nil, schemeCommand: SchemeCommand? = .launch, persistent: Bool = false, serial: Bool = false, buildOutputMap: [String:String]? = nil, signableTargets: Set = [], signableTargetInputs: [String: ProvisioningTaskInputs] = [:], attachBuildArifacts: Bool = true, clientDelegate: (any ClientDelegate)? = nil, sourceLocation: SourceLocation = #_sourceLocation, body: (BuildResults) async throws -> T) async throws -> T { + try await checkBuild(name, parameters: parameters, runDestination: runDestination, buildRequest: inputBuildRequest, buildCommand: buildCommand, schemeCommand: schemeCommand, persistent: persistent, serial: serial, buildOutputMap: buildOutputMap, signableTargets: signableTargets, signableTargetInputs: signableTargetInputs, attachBuildArifacts: attachBuildArifacts, clientDelegate: clientDelegate, sourceLocation: sourceLocation, body: body, performBuild: { try await $0.buildWithTimeout() }) } /// Construct the tasks for the given build parameters, and test the result. - @discardableResult package func checkBuild(_ name: String? = nil, parameters: BuildParameters? = nil, runDestination: RunDestinationInfo?, buildRequest inputBuildRequest: BuildRequest? = nil, operationBuildRequest: BuildRequest? = nil, buildCommand: BuildCommand? = nil, schemeCommand: SchemeCommand? = .launch, persistent: Bool = false, serial: Bool = false, buildOutputMap: [String:String]? = nil, signableTargets: Set = [], signableTargetInputs: [String: ProvisioningTaskInputs] = [:], clientDelegate: (any ClientDelegate)? = nil, sourceLocation: SourceLocation = #_sourceLocation, body: (BuildResults) async throws -> T, performBuild: @escaping (any BuildSystemOperation) async throws -> Void) async throws -> T { + @discardableResult package func checkBuild(_ name: String? = nil, parameters: BuildParameters? = nil, runDestination: SWBProtocol.RunDestinationInfo?, buildRequest inputBuildRequest: BuildRequest? = nil, operationBuildRequest: BuildRequest? = nil, buildCommand: BuildCommand? = nil, schemeCommand: SchemeCommand? = .launch, persistent: Bool = false, serial: Bool = false, buildOutputMap: [String:String]? = nil, signableTargets: Set = [], signableTargetInputs: [String: ProvisioningTaskInputs] = [:], attachBuildArifacts: Bool = true, clientDelegate: (any ClientDelegate)? = nil, sourceLocation: SourceLocation = #_sourceLocation, body: (BuildResults) async throws -> T, performBuild: @escaping (any BuildSystemOperation) async throws -> Void) async throws -> T { try await checkBuildDescription(parameters, runDestination: runDestination, buildRequest: inputBuildRequest, buildCommand: buildCommand, schemeCommand: schemeCommand, persistent: persistent, serial: serial, signableTargets: signableTargets, signableTargetInputs: signableTargetInputs, clientDelegate: clientDelegate) { results throws in // Check that there are no duplicate task identifiers - it is a fatal error if there are, unless `continueBuildingAfterErrors` is set. var tasksByTaskIdentifier: [TaskIdentifier: Task] = [:] @@ -1476,19 +1480,14 @@ package final class BuildOperationTester { // Check the results. let results = try BuildResults(core: core, workspace: workspace, buildDescriptionResults: results, tasksByTaskIdentifier: delegate.tasksByTaskIdentifier.merging(delegate.dynamicTasksByTaskIdentifier, uniquingKeysWith: { a, b in a }), fs: fs, events: events, dynamicTaskDependencies: dynamicDependencies, buildDatabasePath: persistent ? results.buildDescription.buildDatabasePath : nil) - /*@MainActor func addAttachments() { - // TODO: This `runActivity` call should be wider in scope, but this would significantly complicate the code flow due to threading requirements without having async/await. - XCTContext.runActivity(named: "Execute Build Operation" + (name.map({ " \"\($0)\"" }) ?? "")) { activity in - // TODO: Longer term, we should find a way to share code with CoreQualificationTester, which has a number of APIs for emitting build operation debug info. - activity.attach(name: "Build Transcript", string: results.buildTranscript) - if localFS.exists(results.buildDescription.packagePath) { - activity.attach(name: "Build Description", from: results.buildDescription.packagePath) - } + // TODO: Longer term, we should find a way to share code with CoreQualificationTester, which has a number of APIs for emitting build operation debug info. + if attachBuildArifacts { + Attachment.record(results.buildTranscript, named: "Build Transcript" + (name.map({ " for Build Operation \"\($0)\"" }) ?? "")) + if localFS.exists(results.buildDescription.packagePath) { + Attachment.record(results.buildDescription.packagePath.str, named: "Build Description" + (name.map({ " for Build Operation \"\($0)\"" }) ?? "")) } } - await addAttachments()*/ - defer { let validationResults = results.validate(sourceLocation: sourceLocation) diff --git a/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift b/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift index aa8390b6..3d75222e 100644 --- a/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift +++ b/Tests/SWBBuildSystemTests/BuildTaskBehaviorTests.swift @@ -229,6 +229,8 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { let startBuilds = WaitCondition() let buildsDone = WaitCondition() + // The threads this test spawns to run the build can outlive the lifetime of the test itself, so it passed attachBuildArifacts = false since if that happens then the test might crash because the reporter needed to add the attachments will no longer exist. + // build1 cancels immediately _Concurrency.Task { do { @@ -236,7 +238,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { tester.userPreferences = prefs - try await tester.checkBuild(runDestination: .host, body: { results in + try await tester.checkBuild(runDestination: .host, attachBuildArifacts: false, body: { results in results.checkCapstoneEvents(last: .buildCancelled) }) { operation in build1Ready.signal() @@ -264,7 +266,7 @@ fileprivate struct BuildTaskBehaviorTests: CoreBasedTests { tester.userPreferences = prefs - try await tester.checkBuild(runDestination: .host, body: { results in + try await tester.checkBuild(runDestination: .host, attachBuildArifacts: false, body: { results in #expect(results.events.first! == .buildStarted) let waitTask = try #require(results.getTask(.matchRule(["wait"])))