BeCloseTo.swift 5.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149
  1. import Foundation
  2. // swiftlint:disable:next identifier_name
  3. public let DefaultDelta = 0.0001
  4. internal func isCloseTo(_ actualValue: NMBDoubleConvertible?,
  5. expectedValue: NMBDoubleConvertible,
  6. delta: Double)
  7. -> PredicateResult {
  8. let errorMessage = "be close to <\(stringify(expectedValue))> (within \(stringify(delta)))"
  9. return PredicateResult(
  10. bool: actualValue != nil &&
  11. abs(actualValue!.doubleValue - expectedValue.doubleValue) < delta,
  12. message: .expectedCustomValueTo(errorMessage, "<\(stringify(actualValue))>")
  13. )
  14. }
  15. /// A Nimble matcher that succeeds when a value is close to another. This is used for floating
  16. /// point values which can have imprecise results when doing arithmetic on them.
  17. ///
  18. /// @see equal
  19. public func beCloseTo(_ expectedValue: Double, within delta: Double = DefaultDelta) -> Predicate<Double> {
  20. return Predicate.define { actualExpression in
  21. return isCloseTo(try actualExpression.evaluate(), expectedValue: expectedValue, delta: delta)
  22. }
  23. }
  24. /// A Nimble matcher that succeeds when a value is close to another. This is used for floating
  25. /// point values which can have imprecise results when doing arithmetic on them.
  26. ///
  27. /// @see equal
  28. public func beCloseTo(_ expectedValue: NMBDoubleConvertible, within delta: Double = DefaultDelta) -> Predicate<NMBDoubleConvertible> {
  29. return Predicate.define { actualExpression in
  30. return isCloseTo(try actualExpression.evaluate(), expectedValue: expectedValue, delta: delta)
  31. }
  32. }
  33. #if canImport(Darwin)
  34. public class NMBObjCBeCloseToMatcher: NSObject, NMBMatcher {
  35. // swiftlint:disable identifier_name
  36. var _expected: NSNumber
  37. var _delta: CDouble
  38. // swiftlint:enable identifier_name
  39. init(expected: NSNumber, within: CDouble) {
  40. _expected = expected
  41. _delta = within
  42. }
  43. @objc public func matches(_ actualExpression: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
  44. let actualBlock: () -> NMBDoubleConvertible? = ({
  45. return actualExpression() as? NMBDoubleConvertible
  46. })
  47. let expr = Expression(expression: actualBlock, location: location)
  48. let predicate = beCloseTo(self._expected, within: self._delta)
  49. do {
  50. let result = try predicate.satisfies(expr)
  51. result.message.update(failureMessage: failureMessage)
  52. return result.toBoolean(expectation: .toMatch)
  53. } catch let error {
  54. failureMessage.stringValue = "unexpected error thrown: <\(error)>"
  55. return false
  56. }
  57. }
  58. @objc public func doesNotMatch(_ actualExpression: @escaping () -> NSObject?, failureMessage: FailureMessage, location: SourceLocation) -> Bool {
  59. let actualBlock: () -> NMBDoubleConvertible? = ({
  60. return actualExpression() as? NMBDoubleConvertible
  61. })
  62. let expr = Expression(expression: actualBlock, location: location)
  63. let predicate = beCloseTo(self._expected, within: self._delta)
  64. do {
  65. let result = try predicate.satisfies(expr)
  66. result.message.update(failureMessage: failureMessage)
  67. return result.toBoolean(expectation: .toNotMatch)
  68. } catch let error {
  69. failureMessage.stringValue = "unexpected error thrown: <\(error)>"
  70. return false
  71. }
  72. }
  73. @objc public var within: (CDouble) -> NMBObjCBeCloseToMatcher {
  74. return { delta in
  75. return NMBObjCBeCloseToMatcher(expected: self._expected, within: delta)
  76. }
  77. }
  78. }
  79. extension NMBObjCMatcher {
  80. @objc public class func beCloseToMatcher(_ expected: NSNumber, within: CDouble) -> NMBObjCBeCloseToMatcher {
  81. return NMBObjCBeCloseToMatcher(expected: expected, within: within)
  82. }
  83. }
  84. #endif
  85. public func beCloseTo(_ expectedValues: [Double], within delta: Double = DefaultDelta) -> Predicate<[Double]> {
  86. let errorMessage = "be close to <\(stringify(expectedValues))> (each within \(stringify(delta)))"
  87. return Predicate.simple(errorMessage) { actualExpression in
  88. if let actual = try actualExpression.evaluate() {
  89. if actual.count != expectedValues.count {
  90. return .doesNotMatch
  91. } else {
  92. for (index, actualItem) in actual.enumerated() {
  93. if fabs(actualItem - expectedValues[index]) > delta {
  94. return .doesNotMatch
  95. }
  96. }
  97. return .matches
  98. }
  99. }
  100. return .doesNotMatch
  101. }
  102. }
  103. // MARK: - Operators
  104. infix operator ≈ : ComparisonPrecedence
  105. // swiftlint:disable:next identifier_name
  106. public func ≈(lhs: Expectation<[Double]>, rhs: [Double]) {
  107. lhs.to(beCloseTo(rhs))
  108. }
  109. // swiftlint:disable:next identifier_name
  110. public func ≈(lhs: Expectation<NMBDoubleConvertible>, rhs: NMBDoubleConvertible) {
  111. lhs.to(beCloseTo(rhs))
  112. }
  113. // swiftlint:disable:next identifier_name
  114. public func ≈(lhs: Expectation<NMBDoubleConvertible>, rhs: (expected: NMBDoubleConvertible, delta: Double)) {
  115. lhs.to(beCloseTo(rhs.expected, within: rhs.delta))
  116. }
  117. public func == (lhs: Expectation<NMBDoubleConvertible>, rhs: (expected: NMBDoubleConvertible, delta: Double)) {
  118. lhs.to(beCloseTo(rhs.expected, within: rhs.delta))
  119. }
  120. // make this higher precedence than exponents so the Doubles either end aren't pulled in
  121. // unexpectantly
  122. precedencegroup PlusMinusOperatorPrecedence {
  123. higherThan: BitwiseShiftPrecedence
  124. }
  125. infix operator ± : PlusMinusOperatorPrecedence
  126. // swiftlint:disable:next identifier_name
  127. public func ±(lhs: NMBDoubleConvertible, rhs: Double) -> (expected: NMBDoubleConvertible, delta: Double) {
  128. return (expected: lhs, delta: rhs)
  129. }