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