-
Notifications
You must be signed in to change notification settings - Fork 102
Expand the 'Known Issues' documentation article #823
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
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,13 +10,146 @@ See https://swift.org/LICENSE.txt for license information | |
See https://swift.org/CONTRIBUTORS.txt for Swift project authors | ||
--> | ||
|
||
Highlight known issues when running tests. | ||
Mark issues as known when running tests. | ||
|
||
## Overview | ||
|
||
The testing library provides a function, `withKnownIssue()`, that you | ||
use to mark issues as known. Use this function to inform the testing library | ||
at runtime not to mark the test as failing when issues occur. | ||
The testing library provides several functions named `withKnownIssue()` that | ||
you can use to mark issues as known. Use them to inform the testing library that | ||
a test should not be marked as failing if only known issues are recorded. | ||
|
||
### Mark an expectation failure as known | ||
|
||
Consider a test function with a single expectation: | ||
|
||
```swift | ||
@Test func grillHeating() throws { | ||
var foodTruck = FoodTruck() | ||
try foodTruck.startGrill() | ||
#expect(foodTruck.grill.isHeating) // ❌ Expectation failed | ||
} | ||
``` | ||
|
||
If the value of the `isHeating` property is `false`, `#expect` will record an | ||
issue. If you cannot fix the underlying problem, you can surround the failing | ||
code in a closure passed to `withKnownIssue()`: | ||
|
||
```swift | ||
@Test func grillHeating() throws { | ||
var foodTruck = FoodTruck() | ||
try foodTruck.startGrill() | ||
withKnownIssue("Propane tank is empty") { | ||
#expect(foodTruck.grill.isHeating) // Known issue | ||
} | ||
} | ||
``` | ||
|
||
The issue recorded by `#expect` will then be considered "known" and the test | ||
will not be marked as a failure. You may include an optional comment to explain | ||
the problem or provide context. | ||
|
||
### Mark a thrown error as known | ||
|
||
If an `Error` is caught by the closure passed to `withKnownIssue()`, the issue | ||
stmontgomery marked this conversation as resolved.
Show resolved
Hide resolved
|
||
representing that caught error will be marked as known. Continuing the previous | ||
example, suppose the problem is that the `startGrill()` function is throwing an | ||
error. You can apply `withKnownIssue()` to this situation as well: | ||
|
||
```swift | ||
@Test func grillHeating() { | ||
var foodTruck = FoodTruck() | ||
withKnownIssue { | ||
try foodTruck.startGrill() // Known issue | ||
#expect(foodTruck.grill.isHeating) | ||
} | ||
} | ||
``` | ||
|
||
Because all errors thrown from the closure are caught and interpreted as known | ||
issues, the `withKnownIssue()` function is not throwing. Consequently, any | ||
subsequent code which depends on the throwing call having succeeded (such as the | ||
`#expect` after `startGrill()`) must be included in the closure to avoid | ||
additional issues. | ||
|
||
- Note: `withKnownIssue()` is recommended instead of `#expect(throws:)` for any | ||
error which is considered a known issue so that the test status and results | ||
will reflect the situation more accurately. | ||
|
||
### Match a specific issue | ||
|
||
By default, `withKnownIssue()` considers all issues recorded while invoking the | ||
body closure known. If multiple issues may be recorded, you can pass a trailing | ||
closure labeled `matching:` which will be called once for each recorded issue | ||
to determine whether it should be treated as known: | ||
|
||
```swift | ||
@Test func batteryLevel() throws { | ||
var foodTruck = FoodTruck() | ||
try withKnownIssue { | ||
let batteryLevel = try #require(foodTruck.batteryLevel) // Known | ||
#expect(batteryLevel >= 0.8) // Not considered known | ||
} matching: { issue in | ||
guard case .expectationFailed(let expectation) = issue.kind else { | ||
return false | ||
} | ||
return expectation.isRequired | ||
} | ||
} | ||
``` | ||
|
||
### Resolve a known issue | ||
|
||
If there are no issues recorded while calling `function`, `withKnownIssue()` | ||
will record a distinct issue about the lack of any issues having been recorded. | ||
This notifies you that the underlying problem may have been resolved so that you | ||
can investigate and consider removing `withKnownIssue()` if it's no longer | ||
necessary. | ||
|
||
### Handle a nondeterministic failure | ||
|
||
If `withKnownIssue()` sometimes succeeds but other times records an issue | ||
indicating there were no known issues, this may indicate a nondeterministic | ||
failure or a "flaky" test. | ||
stmontgomery marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
The first step in resolving a nondeterministic test failure is to analyze the | ||
code being tested and determine the source of the unpredictable behavior. If | ||
you discover a bug such as a race condition, the ideal resolution is to fix | ||
the underlying problem so that the code always behaves consistently even if | ||
it continues to exhibit the known issue. | ||
|
||
If the underlying problem only occurs in certain circumstances, consider | ||
including a precondition. For example, if the grill only fails to heat when | ||
there's no propane, you can pass a trailing closure labeled `when:` which | ||
determines whether issues recorded in the body closure should be considered | ||
known: | ||
|
||
```swift | ||
@Test func grillHeating() throws { | ||
var foodTruck = FoodTruck() | ||
try foodTruck.startGrill() | ||
withKnownIssue { | ||
// Only considered known when hasPropane == false | ||
#expect(foodTruck.grill.isHeating) | ||
} when: { | ||
!hasPropane | ||
} | ||
} | ||
``` | ||
|
||
If the underlying problem is unpredictable and fails at random, you can pass | ||
`isIntermittent: true` to let the testing library know that it will not always | ||
occur. Then, the testing library will not record an issue when zero known issues | ||
are recorded: | ||
|
||
```swift | ||
@Test func grillHeating() throws { | ||
var foodTruck = FoodTruck() | ||
try foodTruck.startGrill() | ||
withKnownIssue(isIntermittent: true) { | ||
#expect(foodTruck.grill.isHeating) | ||
} | ||
} | ||
``` | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you want to add one more example showing all of the knobs and doodads in a single call? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm going to decline to do this right now, mainly because the combinatorics make it difficult to have a coherent example. I could imagine offering more real-world examples in the future via some sort of example code repo/corpus. |
||
|
||
## Topics | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this specific phrasing came from our DevPubs friends? So you might want to ask them to look too.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've solicited feedback offline from @medreisbach @chuckdude @iamleeg