Skip to content

Commit 3057379

Browse files
suzannaratcliffSuzy Silver
andauthored
[6.4.x] Combine a parameterized test case's argument IDs into a single hashed ID (#1806)
**- Explanation**: Simplifies `Test.Case.ID.argumentIDs` to a single combined ID that identifies the whole combination of arguments **- Scope**: `Test.Case.ID.argumentIDs` **- Issues**: N/A **- Original PRs**: #1799 **- Risk**: Low **- Testing**: New unit test added. **- Reviewers**: @stmontgomery @grynspan rdar://182725918 Co-authored-by: Suzy Silver <suzysilver@apple.com>
1 parent f9aa6e0 commit 3057379

3 files changed

Lines changed: 69 additions & 5 deletions

File tree

Sources/Testing/Parameterization/CustomTestArgumentEncodable.swift

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,25 @@ extension Test.Case.Argument.ID {
9999
}
100100
}
101101

102+
extension Test.Case.Argument.ID {
103+
/// Initialize an ID from the IDs of a test case's arguments.
104+
///
105+
/// - Parameters:
106+
/// - argumentIDs: The argument IDs to combine, in order.
107+
///
108+
/// The argument IDs are concatenated and hashed to form a single ID. A stable
109+
/// argument's ID is itself a fixed-size hash, so the concatenation is
110+
/// unambiguous. A single argument's ID already identifies the case, so it is
111+
/// used directly rather than combined.
112+
init(combining argumentIDs: some Collection<Test.Case.Argument.ID>) {
113+
if let argumentID = argumentIDs.first, argumentIDs.count == 1 {
114+
self = argumentID
115+
} else {
116+
self.init(bytes: SHA256.hash(argumentIDs.flatMap(\.bytes)))
117+
}
118+
}
119+
}
120+
102121
#if !SWT_NO_CODABLE
103122
extension Test.Case.Argument.ID {
104123
/// Encode the specified test argument value and store its encoded

Sources/Testing/Parameterization/Test.Case.ID.swift

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,12 @@ extension Test.Case {
1616
/// different ``Test`` instances.
1717
@_spi(ForToolsIntegrationOnly)
1818
public struct ID: Sendable {
19-
/// The IDs of the arguments of this instance's associated ``Test/Case``, in
20-
/// the order they appear in ``Test/Case/arguments``.
19+
/// The IDs of the arguments of this instance's associated ``Test/Case``.
2120
///
22-
/// The value of this property is `nil` for the ID of the single test case
23-
/// associated with a non-parameterized test function.
21+
/// For a parameterized test case, this array contains a single element: an
22+
/// ``Test/Case/Argument/ID-swift.struct`` that combines the IDs of every
23+
/// argument. The value of this property is `nil` for the ID of the single
24+
/// test case associated with a non-parameterized test function.
2425
public var argumentIDs: [Argument.ID]?
2526

2627
/// A number used to distinguish this test case from others associated with
@@ -49,7 +50,8 @@ extension Test.Case {
4950

5051
@_spi(ForToolsIntegrationOnly)
5152
public var id: ID {
52-
ID(argumentIDs: arguments.map { $0.map(\.id) }, discriminator: discriminator, isStable: isStable)
53+
let argumentIDs = arguments.map { [Argument.ID(combining: $0.map(\.id))] }
54+
return ID(argumentIDs: argumentIDs, discriminator: discriminator, isStable: isStable)
5355
}
5456
}
5557

Tests/TestingTests/Test.CaseTests.swift

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,49 @@ struct Test_CaseTests {
9292
#expect(testCaseID.discriminator == 0)
9393
}
9494
}
95+
96+
@Suite("Combined argument ID Tests")
97+
struct CombinedArgumentIDTests {
98+
private func makeID(_ values: [any Sendable]) -> Test.Case.ID {
99+
let parameters = values.indices.map {
100+
Test.Parameter(index: $0, firstName: "p\($0)", type: Int.self)
101+
}
102+
return Test.Case(values: values, parameters: parameters, body: {}).id
103+
}
104+
105+
@Test("Multiple arguments are folded into a single combined ID")
106+
func multipleArgumentsFoldIntoOneID() {
107+
let id = makeID([1, 2, 3])
108+
#expect(id.argumentIDs?.count == 1)
109+
}
110+
111+
@Test("Equal arguments produce equal combined IDs")
112+
func equalArgumentsAreDeterministic() {
113+
#expect(makeID([1, 2]).argumentIDs == makeID([1, 2]).argumentIDs)
114+
}
115+
116+
@Test("Reordered arguments produce different combined IDs")
117+
func combinedIDIsOrderSensitive() {
118+
#expect(makeID([1, 2]).argumentIDs != makeID([2, 1]).argumentIDs)
119+
}
120+
121+
@Test("A single argument's ID is used directly without combining")
122+
func singleArgumentIDIsUsedDirectly() throws {
123+
let parameter = Test.Parameter(index: 0, firstName: "x", type: Int.self)
124+
let argumentID = try #require(try Test.Case.Argument.ID(identifying: 1, parameter: parameter))
125+
#expect(makeID([1]).argumentIDs == [argumentID])
126+
}
127+
128+
@Test("Combining is stable when all arguments are stable")
129+
func combinedIDIsStableForStableArguments() {
130+
#expect(makeID([1, 2]).isStable)
131+
}
132+
133+
@Test("Combining preserves the nil ID for non-parameterized cases")
134+
func nonParameterizedCaseHasNilArgumentIDs() {
135+
#expect(Test.Case(body: {}).id.argumentIDs == nil)
136+
}
137+
}
95138
#endif
96139
}
97140

0 commit comments

Comments
 (0)