Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
60 changes: 44 additions & 16 deletions Sources/SWBTestSupport/BuildOperationTester.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ package import SWBBuildSystem
@_spi(Testing) package import SWBCore
package import SWBTaskConstruction
package import SWBTaskExecution
package import SWBUtil
@_spi(Testing) package import SWBUtil

private import class SWBLLBuild.BuildDB
private import class SWBLLBuild.BuildKey
Expand Down Expand Up @@ -713,6 +713,18 @@ package final class BuildOperationTester {

}

package func checkNoTaskWithBacktraces(_ conditions: TaskCondition..., sourceLocation: SourceLocation = #_sourceLocation) {
for matchedTask in findMatchingTasks(conditions) {
Issue.record("found unexpected task matching conditions '\(conditions)', found: \(matchedTask)", sourceLocation: sourceLocation)

if let frameID = getBacktraceID(matchedTask, sourceLocation: sourceLocation) {
enumerateBacktraces(frameID) { _, category, description in
Issue.record("...<category='\(category)' description='\(description)'>", sourceLocation: sourceLocation)
}
}
}
}

/// Check whether the results contains a dependency cycle error. If so, then consume the error and create a `CycleChecking` object and pass it to the block. Otherwise fail.
package func checkDependencyCycle(_ pattern: StringPattern, kind: DiagnosticKind = .error, failIfNotFound: Bool = true, sourceLocation: SourceLocation = #_sourceLocation, body: (CycleChecker) async throws -> Void) async throws {
guard let message = getDiagnosticMessage(pattern, kind: kind, checkDiagnostic: { _ in true }) else {
Expand Down Expand Up @@ -1011,8 +1023,21 @@ package final class BuildOperationTester {
startedTasks.remove(task)
}

package func checkBacktrace(_ identifier: BuildOperationBacktraceFrameEmitted.Identifier, _ patterns: [StringPattern], sourceLocation: SourceLocation = #_sourceLocation) {
var frameDescriptions: [String] = []
private func getBacktraceID(_ task: Task, sourceLocation: SourceLocation = #_sourceLocation) -> BuildOperationBacktraceFrameEmitted.Identifier? {
guard let frameID: BuildOperationBacktraceFrameEmitted.Identifier = events.compactMap ({ (event) -> BuildOperationBacktraceFrameEmitted.Identifier? in
guard case .emittedBuildBacktraceFrame(identifier: let identifier, previousFrameIdentifier: _, category: _, description: _) = event, case .task(let signature) = identifier, BuildOperationTaskSignature.taskIdentifier(ByteString(encodingAsUTF8: task.identifier.rawValue)) == signature else {
return nil
}
return identifier
// Iff the task is a dynamic task, there may be more than one corresponding frame if it was requested multiple times, in which case we choose the first. Non-dynamic tasks always have a 1-1 relationship with frames.
}).sorted().first else {
Issue.record("Did not find a single build backtrace frame for task: \(task.identifier)", sourceLocation: sourceLocation)
return nil
}
return frameID
}

private func enumerateBacktraces(_ identifier: BuildOperationBacktraceFrameEmitted.Identifier, _ handleFrameInfo: (_ identifier: BuildOperationBacktraceFrameEmitted.Identifier?, _ category: BuildOperationBacktraceFrameEmitted.Category, _ description: String) -> ()) {
var currentFrameID: BuildOperationBacktraceFrameEmitted.Identifier? = identifier
while let id = currentFrameID {
if let frameInfo: (BuildOperationBacktraceFrameEmitted.Identifier?, BuildOperationBacktraceFrameEmitted.Category, String) = events.compactMap({ (event) -> (BuildOperationBacktraceFrameEmitted.Identifier?, BuildOperationBacktraceFrameEmitted.Category, String)? in
Expand All @@ -1022,28 +1047,29 @@ package final class BuildOperationTester {
return (previousFrameIdentifier, category, description)
// Iff the task is a dynamic task, there may be more than one corresponding frame if it was requested multiple times, in which case we choose the first. Non-dynamic tasks always have a 1-1 relationship with frames.
}).sorted(by: { $0.0 }).first {
frameDescriptions.append("<category='\(frameInfo.1)' description='\(frameInfo.2)'>")
handleFrameInfo(frameInfo.0, frameInfo.1, frameInfo.2)
currentFrameID = frameInfo.0
} else {
currentFrameID = nil
}
}
}

package func checkBacktrace(_ identifier: BuildOperationBacktraceFrameEmitted.Identifier, _ patterns: [StringPattern], sourceLocation: SourceLocation = #_sourceLocation) {
var frameDescriptions: [String] = []
enumerateBacktraces(identifier) { (_, category, description) in
frameDescriptions.append("<category='\(category)' description='\(description)'>")
}

XCTAssertMatch(frameDescriptions, patterns, sourceLocation: sourceLocation)
}

package func checkBacktrace(_ task: Task, _ patterns: [StringPattern], sourceLocation: SourceLocation = #_sourceLocation) {
guard let frameID: BuildOperationBacktraceFrameEmitted.Identifier = events.compactMap ({ (event) -> BuildOperationBacktraceFrameEmitted.Identifier? in
guard case .emittedBuildBacktraceFrame(identifier: let identifier, previousFrameIdentifier: _, category: _, description: _) = event, case .task(let signature) = identifier, BuildOperationTaskSignature.taskIdentifier(ByteString(encodingAsUTF8: task.identifier.rawValue)) == signature else {
return nil
}
return identifier
// Iff the task is a dynamic task, there may be more than one corresponding frame if it was requested multiple times, in which case we choose the first. Non-dynamic tasks always have a 1-1 relationship with frames.
}).sorted().first else {
Issue.record("Did not find a single build backtrace frame for task: \(task.identifier)", sourceLocation: sourceLocation)
return
if let frameID = getBacktraceID(task, sourceLocation: sourceLocation) {
checkBacktrace(frameID, patterns, sourceLocation: sourceLocation)
} else {
// already recorded an issue
}
checkBacktrace(frameID, patterns, sourceLocation: sourceLocation)
}

private class TaskDependencyResolver {
Expand Down Expand Up @@ -1520,7 +1546,7 @@ package final class BuildOperationTester {

func body(results: BuildResults) throws -> Void {
results.consumeTasksMatchingRuleTypes(excludedTasks)
results.checkNoTask(sourceLocation: sourceLocation)
results.checkNoTaskWithBacktraces(sourceLocation: sourceLocation)

results.checkNote(.equal("Building targets in dependency order"), failIfNotFound: false)
results.checkNote(.prefix("Target dependency graph"), failIfNotFound: false)
Expand All @@ -1546,7 +1572,9 @@ package final class BuildOperationTester {
}
}

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)
try await UserDefaults.withEnvironment(["EnableBuildBacktraceRecording": "true"]) {
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)
}
}

package static func buildRequestForIndexOperation(
Expand Down