Generic utility for invoking a TestScoping trait

In a recent pitch thread ([Pitch] Issue Handling Traits), @grynspan and @x-sheep indicated interest in having the capabilities of the proposed trait be made available for use more narrowly, to enclose smaller portions of a test function or compose with other utilities.

While contemplating that, it occurred to me that many, if not most, traits that provide custom test scoping logic would likely benefit from being available either as a trait and more narrowly as a standalone function. I imagine traits which do work once-per-test (as opposed to once-per-suite) could be especially useful in more granular scopes. Traits which do some "expensive" action, of course, may make less sense to use in this way, so it would need to be a case-by-case decision on the part of the test author.

Setting aside which traits this would or wouldn't be useful for, anyone can accomplish this today, for any trait, by leveraging ST-0007: Test Scoping Traits via a small utility function such as the following:

/// Perform a function with scope provided by the specified trait.
///
/// - Parameters:
///   - trait: The trait which should provide custom scope (if applicable).
///   - function: The function to perform.
///
/// This first gets the test scope provider for `trait`. If it is `nil`, then
/// `function` is called immediately. If the trait has a non-`nil` scope
/// provider, `function` is passed to `provideScope()` so that it's called with
/// scope provided by the trait's scope provider.
func withScoping(by trait: some Trait, perform function: @Sendable () async throws -> Void) async throws {
  guard let test = Test.current,
        let scopeProvider = trait.scopeProvider(for: test, testCase: .current)
  else {
    return try await function()
  }

  try await scopeProvider.provideScope(for: test, testCase: .current, performing: function)
}

This is out of scope for the proposal pitched in that thread, but it occurred to me something like this might be worth adding to the testing library as a generic helper utility, to remove the need for every scoping trait to offer a bespoke standalone function. I'm curious if anyone else thinks this would be worth pursuing.

2 Likes

Immediate thoughts:

  • I see the appeal of being able to generalize this to something like withScope {}
  • For some traits like ConditionTrait, the correct behaviour here may not be obvious (can't skip a test in mid-flight so maybe it just silently suppresses the body closure?) but in that case, we need some mechanism to identify the "local scope" distinct from the current suite, test, or test case
  • That's starting to smell an awful lot like activities, no?
  • Perhaps we define a new ActivityTrait protocol that operates over activities rather than whole tests, and withScope {} only takes/applies to/whatevers them rather than any trait

We know we want to design the equivalent of XCTest's activities feature, and I see a lot of overlap here, so I don't want to accidentally build something that sort of covers activities' turf but isn't entirely compatible with them when we go to build them.