@@ -433,7 +433,6 @@ fileprivate extension Array<AnnotatedTestItem> {
433
433
/// A node's parent is identified by the node's ID with the last component dropped.
434
434
func mergingTestsInExtensions( ) -> [ TestItem ] {
435
435
var itemDict : [ String : AnnotatedTestItem ] = [ : ]
436
- var duplicatedIds : Set < String > = [ ]
437
436
for item in self {
438
437
let id = item. testItem. id
439
438
if var rootItem = itemDict [ id] {
@@ -442,14 +441,13 @@ fileprivate extension Array<AnnotatedTestItem> {
442
441
// as the root item.
443
442
if rootItem. isExtension && !item. isExtension {
444
443
var newItem = item
445
- newItem. testItem. children += rootItem. testItem. children
444
+ newItem. testItem. children = ( newItem . testItem . children + rootItem. testItem. children) . deduplicateIds ( )
446
445
rootItem = newItem
447
- } else if ( rootItem. testItem. children. isEmpty && item. testItem. children. isEmpty) {
448
- duplicatedIds. insert ( item. testItem. id)
449
- itemDict [ item. testItem. fullyQualifiedTestId] = item
446
+ } else if rootItem. testItem. children. isEmpty && item. testItem. children. isEmpty {
447
+ itemDict [ item. testItem. ambiguousTestDifferentiator] = item
450
448
continue
451
449
} else {
452
- rootItem. testItem. children += item. testItem. children
450
+ rootItem. testItem. children = ( rootItem . testItem . children + item. testItem. children) . deduplicateIds ( )
453
451
}
454
452
455
453
itemDict [ id] = rootItem
@@ -480,32 +478,16 @@ fileprivate extension Array<AnnotatedTestItem> {
480
478
. sorted { ( $0. isExtension != $1. isExtension) ? !$0. isExtension : ( $0. testItem. location < $1. testItem. location) }
481
479
482
480
let result = sortedItems. map {
483
- var newItem = $0. testItem
484
-
485
- // If multiple testItems share the same ID we add more context to make it unique.
486
- // Two tests can share the same ID when two swift testing tests accept
487
- // arguments of different types, i.e:
488
- // @Test(arguments: [1,2,3]) func foo(_ x: Int) {}
489
- // @Test(arguments: ["a", "b", "c"]) func foo(_ x: String) {}
490
- // or when tests are in separate files but don't conflict because they are marked
491
- // private, i.e:
492
- // File1.swift: @Test private func foo() {}
493
- // File2.swift: @Test private func foo() {}
494
- // If we encounter one of these cases, we need to deduplicate the ID
495
- // by appending /filename:filename:lineNumber.
496
- if duplicatedIds. contains ( newItem. id) {
497
- newItem. id = newItem. fullyQualifiedTestId
498
- }
499
-
500
481
guard !$0. testItem. children. isEmpty, mergedIds. contains ( $0. testItem. id) else {
501
- return newItem
482
+ return $0 . testItem
502
483
}
484
+ var newItem = $0. testItem
503
485
newItem. children = newItem. children
504
486
. map { AnnotatedTestItem ( testItem: $0, isExtension: false ) }
505
487
. mergingTestsInExtensions ( )
506
488
return newItem
507
489
}
508
- return result
490
+ return result. deduplicateIds ( )
509
491
}
510
492
511
493
func prefixTestsWithModuleName( workspace: Workspace ) async -> Self {
@@ -518,19 +500,58 @@ fileprivate extension Array<AnnotatedTestItem> {
518
500
}
519
501
}
520
502
521
- extension TestItem {
522
- fileprivate var fullyQualifiedTestId : String {
523
- return " \( self . id) / \( self . sourceLocation) "
503
+ fileprivate extension Array < TestItem > {
504
+ /// If multiple testItems share the same ID we add more context to make it unique.
505
+ /// Two tests can share the same ID when two swift testing tests accept
506
+ /// arguments of different types, i.e:
507
+ /// ```
508
+ /// @Test(arguments: [1,2,3]) func foo(_ x: Int) {}
509
+ /// @Test(arguments: ["a", "b", "c"]) func foo(_ x: String) {}
510
+ /// ```
511
+ ///
512
+ /// or when tests are in separate files but don't conflict because they are marked
513
+ /// private, i.e:
514
+ /// ```
515
+ /// File1.swift: @Test private func foo() {}
516
+ /// File2.swift: @Test private func foo() {}
517
+ /// ```
518
+ ///
519
+ /// If we encounter one of these cases, we need to deduplicate the ID
520
+ /// by appending `/filename:filename:lineNumber`.
521
+ func deduplicateIds( ) -> [ TestItem ] {
522
+ var idCounts : [ String : Int ] = [ : ]
523
+ var result : [ TestItem ] = [ ]
524
+
525
+ for element in self where element. children. isEmpty {
526
+ idCounts [ element. id, default: 0 ] += 1
527
+ }
528
+
529
+ for element in self {
530
+ if idCounts [ element. id] ?? 0 > 1 {
531
+ var newItem = element
532
+ newItem. id = newItem. ambiguousTestDifferentiator
533
+ result. append ( newItem)
534
+ } else {
535
+ result. append ( element)
536
+ }
537
+ }
538
+
539
+ return result
524
540
}
541
+ }
525
542
526
- private var sourceLocation : String {
543
+ extension TestItem {
544
+ /// A fully qualified name to disambiguate identical TestItem IDs.
545
+ /// This matches the IDs produced by `swift test list` when there are
546
+ /// tests that cannot be disambiguated by their simple ID.
547
+ fileprivate var ambiguousTestDifferentiator : String {
527
548
let filename = self . location. uri. arbitrarySchemeURL. lastPathComponent
528
549
let position = location. range. lowerBound
529
550
// Lines and columns start at 1.
530
551
// swift-testing tests start from _after_ the @ symbol in @Test, so we need to add an extra column.
531
552
// see https://github.com/swiftlang/swift-testing/blob/cca6de2be617aded98ecdecb0b3b3a81eec013f3/Sources/TestingMacros/Support/AttributeDiscovery.swift#L153
532
553
let columnOffset = self . style == TestStyle . swiftTesting ? 2 : 1
533
- return " \( filename) : \( position. line + 1 ) : \( position. utf16index + columnOffset) "
554
+ return " \( self . id ) / \( filename) : \( position. line + 1 ) : \( position. utf16index + columnOffset) "
534
555
}
535
556
536
557
fileprivate func prefixIDWithModuleName( workspace: Workspace ) async -> TestItem {
0 commit comments