From 9c287c4abf0c9ba46a85d2023a9057f56e207cc8 Mon Sep 17 00:00:00 2001 From: Michael Rawdon Date: Fri, 12 Dec 2025 14:55:02 -0800 Subject: [PATCH] Attach the build transcript and path to the build description to the test at the end of BuildOperationTester.checkBuild(). We can do this now that Swift Testing support attachments. Also give callers of checkBuild() the opportunity to opt out of adding these attachments, and have BuildTaskBehaviorTests.stressConcurrentCancellation() do so, as its particular nature can sometimes crash the tests when adding attachments. --- .../SWBTestSupport/BuildOperationTester.swift | 25 +++++++++---------- .../BuildTaskBehaviorTests.swift | 6 +++-- 2 files changed, 16 insertions(+), 15 deletions(-) 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"])))