From 27770b99e351c0fc5ba067e86b6ad7e196742820 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 12:28:30 -0600 Subject: [PATCH 1/7] Deprecate the SPIAwareTrait SPI protocol and adopt TestScoping in its place --- Sources/Testing/Running/Runner.Plan.swift | 29 ++---- Sources/Testing/Running/Runner.swift | 92 ++++++++++--------- .../Testing/Traits/ParallelizationTrait.swift | 27 +++--- Sources/Testing/Traits/SPIAwareTrait.swift | 3 +- .../Traits/ParallelizationTraitTests.swift | 10 -- 5 files changed, 71 insertions(+), 90 deletions(-) diff --git a/Sources/Testing/Running/Runner.Plan.swift b/Sources/Testing/Running/Runner.Plan.swift index 7553acf6e..26cb00d14 100644 --- a/Sources/Testing/Running/Runner.Plan.swift +++ b/Sources/Testing/Running/Runner.Plan.swift @@ -28,7 +28,13 @@ extension Runner { /// ## See Also /// /// - ``ParallelizationTrait`` - public var isParallelizationEnabled: Bool + @available(*, deprecated, message: "The 'isParallelizationEnabled' property is deprecated and no longer used. Its value is always false.") + public var isParallelizationEnabled: Bool { + get { + false + } + set {} + } } /// The test should be run. @@ -65,19 +71,6 @@ extension Runner { return true } } - - /// Whether or not this action enables parallelization. - /// - /// If this action is of case ``run(options:)``, the value of this - /// property equals the value of its associated - /// ``RunOptions/isParallelizationEnabled`` property. Otherwise, the value - /// of this property is `nil`. - var isParallelizationEnabled: Bool? { - if case let .run(options) = self { - return options.isParallelizationEnabled - } - return nil - } } /// A type describing a step in a runner plan. @@ -218,7 +211,7 @@ extension Runner.Plan { // Convert the list of test into a graph of steps. The actions for these // steps will all be .run() *unless* an error was thrown while examining // them, in which case it will be .recordIssue(). - let runAction = Action.run(options: .init(isParallelizationEnabled: configuration.isParallelizationEnabled)) + let runAction = Action.run(options: .init()) var testGraph = Graph() var actionGraph = Graph(value: runAction) for test in tests { @@ -278,11 +271,7 @@ extension Runner.Plan { // `SkipInfo`, the error should not be recorded. for trait in test.traits { do { - if let trait = trait as? any SPIAwareTrait { - try await trait.prepare(for: test, action: &action) - } else { - try await trait.prepare(for: test) - } + try await trait.prepare(for: test) } catch let error as SkipInfo { action = .skip(error) break diff --git a/Sources/Testing/Running/Runner.swift b/Sources/Testing/Running/Runner.swift index 4e20b4b4e..17d43a5a6 100644 --- a/Sources/Testing/Running/Runner.swift +++ b/Sources/Testing/Running/Runner.swift @@ -70,7 +70,7 @@ extension Runner { /// /// - Throws: Whatever is thrown by `body` or by any of the /// ``TestScoping/provideScope(for:testCase:performing:)`` function calls. - private func _applyScopingTraits( + private static func _applyScopingTraits( for test: Test, testCase: Test.Case?, _ body: @escaping @Sendable () async throws -> Void @@ -103,21 +103,18 @@ extension Runner { /// /// - Parameters: /// - sequence: The sequence to enumerate. - /// - step: The plan step that controls parallelization. If `nil`, or if its - /// ``Runner/Plan/Step/action`` property is not of case - /// ``Runner/Plan/Action/run(options:)``, the - /// ``Configuration/isParallelizationEnabled`` property of this runner's - /// ``configuration`` property is used instead to determine if - /// parallelization is enabled. + /// - configuration: The configuration with which the elements of `sequence` + /// should be enumerated. This function uses the configuration to + /// determine whether parallelization is enabled. The default value is the + /// current configuration, if any, or else the default configuration. /// - body: The function to invoke. /// /// - Throws: Whatever is thrown by `body`. - private func _forEach( + private static func _forEach( in sequence: some Sequence, - for step: Plan.Step?, + configuration: Configuration = .current ?? .init(), _ body: @Sendable @escaping (E) async throws -> Void ) async throws where E: Sendable { - let isParallelizationEnabled = step?.action.isParallelizationEnabled ?? configuration.isParallelizationEnabled try await withThrowingTaskGroup(of: Void.self) { taskGroup in for element in sequence { // Each element gets its own subtask to run in. @@ -126,7 +123,7 @@ extension Runner { } // If not parallelizing, wait after each task. - if !isParallelizationEnabled { + if !configuration.isParallelizationEnabled { try await taskGroup.waitForAll() } } @@ -137,12 +134,9 @@ extension Runner { /// /// - Parameters: /// - stepGraph: The subgraph whose root value, a step, is to be run. - /// - depth: How deep into the step graph this call is. The first call has a - /// depth of `0`. - /// - lastAncestorStep: The last-known ancestral step, if any, of the step - /// at the root of `stepGraph`. The options in this step (if its action is - /// of case ``Runner/Plan/Action/run(options:)``) inform the execution of - /// `stepGraph`. + /// - configuration: The configuration with which the step should run. The + /// default value is the current configuration, if any, or else the + /// default configuration. /// /// - Throws: Whatever is thrown from the test body. Thrown errors are /// normally reported as test failures. @@ -157,7 +151,10 @@ extension Runner { /// ## See Also /// /// - ``Runner/run()`` - private func _runStep(atRootOf stepGraph: Graph, depth: Int, lastAncestorStep: Plan.Step?) async throws { + private static func _runStep( + atRootOf stepGraph: Graph, + configuration: Configuration = .current ?? .init() + ) async throws { // Exit early if the task has already been cancelled. try Task.checkCancellation() @@ -204,14 +201,14 @@ extension Runner { } // Run the children of this test (i.e. the tests in this suite.) - try await _runChildren(of: stepGraph, depth: depth, lastAncestorStep: lastAncestorStep) + try await _runChildren(of: stepGraph) } } } } else { // There is no test at this node in the graph, so just skip down to the // child nodes. - try await _runChildren(of: stepGraph, depth: depth, lastAncestorStep: lastAncestorStep) + try await _runChildren(of: stepGraph) } } @@ -222,7 +219,7 @@ extension Runner { /// /// - Returns: The source location of the root node of `stepGraph`, or of the /// first descendant node thereof (sorted by source location.) - private func _sourceLocation(of stepGraph: Graph) -> SourceLocation? { + private static func _sourceLocation(of stepGraph: Graph) -> SourceLocation? { if let result = stepGraph.value?.test.sourceLocation { return result } @@ -234,26 +231,19 @@ extension Runner { /// Recursively run the tests that are children of a given plan step. /// /// - Parameters: - /// - stepGraph: The subgraph whose root value, a step, is to be run. - /// - depth: How deep into the step graph this call is. The first call has a - /// depth of `0`. - /// - lastAncestorStep: The last-known ancestral step, if any, of the step - /// at the root of `stepGraph`. The options in this step (if its action is - /// of case ``Runner/Plan/Action/run(options:)``) inform the execution of - /// `stepGraph`. + /// - stepGraph: The subgraph whose root value, a step, will be used to + /// find children to run. + /// - configuration: The configuration with which the children of `stepGraph` + /// will be run. The default value is the current configuration, if any, + /// or else the default configuration. /// /// - Throws: Whatever is thrown from the test body. Thrown errors are /// normally reported as test failures. - private func _runChildren(of stepGraph: Graph, depth: Int, lastAncestorStep: Plan.Step?) async throws { - // Figure out the last-good step, either the one at the root of `stepGraph` - // or, if it is nil, the one passed into this function. We need to track - // this value in case we run into sparse sections of the graph so we don't - // lose track of the recursive `isParallelizationEnabled` property in the - // runnable steps' options. - let stepOrAncestor = stepGraph.value ?? lastAncestorStep - - let isParallelizationEnabled = stepOrAncestor?.action.isParallelizationEnabled ?? configuration.isParallelizationEnabled - let childGraphs = if isParallelizationEnabled { + private static func _runChildren( + of stepGraph: Graph, + configuration: Configuration = .current ?? .init() + ) async throws { + let childGraphs = if configuration.isParallelizationEnabled { // Explicitly shuffle the steps to help detect accidental dependencies // between tests due to their ordering. Array(stepGraph.children) @@ -282,8 +272,8 @@ extension Runner { } // Run the child nodes. - try await _forEach(in: childGraphs, for: stepOrAncestor) { _, childGraph in - try await _runStep(atRootOf: childGraph, depth: depth + 1, lastAncestorStep: stepOrAncestor) + try await _forEach(in: childGraphs) { _, childGraph in + try await _runStep(atRootOf: childGraph) } } @@ -292,19 +282,26 @@ extension Runner { /// - Parameters: /// - testCases: The test cases to be run. /// - step: The runner plan step associated with this test case. + /// - configuration: The configuration with which the test cases should run. + /// The default value is the current configuration, if any, or else the + /// default configuration. /// /// - Throws: Whatever is thrown from a test case's body. Thrown errors are /// normally reported as test failures. /// /// If parallelization is supported and enabled, the generated test cases will /// be run in parallel using a task group. - private func _runTestCases(_ testCases: some Sequence, within step: Plan.Step) async throws { + private static func _runTestCases( + _ testCases: some Sequence, + within step: Plan.Step, + configuration: Configuration = .current ?? .init() + ) async throws { // Apply the configuration's test case filter. let testCases = testCases.lazy.filter { testCase in configuration.testCaseFilter(testCase, step.test) } - try await _forEach(in: testCases, for: step) { testCase in + try await _forEach(in: testCases) { testCase in try await _runTestCase(testCase, within: step) } } @@ -314,13 +311,20 @@ extension Runner { /// - Parameters: /// - testCase: The test case to run. /// - step: The runner plan step associated with this test case. + /// - configuration: The configuration with which the test case should run. + /// The default value is the current configuration, if any, or else the + /// default configuration. /// /// - Throws: Whatever is thrown from the test case's body. Thrown errors /// are normally reported as test failures. /// /// This function sets ``Test/Case/current``, then invokes the test case's /// body closure. - private func _runTestCase(_ testCase: Test.Case, within step: Plan.Step) async throws { + private static func _runTestCase( + _ testCase: Test.Case, + within step: Plan.Step, + configuration: Configuration = .current ?? .init() + ) async throws { // Exit early if the task has already been cancelled. try Task.checkCancellation() @@ -399,7 +403,7 @@ extension Runner { await withTaskGroup(of: Void.self) { [runner] taskGroup in _ = taskGroup.addTaskUnlessCancelled { - try? await runner._runStep(atRootOf: runner.plan.stepGraph, depth: 0, lastAncestorStep: nil) + try? await _runStep(atRootOf: runner.plan.stepGraph) } await taskGroup.waitForAll() } diff --git a/Sources/Testing/Traits/ParallelizationTrait.swift b/Sources/Testing/Traits/ParallelizationTrait.swift index d41052e25..b8382d503 100644 --- a/Sources/Testing/Traits/ParallelizationTrait.swift +++ b/Sources/Testing/Traits/ParallelizationTrait.swift @@ -16,9 +16,9 @@ /// test suite, this trait causes that suite to run its contained test functions /// and sub-suites serially instead of in parallel. /// -/// This trait is recursively applied: if it is applied to a suite, any -/// parameterized tests or test suites contained in that suite are also -/// serialized (as are any tests contained in those suites, and so on.) +/// If this trait is applied to a suite, any test functions or test suites +/// contained in that suite are also serialized (as are any tests contained in +/// those suites, and so on.) /// /// This trait does not affect the execution of a test relative to its peers or /// to unrelated tests. This trait has no effect if test parallelization is @@ -26,21 +26,18 @@ /// `swift test` command.) /// /// To add this trait to a test, use ``Trait/serialized``. -public struct ParallelizationTrait: TestTrait, SuiteTrait { - public var isRecursive: Bool { - true - } -} +public struct ParallelizationTrait: TestTrait, SuiteTrait {} -// MARK: - SPIAwareTrait +// MARK: - TestScoping -@_spi(ForToolsIntegrationOnly) -extension ParallelizationTrait: SPIAwareTrait { - public func prepare(for test: Test, action: inout Runner.Plan.Action) async throws { - if case var .run(options) = action { - options.isParallelizationEnabled = false - action = .run(options: options) +extension ParallelizationTrait: TestScoping { + public func provideScope(for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void) async throws { + guard var configuration = Configuration.current else { + throw SystemError(description: "There is no current Configuration when attempting to provide scope for test '\(test.name)'") } + + configuration.isParallelizationEnabled = false + try await Configuration.withCurrent(configuration, perform: function) } } diff --git a/Sources/Testing/Traits/SPIAwareTrait.swift b/Sources/Testing/Traits/SPIAwareTrait.swift index 780dba2d6..72171b016 100644 --- a/Sources/Testing/Traits/SPIAwareTrait.swift +++ b/Sources/Testing/Traits/SPIAwareTrait.swift @@ -14,7 +14,8 @@ /// This protocol refines ``Trait`` in various ways that require the use of SPI. /// Ideally, such requirements will be promoted to API when their design /// stabilizes. -@_spi(Experimental) @_spi(ForToolsIntegrationOnly) +@available(*, deprecated, message: "The SPIAwareTrait protocol is deprecated and no longer used.") +@_spi(ForToolsIntegrationOnly) public protocol SPIAwareTrait: Trait { /// Prepare to run the test to which this trait was added. /// diff --git a/Tests/TestingTests/Traits/ParallelizationTraitTests.swift b/Tests/TestingTests/Traits/ParallelizationTraitTests.swift index 85f69c06c..e43ca50b7 100644 --- a/Tests/TestingTests/Traits/ParallelizationTraitTests.swift +++ b/Tests/TestingTests/Traits/ParallelizationTraitTests.swift @@ -12,16 +12,6 @@ @Suite("Parallelization Trait Tests", .tags(.traitRelated)) struct ParallelizationTraitTests { - @Test(".serialized trait is recursively applied") - func serializedTrait() async { - var configuration = Configuration() - configuration.isParallelizationEnabled = true - let plan = await Runner.Plan(selecting: OuterSuite.self, configuration: configuration) - for step in plan.steps { - #expect(step.action.isParallelizationEnabled == false, "Step \(step) should have had parallelization disabled") - } - } - @Test(".serialized trait serializes parameterized test") func serializesParameterizedTestFunction() async { var configuration = Configuration() From f47ed3145ffbb8c5ff04168fc385f85ce2911906 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 15:12:13 -0600 Subject: [PATCH 2/7] Use a computed property in Runner --- Sources/Testing/Running/Runner.swift | 79 ++++++++++------------------ 1 file changed, 29 insertions(+), 50 deletions(-) diff --git a/Sources/Testing/Running/Runner.swift b/Sources/Testing/Running/Runner.swift index 17d43a5a6..1a186117d 100644 --- a/Sources/Testing/Running/Runner.swift +++ b/Sources/Testing/Running/Runner.swift @@ -56,6 +56,16 @@ public struct Runner: Sendable { // MARK: - Running tests extension Runner { + /// The current configuration _while_ running. + /// + /// This should be used from the functions in this extension which access the + /// current configuration. This is important since individual tests or suites + /// may have traits which customize the execution scope of their children, + /// including potentially modifying the current configuration. + private static var _configuration: Configuration { + .current ?? .init() + } + /// Apply the custom scope for any test scope providers of the traits /// associated with a specified test by calling their /// ``TestScoping/provideScope(for:testCase:performing:)`` function. @@ -103,16 +113,11 @@ extension Runner { /// /// - Parameters: /// - sequence: The sequence to enumerate. - /// - configuration: The configuration with which the elements of `sequence` - /// should be enumerated. This function uses the configuration to - /// determine whether parallelization is enabled. The default value is the - /// current configuration, if any, or else the default configuration. /// - body: The function to invoke. /// /// - Throws: Whatever is thrown by `body`. private static func _forEach( in sequence: some Sequence, - configuration: Configuration = .current ?? .init(), _ body: @Sendable @escaping (E) async throws -> Void ) async throws where E: Sendable { try await withThrowingTaskGroup(of: Void.self) { taskGroup in @@ -123,7 +128,7 @@ extension Runner { } // If not parallelizing, wait after each task. - if !configuration.isParallelizationEnabled { + if !_configuration.isParallelizationEnabled { try await taskGroup.waitForAll() } } @@ -134,9 +139,6 @@ extension Runner { /// /// - Parameters: /// - stepGraph: The subgraph whose root value, a step, is to be run. - /// - configuration: The configuration with which the step should run. The - /// default value is the current configuration, if any, or else the - /// default configuration. /// /// - Throws: Whatever is thrown from the test body. Thrown errors are /// normally reported as test failures. @@ -151,10 +153,7 @@ extension Runner { /// ## See Also /// /// - ``Runner/run()`` - private static func _runStep( - atRootOf stepGraph: Graph, - configuration: Configuration = .current ?? .init() - ) async throws { + private static func _runStep(atRootOf stepGraph: Graph) async throws { // Exit early if the task has already been cancelled. try Task.checkCancellation() @@ -165,18 +164,18 @@ extension Runner { // Determine what action to take for this step. if let step = stepGraph.value { - Event.post(.planStepStarted(step), for: (step.test, nil), configuration: configuration) + Event.post(.planStepStarted(step), for: (step.test, nil), configuration: _configuration) // Determine what kind of event to send for this step based on its action. switch step.action { case .run: - Event.post(.testStarted, for: (step.test, nil), configuration: configuration) + Event.post(.testStarted, for: (step.test, nil), configuration: _configuration) shouldSendTestEnded = true case let .skip(skipInfo): - Event.post(.testSkipped(skipInfo), for: (step.test, nil), configuration: configuration) + Event.post(.testSkipped(skipInfo), for: (step.test, nil), configuration: _configuration) shouldSendTestEnded = false case let .recordIssue(issue): - Event.post(.issueRecorded(issue), for: (step.test, nil), configuration: configuration) + Event.post(.issueRecorded(issue), for: (step.test, nil), configuration: _configuration) shouldSendTestEnded = false } } else { @@ -185,15 +184,15 @@ extension Runner { defer { if let step = stepGraph.value { if shouldSendTestEnded { - Event.post(.testEnded, for: (step.test, nil), configuration: configuration) + Event.post(.testEnded, for: (step.test, nil), configuration: _configuration) } - Event.post(.planStepEnded(step), for: (step.test, nil), configuration: configuration) + Event.post(.planStepEnded(step), for: (step.test, nil), configuration: _configuration) } } if let step = stepGraph.value, case .run = step.action { await Test.withCurrent(step.test) { - _ = await Issue.withErrorRecording(at: step.test.sourceLocation, configuration: configuration) { + _ = await Issue.withErrorRecording(at: step.test.sourceLocation, configuration: _configuration) { try await _applyScopingTraits(for: step.test, testCase: nil) { // Run the test function at this step (if one is present.) if let testCases = step.test.testCases { @@ -233,17 +232,11 @@ extension Runner { /// - Parameters: /// - stepGraph: The subgraph whose root value, a step, will be used to /// find children to run. - /// - configuration: The configuration with which the children of `stepGraph` - /// will be run. The default value is the current configuration, if any, - /// or else the default configuration. /// /// - Throws: Whatever is thrown from the test body. Thrown errors are /// normally reported as test failures. - private static func _runChildren( - of stepGraph: Graph, - configuration: Configuration = .current ?? .init() - ) async throws { - let childGraphs = if configuration.isParallelizationEnabled { + private static func _runChildren(of stepGraph: Graph) async throws { + let childGraphs = if _configuration.isParallelizationEnabled { // Explicitly shuffle the steps to help detect accidental dependencies // between tests due to their ordering. Array(stepGraph.children) @@ -282,23 +275,16 @@ extension Runner { /// - Parameters: /// - testCases: The test cases to be run. /// - step: The runner plan step associated with this test case. - /// - configuration: The configuration with which the test cases should run. - /// The default value is the current configuration, if any, or else the - /// default configuration. /// /// - Throws: Whatever is thrown from a test case's body. Thrown errors are /// normally reported as test failures. /// /// If parallelization is supported and enabled, the generated test cases will /// be run in parallel using a task group. - private static func _runTestCases( - _ testCases: some Sequence, - within step: Plan.Step, - configuration: Configuration = .current ?? .init() - ) async throws { + private static func _runTestCases(_ testCases: some Sequence, within step: Plan.Step) async throws { // Apply the configuration's test case filter. let testCases = testCases.lazy.filter { testCase in - configuration.testCaseFilter(testCase, step.test) + _configuration.testCaseFilter(testCase, step.test) } try await _forEach(in: testCases) { testCase in @@ -311,32 +297,25 @@ extension Runner { /// - Parameters: /// - testCase: The test case to run. /// - step: The runner plan step associated with this test case. - /// - configuration: The configuration with which the test case should run. - /// The default value is the current configuration, if any, or else the - /// default configuration. /// /// - Throws: Whatever is thrown from the test case's body. Thrown errors /// are normally reported as test failures. /// /// This function sets ``Test/Case/current``, then invokes the test case's /// body closure. - private static func _runTestCase( - _ testCase: Test.Case, - within step: Plan.Step, - configuration: Configuration = .current ?? .init() - ) async throws { + private static func _runTestCase(_ testCase: Test.Case, within step: Plan.Step) async throws { // Exit early if the task has already been cancelled. try Task.checkCancellation() - Event.post(.testCaseStarted, for: (step.test, testCase), configuration: configuration) + Event.post(.testCaseStarted, for: (step.test, testCase), configuration: _configuration) defer { - Event.post(.testCaseEnded, for: (step.test, testCase), configuration: configuration) + Event.post(.testCaseEnded, for: (step.test, testCase), configuration: _configuration) } await Test.Case.withCurrent(testCase) { let sourceLocation = step.test.sourceLocation - await Issue.withErrorRecording(at: sourceLocation, configuration: configuration) { - try await withTimeLimit(for: step.test, configuration: configuration) { + await Issue.withErrorRecording(at: sourceLocation, configuration: _configuration) { + try await withTimeLimit(for: step.test, configuration: _configuration) { try await _applyScopingTraits(for: step.test, testCase: testCase) { try await testCase.body() } @@ -346,7 +325,7 @@ extension Runner { comments: [], sourceContext: .init(backtrace: .current(), sourceLocation: sourceLocation) ) - issue.record(configuration: configuration) + issue.record(configuration: _configuration) } } } From f79bbe7cf0bc7ab02aea4b60e59794508d38c93b Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 15:23:54 -0600 Subject: [PATCH 3/7] Continue tweaking phrasing of ParallelizationTrait documentation --- Sources/Testing/Traits/ParallelizationTrait.swift | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/Sources/Testing/Traits/ParallelizationTrait.swift b/Sources/Testing/Traits/ParallelizationTrait.swift index b8382d503..9105d9736 100644 --- a/Sources/Testing/Traits/ParallelizationTrait.swift +++ b/Sources/Testing/Traits/ParallelizationTrait.swift @@ -11,14 +11,13 @@ /// A type that affects whether or not a test or suite is parallelized. /// /// When added to a parameterized test function, this trait causes that test to -/// run its cases serially instead of in parallel. When applied to a -/// non-parameterized test function, this trait has no effect. When applied to a -/// test suite, this trait causes that suite to run its contained test functions -/// and sub-suites serially instead of in parallel. +/// run its cases serially instead of in parallel. When added to a +/// non-parameterized test function, this trait has no effect. /// -/// If this trait is applied to a suite, any test functions or test suites -/// contained in that suite are also serialized (as are any tests contained in -/// those suites, and so on.) +/// When added to a test suite, this trait causes that suite to run its +/// contained test functions (including their cases, when parameterized) and +/// sub-suites serially instead of in parallel. Any children of sub-suites are +/// also run serially. /// /// This trait does not affect the execution of a test relative to its peers or /// to unrelated tests. This trait has no effect if test parallelization is From 65704b5efa280cf6f035d333ede49d4402dd6173 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 15:25:02 -0600 Subject: [PATCH 4/7] Include bug report link --- Sources/Testing/Traits/ParallelizationTrait.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Testing/Traits/ParallelizationTrait.swift b/Sources/Testing/Traits/ParallelizationTrait.swift index 9105d9736..df34f4d63 100644 --- a/Sources/Testing/Traits/ParallelizationTrait.swift +++ b/Sources/Testing/Traits/ParallelizationTrait.swift @@ -32,7 +32,7 @@ public struct ParallelizationTrait: TestTrait, SuiteTrait {} extension ParallelizationTrait: TestScoping { public func provideScope(for test: Test, testCase: Test.Case?, performing function: @Sendable () async throws -> Void) async throws { guard var configuration = Configuration.current else { - throw SystemError(description: "There is no current Configuration when attempting to provide scope for test '\(test.name)'") + throw SystemError(description: "There is no current Configuration when attempting to provide scope for test '\(test.name)'. Please file a bug report at https://github.com/swiftlang/swift-testing/issues/new") } configuration.isParallelizationEnabled = false From e9b9dc9ea7f2464031b912c358ca936187466102 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 15:29:24 -0600 Subject: [PATCH 5/7] Delete obsolete SPI instead of only deprecating --- Sources/Testing/CMakeLists.txt | 1 - Sources/Testing/Running/Runner.Plan.swift | 22 +----------- Sources/Testing/Traits/SPIAwareTrait.swift | 39 ---------------------- 3 files changed, 1 insertion(+), 61 deletions(-) delete mode 100644 Sources/Testing/Traits/SPIAwareTrait.swift diff --git a/Sources/Testing/CMakeLists.txt b/Sources/Testing/CMakeLists.txt index f205561a8..16a173b4b 100644 --- a/Sources/Testing/CMakeLists.txt +++ b/Sources/Testing/CMakeLists.txt @@ -96,7 +96,6 @@ add_library(Testing Traits/ConditionTrait+Macro.swift Traits/HiddenTrait.swift Traits/ParallelizationTrait.swift - Traits/SPIAwareTrait.swift Traits/Tags/Tag.Color.swift Traits/Tags/Tag.Color+Loading.swift Traits/Tags/Tag.List.swift diff --git a/Sources/Testing/Running/Runner.Plan.swift b/Sources/Testing/Running/Runner.Plan.swift index 26cb00d14..519c548a0 100644 --- a/Sources/Testing/Running/Runner.Plan.swift +++ b/Sources/Testing/Running/Runner.Plan.swift @@ -15,27 +15,7 @@ extension Runner { public enum Action: Sendable { /// A type describing options to apply to actions of case /// ``Runner/Plan/Action/run(options:)`` when they are run. - public struct RunOptions: Sendable, Codable { - /// Whether or not this step should be run in parallel with other tests. - /// - /// By default, all steps in a runner plan are run in parallel if the - /// ``Configuration/isParallelizationEnabled`` property of the - /// configuration passed during initialization has a value of `true`. - /// - /// Traits such as ``Trait/serialized`` applied to individual tests may - /// affect whether or not that test is parallelized. - /// - /// ## See Also - /// - /// - ``ParallelizationTrait`` - @available(*, deprecated, message: "The 'isParallelizationEnabled' property is deprecated and no longer used. Its value is always false.") - public var isParallelizationEnabled: Bool { - get { - false - } - set {} - } - } + public struct RunOptions: Sendable, Codable {} /// The test should be run. /// diff --git a/Sources/Testing/Traits/SPIAwareTrait.swift b/Sources/Testing/Traits/SPIAwareTrait.swift deleted file mode 100644 index 72171b016..000000000 --- a/Sources/Testing/Traits/SPIAwareTrait.swift +++ /dev/null @@ -1,39 +0,0 @@ -// -// This source file is part of the Swift.org open source project -// -// Copyright (c) 2024 Apple Inc. and the Swift project authors -// Licensed under Apache License v2.0 with Runtime Library Exception -// -// See https://swift.org/LICENSE.txt for license information -// See https://swift.org/CONTRIBUTORS.txt for Swift project authors -// - -/// A protocol describing traits that can be added to a test function or to a -/// test suite and that make use of SPI symbols in the testing library. -/// -/// This protocol refines ``Trait`` in various ways that require the use of SPI. -/// Ideally, such requirements will be promoted to API when their design -/// stabilizes. -@available(*, deprecated, message: "The SPIAwareTrait protocol is deprecated and no longer used.") -@_spi(ForToolsIntegrationOnly) -public protocol SPIAwareTrait: Trait { - /// Prepare to run the test to which this trait was added. - /// - /// - Parameters: - /// - test: The test to which this trait was added. - /// - action: The test plan action to use with `test`. The implementation - /// may modify this value. - /// - /// - Throws: Any error that would prevent the test from running. If an error - /// is thrown from this method, the test will be skipped and the error will - /// be recorded as an ``Issue``. - /// - /// This method is called after all tests and their traits have been - /// discovered by the testing library, but before any test has begun running. - /// It may be used to prepare necessary internal state, or to influence - /// whether the test should run. - /// - /// For types that conform to this protocol, ``Runner/Plan`` calls this method - /// instead of ``Trait/prepare(for:)``. - func prepare(for test: Test, action: inout Runner.Plan.Action) async throws -} From c05542a5be67549c77184d9fac20dd06723dee9f Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 16:58:18 -0600 Subject: [PATCH 6/7] Bring back Runner.Plan.Action.RunOptions.isParallelizationEnabled since it wasn't experimental --- Sources/Testing/Running/Runner.Plan.swift | 22 +++++++++++++++++++++- 1 file changed, 21 insertions(+), 1 deletion(-) diff --git a/Sources/Testing/Running/Runner.Plan.swift b/Sources/Testing/Running/Runner.Plan.swift index 519c548a0..26cb00d14 100644 --- a/Sources/Testing/Running/Runner.Plan.swift +++ b/Sources/Testing/Running/Runner.Plan.swift @@ -15,7 +15,27 @@ extension Runner { public enum Action: Sendable { /// A type describing options to apply to actions of case /// ``Runner/Plan/Action/run(options:)`` when they are run. - public struct RunOptions: Sendable, Codable {} + public struct RunOptions: Sendable, Codable { + /// Whether or not this step should be run in parallel with other tests. + /// + /// By default, all steps in a runner plan are run in parallel if the + /// ``Configuration/isParallelizationEnabled`` property of the + /// configuration passed during initialization has a value of `true`. + /// + /// Traits such as ``Trait/serialized`` applied to individual tests may + /// affect whether or not that test is parallelized. + /// + /// ## See Also + /// + /// - ``ParallelizationTrait`` + @available(*, deprecated, message: "The 'isParallelizationEnabled' property is deprecated and no longer used. Its value is always false.") + public var isParallelizationEnabled: Bool { + get { + false + } + set {} + } + } /// The test should be run. /// From 3b30eb47e7a9164ccadb423ff8a2281e9fab7b84 Mon Sep 17 00:00:00 2001 From: Stuart Montgomery Date: Wed, 8 Jan 2025 17:14:24 -0600 Subject: [PATCH 7/7] Simplify Runner changes more --- Sources/Testing/Running/Runner.swift | 34 +++++++++++++++------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/Sources/Testing/Running/Runner.swift b/Sources/Testing/Running/Runner.swift index 1a186117d..16eff103f 100644 --- a/Sources/Testing/Running/Runner.swift +++ b/Sources/Testing/Running/Runner.swift @@ -162,20 +162,22 @@ extension Runner { // example, a skip event only sends `.testSkipped`. let shouldSendTestEnded: Bool + let configuration = _configuration + // Determine what action to take for this step. if let step = stepGraph.value { - Event.post(.planStepStarted(step), for: (step.test, nil), configuration: _configuration) + Event.post(.planStepStarted(step), for: (step.test, nil), configuration: configuration) // Determine what kind of event to send for this step based on its action. switch step.action { case .run: - Event.post(.testStarted, for: (step.test, nil), configuration: _configuration) + Event.post(.testStarted, for: (step.test, nil), configuration: configuration) shouldSendTestEnded = true case let .skip(skipInfo): - Event.post(.testSkipped(skipInfo), for: (step.test, nil), configuration: _configuration) + Event.post(.testSkipped(skipInfo), for: (step.test, nil), configuration: configuration) shouldSendTestEnded = false case let .recordIssue(issue): - Event.post(.issueRecorded(issue), for: (step.test, nil), configuration: _configuration) + Event.post(.issueRecorded(issue), for: (step.test, nil), configuration: configuration) shouldSendTestEnded = false } } else { @@ -184,15 +186,15 @@ extension Runner { defer { if let step = stepGraph.value { if shouldSendTestEnded { - Event.post(.testEnded, for: (step.test, nil), configuration: _configuration) + Event.post(.testEnded, for: (step.test, nil), configuration: configuration) } - Event.post(.planStepEnded(step), for: (step.test, nil), configuration: _configuration) + Event.post(.planStepEnded(step), for: (step.test, nil), configuration: configuration) } } if let step = stepGraph.value, case .run = step.action { await Test.withCurrent(step.test) { - _ = await Issue.withErrorRecording(at: step.test.sourceLocation, configuration: _configuration) { + _ = await Issue.withErrorRecording(at: step.test.sourceLocation, configuration: configuration) { try await _applyScopingTraits(for: step.test, testCase: nil) { // Run the test function at this step (if one is present.) if let testCases = step.test.testCases { @@ -283,8 +285,9 @@ extension Runner { /// be run in parallel using a task group. private static func _runTestCases(_ testCases: some Sequence, within step: Plan.Step) async throws { // Apply the configuration's test case filter. + let testCaseFilter = _configuration.testCaseFilter let testCases = testCases.lazy.filter { testCase in - _configuration.testCaseFilter(testCase, step.test) + testCaseFilter(testCase, step.test) } try await _forEach(in: testCases) { testCase in @@ -307,15 +310,17 @@ extension Runner { // Exit early if the task has already been cancelled. try Task.checkCancellation() - Event.post(.testCaseStarted, for: (step.test, testCase), configuration: _configuration) + let configuration = _configuration + + Event.post(.testCaseStarted, for: (step.test, testCase), configuration: configuration) defer { - Event.post(.testCaseEnded, for: (step.test, testCase), configuration: _configuration) + Event.post(.testCaseEnded, for: (step.test, testCase), configuration: configuration) } await Test.Case.withCurrent(testCase) { let sourceLocation = step.test.sourceLocation - await Issue.withErrorRecording(at: sourceLocation, configuration: _configuration) { - try await withTimeLimit(for: step.test, configuration: _configuration) { + await Issue.withErrorRecording(at: sourceLocation, configuration: configuration) { + try await withTimeLimit(for: step.test, configuration: configuration) { try await _applyScopingTraits(for: step.test, testCase: testCase) { try await testCase.body() } @@ -325,7 +330,7 @@ extension Runner { comments: [], sourceContext: .init(backtrace: .current(), sourceLocation: sourceLocation) ) - issue.record(configuration: _configuration) + issue.record(configuration: configuration) } } } @@ -340,9 +345,6 @@ extension Runner { /// /// - Parameters: /// - runner: The runner to run. - /// - configuration: The configuration to use for running. The value of this - /// argument temporarily replaces the value of `runner`'s - /// ``Runner/configuration`` property. /// /// This function is `static` so that it cannot accidentally reference `self` /// or `self.configuration` when it should use a modified copy of either.