Skip to content

Split .bug() into two taking a URL or a number. #401

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 1 commit into from
May 24, 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
48 changes: 34 additions & 14 deletions Sources/Testing/Testing.docc/AssociatingBugs.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,35 +25,55 @@ specific bugs with tests that reproduce them or verify they are fixed.

## Associate a bug with a test

To associate a bug with a test, use the ``Trait/bug(_:_:)-2u8j9`` or
``Trait/bug(_:_:)-7mo2w`` function. The first argument to this function is the
bug's _identifier_ in its bug-tracking system:
To associate a bug with a test, use one of these functions:
- ``Trait/bug(_:_:)``
- ``Trait/bug(_:id:_:)-10yf5``
- ``Trait/bug(_:id:_:)-3vtpl``

The first argument to these functions is a URL representing the bug in its
bug-tracking system:

```swift
@Test("Food truck engine works", .bug("https://www.example.com/issues/12345"))
func engineWorks() async {
var foodTruck = FoodTruck()
await foodTruck.engine.start()
#expect(foodTruck.engine.isRunning)
}
```

You can also specify the bug's _unique identifier_ in its bug-tracking system in
addition to, or instead of, its URL:

```swift
@Test("Food truck engine works", .bug("12345"), .bug(67890))
@Test(
"Food truck engine works",
.bug(id: "12345"),
.bug("https://www.example.com/issues/67890", id: 67890)
)
func engineWorks() async {
var foodTruck = FoodTruck()
await foodTruck.engine.start()
#expect(foodTruck.engine.isRunning)
}
```

The bug identifier can be specified as an integer or as a string; if it is
specified as a string, it must be parseable as an unsigned integer or as a URL.
For more information on the formats recognized by the testing library, see
<doc:BugIdentifiers>.
A bug's URL is passed as a string and must be parseable according to
[RFC&nbsp;3986](https://www.ietf.org/rfc/rfc3986.txt). A bug's unique identifier
can be passed as an integer or as a string. For more information on the formats
recognized by the testing library, see <doc:BugIdentifiers>.

## Add comments to associated bugs
## Add titles to associated bugs

A bug identifier may be insufficient to uniquely and clearly identify a bug
associated with a test. Bug trackers universally provide a "title" field for
bugs that is not visible to the testing library. To add a bug's title to a test,
include it after the bug's identifier:
A bug's unique identifier or URL may be insufficient to uniquely and clearly
identify a bug associated with a test. Bug trackers universally provide a
"title" field for bugs that is not visible to the testing library. To add a
bug's title to a test, include it after the bug's unique identifier or URL:

```swift
@Test(
"Food truck has napkins",
.bug("12345", "Forgot to buy more napkins")
.bug(id: "12345", "Forgot to buy more napkins")
)
func hasNapkins() async {
...
Expand Down
63 changes: 29 additions & 34 deletions Sources/Testing/Testing.docc/BugIdentifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,46 +14,41 @@ Examine how the testing library interprets bug identifiers provided by developer

## Overview

As a convenience, the testing library assumes that bug identifiers with specific
formats are associated with some common bug-tracking systems.
The testing library supports two distinct ways to identify a bug:

1. A URL linking to more information about the bug; and
2. A unique identifier in the bug's associated bug-tracking system.

- Note: "Bugs" as described in this document may also be referred to as
"issues." To avoid confusion with the ``Issue`` type in the testing library,
this document consistently refers to them as "bugs."

### Recognized formats

- If the bug identifier can be parsed as a URL according to
[RFC 3986](https://www.ietf.org/rfc/rfc3986.txt), it is assumed to represent
an issue tracked at that URL.
- If the bug identifier begins with `"FB"` and the rest of it can be parsed as
an unsigned integer, it's assumed to represent a bug filed with the
[Apple Feedback Assistant](https://feedbackassistant.apple.com).
- If the bug identifier can be parsed as an unsigned integer, it's assumed to
represent an issue with that numeric identifier in an unspecified bug-tracking
system.
- All other bug identifiers are considered invalid and will cause the compiler
to generate an error at compile time.
"issues." To avoid confusion with the ``Issue`` type in the testing library,
this document consistently refers to them as "bugs."

<!--
Possible additional formats we could recognize (which would require special
handling to detect:
A bug may have both an associated URL _and_ an associated unique identifier. It
must have at least one or the other in order for the testing library to be able
to interpret it correctly.

- If the bug identifier begins with `"#"` and can be parsed as a positive
integer, it is assumed to represent a [GitHub](https://github.com) issue in
the same repository as the test.
-->
To create an instance of ``Bug`` with a URL, use the ``Trait/bug(_:_:)`` trait.
At compile time, the testing library will validate that the given string can be
parsed as a URL according to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt).

To create an instance of ``Bug`` with a bug's unique identifier, use the
``Trait/bug(_:id:_:)-10yf5`` trait. The testing library does not require that a
bug's unique identifier match any particular format, but will interpret unique
identifiers starting with `"FB"` as referring to bugs tracked with the
[Apple Feedback Assistant](https://feedbackassistant.apple.com). For
convenience, you can also directly pass an integer as a bug's identifier using
``Trait/bug(_:id:_:)-3vtpl``.

### Examples

| Trait function | Valid | Inferred bug-tracking system |
|-|:-:|-|
| `.bug(12345)` | Yes | None |
| `.bug("12345")` | Yes | None |
| `.bug("Things don't work")` | **No** | None |
| `.bug("https://github.com/apple/swift/pull/12345")` | Yes | [GitHub Issues for the Swift project](https://github.com/apple/swift/issues) |
| `.bug("https://bugs.webkit.org/show_bug.cgi?id=12345")` | Yes | [WebKit Bugzilla](https://bugs.webkit.org/) |
| `.bug("FB12345")` | Yes | Apple Feedback Assistant | <!-- SEE ALSO: rdar://104582015 -->
| Trait Function | Inferred Bug-Tracking System |
|-|-|
| `.bug(id: 12345)` | None |
| `.bug(id: "12345")` | None |
| `.bug("https://www.example.com?id=12345", id: "12345")` | None |
| `.bug("https://github.com/apple/swift/pull/12345")` | [GitHub Issues for the Swift project](https://github.com/apple/swift/issues) |
| `.bug("https://bugs.webkit.org/show_bug.cgi?id=12345")` | [WebKit Bugzilla](https://bugs.webkit.org/) |
| `.bug(id: "FB12345")` | Apple Feedback Assistant | <!-- SEE ALSO: rdar://104582015 -->
<!--
| `.bug("#12345")` | Yes | GitHub Issues for the current repository (if hosted there) |
| `.bug(id: "#12345")` | GitHub Issues for the current repository (if hosted there) |
-->
14 changes: 10 additions & 4 deletions Sources/Testing/Testing.docc/EnablingAndDisabling.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,14 @@ func isCold() async throws { ... }
```

If a test is disabled because of a problem for which there is a corresponding
bug report, you can use the ``Trait/bug(_:_:)-2u8j9`` or
``Trait/bug(_:_:)-7mo2w`` function to show the relationship:
bug report, you can use one of these functions to show the relationship
between the test and the bug report:

- ``Trait/bug(_:_:)``
- ``Trait/bug(_:id:_:)-10yf5``
- ``Trait/bug(_:id:_:)-3vtpl``

For example, the following test cannot run due to bug number `"12345"`:

```swift
@Test(
Expand All @@ -96,8 +102,8 @@ func isCold() async throws { ... }
```

If a test has multiple conditions applied to it, they must _all_ pass for it to
run. Otherwise, the test notes the first condition to fail as the reason the test
is skipped.
run. Otherwise, the test notes the first condition to fail as the reason the
test is skipped.

### Handle complex conditions

Expand Down
5 changes: 3 additions & 2 deletions Sources/Testing/Testing.docc/Traits.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,9 @@ HIDDEN: .serialized is experimental SPI pending feature review.
- <doc:AssociatingBugs>
- <doc:BugIdentifiers>
- ``Tag()``
- ``Trait/bug(_:_:)-2u8j9``
- ``Trait/bug(_:_:)-7mo2w``
- ``Trait/bug(_:_:)``
- ``Trait/bug(_:id:_:)-10yf5``
- ``Trait/bug(_:id:_:)-3vtpl``

### Creating custom traits

Expand Down
5 changes: 3 additions & 2 deletions Sources/Testing/Testing.docc/Traits/Trait.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors

### Associating bugs

- ``Trait/bug(_:_:)-2u8j9``
- ``Trait/bug(_:_:)-7mo2w``
- ``Trait/bug(_:_:)``
- ``Trait/bug(_:id:_:)-10yf5``
- ``Trait/bug(_:id:_:)-3vtpl``

### Adding information to tests
- ``Trait/comments``
Expand Down
115 changes: 47 additions & 68 deletions Sources/Testing/Traits/Bug.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,112 +12,91 @@
///
/// To add this trait to a test, use one of the following functions:
///
/// - ``Trait/bug(_:_:)-2u8j9``
/// - ``Trait/bug(_:_:)-7mo2w``
/// - ``Trait/bug(_:_:)``
/// - ``Trait/bug(_:id:_:)-10yf5``
/// - ``Trait/bug(_:id:_:)-3vtpl``
public struct Bug {
/// The identifier of this bug in the associated bug-tracking system.
/// A URL linking to more information about the bug, if available.
///
/// The value of this property represents a URL conforming to
/// [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt).
public var url: String?

/// A unique identifier in this bug's associated bug-tracking system, if
/// available.
///
/// For more information on how the testing library interprets bug
/// identifiers, see <doc:BugIdentifiers>.
public var identifier: String
public var id: String?

/// An optional, user-specified comment describing this trait.
public var comment: Comment?
/// The human-readable title of the bug, if specified by the test author.
public var title: Comment?
}

// MARK: - Equatable, Hashable, Comparable
// MARK: - Equatable, Hashable

extension Bug: Equatable, Hashable, Comparable {
extension Bug: Equatable, Hashable {
public static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.identifier == rhs.identifier
lhs.url == rhs.url && lhs.id == rhs.id
}

public func hash(into hasher: inout Hasher) {
hasher.combine(identifier)
}

public static func <(lhs: Self, rhs: Self) -> Bool {
lhs.identifier < rhs.identifier
hasher.combine(url)
hasher.combine(id)
}
}

// MARK: - Codable

extension Bug: Codable {
/// A temporary explicit implementation of this type's coding keys enumeration
/// to support the refactored form of `Bug` from [#412](https://github.com/apple/swift-testing/pull/412).
private enum _CodingKeys: String, CodingKey {
// Existing properties.
case identifier = "identifier"
case comment = "comment"

// Proposed new properties.
case id = "id"
case url = "url"
case title = "title"
}

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: _CodingKeys.self)

try container.encode(identifier, forKey: .identifier)
try container.encodeIfPresent(comment, forKey: .comment)

// Temporary compatibility shims to support the refactored form of Bug from
// https://github.com/apple/swift-testing/pull/412 .
if identifier.contains(":") {
try container.encode(identifier, forKey: .url)
} else {
try container.encode(identifier, forKey: .id)
}
try container.encodeIfPresent(comment, forKey: .title)
}

public init(from decoder: any Decoder) throws {
let container = try decoder.container(keyedBy: _CodingKeys.self)
identifier = try container.decodeIfPresent(String.self, forKey: .identifier)
// Temporary compatibility shims to support the refactored form of Bug
// from https://github.com/apple/swift-testing/pull/412 .
?? container.decodeIfPresent(String.self, forKey: .id)
?? container.decode(String.self, forKey: .url)
comment = try container.decodeIfPresent(Comment.self, forKey: .comment)
?? container.decodeIfPresent(Comment.self, forKey: .title)
}
}
extension Bug: Codable {}

// MARK: - Trait, TestTrait, SuiteTrait

extension Bug: TestTrait, SuiteTrait {
public var comments: [Comment] {
Array(comment)
Array(title)
}
}

extension Trait where Self == Bug {
/// Construct a bug to track with a test.
///
/// - Parameters:
/// - identifier: The identifier of this bug in the associated bug-tracking
/// system. For more information on how this value is interpreted, see the
/// documentation for ``Bug``.
/// - comment: An optional, user-specified comment describing this trait.
/// - url: A URL referring to this bug in the associated bug-tracking
/// system.
/// - title: Optionally, the human-readable title of the bug.
///
/// - Returns: An instance of ``Bug`` representing the specified bug.
public static func bug(_ url: _const String, _ title: Comment? = nil) -> Self {
Self(url: url, title: title)
}

/// Construct a bug to track with a test.
///
/// - Parameters:
/// - url: A URL referring to this bug in the associated bug-tracking
/// system.
/// - id: The unique identifier of this bug in its associated bug-tracking
/// system.
/// - title: Optionally, the human-readable title of the bug.
///
/// - Returns: An instance of ``Bug`` representing the specified bug.
public static func bug(_ identifier: String, _ comment: Comment? = nil) -> Self {
Self(identifier: identifier, comment: comment)
public static func bug(_ url: _const String? = nil, id: some Numeric, _ title: Comment? = nil) -> Self {
Self(url: url, id: String(describing: id), title: title)
}

/// Construct a bug to track with a test.
///
/// - Parameters:
/// - identifier: The identifier of this bug in the associated bug-tracking
/// system. For more information on how this value is interpreted, see the
/// documentation for ``Bug``.
/// - comment: An optional, user-specified comment describing this trait.
/// - url: A URL referring to this bug in the associated bug-tracking
/// system.
/// - id: The unique identifier of this bug in its associated bug-tracking
/// system.
/// - title: Optionally, the human-readable title of the bug.
///
/// - Returns: An instance of ``Bug`` representing the specified bug.
public static func bug(_ identifier: some Numeric, _ comment: Comment? = nil) -> Self {
Self(identifier: String(describing: identifier), comment: comment)
public static func bug(_ url: _const String? = nil, id: _const String, _ title: Comment? = nil) -> Self {
Self(url: url, id: id, title: title)
}
}

Expand Down
6 changes: 4 additions & 2 deletions Sources/Testing/Traits/Comment.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@
/// or `@Suite` attribute. See <doc:AddingComments> for more details.
///
/// - Note: This type is not intended to reference bugs related to a test.
/// Instead, use ``Trait/bug(_:_:)-2u8j9`` or ``Trait/bug(_:_:)-7mo2w``.
/// Instead, use ``Trait/bug(_:_:)``, ``Trait/bug(_:id:_:)-10yf5``, or
/// ``Trait/bug(_:id:_:)-3vtpl``.
public struct Comment: RawRepresentable, Sendable {
/// The single comment string contained in this instance.
///
Expand Down Expand Up @@ -121,7 +122,8 @@ extension Trait where Self == Comment {
/// comment.
///
/// - Note: This function is not intended to reference bugs related to a test.
/// Instead, use ``Trait/bug(_:_:)-2u8j9`` or ``Trait/bug(_:_:)-7mo2w``.
/// Instead, use ``Trait/bug(_:_:)``, ``Trait/bug(_:id:_:)-10yf5``, or
/// ``Trait/bug(_:id:_:)-3vtpl``.
public static func comment(_ comment: String) -> Self {
Self(rawValue: comment, kind: .trait)
}
Expand Down
Loading