Skip to content
This repository was archived by the owner on Aug 15, 2025. It is now read-only.

Commit 1e18920

Browse files
committed
ci: update GitHub Actions workflow and add property-based testing
1 parent 1eb1966 commit 1e18920

File tree

4 files changed

+639
-15
lines changed

4 files changed

+639
-15
lines changed

.github/workflows/swift.yml

Lines changed: 16 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -2,37 +2,39 @@ name: Swift
22

33
on:
44
push:
5-
branches: [ main, master ]
5+
branches: [ main ]
66
pull_request:
7-
branches: [ main, master ]
7+
branches: [ main ]
88

99
jobs:
1010
test-macos:
11-
name: Test on macOS
1211
runs-on: macos-latest
12+
1313
steps:
1414
- uses: actions/checkout@v4
1515

16-
- name: Swift version
17-
run: swift --version
18-
16+
- name: Setup Swift
17+
uses: swift-actions/setup-swift@v2
18+
with:
19+
swift-version: '6.1'
20+
1921
- name: Build
2022
run: swift build -v
2123

2224
- name: Run tests
2325
run: swift test -v
2426

25-
test-ubuntu:
26-
name: Test on Ubuntu
27-
runs-on: ubuntu-22.04
28-
container:
29-
image: swift:6.0.3-jammy
27+
test-linux:
28+
runs-on: ubuntu-latest
29+
3030
steps:
3131
- uses: actions/checkout@v4
3232

33-
- name: Swift version
34-
run: swift --version
35-
33+
- name: Setup Swift
34+
uses: swift-actions/setup-swift@v2
35+
with:
36+
swift-version: '6.0'
37+
3638
- name: Build
3739
run: swift build -v
3840

README.md

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ A Swift library that provides cross-platform SIMD (Single Instruction, Multiple
1818
- **Safe error handling** with Result types instead of fatal errors
1919
- **Flexible SIMD width support** (SIMD2, SIMD4, SIMD8, SIMD16)
2020
- **Performance optimizations** with adaptive SIMD width selection
21-
- **Comprehensive test coverage** (72 tests) including edge cases and performance benchmarks
21+
- **Comprehensive test coverage** (89 tests) including edge cases, performance benchmarks, and property-based testing
2222

2323
## Installation
2424

@@ -215,12 +215,32 @@ This library automatically uses SIMD instructions available on the target platfo
215215

216216
## Testing
217217

218+
The library includes comprehensive testing with 89 tests covering:
219+
220+
- **Unit tests**: Basic functionality for all operations
221+
- **Error handling tests**: Edge cases and invalid inputs
222+
- **Performance benchmarks**: Comparing different implementations
223+
- **Property-based tests**: Mathematical properties like commutativity
224+
- **Cross-platform tests**: Ensuring consistency across platforms
225+
218226
Run tests using Swift's built-in testing framework:
219227

220228
```bash
221229
swift test
222230
```
223231

232+
### Property-Based Testing
233+
234+
The library includes property-based tests that verify mathematical properties such as:
235+
236+
- **Commutativity**: `a + b == b + a`, `a * b == b * a`
237+
- **Identity**: `a + 0 == a`, `a * 1 == a`
238+
- **Distributivity**: `k * (a + b) == k * a + k * b`
239+
- **Consistency**: Array-based vs direct SIMD operations produce identical results
240+
- **Bitwise properties**: AND, OR, XOR commutativity for integer operations
241+
242+
These tests run with randomly generated inputs to catch edge cases that traditional example-based tests might miss.
243+
224244
## Requirements
225245

226246
- Swift 6.1+
Lines changed: 192 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,192 @@
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

Comments
 (0)