Skip to content

Commit 2363135

Browse files
committed
Improved assert helper ergonomics
1 parent ab456c3 commit 2363135

File tree

2 files changed

+188
-38
lines changed

2 files changed

+188
-38
lines changed

Tests/SurgeTests/MatrixTests.swift

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -49,12 +49,14 @@ class MatrixTests: XCTestCase {
4949

5050
func testSetRow() {
5151
matrix[row: 0] = [13.0, 14.0, 15.0, 16.0]
52-
XCTAssertTrue(matrix == Matrix<Double>([[13, 14, 15, 16], [5, 6, 7, 8], [9, 10, 11, 12]]))
52+
let expectedResult: Matrix<Double> = Matrix<Double>([[13, 14, 15, 16], [5, 6, 7, 8], [9, 10, 11, 12]])
53+
XCTAssertEqual(matrix, expectedResult)
5354
}
5455

5556
func testSetColumn() {
5657
matrix[column: 0] = [20, 30, 40]
57-
XCTAssertEqual(matrix, Matrix<Double>([[20, 2, 3, 4], [30, 6, 7, 8], [40, 10, 11, 12]]))
58+
let expectedResult: Matrix<Double> = Matrix<Double>([[20, 2, 3, 4], [30, 6, 7, 8], [40, 10, 11, 12]])
59+
XCTAssertEqual(matrix, expectedResult)
5860
}
5961

6062
func testMatrixPower() {
@@ -74,7 +76,8 @@ class MatrixTests: XCTestCase {
7476

7577
func testElementWiseMultiplication() {
7678
let matrix2 = Matrix<Double>([[2, 3, 4, 5], [6, 7, 8, 9], [10, 11, 12, 13]])
77-
XCTAssertEqual(elmul(matrix, matrix2), Matrix<Double>([[2, 6, 12, 20], [30, 42, 56, 72], [90, 110, 132, 156]]))
79+
let expectedResult: Matrix<Double> = Matrix<Double>([[2, 6, 12, 20], [30, 42, 56, 72], [90, 110, 132, 156]])
80+
XCTAssertEqual(elmul(matrix, matrix2), expectedResult)
7881
}
7982

8083
func testDeterminantFloat() {

Tests/SurgeTests/XCTAssert+Surge.swift

Lines changed: 182 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,127 @@
2121
import Foundation
2222
import XCTest
2323

24+
@testable import Surge
25+
26+
private struct ValueError: Swift.Error, CustomStringConvertible {
27+
let message: String
28+
29+
var description: String {
30+
return self.message
31+
}
32+
}
33+
34+
private enum ArrayError: Swift.Error, CustomStringConvertible {
35+
case size(message: String)
36+
case content(index: Int, content: ValueError)
37+
38+
var description: String {
39+
switch self {
40+
case let .size(message):
41+
return message
42+
case let .content(index, error):
43+
return "Failure at index [\(index)]: \(error)"
44+
}
45+
}
46+
}
47+
48+
private enum GridError: Swift.Error, CustomStringConvertible {
49+
case size(message: String)
50+
case content(index: Int, content: ArrayError)
51+
52+
var description: String {
53+
switch self {
54+
case let .size(message):
55+
return message
56+
case let .content(gridIndex, arrayError):
57+
switch arrayError {
58+
case let .size(message):
59+
return "Failure at index [\(gridIndex), ..]: \(message)"
60+
case let .content(arrayIndex, valueError):
61+
return "Failure at index [\(gridIndex), \(arrayIndex)]: \(valueError)"
62+
}
63+
}
64+
}
65+
}
66+
67+
private func checkValue<T>(
68+
_ actualValue: T,
69+
_ expectedValue: T,
70+
accuracy: T
71+
) -> Result<(), ValueError> where T: FloatingPoint {
72+
guard abs(actualValue - expectedValue) <= abs(accuracy) else {
73+
let (actual, expected) = (actualValue, expectedValue)
74+
let message = "(\(actual)) is not equal to (\(expected)) +/- (\(accuracy))"
75+
return .failure(ValueError(message: message))
76+
}
77+
78+
return .success(())
79+
}
80+
81+
private func checkArray<T, U>(
82+
_ actualArray: T,
83+
_ expectedArray: T,
84+
accuracy: U
85+
) -> Result<(), ArrayError> where T: Collection, T.Element == U, U: FloatingPoint {
86+
guard actualArray.count == expectedArray.count else {
87+
let (actual, expected) = (actualArray.count, expectedArray.count)
88+
let message = "Values have different size: (\(actual)) is not equal to (\(expected))"
89+
return .failure(.size(message: message))
90+
}
91+
92+
for (index, (actualValue, expectedValue)) in Swift.zip(actualArray, expectedArray).enumerated() {
93+
switch checkValue(actualValue, expectedValue, accuracy: accuracy) {
94+
case .success:
95+
continue
96+
case .failure(let error):
97+
return .failure(.content(index: index, content: error))
98+
}
99+
}
100+
101+
return .success(())
102+
}
103+
104+
private func checkGrid<T, U, V>(
105+
_ actualGrid: T,
106+
_ expectedGrid: T,
107+
accuracy: V
108+
) -> Result<(), GridError> where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint {
109+
guard actualGrid.count == expectedGrid.count else {
110+
let (actual, expected) = (actualGrid.count, expectedGrid.count)
111+
let message = "Values have different size: (\(actual) × _) is not equal to (\(expected) × _)"
112+
return .failure(.size(message: message))
113+
}
114+
115+
for (index, (actualArray, expectedArray)) in Swift.zip(actualGrid, expectedGrid).enumerated() {
116+
switch checkArray(actualArray, expectedArray, accuracy: accuracy) {
117+
case .success:
118+
continue
119+
case .failure(let error):
120+
return .failure(.content(index: index, content: error))
121+
}
122+
}
123+
124+
return .success(())
125+
}
126+
127+
private enum Prefix: String {
128+
case assertEqual = "XCTAssertEqual"
129+
case assertEqualWithAccuracy = "XCTAssertEqualWithAccuracy"
130+
}
131+
132+
private func fail(
133+
prefix: Prefix,
134+
failureMessage: String,
135+
userMessage: String? = nil,
136+
file: StaticString,
137+
line: UInt
138+
) {
139+
let prefix = "\(prefix.rawValue) failed: "
140+
let suffix = userMessage.map { " - \($0)" } ?? ""
141+
let message = "\(prefix)\(failureMessage)\(suffix)"
142+
XCTFail(message, file: file, line: line)
143+
}
144+
24145
/// Allows comparing:
25146
///
26147
/// ```
@@ -33,41 +154,50 @@ import XCTest
33154
/// Useful for comparing:
34155
/// - `[Float]`
35156
/// - `[Double]`
36-
@discardableResult
37157
func XCTAssertEqual<T, U>(
38158
_ expression1: @autoclosure () throws -> T,
39159
_ expression2: @autoclosure () throws -> T,
40-
accuracy: U,
160+
accuracy: U? = nil,
161+
_ message: @autoclosure () -> String = "",
162+
file: StaticString = #file,
163+
line: UInt = #line
164+
) where T: Collection, T.Element == U, U: FloatingPoint & ExpressibleByFloatLiteral {
165+
XCTAssertEqual1D(
166+
try expression1(),
167+
try expression2(),
168+
accuracy: accuracy,
169+
message(),
170+
file: file,
171+
line: line
172+
)
173+
}
174+
175+
func XCTAssertEqual1D<T, U>(
176+
_ expression1: @autoclosure () throws -> T,
177+
_ expression2: @autoclosure () throws -> T,
178+
accuracy: U? = nil,
41179
_ message: @autoclosure () -> String = "",
42180
file: StaticString = #file,
43181
line: UInt = #line
44-
) -> Bool
45-
where T: Collection, T.Element == U, U: FloatingPoint {
46-
let (actualValues, expectedValues): (T, T)
182+
) where T: Collection, T.Element == U, U: FloatingPoint & ExpressibleByFloatLiteral {
183+
let prefix: Prefix = (accuracy == nil) ? .assertEqual : .assertEqualWithAccuracy
184+
185+
let (actual, expected): (T, T)
47186

48187
do {
49-
(actualValues, expectedValues) = (try expression1(), try expression2())
188+
(actual, expected) = (try expression1(), try expression2())
50189
} catch let error {
51-
XCTFail("Error: \(error)", file: file, line: line)
52-
return false
190+
let message = String(describing: error)
191+
return fail(prefix: prefix, failureMessage: message, file: file, line: line)
53192
}
54193

55-
XCTAssertEqual(actualValues.count, expectedValues.count, file: file, line: line)
56-
57-
for (actual, expected) in Swift.zip(actualValues, expectedValues) {
58-
guard abs(actual - expected) > abs(accuracy) else {
59-
continue
60-
}
61-
62-
let failureMessage = "XCTAssertEqualWithAccuracy failed: (\(actual)) is not equal to (\(expected)) +/- (\(accuracy))"
63-
let userMessage = message()
64-
let message = "\(failureMessage) - \(userMessage)"
65-
XCTFail(message, file: file, line: line)
194+
let result = checkArray(actual, expected, accuracy: accuracy ?? 0.0)
66195

67-
return false
196+
guard case .failure(let error) = result else {
197+
return
68198
}
69199

70-
return true
200+
return fail(prefix: prefix, failureMessage: error.description, file: file, line: line)
71201
}
72202

73203
/// Allows comparing:
@@ -86,31 +216,48 @@ func XCTAssertEqual<T, U>(
86216
/// - `[[Double]]`
87217
/// - `Matrix<Float>`
88218
/// - `Matrix<Double>`
89-
@discardableResult
90219
func XCTAssertEqual<T, U, V>(
91220
_ expression1: @autoclosure () throws -> T,
92221
_ expression2: @autoclosure () throws -> T,
93-
accuracy: V,
222+
accuracy: V? = nil,
94223
_ message: @autoclosure () -> String = "",
95224
file: StaticString = #file,
96225
line: UInt = #line
97-
) -> Bool
98-
where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint {
99-
let (actualValues, expectedValues): (T, T)
226+
) where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint & ExpressibleByFloatLiteral {
227+
XCTAssertEqual2D(
228+
try expression1(),
229+
try expression2(),
230+
accuracy: accuracy,
231+
message(),
232+
file: file,
233+
line: line
234+
)
235+
}
236+
237+
func XCTAssertEqual2D<T, U, V>(
238+
_ expression1: @autoclosure () throws -> T,
239+
_ expression2: @autoclosure () throws -> T,
240+
accuracy: V? = nil,
241+
_ message: @autoclosure () -> String = "",
242+
file: StaticString = #file,
243+
line: UInt = #line
244+
) where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint & ExpressibleByFloatLiteral {
245+
let prefix: Prefix = (accuracy == nil) ? .assertEqual : .assertEqualWithAccuracy
246+
247+
let (actual, expected): (T, T)
100248

101249
do {
102-
(actualValues, expectedValues) = (try expression1(), try expression2())
250+
(actual, expected) = (try expression1(), try expression2())
103251
} catch let error {
104-
XCTFail("Error: \(error)", file: file, line: line)
105-
return false
252+
let message = String(describing: error)
253+
return fail(prefix: prefix, failureMessage: message, file: file, line: line)
106254
}
107255

108-
XCTAssertEqual(actualValues.count, expectedValues.count, file: file, line: line)
256+
let result = checkGrid(actual, expected, accuracy: accuracy ?? 0.0)
109257

110-
for (actual, expected) in Swift.zip(actualValues, expectedValues) {
111-
guard XCTAssertEqual(actual, expected, accuracy: accuracy) else {
112-
return false
113-
}
258+
guard case .failure(let error) = result else {
259+
return
114260
}
115-
return true
261+
262+
return fail(prefix: prefix, failureMessage: error.description, file: file, line: line)
116263
}

0 commit comments

Comments
 (0)