Surge/Tests/SurgeTests/XCTAssert+Surge.swift

282 lines
11 KiB
Swift
Raw Normal View History

// Copyright © 2014-2019 the Surge contributors
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
import Foundation
import XCTest
2019-08-29 21:22:58 +08:00
@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 zip(actualArray, expectedArray).enumerated() {
2019-08-29 21:22:58 +08:00
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 zip(actualGrid, expectedGrid).enumerated() {
2019-08-29 21:22:58 +08:00
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,
2019-08-29 21:22:58 +08:00
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) where T: Collection, T.Element: FloatingPoint & ExpressibleByFloatLiteral {
2019-08-29 21:22:58 +08:00
XCTAssertEqual1D(
try expression1(),
try expression2(),
accuracy: accuracy,
message(),
file: file,
line: line
)
}
/// Asserts that two values are equal within a certain accuracy.
///
/// Semantically the same as its `XCTAssertEqual<T>(_:_:accuracy:_:file:line:)` counterpart
/// (i.e. without the `1D` suffix), but with improved error messages.
///
/// - 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>(
2019-08-29 21:22:58 +08:00
_ 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 {
2019-08-29 21:22:58 +08:00
let prefix: Prefix = (accuracy == nil) ? .assertEqual : .assertEqualWithAccuracy
let (actual, expected): (T, T)
2019-08-07 01:26:46 +08:00
do {
2019-08-29 21:22:58 +08:00
(actual, expected) = (try expression1(), try expression2())
} catch let error {
2019-08-29 21:22:58 +08:00
let message = String(describing: error)
return fail(prefix: prefix, failureMessage: message, file: file, line: line)
}
2019-08-07 01:26:46 +08:00
2019-08-29 21:22:58 +08:00
let result = checkArray(actual, expected, accuracy: accuracy ?? 0.0)
2019-08-07 01:26:46 +08:00
2019-08-29 21:22:58 +08:00
guard case .failure(let error) = result else {
return
}
2019-08-07 01:26:46 +08:00
2019-08-29 21:22:58 +08:00
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 {
2019-08-29 21:22:58 +08:00
XCTAssertEqual2D(
try expression1(),
try expression2(),
accuracy: accuracy,
message(),
file: file,
line: line
)
}
/// Asserts that two values are equal within a certain accuracy.
///
/// Semantically the same as its `XCTAssertEqual<T>(_:_:accuracy:_:file:line:)` counterpart
/// (i.e. without the `2D` suffix), but with improved error messages.
///
/// - 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>(
2019-08-29 21:22:58 +08:00
_ expression1: @autoclosure () throws -> T,
_ expression2: @autoclosure () throws -> T,
accuracy: U.Element? = nil,
2019-08-29 21:22:58 +08:00
_ message: @autoclosure () -> String = "",
file: StaticString = #file,
line: UInt = #line
) where T: Collection, U: Collection, T.Element == U, U.Element: FloatingPoint & ExpressibleByFloatLiteral {
2019-08-29 21:22:58 +08:00
let prefix: Prefix = (accuracy == nil) ? .assertEqual : .assertEqualWithAccuracy
let (actual, expected): (T, T)
2019-08-07 01:26:46 +08:00
do {
2019-08-29 21:22:58 +08:00
(actual, expected) = (try expression1(), try expression2())
} catch let error {
2019-08-29 21:22:58 +08:00
let message = String(describing: error)
return fail(prefix: prefix, failureMessage: message, file: file, line: line)
}
2019-08-07 01:26:46 +08:00
2019-08-29 21:22:58 +08:00
let result = checkGrid(actual, expected, accuracy: accuracy ?? 0.0)
2019-08-07 01:26:46 +08:00
2019-08-29 21:22:58 +08:00
guard case .failure(let error) = result else {
return
}
2019-08-29 21:22:58 +08:00
return fail(prefix: prefix, failureMessage: error.description, file: file, line: line)
}