Skip to content

Commit 1a5b0f6

Browse files
committed
Ensure that a CustomExecutionTrait's teardown logic occurs _after_ nested tests run.
This PR reorders operations in `Runner` such that a test with a custom execution trait will execute operations in this order: 1. Custom execution trait "before" logic; 1. Test logic; 1. Child test logic; and finally 1. Custom execution trait "after" logic. There is currently a bug here where child test logic runs _after_ the custom execution trait runs its "after" logic, which is not intentional. Resolves rdar://125115497.
1 parent 3c93f6f commit 1a5b0f6

File tree

2 files changed

+71
-20
lines changed

2 files changed

+71
-20
lines changed

Sources/Testing/Running/Runner.swift

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,11 +209,33 @@ extension Runner {
209209
if let testCases = step.test.testCases {
210210
try await _runTestCases(testCases, within: step)
211211
}
212+
213+
// Run the children of this test (i.e. the tests in this suite.)
214+
try await _runChildren(of: stepGraph, depth: depth, lastAncestorStep: lastAncestorStep)
212215
}
213216
}
214217
}
218+
} else {
219+
// There is no test at this node in the graph, so just skip down to the
220+
// child nodes.
221+
try await _runChildren(of: stepGraph, depth: depth, lastAncestorStep: lastAncestorStep)
215222
}
223+
}
216224

225+
/// Recursively run the tests that are children of a given plan step.
226+
///
227+
/// - Parameters:
228+
/// - stepGraph: The subgraph whose root value, a step, is to be run.
229+
/// - depth: How deep into the step graph this call is. The first call has a
230+
/// depth of `0`.
231+
/// - lastAncestorStep: The last-known ancestral step, if any, of the step
232+
/// at the root of `stepGraph`. The options in this step (if its action is
233+
/// of case ``Runner/Plan/Action/run(options:)``) inform the execution of
234+
/// `stepGraph`.
235+
///
236+
/// - Throws: Whatever is thrown from the test body. Thrown errors are
237+
/// normally reported as test failures.
238+
private func _runChildren(of stepGraph: Graph<String, Plan.Step?>, depth: Int, lastAncestorStep: Plan.Step?) async throws {
217239
// Figure out the last-good step, either the one at the root of `stepGraph`
218240
// or, if it is nil, the one passed into this function. We need to track
219241
// this value in case we run into sparse sections of the graph so we don't

Tests/TestingTests/Traits/CustomExecutionTraitTests.swift

Lines changed: 49 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -10,26 +10,6 @@
1010

1111
@testable @_spi(Experimental) @_spi(ForToolsIntegrationOnly) import Testing
1212

13-
private struct CustomTrait: CustomExecutionTrait, TestTrait {
14-
var before: Confirmation
15-
var after: Confirmation
16-
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
17-
before()
18-
defer {
19-
after()
20-
}
21-
try await function()
22-
}
23-
}
24-
25-
private struct CustomThrowingErrorTrait: CustomExecutionTrait, TestTrait {
26-
fileprivate struct CustomTraitError: Error {}
27-
28-
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
29-
throw CustomTraitError()
30-
}
31-
}
32-
3313
@Suite("CustomExecutionTrait Tests")
3414
struct CustomExecutionTraitTests {
3515
@Test("Execute code before and after a non-parameterized test.")
@@ -76,4 +56,53 @@ struct CustomExecutionTraitTests {
7656
}.run(configuration: configuration)
7757
}
7858
}
59+
60+
@Test("Teardown occurs after child tests run")
61+
func teardownOccursAtEnd() async throws {
62+
var configuration = Configuration()
63+
configuration.eventHandler = { event, _ in
64+
print(event)
65+
}
66+
await runTest(for: TestsWithCustomTrait.self, configuration: configuration)
67+
}
68+
}
69+
70+
// MARK: - Fixtures
71+
72+
private struct CustomTrait: CustomExecutionTrait, TestTrait {
73+
var before: Confirmation
74+
var after: Confirmation
75+
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
76+
before()
77+
defer {
78+
after()
79+
}
80+
try await function()
81+
}
82+
}
83+
84+
private struct CustomThrowingErrorTrait: CustomExecutionTrait, TestTrait {
85+
fileprivate struct CustomTraitError: Error {}
86+
87+
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
88+
throw CustomTraitError()
89+
}
90+
}
91+
92+
struct DoSomethingBeforeAndAfterTrait: CustomExecutionTrait, SuiteTrait, TestTrait {
93+
static let state = Locked(rawValue: 0)
94+
95+
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Testing.Test, testCase: Testing.Test.Case?) async throws {
96+
#expect(Self.state.increment() == 1)
97+
98+
try await function()
99+
#expect(Self.state.increment() == 3)
100+
}
101+
}
102+
103+
@Suite(.hidden, DoSomethingBeforeAndAfterTrait())
104+
struct TestsWithCustomTrait { // Trait should only excecute once for each test since it is a suite trait, if we want to execute trait logic for each test set isRecursive to true
105+
@Test(.hidden) func f() async {
106+
#expect(DoSomethingBeforeAndAfterTrait.state.increment() == 2)
107+
}
79108
}

0 commit comments

Comments
 (0)