Skip to content
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
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,25 @@ extension Test.Case.Argument.ID {
}
}

extension Test.Case.Argument.ID {
/// Initialize an ID from the IDs of a test case's arguments.
///
/// - Parameters:
/// - argumentIDs: The argument IDs to combine, in order.
///
/// The argument IDs are concatenated and hashed to form a single ID. A stable
/// argument's ID is itself a fixed-size hash, so the concatenation is
/// unambiguous. A single argument's ID already identifies the case, so it is
/// used directly rather than combined.
init(combining argumentIDs: some Collection<Test.Case.Argument.ID>) {
if let argumentID = argumentIDs.first, argumentIDs.count == 1 {
self = argumentID
} else {
self.init(bytes: SHA256.hash(argumentIDs.flatMap(\.bytes)))
}
}
}

#if !SWT_NO_CODABLE
extension Test.Case.Argument.ID {
/// Encode the specified test argument value and store its encoded
Expand Down
12 changes: 7 additions & 5 deletions Sources/Testing/Parameterization/Test.Case.ID.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ extension Test.Case {
/// different ``Test`` instances.
@_spi(ForToolsIntegrationOnly)
public struct ID: Sendable {
/// The IDs of the arguments of this instance's associated ``Test/Case``, in
/// the order they appear in ``Test/Case/arguments``.
/// The IDs of the arguments of this instance's associated ``Test/Case``.
///
/// The value of this property is `nil` for the ID of the single test case
/// associated with a non-parameterized test function.
/// For a parameterized test case, this array contains a single element: an
/// ``Test/Case/Argument/ID-swift.struct`` that combines the IDs of every
/// argument. The value of this property is `nil` for the ID of the single
/// test case associated with a non-parameterized test function.
public var argumentIDs: [Argument.ID]?

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

@_spi(ForToolsIntegrationOnly)
public var id: ID {
ID(argumentIDs: arguments.map { $0.map(\.id) }, discriminator: discriminator, isStable: isStable)
let argumentIDs = arguments.map { [Argument.ID(combining: $0.map(\.id))] }
return ID(argumentIDs: argumentIDs, discriminator: discriminator, isStable: isStable)
}
}

Expand Down
43 changes: 43 additions & 0 deletions Tests/TestingTests/Test.CaseTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,49 @@ struct Test_CaseTests {
#expect(testCaseID.discriminator == 0)
}
}

@Suite("Combined argument ID Tests")
struct CombinedArgumentIDTests {
private func makeID(_ values: [any Sendable]) -> Test.Case.ID {
let parameters = values.indices.map {
Test.Parameter(index: $0, firstName: "p\($0)", type: Int.self)
}
return Test.Case(values: values, parameters: parameters, body: {}).id
}

@Test("Multiple arguments are folded into a single combined ID")
func multipleArgumentsFoldIntoOneID() {
let id = makeID([1, 2, 3])
#expect(id.argumentIDs?.count == 1)
}

@Test("Equal arguments produce equal combined IDs")
func equalArgumentsAreDeterministic() {
#expect(makeID([1, 2]).argumentIDs == makeID([1, 2]).argumentIDs)
}

@Test("Reordered arguments produce different combined IDs")
func combinedIDIsOrderSensitive() {
#expect(makeID([1, 2]).argumentIDs != makeID([2, 1]).argumentIDs)
}

@Test("A single argument's ID is used directly without combining")
func singleArgumentIDIsUsedDirectly() throws {
let parameter = Test.Parameter(index: 0, firstName: "x", type: Int.self)
let argumentID = try #require(try Test.Case.Argument.ID(identifying: 1, parameter: parameter))
#expect(makeID([1]).argumentIDs == [argumentID])
}

@Test("Combining is stable when all arguments are stable")
func combinedIDIsStableForStableArguments() {
#expect(makeID([1, 2]).isStable)
}

@Test("Combining preserves the nil ID for non-parameterized cases")
func nonParameterizedCaseHasNilArgumentIDs() {
#expect(Test.Case(body: {}).id.argumentIDs == nil)
}
}
#endif
}

Expand Down
Loading