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