Skip to content
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

[SWT-NNNN] Introduce API allowing traits to customize test execution #733

Open
wants to merge 10 commits into
base: main
Choose a base branch
from

Conversation

stmontgomery
Copy link
Contributor

This includes an API proposal and code changes to introduce new API for custom traits to customize test execution.

View the API proposal for more details.

Motivation:

One of the primary motivations for the trait system in Swift Testing, as described in the vision document, is to provide a way to customize the behavior of tests which have things in common. If all the tests in a given suite type need the same custom behavior, init and/or deinit (if applicable) can be used today. But if only some of the tests in a suite need custom behavior, or tests across different levels of the suite hierarchy need it, traits would be a good place to encapsulate common logic since they can be applied granularly per-test or per-suite. This aspect of the vision for traits hasn't been realized yet, though: the Trait protocol does not offer a way for a trait to customize the execution of the tests or suites it's applied to.

Customizing a test's behavior typically means running code either before or after it runs, or both. Consolidating common set-up and tear-down logic allows each test function to be more succinct with less repetitive boilerplate so it can focus on what makes it unique.

Checklist:

  • Code and documentation should follow the style of the Style Guide.
  • If public symbols are renamed or modified, DocC references should be updated.

@stmontgomery stmontgomery added documentation Improvements or additions to documentation enhancement New feature or request public-api Affects public API api-proposal API proposal PRs (documentation only) labels Sep 25, 2024
@stmontgomery stmontgomery added this to the Swift 6.1 milestone Sep 25, 2024
@stmontgomery stmontgomery self-assigned this Sep 25, 2024
@stmontgomery stmontgomery force-pushed the publicize-CustomExecutionTrait branch from 75975cf to 75dd9b6 Compare September 25, 2024 21:18
@grynspan grynspan changed the title Introduce API allowing traits to customize test execution [SWT-NNNN] Introduce API allowing traits to customize test execution Sep 25, 2024
@stmontgomery
Copy link
Contributor Author

@swift-ci please test Windows

struct MockAPICredentialsTrait: TestTrait, CustomTestExecuting {
func execute(_ function: @Sendable () async throws -> Void, for test: Test, testCase: Test.Case?) async throws {
let mockCredentials = APICredentials(apiKey: "...")
try await APICredentials.$current.withValue(mockCredentials) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We think task locals are going to be a particularly common use case, right? Perhaps we should build out a trait type that sets a task local:

@Test(.withValue(123, for: $foo)) func f() { ... }

Or even if we don't make it a trait, can we make it easier to write a trait that sets a task local with minimal boilerplate?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agree that would be a very useful, built-in trait to have. Let's consider that separately, since it would ultimately leverage the API proposed here

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Doesn't need to be part of this proposal, but I think it does affect this proposal. Might be worth coming up with an example of when you'd use this stuff other than to set a task local.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. Briefly: not all work before/after a test is simply mutating state in that process. It may involve writing to the filesystem, or interacting with other system-wide resources. (Clearly, parallelism would need to be considered in such scenarios.)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also: Users can still use this facility to mutate global or static state, as long as it complies with Swift concurrency rules

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we make sure to add this example to the discussion?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Where, specifically, do you recommend we place this example code snippet?

@jakepetroules
Copy link

@stmontgomery Per our conversation offline you mentioned that order of execution of custom execution traits is well-defined to be outer-to-inner, left-to-right -- it would be great if we could explicitly add a note about that in the documentation so that folks are aware of the expected behavior!

@grynspan grynspan modified the milestones: Swift 6.1, Swift 6.2 Nov 19, 2024
@stmontgomery
Copy link
Contributor Author

@stmontgomery Per our conversation offline you mentioned that order of execution of custom execution traits is well-defined to be outer-to-inner, left-to-right -- it would be great if we could explicitly add a note about that in the documentation so that folks are aware of the expected behavior!

I've added language specifying this ordering to the updated proposal and PR

@stmontgomery stmontgomery force-pushed the publicize-CustomExecutionTrait branch from fb4f00e to c53220e Compare November 20, 2024 20:32
@stmontgomery
Copy link
Contributor Author

@swift-ci please test

suite type (including `init`, `deinit`, and the test function itself) can access
`self`, so this would grant traits applied to an instance test method access to
the instance as well. This is certainly interesting, but poses several technical
challenges that puts it out of scope of this proposal.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Mind adding a quick note about a task-local trait here? Danke.

@@ -72,43 +192,14 @@ extension Trait {
}
}

extension Trait where TestScopeProvider == Never {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These extensions will show up (at least minimally) in DocC, so we might want to consider formally documenting them with full markup since they have some interesting behaviours.

(Not a blocker for the PR, just noting.)

}

extension SuiteTrait where Self: TestScoping {
// If `test` is a suite, returns `nil` if `isRecursive` is `true`, else `self`.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The comments here literally describe the current implementations but don't really explain why they are what they are. Why does this function return nil if isRecursive is true? etc.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
api-proposal API proposal PRs (documentation only) documentation Improvements or additions to documentation enhancement New feature or request public-api Affects public API
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants