@@ -170,7 +170,7 @@ func expectNoChanges<T: BinaryInteger>(_ check: @autoclosure () -> T, by differe
170
170
///
171
171
/// - Note: `oracle` is also checked for conformance to the
172
172
/// laws.
173
- public func checkEquatable < Instances: Collection > (
173
+ public func XCTCheckEquatable < Instances: Collection > (
174
174
_ instances: Instances ,
175
175
oracle: ( Instances . Index , Instances . Index ) -> Bool ,
176
176
allowBrokenTransitivity: Bool = false ,
@@ -179,7 +179,7 @@ public func checkEquatable<Instances: Collection>(
179
179
line: UInt = #line
180
180
) where Instances. Element: Equatable {
181
181
let indices = Array ( instances. indices)
182
- _checkEquatableImpl (
182
+ _XCTCheckEquatableImpl (
183
183
Array ( instances) ,
184
184
oracle: { oracle ( indices [ $0] , indices [ $1] ) } ,
185
185
allowBrokenTransitivity: allowBrokenTransitivity,
@@ -188,15 +188,7 @@ public func checkEquatable<Instances: Collection>(
188
188
line: line)
189
189
}
190
190
191
- private class Box < T> {
192
- var value : T
193
-
194
- init ( _ value: T ) {
195
- self . value = value
196
- }
197
- }
198
-
199
- internal func _checkEquatableImpl< Instance : Equatable > (
191
+ internal func _XCTCheckEquatableImpl< Instance : Equatable > (
200
192
_ instances: [ Instance ] ,
201
193
oracle: ( Int , Int ) -> Bool ,
202
194
allowBrokenTransitivity: Bool = false ,
@@ -271,23 +263,14 @@ internal func _checkEquatableImpl<Instance : Equatable>(
271
263
}
272
264
}
273
265
274
- func hash< H: Hashable > ( _ value: H , salt: Int ? = nil ) -> Int {
275
- var hasher = Hasher ( )
276
- if let salt = salt {
277
- hasher. combine ( salt)
278
- }
279
- hasher. combine ( value)
280
- return hasher. finalize ( )
281
- }
282
-
283
- public func checkHashable< Instances: Collection > (
266
+ public func XCTCheckHashable< Instances: Collection > (
284
267
_ instances: Instances ,
285
268
equalityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
286
269
allowIncompleteHashing: Bool = false ,
287
270
_ message: @autoclosure ( ) -> String = " " ,
288
271
file: StaticString = #filePath, line: UInt = #line
289
272
) where Instances. Element: Hashable {
290
- checkHashable (
273
+ XCTCheckHashable (
291
274
instances,
292
275
equalityOracle: equalityOracle,
293
276
hashEqualityOracle: equalityOracle,
@@ -298,7 +281,7 @@ public func checkHashable<Instances: Collection>(
298
281
}
299
282
300
283
301
- public func checkHashable < Instances: Collection > (
284
+ public func XCTCheckHashable < Instances: Collection > (
302
285
_ instances: Instances ,
303
286
equalityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
304
287
hashEqualityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
@@ -307,7 +290,7 @@ public func checkHashable<Instances: Collection>(
307
290
file: StaticString = #filePath, line: UInt = #line
308
291
) where Instances. Element: Hashable {
309
292
310
- checkEquatable (
293
+ XCTCheckEquatable (
311
294
instances,
312
295
oracle: equalityOracle,
313
296
message ( ) ,
@@ -390,7 +373,7 @@ public func checkHashable<Instances: Collection>(
390
373
/// Test that the elements of `groups` consist of instances that satisfy the
391
374
/// semantic requirements of `Hashable`, with each group defining a distinct
392
375
/// equivalence class under `==`.
393
- public func checkHashableGroups < Groups: Collection > (
376
+ public func XCTCheckHashableGroups < Groups: Collection > (
394
377
_ groups: Groups ,
395
378
_ message: @autoclosure ( ) -> String = " " ,
396
379
allowIncompleteHashing: Bool = false ,
@@ -405,7 +388,7 @@ public func checkHashableGroups<Groups: Collection>(
405
388
func equalityOracle( _ lhs: Int , _ rhs: Int ) -> Bool {
406
389
return groupIndices [ lhs] == groupIndices [ rhs]
407
390
}
408
- checkHashable (
391
+ XCTCheckHashable (
409
392
instances,
410
393
equalityOracle: equalityOracle,
411
394
hashEqualityOracle: equalityOracle,
@@ -477,3 +460,259 @@ func testExpectedToFailWithCheck<T>(check: (String) -> Bool, _ test: @escaping
477
460
}
478
461
}
479
462
463
+ // MARK: - swift-testing Helpers
464
+
465
+ import Testing
466
+
467
+ /// Test that the elements of `instances` satisfy the semantic
468
+ /// requirements of `Equatable`, using `oracle` to generate equality
469
+ /// expectations from pairs of positions in `instances`.
470
+ ///
471
+ /// - Note: `oracle` is also checked for conformance to the
472
+ /// laws.
473
+ func checkEquatable< Instances : Collection > (
474
+ _ instances: Instances ,
475
+ oracle: ( Instances . Index , Instances . Index ) -> Bool ,
476
+ allowBrokenTransitivity: Bool = false ,
477
+ _ message: @autoclosure ( ) -> String = " " ,
478
+ sourceLocation: SourceLocation = #_sourceLocation
479
+ ) where Instances. Element: Equatable {
480
+ let indices = Array ( instances. indices)
481
+ _checkEquatable (
482
+ instances,
483
+ oracle: { oracle ( indices [ $0] , indices [ $1] ) } ,
484
+ allowBrokenTransitivity: allowBrokenTransitivity,
485
+ message ( ) ,
486
+ sourceLocation: sourceLocation
487
+ )
488
+ }
489
+
490
+ func _checkEquatable< Instances : Collection > (
491
+ _ _instances: Instances ,
492
+ oracle: ( Int , Int ) -> Bool ,
493
+ allowBrokenTransitivity: Bool = false ,
494
+ _ message: @autoclosure ( ) -> String = " " ,
495
+ sourceLocation: SourceLocation = #_sourceLocation
496
+ ) where Instances. Element: Equatable {
497
+ let instances = Array ( _instances)
498
+
499
+ // For each index (which corresponds to an instance being tested) track the
500
+ // set of equal instances.
501
+ var transitivityScoreboard : [ Box < Set < Int > > ] =
502
+ instances. indices. map { _ in Box ( [ ] ) }
503
+
504
+ for i in instances. indices {
505
+ let x = instances [ i]
506
+ #expect( oracle ( i, i) , " bad oracle: broken reflexivity at index \( i) " )
507
+
508
+ for j in instances. indices {
509
+ let y = instances [ j]
510
+
511
+ let predictedXY = oracle ( i, j)
512
+ #expect(
513
+ predictedXY == oracle ( j, i) ,
514
+ " bad oracle: broken symmetry between indices \( i) , \( j) " ,
515
+ sourceLocation: sourceLocation
516
+ )
517
+
518
+ let isEqualXY = x == y
519
+ #expect(
520
+ predictedXY == isEqualXY,
521
+ """
522
+ \( ( predictedXY
523
+ ? " expected equal, found not equal "
524
+ : " expected not equal, found equal " ) )
525
+ lhs (at index \( i) ): \( String ( reflecting: x) )
526
+ rhs (at index \( j) ): \( String ( reflecting: y) )
527
+ """ ,
528
+ sourceLocation: sourceLocation
529
+ )
530
+
531
+ // Not-equal is an inverse of equal.
532
+ #expect(
533
+ isEqualXY != ( x != y) ,
534
+ """
535
+ lhs (at index \( i) ): \( String ( reflecting: x) )
536
+ rhs (at index \( j) ): \( String ( reflecting: y) )
537
+ """ ,
538
+ sourceLocation: sourceLocation
539
+ )
540
+
541
+ if !allowBrokenTransitivity {
542
+ // Check transitivity of the predicate represented by the oracle.
543
+ // If we are adding the instance `j` into an equivalence set, check that
544
+ // it is equal to every other instance in the set.
545
+ if predictedXY && i < j && transitivityScoreboard [ i] . value. insert ( j) . inserted {
546
+ if transitivityScoreboard [ i] . value. count == 1 {
547
+ transitivityScoreboard [ i] . value. insert ( i)
548
+ }
549
+ for k in transitivityScoreboard [ i] . value {
550
+ #expect(
551
+ oracle ( j, k) ,
552
+ " bad oracle: broken transitivity at indices \( i) , \( j) , \( k) " ,
553
+ sourceLocation: sourceLocation
554
+ )
555
+ // No need to check equality between actual values, we will check
556
+ // them with the checks above.
557
+ }
558
+ precondition ( transitivityScoreboard [ j] . value. isEmpty)
559
+ transitivityScoreboard [ j] = transitivityScoreboard [ i]
560
+ }
561
+ }
562
+ }
563
+ }
564
+ }
565
+
566
+ public func checkHashable< Instances: Collection > (
567
+ _ instances: Instances ,
568
+ equalityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
569
+ allowIncompleteHashing: Bool = false ,
570
+ _ message: @autoclosure ( ) -> String = " " ,
571
+ sourceLocation: SourceLocation = #_sourceLocation
572
+ ) where Instances. Element: Hashable {
573
+ checkHashable (
574
+ instances,
575
+ equalityOracle: equalityOracle,
576
+ hashEqualityOracle: equalityOracle,
577
+ allowIncompleteHashing: allowIncompleteHashing,
578
+ message ( ) ,
579
+ sourceLocation: sourceLocation)
580
+ }
581
+
582
+ func checkHashable< Instances: Collection > (
583
+ _ instances: Instances ,
584
+ equalityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
585
+ hashEqualityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
586
+ allowIncompleteHashing: Bool = false ,
587
+ _ message: @autoclosure ( ) -> String = " " ,
588
+ sourceLocation: SourceLocation = #_sourceLocation
589
+ ) where Instances. Element: Hashable {
590
+ checkEquatable (
591
+ instances,
592
+ oracle: equalityOracle,
593
+ message ( ) ,
594
+ sourceLocation: sourceLocation
595
+ )
596
+
597
+ for i in instances. indices {
598
+ let x = instances [ i]
599
+ for j in instances. indices {
600
+ let y = instances [ j]
601
+ let predicted = hashEqualityOracle ( i, j)
602
+ #expect(
603
+ predicted == hashEqualityOracle ( j, i) ,
604
+ " bad hash oracle: broken symmetry between indices \( i) , \( j) " ,
605
+ sourceLocation: sourceLocation
606
+ )
607
+ if x == y {
608
+ #expect(
609
+ predicted,
610
+ """
611
+ bad hash oracle: equality must imply hash equality
612
+ lhs (at index \( i) ): \( x)
613
+ rhs (at index \( j) ): \( y)
614
+ """ ,
615
+ sourceLocation: sourceLocation
616
+ )
617
+ }
618
+ if predicted {
619
+ #expect(
620
+ hash ( x) == hash ( y) ,
621
+ """
622
+ hash(into:) expected to match, found to differ
623
+ lhs (at index \( i) ): \( x)
624
+ rhs (at index \( j) ): \( y)
625
+ """ ,
626
+ sourceLocation: sourceLocation
627
+ )
628
+ #expect(
629
+ x. hashValue == y. hashValue,
630
+ """
631
+ hashValue expected to match, found to differ
632
+ lhs (at index \( i) ): \( x)
633
+ rhs (at index \( j) ): \( y)
634
+ """ ,
635
+ sourceLocation: sourceLocation
636
+ )
637
+ #expect(
638
+ x. _rawHashValue ( seed: 0 ) == y. _rawHashValue ( seed: 0 ) ,
639
+ """
640
+ _rawHashValue(seed:) expected to match, found to differ
641
+ lhs (at index \( i) ): \( x)
642
+ rhs (at index \( j) ): \( y)
643
+ """ ,
644
+ sourceLocation: sourceLocation
645
+ )
646
+ } else if !allowIncompleteHashing {
647
+ // Try a few different seeds; at least one of them should discriminate
648
+ // between the hashes. It is extremely unlikely this check will fail
649
+ // all ten attempts, unless the type's hash encoding is not unique,
650
+ // or unless the hash equality oracle is wrong.
651
+ #expect(
652
+ ( 0 ..< 10 ) . contains { hash ( x, salt: $0) != hash ( y, salt: $0) } ,
653
+ """
654
+ hash(into:) expected to differ, found to match
655
+ lhs (at index \( i) ): \( x)
656
+ rhs (at index \( j) ): \( y)
657
+ """ ,
658
+ sourceLocation: sourceLocation
659
+ )
660
+ #expect(
661
+ ( 0 ..< 10 ) . contains { i in
662
+ x. _rawHashValue ( seed: i) != y. _rawHashValue ( seed: i)
663
+ } ,
664
+ """
665
+ _rawHashValue(seed:) expected to differ, found to match
666
+ lhs (at index \( i) ): \( x)
667
+ rhs (at index \( j) ): \( y)
668
+ """ ,
669
+ sourceLocation: sourceLocation
670
+ )
671
+ }
672
+ }
673
+ }
674
+ }
675
+
676
+ /// Test that the elements of `groups` consist of instances that satisfy the
677
+ /// semantic requirements of `Hashable`, with each group defining a distinct
678
+ /// equivalence class under `==`.
679
+ public func checkHashableGroups< Groups: Collection > (
680
+ _ groups: Groups ,
681
+ _ message: @autoclosure ( ) -> String = " " ,
682
+ allowIncompleteHashing: Bool = false ,
683
+ sourceLocation: SourceLocation = #_sourceLocation
684
+ ) where Groups. Element: Collection , Groups. Element. Element: Hashable {
685
+ let instances = groups. flatMap { $0 }
686
+ // groupIndices[i] is the index of the element in groups that contains
687
+ // instances[i].
688
+ let groupIndices =
689
+ zip ( 0 ... , groups) . flatMap { i, group in group. map { _ in i } }
690
+ func equalityOracle( _ lhs: Int , _ rhs: Int ) -> Bool {
691
+ return groupIndices [ lhs] == groupIndices [ rhs]
692
+ }
693
+ checkHashable (
694
+ instances,
695
+ equalityOracle: equalityOracle,
696
+ hashEqualityOracle: equalityOracle,
697
+ allowIncompleteHashing: allowIncompleteHashing,
698
+ sourceLocation: sourceLocation)
699
+ }
700
+
701
+ // MARK: - Private Types
702
+
703
+ private class Box < T> {
704
+ var value : T
705
+
706
+ init ( _ value: T ) {
707
+ self . value = value
708
+ }
709
+ }
710
+
711
+ private func hash< H: Hashable > ( _ value: H , salt: Int ? = nil ) -> Int {
712
+ var hasher = Hasher ( )
713
+ if let salt = salt {
714
+ hasher. combine ( salt)
715
+ }
716
+ hasher. combine ( value)
717
+ return hasher. finalize ( )
718
+ }
0 commit comments