Improved assert helper ergonomics

This commit is contained in:
Vincent Esche 2019-08-29 15:22:58 +02:00
parent 3199fbce33
commit f0b34e00c5
2 changed files with 188 additions and 38 deletions

View File

@ -49,12 +49,14 @@ class MatrixTests: XCTestCase {
func testSetRow() {
matrix[row: 0] = [13.0, 14.0, 15.0, 16.0]
XCTAssertTrue(matrix == Matrix<Double>([[13, 14, 15, 16], [5, 6, 7, 8], [9, 10, 11, 12]]))
let expectedResult: Matrix<Double> = Matrix<Double>([[13, 14, 15, 16], [5, 6, 7, 8], [9, 10, 11, 12]])
XCTAssertEqual(matrix, expectedResult)
}
func testSetColumn() {
matrix[column: 0] = [20, 30, 40]
XCTAssertEqual(matrix, Matrix<Double>([[20, 2, 3, 4], [30, 6, 7, 8], [40, 10, 11, 12]]))
let expectedResult: Matrix<Double> = Matrix<Double>([[20, 2, 3, 4], [30, 6, 7, 8], [40, 10, 11, 12]])
XCTAssertEqual(matrix, expectedResult)
}
func testMatrixPower() {
@ -74,7 +76,8 @@ class MatrixTests: XCTestCase {
func testElementWiseMultiplication() {
let matrix2 = Matrix<Double>([[2, 3, 4, 5], [6, 7, 8, 9], [10, 11, 12, 13]])
XCTAssertEqual(elmul(matrix, matrix2), Matrix<Double>([[2, 6, 12, 20], [30, 42, 56, 72], [90, 110, 132, 156]]))
let expectedResult: Matrix<Double> = Matrix<Double>([[2, 6, 12, 20], [30, 42, 56, 72], [90, 110, 132, 156]])
XCTAssertEqual(elmul(matrix, matrix2), expectedResult)
}
func testDeterminantFloat() {

View File

@ -21,6 +21,127 @@
import Foundation
import XCTest
@testable import Surge
private struct ValueError: Swift.Error, CustomStringConvertible {
let message: String
var description: String {
return self.message
}
}
private enum ArrayError: Swift.Error, CustomStringConvertible {
case size(message: String)
case content(index: Int, content: ValueError)
var description: String {
switch self {
case let .size(message):
return message
case let .content(index, error):
return "Failure at index [\(index)]: \(error)"
}
}
}
private enum GridError: Swift.Error, CustomStringConvertible {
case size(message: String)
case content(index: Int, content: ArrayError)
var description: String {
switch self {
case let .size(message):
return message
case let .content(gridIndex, arrayError):
switch arrayError {
case let .size(message):
return "Failure at index [\(gridIndex), ..]: \(message)"
case let .content(arrayIndex, valueError):
return "Failure at index [\(gridIndex), \(arrayIndex)]: \(valueError)"
}
}
}
}
private func checkValue<T>(
_ actualValue: T,
_ expectedValue: T,
accuracy: T
) -> Result<(), ValueError> where T: FloatingPoint {
guard abs(actualValue - expectedValue) <= abs(accuracy) else {
let (actual, expected) = (actualValue, expectedValue)
let message = "(\(actual)) is not equal to (\(expected)) +/- (\(accuracy))"
return .failure(ValueError(message: message))
}
return .success(())
}
private func checkArray<T, U>(
_ actualArray: T,
_ expectedArray: T,
accuracy: U
) -> Result<(), ArrayError> where T: Collection, T.Element == U, U: FloatingPoint {
guard actualArray.count == expectedArray.count else {
let (actual, expected) = (actualArray.count, expectedArray.count)
let message = "Values have different size: (\(actual)) is not equal to (\(expected))"
return .failure(.size(message: message))
}
for (index, (actualValue, expectedValue)) in Swift.zip(actualArray, expectedArray).enumerated() {
switch checkValue(actualValue, expectedValue, accuracy: accuracy) {
case .success:
continue
case .failure(let error):
return .failure(.content(index: index, content: error))
}
}
return .success(())
}
private func checkGrid<T, U, V>(
_ actualGrid: T,
_ expectedGrid: T,
accuracy: V
) -> Result<(), GridError> where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint {
guard actualGrid.count == expectedGrid.count else {
let (actual, expected) = (actualGrid.count, expectedGrid.count)
let message = "Values have different size: (\(actual) × _) is not equal to (\(expected) × _)"
return .failure(.size(message: message))
}
for (index, (actualArray, expectedArray)) in Swift.zip(actualGrid, expectedGrid).enumerated() {
switch checkArray(actualArray, expectedArray, accuracy: accuracy) {
case .success:
continue
case .failure(let error):
return .failure(.content(index: index, content: error))
}
}
return .success(())
}
private enum Prefix: String {
case assertEqual = "XCTAssertEqual"
case assertEqualWithAccuracy = "XCTAssertEqualWithAccuracy"
}
private func fail(
prefix: Prefix,
failureMessage: String,
userMessage: String? = nil,
file: StaticString,
line: UInt
) {
let prefix = "\(prefix.rawValue) failed: "
let suffix = userMessage.map { " - \($0)" } ?? ""
let message = "\(prefix)\(failureMessage)\(suffix)"
XCTFail(message, file: file, line: line)
}
/// Allows comparing:
///
/// ```
@ -33,41 +154,50 @@ import XCTest
/// Useful for comparing:
/// - `[Float]`
/// - `[Double]`
@discardableResult
func XCTAssertEqual<T, U>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: U,
accuracy: U? = nil,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) -> Bool
where T: Collection, T.Element == U, U: FloatingPoint {
let (actualValues, expectedValues): (T, T)
) where T: Collection, T.Element == U, U: FloatingPoint & ExpressibleByFloatLiteral {
XCTAssertEqual1D(
try expression1(),
try expression2(),
accuracy: accuracy,
message(),
file: file,
line: line
)
}
func XCTAssertEqual1D<T, U>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: U? = nil,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) where T: Collection, T.Element == U, U: FloatingPoint & ExpressibleByFloatLiteral {
let prefix: Prefix = (accuracy == nil) ? .assertEqual : .assertEqualWithAccuracy
let (actual, expected): (T, T)
do {
(actualValues, expectedValues) = (try expression1(), try expression2())
(actual, expected) = (try expression1(), try expression2())
} catch let error {
XCTFail("Error: \(error)", file: file, line: line)
return false
let message = String(describing: error)
return fail(prefix: prefix, failureMessage: message, file: file, line: line)
}
XCTAssertEqual(actualValues.count, expectedValues.count, file: file, line: line)
let result = checkArray(actual, expected, accuracy: accuracy ?? 0.0)
for (actual, expected) in Swift.zip(actualValues, expectedValues) {
guard abs(actual - expected) > abs(accuracy) else {
continue
}
let failureMessage = "XCTAssertEqualWithAccuracy failed: (\(actual)) is not equal to (\(expected)) +/- (\(accuracy))"
let userMessage = message()
let message = "\(failureMessage) - \(userMessage)"
XCTFail(message, file: file, line: line)
return false
guard case .failure(let error) = result else {
return
}
return true
return fail(prefix: prefix, failureMessage: error.description, file: file, line: line)
}
/// Allows comparing:
@ -86,31 +216,48 @@ func XCTAssertEqual<T, U>(
/// - `[[Double]]`
/// - `Matrix<Float>`
/// - `Matrix<Double>`
@discardableResult
func XCTAssertEqual<T, U, V>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: V,
accuracy: V? = nil,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) -> Bool
where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint {
let (actualValues, expectedValues): (T, T)
) where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint & ExpressibleByFloatLiteral {
XCTAssertEqual2D(
try expression1(),
try expression2(),
accuracy: accuracy,
message(),
file: file,
line: line
)
}
func XCTAssertEqual2D<T, U, V>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: V? = nil,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) where T: Collection, U: Collection, T.Element == U, U.Element == V, V: FloatingPoint & ExpressibleByFloatLiteral {
let prefix: Prefix = (accuracy == nil) ? .assertEqual : .assertEqualWithAccuracy
let (actual, expected): (T, T)
do {
(actualValues, expectedValues) = (try expression1(), try expression2())
(actual, expected) = (try expression1(), try expression2())
} catch let error {
XCTFail("Error: \(error)", file: file, line: line)
return false
let message = String(describing: error)
return fail(prefix: prefix, failureMessage: message, file: file, line: line)
}
XCTAssertEqual(actualValues.count, expectedValues.count, file: file, line: line)
let result = checkGrid(actual, expected, accuracy: accuracy ?? 0.0)
for (actual, expected) in Swift.zip(actualValues, expectedValues) {
guard XCTAssertEqual(actual, expected, accuracy: accuracy) else {
return false
}
guard case .failure(let error) = result else {
return
}
return true
return fail(prefix: prefix, failureMessage: error.description, file: file, line: line)
}