Update Test.traits setter to filter applicable values rather than crashing when non-applicable values are encountered#1548
Conversation
grynspan
left a comment
There was a problem hiding this comment.
Works for me. I'd like at least one of my colleagues to also review before we merge it. :)
grynspan
left a comment
There was a problem hiding this comment.
Oh, we need some test coverage!
|
Good shout! I'm also adding some test coverage over in #1531. I'll get cooking on those. |
|
Thank you @gmedori for taking this on! I recall @grynspan proposing this solution once in the past, and back then, I raised a concern that there was one potential use case that this solution would make impossible:
It’s fair to ask whether that's a use case that matters or is useful. So far, the only speculative use case I have come up with is a trait which only conforms to Some time has passed since |
|
@gmedori actually independently raised that question with me in DMs. As I replied there:
|
|
@stmontgomery thanks for the thoughtful comment!
Yup as Jonathan said, this was the exact scenario I thought of that is basically unsupported by this change. This scenario was described in a forum post and was the impetus for the original issue being filed, so I wanted to at least consider it. When I brought it up, I was actually concerned that where we used to crash, now we just silently ignore. But I convinced myself that the behavior merely matches the semantics of the declaration. That is, if you make your trait conform to When reading the original forum post, it seemed like the original issue author brought it up not as a production issue, but rather an educational one. And if we haven't seen more concern/discussion about it, I'd agree that this use case is unlikely to be important, which makes me more bullish on this approach. |
|
If we think there's a practical issue here, we could generate a |
|
Yeah that was what I was thinking when I posted #1048 (comment) in the original issue. The more that I think about it, though, the more that I'm feeling that the thing we want to detect is not really detectable. Specifically, this is the scenario that we would consider "invalid": struct RecursiveTrait: SuiteTrait, TestScoping { // <-- ⚠️ No TestTrait conformance
let isRecursive: Bool = true
func provideScope(for test: Test, testCase: Test.Case?, performing function: () async throws -> Void) async throws {
if test.isSuite {
// 1. Do expensive work before the suite
// ...
// 2. Then run the test suite
try await function()
} else {
// Do cheap work before each test
// ❌ Will never happen because this trait is never applied to tests
}
}
func scopeProvider(for test: Test, testCase: Test.Case?) -> RecursiveTrait? {
return Self() // Provide a non-nil value always, regardless of suite or test
}
}I'm still a bit new to all this so please correct me if I'm missing something, but my understanding is that it's perfectly valid to have a ... Can we? ( So I'll preface this by saying I don't think the juice is worth the squeeze, but purely as a thought exercise, we could build in some sort of alerting mechanism inside of the getter for But that sounds super complex, hacky, and fragile. Furthermore the most refined signal we could get is whether |
2d0f746 to
e1235a0
Compare
… than crashing when non-applicable values were encountered
9432a50 to
0cb7f9e
Compare
Prior to this PR, when a developer would define their own
SuiteTraitand provide an implementation forisRecursivethat resolved totrue, the test process would crash unless they also had the trait conform toTestTrait. This happened because we consider a non-suiteTesthaving aSuiteTraitto be invalid, so we placed a precondition failure in the setter forTest.traits. When we would recursively attempt to apply the trait, it would do so for all sub-suites and tests, tripping the precondition.While this is technically correct behavior, the failure mode is a poor experience that doesn't adequately communicate what went wrong. Here's an example of the log when the crash happens:
To avoid this crash and to allow for previously infeasible trait configurations, this change updates the
Test.traitssetter to:As a short cookbook, we then have the following:
SuiteTraitand setisRecursivetotrue(the previous crash scenario). The nested tests will ignore the trait when we attempt to set it on them.SuiteTraitandTestTrait, and setisRecursivetotrue. With the extra conformance, nested tests will pick up the trait.Resolves #1048
Checklist: