@@ -15,50 +15,69 @@ extension Test.Case {
15
15
/// parameterized test function. They are not necessarily unique across two
16
16
/// different ``Test`` instances.
17
17
@_spi ( ForToolsIntegrationOnly)
18
- public struct ID : Sendable , Equatable , Hashable {
18
+ public struct ID : Sendable {
19
19
/// The IDs of the arguments of this instance's associated ``Test/Case``, in
20
20
/// the order they appear in ``Test/Case/arguments``.
21
+ public var argumentIDs : [ Argument . ID ]
22
+
23
+ /// A number used to distinguish this test case from others associated with
24
+ /// the same test function whose arguments have the same ID.
25
+ ///
26
+ /// ## See Also
21
27
///
22
- /// The value of this property is `nil` if _any_ of the associated test
23
- /// case's arguments has a `nil` ID.
24
- public var argumentIDs : [ Argument . ID ] ?
28
+ /// - ``Test/Case/discriminator``
29
+ public var discriminator : Int
25
30
26
- public init ( argumentIDs: [ Argument . ID ] ? ) {
31
+ public init ( argumentIDs: [ Argument . ID ] , discriminator : Int ) {
27
32
self . argumentIDs = argumentIDs
33
+ self . discriminator = discriminator
34
+ }
35
+
36
+ /// Whether or not this test case ID is considered stable across successive
37
+ /// runs.
38
+ ///
39
+ /// The value of this property is `true` if all of the argument IDs for this
40
+ /// instance are stable, otherwise it is `false`.
41
+ public var isStable : Bool {
42
+ argumentIDs. allSatisfy ( \. isStable)
28
43
}
29
44
}
30
45
31
46
@_spi ( ForToolsIntegrationOnly)
32
47
public var id : ID {
33
- let argumentIDs = arguments. compactMap ( \. id)
34
- guard argumentIDs. count == arguments. count else {
35
- return ID ( argumentIDs: nil )
36
- }
37
-
38
- return ID ( argumentIDs: argumentIDs)
48
+ ID ( argumentIDs: arguments. map ( \. id) , discriminator: discriminator)
39
49
}
40
50
}
41
51
42
52
// MARK: - CustomStringConvertible
43
53
44
54
extension Test . Case . ID : CustomStringConvertible {
45
55
public var description : String {
46
- " argumentIDs: \( String ( describing : argumentIDs) ) "
56
+ " argumentIDs: \( argumentIDs) , discriminator: \( discriminator ) "
47
57
}
48
58
}
49
59
50
60
// MARK: - Codable
51
61
52
- extension Test . Case . ID : Codable { }
62
+ extension Test . Case . ID : Codable {
63
+ public init ( from decoder: some Decoder ) throws {
64
+ let container = try decoder. container ( keyedBy: CodingKeys . self)
53
65
54
- // MARK: - Equatable
66
+ // The `argumentIDs` property was optional when this type was first
67
+ // introduced, and a `nil` value represented a non-stable test case ID.
68
+ // To maintain previous behavior, if this value is absent when decoding,
69
+ // default to a single argument ID marked as non-stable.
70
+ let argumentIDs = try container. decodeIfPresent ( [ Test . Case . Argument . ID ] . self, forKey: . argumentIDs)
71
+ ?? [ Test . Case. Argument. ID ( bytes: [ ] , isStable: false ) ]
55
72
56
- // We cannot safely implement Equatable for Test.Case because its values are
57
- // type-erased. It does conform to `Identifiable`, but its ID type is composed
58
- // of the IDs of its arguments, and those IDs are not always available (for
59
- // example, if the type of an argument is not Codable). Thus, we cannot check
60
- // for equality of test cases based on this, because if two test cases had
61
- // different arguments, but the type of those arguments is not Codable, they
62
- // both will have a `nil` ID and would incorrectly be considered equal.
63
- //
64
- // `Test.Case.ID` is Equatable, however.
73
+ // The `discriminator` property was added after this type was first
74
+ // introduced. It can safely default to zero when absent.
75
+ let discriminator = try container. decodeIfPresent ( type ( of: discriminator) , forKey: . discriminator) ?? 0
76
+
77
+ self . init ( argumentIDs: argumentIDs, discriminator: discriminator)
78
+ }
79
+ }
80
+
81
+ // MARK: - Equatable, Hashable
82
+
83
+ extension Test . Case . ID : Hashable { }
0 commit comments