Skip to content

Commit 0c2fa99

Browse files
authored
Split .bug() into two taking a URL or a number. (#401)
This PR changes the initializers for `.bug()` from ones taking an undecorated and unspecified "identifier" to ones taking either a URL (as a string) or a numeric ID ("bug number.") The existing interface is ambiguous to tools authors seeking to integrate with it. They must treat _every_ bug as potentially containing a URL with fallback paths if a bug's ID cannot be parsed as a URL. By splitting URLs and numbers up into two separate properties, tools authors can provide reliable, distinct interfaces for bugs known by number vs. those known by URL. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 2ccdc81 commit 0c2fa99

13 files changed

+182
-185
lines changed

Diff for: Sources/Testing/Testing.docc/AssociatingBugs.md

+34-14
Original file line numberDiff line numberDiff line change
@@ -25,35 +25,55 @@ specific bugs with tests that reproduce them or verify they are fixed.
2525

2626
## Associate a bug with a test
2727

28-
To associate a bug with a test, use the ``Trait/bug(_:_:)-2u8j9`` or
29-
``Trait/bug(_:_:)-7mo2w`` function. The first argument to this function is the
30-
bug's _identifier_ in its bug-tracking system:
28+
To associate a bug with a test, use one of these functions:
29+
- ``Trait/bug(_:_:)``
30+
- ``Trait/bug(_:id:_:)-10yf5``
31+
- ``Trait/bug(_:id:_:)-3vtpl``
32+
33+
The first argument to these functions is a URL representing the bug in its
34+
bug-tracking system:
35+
36+
```swift
37+
@Test("Food truck engine works", .bug("https://www.example.com/issues/12345"))
38+
func engineWorks() async {
39+
var foodTruck = FoodTruck()
40+
await foodTruck.engine.start()
41+
#expect(foodTruck.engine.isRunning)
42+
}
43+
```
44+
45+
You can also specify the bug's _unique identifier_ in its bug-tracking system in
46+
addition to, or instead of, its URL:
3147

3248
```swift
33-
@Test("Food truck engine works", .bug("12345"), .bug(67890))
49+
@Test(
50+
"Food truck engine works",
51+
.bug(id: "12345"),
52+
.bug("https://www.example.com/issues/67890", id: 67890)
53+
)
3454
func engineWorks() async {
3555
var foodTruck = FoodTruck()
3656
await foodTruck.engine.start()
3757
#expect(foodTruck.engine.isRunning)
3858
}
3959
```
4060

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

46-
## Add comments to associated bugs
66+
## Add titles to associated bugs
4767

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

5373
```swift
5474
@Test(
5575
"Food truck has napkins",
56-
.bug("12345", "Forgot to buy more napkins")
76+
.bug(id: "12345", "Forgot to buy more napkins")
5777
)
5878
func hasNapkins() async {
5979
...

Diff for: Sources/Testing/Testing.docc/BugIdentifiers.md

+29-34
Original file line numberDiff line numberDiff line change
@@ -14,46 +14,41 @@ Examine how the testing library interprets bug identifiers provided by developer
1414

1515
## Overview
1616

17-
As a convenience, the testing library assumes that bug identifiers with specific
18-
formats are associated with some common bug-tracking systems.
17+
The testing library supports two distinct ways to identify a bug:
18+
19+
1. A URL linking to more information about the bug; and
20+
2. A unique identifier in the bug's associated bug-tracking system.
1921

2022
- Note: "Bugs" as described in this document may also be referred to as
21-
"issues." To avoid confusion with the ``Issue`` type in the testing library,
22-
this document consistently refers to them as "bugs."
23-
24-
### Recognized formats
25-
26-
- If the bug identifier can be parsed as a URL according to
27-
[RFC 3986](https://www.ietf.org/rfc/rfc3986.txt), it is assumed to represent
28-
an issue tracked at that URL.
29-
- If the bug identifier begins with `"FB"` and the rest of it can be parsed as
30-
an unsigned integer, it's assumed to represent a bug filed with the
31-
[Apple Feedback Assistant](https://feedbackassistant.apple.com).
32-
- If the bug identifier can be parsed as an unsigned integer, it's assumed to
33-
represent an issue with that numeric identifier in an unspecified bug-tracking
34-
system.
35-
- All other bug identifiers are considered invalid and will cause the compiler
36-
to generate an error at compile time.
23+
"issues." To avoid confusion with the ``Issue`` type in the testing library,
24+
this document consistently refers to them as "bugs."
3725

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

42-
- If the bug identifier begins with `"#"` and can be parsed as a positive
43-
integer, it is assumed to represent a [GitHub](https://github.com) issue in
44-
the same repository as the test.
45-
-->
30+
To create an instance of ``Bug`` with a URL, use the ``Trait/bug(_:_:)`` trait.
31+
At compile time, the testing library will validate that the given string can be
32+
parsed as a URL according to [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt).
33+
34+
To create an instance of ``Bug`` with a bug's unique identifier, use the
35+
``Trait/bug(_:id:_:)-10yf5`` trait. The testing library does not require that a
36+
bug's unique identifier match any particular format, but will interpret unique
37+
identifiers starting with `"FB"` as referring to bugs tracked with the
38+
[Apple Feedback Assistant](https://feedbackassistant.apple.com). For
39+
convenience, you can also directly pass an integer as a bug's identifier using
40+
``Trait/bug(_:id:_:)-3vtpl``.
4641

4742
### Examples
4843

49-
| Trait function | Valid | Inferred bug-tracking system |
50-
|-|:-:|-|
51-
| `.bug(12345)` | Yes | None |
52-
| `.bug("12345")` | Yes | None |
53-
| `.bug("Things don't work")` | **No** | None |
54-
| `.bug("https://github.com/apple/swift/pull/12345")` | Yes | [GitHub Issues for the Swift project](https://github.com/apple/swift/issues) |
55-
| `.bug("https://bugs.webkit.org/show_bug.cgi?id=12345")` | Yes | [WebKit Bugzilla](https://bugs.webkit.org/) |
56-
| `.bug("FB12345")` | Yes | Apple Feedback Assistant | <!-- SEE ALSO: rdar://104582015 -->
44+
| Trait Function | Inferred Bug-Tracking System |
45+
|-|-|
46+
| `.bug(id: 12345)` | None |
47+
| `.bug(id: "12345")` | None |
48+
| `.bug("https://www.example.com?id=12345", id: "12345")` | None |
49+
| `.bug("https://github.com/apple/swift/pull/12345")` | [GitHub Issues for the Swift project](https://github.com/apple/swift/issues) |
50+
| `.bug("https://bugs.webkit.org/show_bug.cgi?id=12345")` | [WebKit Bugzilla](https://bugs.webkit.org/) |
51+
| `.bug(id: "FB12345")` | Apple Feedback Assistant | <!-- SEE ALSO: rdar://104582015 -->
5752
<!--
58-
| `.bug("#12345")` | Yes | GitHub Issues for the current repository (if hosted there) |
53+
| `.bug(id: "#12345")` | GitHub Issues for the current repository (if hosted there) |
5954
-->

Diff for: Sources/Testing/Testing.docc/EnablingAndDisabling.md

+10-4
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,14 @@ func isCold() async throws { ... }
8282
```
8383

8484
If a test is disabled because of a problem for which there is a corresponding
85-
bug report, you can use the ``Trait/bug(_:_:)-2u8j9`` or
86-
``Trait/bug(_:_:)-7mo2w`` function to show the relationship:
85+
bug report, you can use one of these functions to show the relationship
86+
between the test and the bug report:
87+
88+
- ``Trait/bug(_:_:)``
89+
- ``Trait/bug(_:id:_:)-10yf5``
90+
- ``Trait/bug(_:id:_:)-3vtpl``
91+
92+
For example, the following test cannot run due to bug number `"12345"`:
8793

8894
```swift
8995
@Test(
@@ -96,8 +102,8 @@ func isCold() async throws { ... }
96102
```
97103

98104
If a test has multiple conditions applied to it, they must _all_ pass for it to
99-
run. Otherwise, the test notes the first condition to fail as the reason the test
100-
is skipped.
105+
run. Otherwise, the test notes the first condition to fail as the reason the
106+
test is skipped.
101107

102108
### Handle complex conditions
103109

Diff for: Sources/Testing/Testing.docc/Traits.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -46,8 +46,9 @@ HIDDEN: .serialized is experimental SPI pending feature review.
4646
- <doc:AssociatingBugs>
4747
- <doc:BugIdentifiers>
4848
- ``Tag()``
49-
- ``Trait/bug(_:_:)-2u8j9``
50-
- ``Trait/bug(_:_:)-7mo2w``
49+
- ``Trait/bug(_:_:)``
50+
- ``Trait/bug(_:id:_:)-10yf5``
51+
- ``Trait/bug(_:id:_:)-3vtpl``
5152

5253
### Creating custom traits
5354

Diff for: Sources/Testing/Testing.docc/Traits/Trait.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,9 @@ See https://swift.org/CONTRIBUTORS.txt for Swift project authors
3030

3131
### Associating bugs
3232

33-
- ``Trait/bug(_:_:)-2u8j9``
34-
- ``Trait/bug(_:_:)-7mo2w``
33+
- ``Trait/bug(_:_:)``
34+
- ``Trait/bug(_:id:_:)-10yf5``
35+
- ``Trait/bug(_:id:_:)-3vtpl``
3536

3637
### Adding information to tests
3738
- ``Trait/comments``

Diff for: Sources/Testing/Traits/Bug.swift

+47-68
Original file line numberDiff line numberDiff line change
@@ -12,112 +12,91 @@
1212
///
1313
/// To add this trait to a test, use one of the following functions:
1414
///
15-
/// - ``Trait/bug(_:_:)-2u8j9``
16-
/// - ``Trait/bug(_:_:)-7mo2w``
15+
/// - ``Trait/bug(_:_:)``
16+
/// - ``Trait/bug(_:id:_:)-10yf5``
17+
/// - ``Trait/bug(_:id:_:)-3vtpl``
1718
public struct Bug {
18-
/// The identifier of this bug in the associated bug-tracking system.
19+
/// A URL linking to more information about the bug, if available.
20+
///
21+
/// The value of this property represents a URL conforming to
22+
/// [RFC 3986](https://www.ietf.org/rfc/rfc3986.txt).
23+
public var url: String?
24+
25+
/// A unique identifier in this bug's associated bug-tracking system, if
26+
/// available.
1927
///
2028
/// For more information on how the testing library interprets bug
2129
/// identifiers, see <doc:BugIdentifiers>.
22-
public var identifier: String
30+
public var id: String?
2331

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

28-
// MARK: - Equatable, Hashable, Comparable
36+
// MARK: - Equatable, Hashable
2937

30-
extension Bug: Equatable, Hashable, Comparable {
38+
extension Bug: Equatable, Hashable {
3139
public static func ==(lhs: Self, rhs: Self) -> Bool {
32-
lhs.identifier == rhs.identifier
40+
lhs.url == rhs.url && lhs.id == rhs.id
3341
}
3442

3543
public func hash(into hasher: inout Hasher) {
36-
hasher.combine(identifier)
37-
}
38-
39-
public static func <(lhs: Self, rhs: Self) -> Bool {
40-
lhs.identifier < rhs.identifier
44+
hasher.combine(url)
45+
hasher.combine(id)
4146
}
4247
}
4348

4449
// MARK: - Codable
4550

46-
extension Bug: Codable {
47-
/// A temporary explicit implementation of this type's coding keys enumeration
48-
/// to support the refactored form of `Bug` from [#412](https://github.com/apple/swift-testing/pull/412).
49-
private enum _CodingKeys: String, CodingKey {
50-
// Existing properties.
51-
case identifier = "identifier"
52-
case comment = "comment"
53-
54-
// Proposed new properties.
55-
case id = "id"
56-
case url = "url"
57-
case title = "title"
58-
}
59-
60-
public func encode(to encoder: any Encoder) throws {
61-
var container = encoder.container(keyedBy: _CodingKeys.self)
62-
63-
try container.encode(identifier, forKey: .identifier)
64-
try container.encodeIfPresent(comment, forKey: .comment)
65-
66-
// Temporary compatibility shims to support the refactored form of Bug from
67-
// https://github.com/apple/swift-testing/pull/412 .
68-
if identifier.contains(":") {
69-
try container.encode(identifier, forKey: .url)
70-
} else {
71-
try container.encode(identifier, forKey: .id)
72-
}
73-
try container.encodeIfPresent(comment, forKey: .title)
74-
}
75-
76-
public init(from decoder: any Decoder) throws {
77-
let container = try decoder.container(keyedBy: _CodingKeys.self)
78-
identifier = try container.decodeIfPresent(String.self, forKey: .identifier)
79-
// Temporary compatibility shims to support the refactored form of Bug
80-
// from https://github.com/apple/swift-testing/pull/412 .
81-
?? container.decodeIfPresent(String.self, forKey: .id)
82-
?? container.decode(String.self, forKey: .url)
83-
comment = try container.decodeIfPresent(Comment.self, forKey: .comment)
84-
?? container.decodeIfPresent(Comment.self, forKey: .title)
85-
}
86-
}
51+
extension Bug: Codable {}
8752

8853
// MARK: - Trait, TestTrait, SuiteTrait
8954

9055
extension Bug: TestTrait, SuiteTrait {
9156
public var comments: [Comment] {
92-
Array(comment)
57+
Array(title)
9358
}
9459
}
9560

9661
extension Trait where Self == Bug {
9762
/// Construct a bug to track with a test.
9863
///
9964
/// - Parameters:
100-
/// - identifier: The identifier of this bug in the associated bug-tracking
101-
/// system. For more information on how this value is interpreted, see the
102-
/// documentation for ``Bug``.
103-
/// - comment: An optional, user-specified comment describing this trait.
65+
/// - url: A URL referring to this bug in the associated bug-tracking
66+
/// system.
67+
/// - title: Optionally, the human-readable title of the bug.
68+
///
69+
/// - Returns: An instance of ``Bug`` representing the specified bug.
70+
public static func bug(_ url: _const String, _ title: Comment? = nil) -> Self {
71+
Self(url: url, title: title)
72+
}
73+
74+
/// Construct a bug to track with a test.
75+
///
76+
/// - Parameters:
77+
/// - url: A URL referring to this bug in the associated bug-tracking
78+
/// system.
79+
/// - id: The unique identifier of this bug in its associated bug-tracking
80+
/// system.
81+
/// - title: Optionally, the human-readable title of the bug.
10482
///
10583
/// - Returns: An instance of ``Bug`` representing the specified bug.
106-
public static func bug(_ identifier: String, _ comment: Comment? = nil) -> Self {
107-
Self(identifier: identifier, comment: comment)
84+
public static func bug(_ url: _const String? = nil, id: some Numeric, _ title: Comment? = nil) -> Self {
85+
Self(url: url, id: String(describing: id), title: title)
10886
}
10987

11088
/// Construct a bug to track with a test.
11189
///
11290
/// - Parameters:
113-
/// - identifier: The identifier of this bug in the associated bug-tracking
114-
/// system. For more information on how this value is interpreted, see the
115-
/// documentation for ``Bug``.
116-
/// - comment: An optional, user-specified comment describing this trait.
91+
/// - url: A URL referring to this bug in the associated bug-tracking
92+
/// system.
93+
/// - id: The unique identifier of this bug in its associated bug-tracking
94+
/// system.
95+
/// - title: Optionally, the human-readable title of the bug.
11796
///
11897
/// - Returns: An instance of ``Bug`` representing the specified bug.
119-
public static func bug(_ identifier: some Numeric, _ comment: Comment? = nil) -> Self {
120-
Self(identifier: String(describing: identifier), comment: comment)
98+
public static func bug(_ url: _const String? = nil, id: _const String, _ title: Comment? = nil) -> Self {
99+
Self(url: url, id: id, title: title)
121100
}
122101
}
123102

Diff for: Sources/Testing/Traits/Comment.swift

+4-2
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@
1818
/// or `@Suite` attribute. See <doc:AddingComments> for more details.
1919
///
2020
/// - Note: This type is not intended to reference bugs related to a test.
21-
/// Instead, use ``Trait/bug(_:_:)-2u8j9`` or ``Trait/bug(_:_:)-7mo2w``.
21+
/// Instead, use ``Trait/bug(_:_:)``, ``Trait/bug(_:id:_:)-10yf5``, or
22+
/// ``Trait/bug(_:id:_:)-3vtpl``.
2223
public struct Comment: RawRepresentable, Sendable {
2324
/// The single comment string contained in this instance.
2425
///
@@ -121,7 +122,8 @@ extension Trait where Self == Comment {
121122
/// comment.
122123
///
123124
/// - Note: This function is not intended to reference bugs related to a test.
124-
/// Instead, use ``Trait/bug(_:_:)-2u8j9`` or ``Trait/bug(_:_:)-7mo2w``.
125+
/// Instead, use ``Trait/bug(_:_:)``, ``Trait/bug(_:id:_:)-10yf5``, or
126+
/// ``Trait/bug(_:id:_:)-3vtpl``.
125127
public static func comment(_ comment: String) -> Self {
126128
Self(rawValue: comment, kind: .trait)
127129
}

0 commit comments

Comments
 (0)