Skip to content

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

Merged
merged 4 commits into from
Nov 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 20 additions & 4 deletions Sources/Testing/Issues/KnownIssue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ public typealias KnownIssueMatcher = @Sendable (_ issue: Issue) -> Bool
/// be attributed.
/// - body: The function to invoke.
///
/// Use this function when a test is known to raise one or more issues that
/// Use this function when a test is known to record one or more issues that
/// should not cause the test to fail. For example:
///
/// ```swift
Expand All @@ -112,6 +112,10 @@ public typealias KnownIssueMatcher = @Sendable (_ issue: Issue) -> Bool
/// while others should continue to cause test failures, use
/// ``withKnownIssue(_:isIntermittent:sourceLocation:_:when:matching:)``
/// instead.
///
/// ## See Also
///
/// - <doc:known-issues>
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
Expand Down Expand Up @@ -143,7 +147,7 @@ public func withKnownIssue(
/// - Throws: Whatever is thrown by `body`, unless it is matched by
/// `issueMatcher`.
///
/// Use this function when a test is known to raise one or more issues that
/// Use this function when a test is known to record one or more issues that
/// should not cause the test to fail, or if a precondition affects whether
/// issues are known to occur. For example:
///
Expand All @@ -165,6 +169,10 @@ public func withKnownIssue(
/// instead.
///
/// - Note: `issueMatcher` may be invoked more than once for the same issue.
///
/// ## See Also
///
/// - <doc:known-issues>
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
Expand Down Expand Up @@ -205,7 +213,7 @@ public func withKnownIssue(
/// be attributed.
/// - body: The function to invoke.
///
/// Use this function when a test is known to raise one or more issues that
/// Use this function when a test is known to record one or more issues that
/// should not cause the test to fail. For example:
///
/// ```swift
Expand All @@ -221,6 +229,10 @@ public func withKnownIssue(
/// while others should continue to cause test failures, use
/// ``withKnownIssue(_:isIntermittent:isolation:sourceLocation:_:when:matching:)``
/// instead.
///
/// ## See Also
///
/// - <doc:known-issues>
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
Expand Down Expand Up @@ -254,7 +266,7 @@ public func withKnownIssue(
/// - Throws: Whatever is thrown by `body`, unless it is matched by
/// `issueMatcher`.
///
/// Use this function when a test is known to raise one or more issues that
/// Use this function when a test is known to record one or more issues that
/// should not cause the test to fail, or if a precondition affects whether
/// issues are known to occur. For example:
///
Expand All @@ -276,6 +288,10 @@ public func withKnownIssue(
/// instead.
///
/// - Note: `issueMatcher` may be invoked more than once for the same issue.
///
/// ## See Also
///
/// - <doc:known-issues>
public func withKnownIssue(
_ comment: Comment? = nil,
isIntermittent: Bool = false,
Expand Down
141 changes: 137 additions & 4 deletions Sources/Testing/Testing.docc/known-issues.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Copy link
Contributor

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.

Copy link
Contributor Author

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

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
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.

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)
}
}
```
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Contributor Author

Choose a reason for hiding this comment

The 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

Expand Down