Skip to content

Ensure that a CustomExecutionTrait's teardown logic occurs _after_ nested tests run. #526

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Show file tree
Hide file tree
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
22 changes: 22 additions & 0 deletions Sources/Testing/Running/Runner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -209,11 +209,33 @@ extension Runner {
if let testCases = step.test.testCases {
try await _runTestCases(testCases, within: step)
}

// Run the children of this test (i.e. the tests in this suite.)
try await _runChildren(of: stepGraph, depth: depth, lastAncestorStep: lastAncestorStep)
}
}
}
} 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)
}
}

/// 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`.
///
/// - Throws: Whatever is thrown from the test body. Thrown errors are
/// normally reported as test failures.
private func _runChildren(of stepGraph: Graph<String, Plan.Step?>, 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
Expand Down
65 changes: 45 additions & 20 deletions Tests/TestingTests/Traits/CustomExecutionTraitTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -10,26 +10,6 @@

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

private struct CustomTrait: CustomExecutionTrait, TestTrait {
var before: Confirmation
var after: Confirmation
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
before()
defer {
after()
}
try await function()
}
}

private struct CustomThrowingErrorTrait: CustomExecutionTrait, TestTrait {
fileprivate struct CustomTraitError: Error {}

func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
throw CustomTraitError()
}
}

@Suite("CustomExecutionTrait Tests")
struct CustomExecutionTraitTests {
@Test("Execute code before and after a non-parameterized test.")
Expand Down Expand Up @@ -76,4 +56,49 @@ struct CustomExecutionTraitTests {
}.run(configuration: configuration)
}
}

@Test("Teardown occurs after child tests run")
func teardownOccursAtEnd() async throws {
await runTest(for: TestsWithCustomTraitWithStrongOrdering.self, configuration: .init())
}
}

// MARK: - Fixtures

private struct CustomTrait: CustomExecutionTrait, TestTrait {
var before: Confirmation
var after: Confirmation
func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
before()
defer {
after()
}
try await function()
}
}

private struct CustomThrowingErrorTrait: CustomExecutionTrait, TestTrait {
fileprivate struct CustomTraitError: Error {}

func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
throw CustomTraitError()
}
}

struct DoSomethingBeforeAndAfterTrait: CustomExecutionTrait, SuiteTrait, TestTrait {
static let state = Locked(rawValue: 0)

func execute(_ function: @escaping @Sendable () async throws -> Void, for test: Testing.Test, testCase: Testing.Test.Case?) async throws {
#expect(Self.state.increment() == 1)

try await function()
#expect(Self.state.increment() == 3)
}
}

@Suite(.hidden, DoSomethingBeforeAndAfterTrait())
struct TestsWithCustomTraitWithStrongOrdering {
@Test(.hidden) func f() async {
#expect(DoSomethingBeforeAndAfterTrait.state.increment() == 2)
}
}