be_numerically_matcher.go 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. // untested sections: 4
  2. package matchers
  3. import (
  4. "fmt"
  5. "math"
  6. "github.com/onsi/gomega/format"
  7. )
  8. type BeNumericallyMatcher struct {
  9. Comparator string
  10. CompareTo []interface{}
  11. }
  12. func (matcher *BeNumericallyMatcher) FailureMessage(actual interface{}) (message string) {
  13. return matcher.FormatFailureMessage(actual, false)
  14. }
  15. func (matcher *BeNumericallyMatcher) NegatedFailureMessage(actual interface{}) (message string) {
  16. return matcher.FormatFailureMessage(actual, true)
  17. }
  18. func (matcher *BeNumericallyMatcher) FormatFailureMessage(actual interface{}, negated bool) (message string) {
  19. if len(matcher.CompareTo) == 1 {
  20. message = fmt.Sprintf("to be %s", matcher.Comparator)
  21. } else {
  22. message = fmt.Sprintf("to be within %v of %s", matcher.CompareTo[1], matcher.Comparator)
  23. }
  24. if negated {
  25. message = "not " + message
  26. }
  27. return format.Message(actual, message, matcher.CompareTo[0])
  28. }
  29. func (matcher *BeNumericallyMatcher) Match(actual interface{}) (success bool, err error) {
  30. if len(matcher.CompareTo) == 0 || len(matcher.CompareTo) > 2 {
  31. return false, fmt.Errorf("BeNumerically requires 1 or 2 CompareTo arguments. Got:\n%s", format.Object(matcher.CompareTo, 1))
  32. }
  33. if !isNumber(actual) {
  34. return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(actual, 1))
  35. }
  36. if !isNumber(matcher.CompareTo[0]) {
  37. return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
  38. }
  39. if len(matcher.CompareTo) == 2 && !isNumber(matcher.CompareTo[1]) {
  40. return false, fmt.Errorf("Expected a number. Got:\n%s", format.Object(matcher.CompareTo[0], 1))
  41. }
  42. switch matcher.Comparator {
  43. case "==", "~", ">", ">=", "<", "<=":
  44. default:
  45. return false, fmt.Errorf("Unknown comparator: %s", matcher.Comparator)
  46. }
  47. if isFloat(actual) || isFloat(matcher.CompareTo[0]) {
  48. var secondOperand float64 = 1e-8
  49. if len(matcher.CompareTo) == 2 {
  50. secondOperand = toFloat(matcher.CompareTo[1])
  51. }
  52. success = matcher.matchFloats(toFloat(actual), toFloat(matcher.CompareTo[0]), secondOperand)
  53. } else if isInteger(actual) {
  54. var secondOperand int64 = 0
  55. if len(matcher.CompareTo) == 2 {
  56. secondOperand = toInteger(matcher.CompareTo[1])
  57. }
  58. success = matcher.matchIntegers(toInteger(actual), toInteger(matcher.CompareTo[0]), secondOperand)
  59. } else if isUnsignedInteger(actual) {
  60. var secondOperand uint64 = 0
  61. if len(matcher.CompareTo) == 2 {
  62. secondOperand = toUnsignedInteger(matcher.CompareTo[1])
  63. }
  64. success = matcher.matchUnsignedIntegers(toUnsignedInteger(actual), toUnsignedInteger(matcher.CompareTo[0]), secondOperand)
  65. } else {
  66. return false, fmt.Errorf("Failed to compare:\n%s\n%s:\n%s", format.Object(actual, 1), matcher.Comparator, format.Object(matcher.CompareTo[0], 1))
  67. }
  68. return success, nil
  69. }
  70. func (matcher *BeNumericallyMatcher) matchIntegers(actual, compareTo, threshold int64) (success bool) {
  71. switch matcher.Comparator {
  72. case "==", "~":
  73. diff := actual - compareTo
  74. return -threshold <= diff && diff <= threshold
  75. case ">":
  76. return (actual > compareTo)
  77. case ">=":
  78. return (actual >= compareTo)
  79. case "<":
  80. return (actual < compareTo)
  81. case "<=":
  82. return (actual <= compareTo)
  83. }
  84. return false
  85. }
  86. func (matcher *BeNumericallyMatcher) matchUnsignedIntegers(actual, compareTo, threshold uint64) (success bool) {
  87. switch matcher.Comparator {
  88. case "==", "~":
  89. if actual < compareTo {
  90. actual, compareTo = compareTo, actual
  91. }
  92. return actual-compareTo <= threshold
  93. case ">":
  94. return (actual > compareTo)
  95. case ">=":
  96. return (actual >= compareTo)
  97. case "<":
  98. return (actual < compareTo)
  99. case "<=":
  100. return (actual <= compareTo)
  101. }
  102. return false
  103. }
  104. func (matcher *BeNumericallyMatcher) matchFloats(actual, compareTo, threshold float64) (success bool) {
  105. switch matcher.Comparator {
  106. case "~":
  107. return math.Abs(actual-compareTo) <= threshold
  108. case "==":
  109. return (actual == compareTo)
  110. case ">":
  111. return (actual > compareTo)
  112. case ">=":
  113. return (actual >= compareTo)
  114. case "<":
  115. return (actual < compareTo)
  116. case "<=":
  117. return (actual <= compareTo)
  118. }
  119. return false
  120. }