Skip to content

Commit e678d1b

Browse files
authored
Ensure that a CustomExecutionTrait's teardown logic occurs _after_ nested tests run. (#526)
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. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent c0b1fd2 commit e678d1b

File tree

2 files changed

+67
-20
lines changed

2 files changed

+67
-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: 45 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,49 @@ struct CustomExecutionTraitTests {
7656
}.run(configuration: configuration)
7757
}
7858
}
59+
60+
@Test("Teardown occurs after child tests run")
61+
func teardownOccursAtEnd() async throws {
62+
await runTest(for: TestsWithCustomTraitWithStrongOrdering.self, configuration: .init())
63+
}
64+
}
65+
66+
// MARK: - Fixtures
67+
68+
private struct CustomTrait: CustomExecutionTrait, TestTrait {
69+
var before: Confirmation
70+
var after: Confirmation
71+
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
72+
before()
73+
defer {
74+
after()
75+
}
76+
try await function()
77+
}
78+
}
79+
80+
private struct CustomThrowingErrorTrait: CustomExecutionTrait, TestTrait {
81+
fileprivate struct CustomTraitError: Error {}
82+
83+
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
84+
throw CustomTraitError()
85+
}
86+
}
87+
88+
struct DoSomethingBeforeAndAfterTrait: CustomExecutionTrait, SuiteTrait, TestTrait {
89+
static let state = Locked(rawValue: 0)
90+
91+
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Testing.Test, testCase: Testing.Test.Case?) async throws {
92+
#expect(Self.state.increment() == 1)
93+
94+
try await function()
95+
#expect(Self.state.increment() == 3)
96+
}
97+
}
98+
99+
@Suite(.hidden, DoSomethingBeforeAndAfterTrait())
100+
struct TestsWithCustomTraitWithStrongOrdering {
101+
@Test(.hidden) func f() async {
102+
#expect(DoSomethingBeforeAndAfterTrait.state.increment() == 2)
103+
}
79104
}

0 commit comments

Comments
 (0)