Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Replaced XCTAssertArray(Float|Double)EqualWithAccuracy(_:_:accuracy:) #104

Merged
merged 3 commits into from
Aug 29, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion Surge.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -522,7 +522,7 @@
attributes = {
LastSwiftMigration = 0710;
LastSwiftUpdateCheck = 0910;
LastUpgradeCheck = 1100;
LastUpgradeCheck = 0930;
ORGANIZATIONNAME = "Mattt Thompson";
TargetAttributes = {
614AD31E1FC0AD99002BFE1C = {
Expand Down
9 changes: 6 additions & 3 deletions Tests/SurgeTests/MatrixTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand All @@ -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() {
Expand Down
287 changes: 226 additions & 61 deletions Tests/SurgeTests/XCTAssert+Surge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,96 +21,261 @@
import Foundation
import XCTest

/// Allows comparing:
@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)
}

/// Asserts that two values are equal within a certain accuracy.
///
/// - Parameters:
/// - expression1: An expression of type `T: Collection`, where `T.Element` conforms `FloatingPoint`.
/// - expression2: An expression of type `T: Collection`, where `T.Element` conforms `FloatingPoint`.
/// - accuracy: An expression of type `T.Element`, where `T.Element` conforms to `FloatingPoint`.
/// Describes the maximum difference between `expression1` and `expression2` for these values to be considered equal.
/// - message: An optional description of the failure.
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
func XCTAssertEqual<T>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: T.Element? = nil,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) where T: Collection, T.Element: FloatingPoint & ExpressibleByFloatLiteral {
XCTAssertEqual1D(
try expression1(),
try expression2(),
accuracy: accuracy,
message(),
file: file,
line: line
)
}

/// Asserts that two values are equal within a certain accuracy.
///
/// ```
/// T where
/// T: Collection,
/// T.Element == U,
/// U: FloatingPoint
/// ```
/// Semantically the same as its `XCTAssertEqual<T>(_:_:accuracy:_:file:line:)` counterpart
/// (i.e. without the `…1D` suffix), but with improved error messages.
///
/// Useful for comparing:
/// - `[Float]`
/// - `[Double]`
@discardableResult
func XCTAssertEqual<T, U>(
/// - Parameters:
/// - expression1: An expression of type `T: Collection`, where `T.Element` conforms `FloatingPoint`.
/// - expression2: An expression of type `T: Collection`, where `T.Element` conforms `FloatingPoint`.
/// - accuracy: An expression of type `T.Element`, where `T.Element` conforms to `FloatingPoint`.
/// Describes the maximum difference between `expression1` and `expression2` for these values to be considered equal.
/// - message: An optional description of the failure.
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
func XCTAssertEqual1D<T>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: U,
accuracy: T.Element? = 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: 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)
}

/// Asserts that two values are equal within a certain accuracy.
///
/// - Parameters:
/// - expression1: An expression of type `T: Collection`, `T.Element == U`, where `U` is `FloatingPoint`.
/// - expression2: An expression of type `T: Collection`, `T.Element == U`, where `U` is `FloatingPoint`.
/// - accuracy: An expression of type `U.Element`, where `U.Element` conforms to `FloatingPoint`.
/// Describes the maximum difference between `expression1` and `expression2` for these values to be considered equal.
/// - message: An optional description of the failure.
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
func XCTAssertEqual<T, U>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: U.Element? = nil,
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) where T: Collection, U: Collection, T.Element == U, U.Element: FloatingPoint & ExpressibleByFloatLiteral {
XCTAssertEqual2D(
try expression1(),
try expression2(),
accuracy: accuracy,
message(),
file: file,
line: line
)
}

/// Allows comparing:
/// Asserts that two values are equal within a certain accuracy.
///
/// ```
/// T where
/// T: Collection,
/// U: Collection,
/// T.Element == U,
/// U.Element == V,
/// V: FloatingPoint
/// ```
/// Semantically the same as its `XCTAssertEqual<T>(_:_:accuracy:_:file:line:)` counterpart
/// (i.e. without the `…2D` suffix), but with improved error messages.
///
/// Useful for comparing:
/// - `[[Float]]`
/// - `[[Double]]`
/// - `Matrix<Float>`
/// - `Matrix<Double>`
@discardableResult
func XCTAssertEqual<T, U, V>(
/// - Parameters:
/// - expression1: An expression of type `T: Collection`, `T.Element == U`, where `U` is `FloatingPoint`.
/// - expression2: An expression of type `T: Collection`, `T.Element == U`, where `U` is `FloatingPoint`.
/// - accuracy: An expression of type `U.Element`, where `U.Element` conforms to `FloatingPoint`.
/// Describes the maximum difference between `expression1` and `expression2` for these values to be considered equal.
/// - message: An optional description of the failure.
/// - file: The file in which failure occurred. Defaults to the file name of the test case in which this function was called.
/// - line: The line number on which failure occurred. Defaults to the line number on which this function was called.
func XCTAssertEqual2D<T, U>(
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: V,
accuracy: U.Element? = 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: 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)
}