1+ import Testing
2+ #if canImport(FoundationEssentials)
3+ import FoundationEssentials
4+ #else
5+ import Foundation
6+ #endif
7+
8+ // MARK: - Simple Property-Based Testing Framework
9+
10+ /// A simple property-based testing framework for Swift Testing
11+ struct PropertyTesting {
12+
13+ /// Default number of test cases to generate
14+ static let defaultTestCases = 100
15+
16+ /// Generates random values for property testing
17+ enum Generator {
18+
19+ /// Generates a random Float in the given range
20+ static func float( min: Float = - 1000 , max: Float = 1000 ) -> Float {
21+ return Float . random ( in: min... max)
22+ }
23+
24+ /// Generates a random Double in the given range
25+ static func double( min: Double = - 1000 , max: Double = 1000 ) -> Double {
26+ return Double . random ( in: min... max)
27+ }
28+
29+ /// Generates a random Int32 in the given range
30+ static func int32( min: Int32 = - 1000 , max: Int32 = 1000 ) -> Int32 {
31+ return Int32 . random ( in: min... max)
32+ }
33+
34+ /// Generates a random Int64 in the given range
35+ static func int64( min: Int64 = - 1000 , max: Int64 = 1000 ) -> Int64 {
36+ return Int64 . random ( in: min... max)
37+ }
38+
39+ /// Generates a random array of Floats
40+ static func floatArray( size: Int = 10 , min: Float = - 100 , max: Float = 100 ) -> [ Float ] {
41+ return ( 0 ..< size) . map { _ in float ( min: min, max: max) }
42+ }
43+
44+ /// Generates a random array of Doubles
45+ static func doubleArray( size: Int = 10 , min: Double = - 100 , max: Double = 100 ) -> [ Double ] {
46+ return ( 0 ..< size) . map { _ in double ( min: min, max: max) }
47+ }
48+
49+ /// Generates a random array of Int32s
50+ static func int32Array( size: Int = 10 , min: Int32 = - 100 , max: Int32 = 100 ) -> [ Int32 ] {
51+ return ( 0 ..< size) . map { _ in int32 ( min: min, max: max) }
52+ }
53+
54+ /// Generates a random array of Int64s
55+ static func int64Array( size: Int = 10 , min: Int64 = - 100 , max: Int64 = 100 ) -> [ Int64 ] {
56+ return ( 0 ..< size) . map { _ in int64 ( min: min, max: max) }
57+ }
58+
59+ /// Generates arrays with specific sizes for SIMD alignment testing
60+ static func floatArrayWithSize( _ size: Int ) -> [ Float ] {
61+ return floatArray ( size: size)
62+ }
63+
64+ /// Generates non-empty arrays
65+ static func nonEmptyFloatArray( maxSize: Int = 20 ) -> [ Float ] {
66+ let size = Int . random ( in: 1 ... maxSize)
67+ return floatArray ( size: size)
68+ }
69+
70+ /// Generates arrays with different sizes for mismatch testing
71+ static func mismatchedFloatArrays( ) -> ( [ Float ] , [ Float ] ) {
72+ let size1 = Int . random ( in: 1 ... 10 )
73+ let size2 = Int . random ( in: 1 ... 10 )
74+ // Ensure they're different
75+ let finalSize2 = size2 == size1 ? size2 + 1 : size2
76+ return ( floatArray ( size: size1) , floatArray ( size: finalSize2) )
77+ }
78+ }
79+
80+ /// Runs a property test with the specified number of test cases
81+ static func forAll< T> (
82+ _ testCases: Int = defaultTestCases,
83+ generate: ( ) -> T ,
84+ property: ( T ) throws -> Bool
85+ ) throws {
86+ for i in 0 ..< testCases {
87+ let value = generate ( )
88+ let result = try property ( value)
89+ if !result {
90+ throw PropertyTestFailure . propertyViolation (
91+ " Property failed on test case \( i + 1 ) / \( testCases) with value: \( value) "
92+ )
93+ }
94+ }
95+ }
96+
97+ /// Runs a property test with two generators
98+ static func forAll< T, U> (
99+ _ testCases: Int = defaultTestCases,
100+ generate1: ( ) -> T ,
101+ generate2: ( ) -> U ,
102+ property: ( T , U ) throws -> Bool
103+ ) throws {
104+ for i in 0 ..< testCases {
105+ let value1 = generate1 ( )
106+ let value2 = generate2 ( )
107+ let result = try property ( value1, value2)
108+ if !result {
109+ throw PropertyTestFailure . propertyViolation (
110+ " Property failed on test case \( i + 1 ) / \( testCases) with values: \( value1) , \( value2) "
111+ )
112+ }
113+ }
114+ }
115+
116+ /// Runs a property test with three generators
117+ static func forAll< T, U, V> (
118+ _ testCases: Int = defaultTestCases,
119+ generate1: ( ) -> T ,
120+ generate2: ( ) -> U ,
121+ generate3: ( ) -> V ,
122+ property: ( T , U , V ) throws -> Bool
123+ ) throws {
124+ for i in 0 ..< testCases {
125+ let value1 = generate1 ( )
126+ let value2 = generate2 ( )
127+ let value3 = generate3 ( )
128+ let result = try property ( value1, value2, value3)
129+ if !result {
130+ throw PropertyTestFailure . propertyViolation (
131+ " Property failed on test case \( i + 1 ) / \( testCases) with values: \( value1) , \( value2) , \( value3) "
132+ )
133+ }
134+ }
135+ }
136+ }
137+
138+ /// Errors that can occur during property testing
139+ enum PropertyTestFailure : Error , LocalizedError {
140+ case propertyViolation( String )
141+
142+ var errorDescription : String ? {
143+ switch self {
144+ case . propertyViolation( let message) :
145+ return " Property test failed: \( message) "
146+ }
147+ }
148+ }
149+
150+ // MARK: - Helper Functions for Approximate Equality
151+
152+ /// Checks if two Float values are approximately equal
153+ func isApproximatelyEqual( _ a: Float , _ b: Float , tolerance: Float = 1e-5 ) -> Bool {
154+ return abs ( a - b) <= tolerance
155+ }
156+
157+ /// Checks if two Double values are approximately equal
158+ func isApproximatelyEqual( _ a: Double , _ b: Double , tolerance: Double = 1e-10 ) -> Bool {
159+ return abs ( a - b) <= tolerance
160+ }
161+
162+ /// Checks if two Float arrays are approximately equal
163+ func areApproximatelyEqual( _ a: [ Float ] , _ b: [ Float ] , tolerance: Float = 1e-5 ) -> Bool {
164+ guard a. count == b. count else { return false }
165+ return zip ( a, b) . allSatisfy { isApproximatelyEqual ( $0, $1, tolerance: tolerance) }
166+ }
167+
168+ /// Checks if two Double arrays are approximately equal
169+ func areApproximatelyEqual( _ a: [ Double ] , _ b: [ Double ] , tolerance: Double = 1e-10 ) -> Bool {
170+ guard a. count == b. count else { return false }
171+ return zip ( a, b) . allSatisfy { isApproximatelyEqual ( $0, $1, tolerance: tolerance) }
172+ }
173+
174+ /// Utility to safely unwrap Result types for property testing
175+ func unwrapResults< T, E: Error > ( _ result1: Result < T , E > , _ result2: Result < T , E > ) -> ( T , T ) ? {
176+ switch ( result1, result2) {
177+ case ( . success( let val1) , . success( let val2) ) :
178+ return ( val1, val2)
179+ default :
180+ return nil
181+ }
182+ }
183+
184+ /// Utility to safely unwrap a single Result for property testing
185+ func unwrapResult< T, E: Error > ( _ result: Result < T , E > ) -> T ? {
186+ switch result {
187+ case . success( let value) :
188+ return value
189+ case . failure:
190+ return nil
191+ }
192+ }
0 commit comments