@@ -170,7 +170,7 @@ func expectNoChanges<T: BinaryInteger>(_ check: @autoclosure () -> T, by differe
170170///
171171/// - Note: `oracle` is also checked for conformance to the
172172/// laws.
173- public func checkEquatable < Instances: Collection > (
173+ public func XCTCheckEquatable < Instances: Collection > (
174174 _ instances: Instances ,
175175 oracle: ( Instances . Index , Instances . Index ) -> Bool ,
176176 allowBrokenTransitivity: Bool = false ,
@@ -179,7 +179,7 @@ public func checkEquatable<Instances: Collection>(
179179 line: UInt = #line
180180) where Instances. Element: Equatable {
181181 let indices = Array ( instances. indices)
182- _checkEquatableImpl (
182+ _XCTCheckEquatableImpl (
183183 Array ( instances) ,
184184 oracle: { oracle ( indices [ $0] , indices [ $1] ) } ,
185185 allowBrokenTransitivity: allowBrokenTransitivity,
@@ -188,15 +188,7 @@ public func checkEquatable<Instances: Collection>(
188188 line: line)
189189}
190190
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 > (
200192 _ instances: [ Instance ] ,
201193 oracle: ( Int , Int ) -> Bool ,
202194 allowBrokenTransitivity: Bool = false ,
@@ -271,23 +263,14 @@ internal func _checkEquatableImpl<Instance : Equatable>(
271263 }
272264}
273265
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 > (
284267 _ instances: Instances ,
285268 equalityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
286269 allowIncompleteHashing: Bool = false ,
287270 _ message: @autoclosure ( ) -> String = " " ,
288271 file: StaticString = #filePath, line: UInt = #line
289272) where Instances. Element: Hashable {
290- checkHashable (
273+ XCTCheckHashable (
291274 instances,
292275 equalityOracle: equalityOracle,
293276 hashEqualityOracle: equalityOracle,
@@ -298,7 +281,7 @@ public func checkHashable<Instances: Collection>(
298281}
299282
300283
301- public func checkHashable < Instances: Collection > (
284+ public func XCTCheckHashable < Instances: Collection > (
302285 _ instances: Instances ,
303286 equalityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
304287 hashEqualityOracle: ( Instances . Index , Instances . Index ) -> Bool ,
@@ -307,7 +290,7 @@ public func checkHashable<Instances: Collection>(
307290 file: StaticString = #filePath, line: UInt = #line
308291) where Instances. Element: Hashable {
309292
310- checkEquatable (
293+ XCTCheckEquatable (
311294 instances,
312295 oracle: equalityOracle,
313296 message ( ) ,
@@ -390,7 +373,7 @@ public func checkHashable<Instances: Collection>(
390373/// Test that the elements of `groups` consist of instances that satisfy the
391374/// semantic requirements of `Hashable`, with each group defining a distinct
392375/// equivalence class under `==`.
393- public func checkHashableGroups < Groups: Collection > (
376+ public func XCTCheckHashableGroups < Groups: Collection > (
394377 _ groups: Groups ,
395378 _ message: @autoclosure ( ) -> String = " " ,
396379 allowIncompleteHashing: Bool = false ,
@@ -405,7 +388,7 @@ public func checkHashableGroups<Groups: Collection>(
405388 func equalityOracle( _ lhs: Int , _ rhs: Int ) -> Bool {
406389 return groupIndices [ lhs] == groupIndices [ rhs]
407390 }
408- checkHashable (
391+ XCTCheckHashable (
409392 instances,
410393 equalityOracle: equalityOracle,
411394 hashEqualityOracle: equalityOracle,
@@ -477,3 +460,259 @@ func testExpectedToFailWithCheck<T>(check: (String) -> Bool, _ test: @escaping
477460 }
478461}
479462
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