Skip to content

Commit 36ab024

Browse files
committed
assert: Fix EqualValues to handle overflow/underflow
The underlying function ObjectsAreEqualValues did not handle overflow/underflow of values while converting one type to another for comparison. For example: EqualValues(t, int(270), int8(14)) would return true, even though the values are not equal. Because, when you convert int(270) to int8, it overflows and becomes 14 (270 % 256 = 14) This commit fixes that by making sure that the conversion always happens from the smaller type to the larger type, and then comparing the values. Additionally, this commit also seperates out the test cases of ObjectsAreEqualValues from TestObjectsAreEqual. Fixes #1462
1 parent c3b0c9b commit 36ab024

File tree

2 files changed

+49
-18
lines changed

2 files changed

+49
-18
lines changed

assert/assertions.go

Lines changed: 25 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -165,19 +165,40 @@ func ObjectsAreEqualValues(expected, actual interface{}) bool {
165165
return true
166166
}
167167

168-
actualType := reflect.TypeOf(actual)
169-
if actualType == nil {
168+
expectedValue := reflect.ValueOf(expected)
169+
actualValue := reflect.ValueOf(actual)
170+
if !expectedValue.IsValid() || !actualValue.IsValid() {
170171
return false
171172
}
172-
expectedValue := reflect.ValueOf(expected)
173-
if expectedValue.IsValid() && expectedValue.Type().ConvertibleTo(actualType) {
173+
174+
expectedType := expectedValue.Type()
175+
actualType := actualValue.Type()
176+
if expectedType.ConvertibleTo(actualType) {
177+
if isNumericType(expectedType) && isNumericType(actualType) {
178+
// If both values are numeric, there are chances of false positives due
179+
// to overflow or underflow. So, we need to make sure to always convert
180+
// the smaller type to a larger type before comparing.
181+
if expectedType.Size() >= actualType.Size() {
182+
return actualValue.Convert(expectedType).Interface() == expected
183+
}
184+
185+
return expectedValue.Convert(actualType).Interface() == actual
186+
}
187+
174188
// Attempt comparison after type conversion
175189
return reflect.DeepEqual(expectedValue.Convert(actualType).Interface(), actual)
176190
}
177191

178192
return false
179193
}
180194

195+
// isNumericType returns true if the type is one of:
196+
// int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64,
197+
// float32, float64
198+
func isNumericType(t reflect.Type) bool {
199+
return t.Kind() >= reflect.Int && t.Kind() <= reflect.Float64
200+
}
201+
181202
/* CallerInfo is necessary because the assert functions use the testing object
182203
internally, causing it to print the file:line of the assert method, rather than where
183204
the problem actually occurred in calling code.*/

assert/assertions_test.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -135,24 +135,34 @@ func TestObjectsAreEqual(t *testing.T) {
135135

136136
})
137137
}
138+
}
138139

139-
// Cases where type differ but values are equal
140-
if !ObjectsAreEqualValues(uint32(10), int32(10)) {
141-
t.Error("ObjectsAreEqualValues should return true")
142-
}
143-
if ObjectsAreEqualValues(0, nil) {
144-
t.Fail()
145-
}
146-
if ObjectsAreEqualValues(nil, 0) {
147-
t.Fail()
148-
}
140+
func TestObjectsAreEqualValues(t *testing.T) {
141+
now := time.Now()
149142

150-
tm := time.Now()
151-
tz := tm.In(time.Local)
152-
if !ObjectsAreEqualValues(tm, tz) {
153-
t.Error("ObjectsAreEqualValues should return true for time.Time objects with different time zones")
143+
cases := []struct {
144+
expected interface{}
145+
actual interface{}
146+
result bool
147+
}{
148+
{uint32(10), int32(10), true},
149+
{0, nil, false},
150+
{nil, 0, false},
151+
{now, now.In(time.Local), true}, // should be time zone independent
152+
{int(270), int8(14), false}, // should handle overflow/underflow
153+
{int8(14), int(270), false},
154+
{[]int{270, 270}, []int8{14, 14}, false},
154155
}
155156

157+
for _, c := range cases {
158+
t.Run(fmt.Sprintf("ObjectsAreEqualValues(%#v, %#v)", c.expected, c.actual), func(t *testing.T) {
159+
res := ObjectsAreEqualValues(c.expected, c.actual)
160+
161+
if res != c.result {
162+
t.Errorf("ObjectsAreEqualValues(%#v, %#v) should return %#v", c.expected, c.actual, c.result)
163+
}
164+
})
165+
}
156166
}
157167

158168
type Nested struct {

0 commit comments

Comments
 (0)