apple / swift-testing

A modern, expressive testing package for Swift

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Support throwing errors from within `@Test` attributes

jmjauer opened this issue · comments

Description

The argument parameter of Test(_:_:arguments:) is currently very limited.

Adding a version of Test(_:_:arguments: () async throws -> C) would greatly improve the parameterisation possibilities (e.g. async loading of parameters from a file).

Expected behavior

No response

Actual behavior

No response

Steps to reproduce

No response

swift-testing version/commit hash

No response

Swift & OS version (output of swift --version && uname -a)

No response

The @Test macro supports async arguments. It doesn't currently support throwing arguments because that's somewhat harder to model in a reasonable way and we need to figure out what it means to throw an error from within an attribute.

Does that help at all?

The @Test macro supports async arguments.

I didn't know that - great!

It doesn't currently support throwing arguments because that's somewhat harder to model in a reasonable way and we need to figure out what it means to throw an error from within an attribute.

Wouldn't it be easy to allow () async throws -> C as an argument and let the test fail if an error is thrown? The advantage of a closure would be that a test could be more self-contained, without helper functions providing the arguments. Allowing a throwing closure would also reduce the boilerplate required by wrapping throwing code in do catch blocks and letting the error fail (or crash). So instead of:

func myArguments() async -> [String] {
    do {
        try await ...
    } catch {
        ...
    }
}

@Test("test all strings", arguments: await myArguments())
func allStrings(stirng: String) {
    ...
}

we could write

@Test("test all strings") {
    try await ...
}
func allStrings(stirng: String) {
    ...
}

I have not spent any time with macros in Swift, so there may be a limitation I am not aware of.

It is not hard to allow the developer to express try in that position—what's hard is figuring out how to actually handle the error if it occurs, and there are multiple answers that seem reasonable.

A failure to generate an arguments collection for a test means we cannot fully instantiate that test as specified by the developer. We've been leaning toward saying that we should instead instantiate a monomorphic Test instance that fails immediately with the thrown error, however this results in a test with a physical structure that does not match what the developer intended, and that could be confusing. We already do something similar for tests with unavailable arguments (i.e. @available prevented them from being resolved) but such tests are skipped at runtime anyway, so it has less of a visible impact on the test output.

we could write

@test("test all strings") {
try await ...
}
func allStrings(stirng: String) {
...
}

This would not be grammatically correct Swift, so you'd have to write something like @Test("test all strings", arguments: { try await ... }) instead. Supporting a closure in that position would require additional overloads to the Test macro. While this is technically feasible, I think we'd need some strong justification for adding those overloads when your initial example (minus the do/catch, as we are planning to support try here in the future) is equivalent.

Ok, that makes sense. Adding try would be a great addition to the api.

Thanks for your time!