diff --git a/assert/assertion_format.go b/assert/assertion_format.go index a19a89279..6078db869 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -366,6 +366,8 @@ func InDeltaf(t TestingT, expected interface{}, actual interface{}, delta float6 } // InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index cd2a86061..7d38786fe 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -713,6 +713,8 @@ func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta flo } // InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -721,6 +723,8 @@ func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, } // InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) bool { if h, ok := a.t.(tHelper); ok { h.Helper() diff --git a/assert/assertions.go b/assert/assertions.go index 1419e4776..aa5551c27 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1508,6 +1508,8 @@ func InDeltaSlice(t TestingT, expected, actual interface{}, delta float64, msgAn } // InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { h.Helper() @@ -1537,14 +1539,23 @@ func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, m return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) } - if !InDelta( - t, - ev.Interface(), - av.Interface(), - delta, - msgAndArgs..., - ) { - return false + af, aok := toFloat(ev.Interface()) + bf, bok := toFloat(av.Interface()) + if !aok || !bok { + return Fail(t, fmt.Sprintf("key[%v]: Parameters must be numerical", k), msgAndArgs...) + } + if math.IsNaN(af) && math.IsNaN(bf) { + continue + } + if math.IsNaN(af) { + return Fail(t, fmt.Sprintf("key[%v]: Expected must not be NaN", k), msgAndArgs...) + } + if math.IsNaN(bf) { + return Fail(t, fmt.Sprintf("key[%v]: Expected %v with delta %v, but was NaN", k, ev.Interface(), delta), msgAndArgs...) + } + dt := af - bf + if dt < -delta || dt > delta { + return Fail(t, fmt.Sprintf("key[%v]: Max difference between %v and %v allowed is %v, but difference was %v", k, ev.Interface(), av.Interface(), delta, dt), msgAndArgs...) } } diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 11642e096..a983c1307 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -2442,6 +2442,56 @@ func TestInDeltaMapValues(t *testing.T) { } } +func TestInDeltaMapValues_ErrorIncludesKey(t *testing.T) { + t.Parallel() + + for _, tc := range []struct { + title string + expected interface{} + actual interface{} + delta float64 + wantSub string + }{ + { + title: "delta exceeded on string key", + expected: map[string]int{"a": 3, "b": 1}, + actual: map[string]int{"a": 4, "b": 1}, + delta: 0.01, + wantSub: "key[a]:", + }, + { + title: "delta exceeded on int key", + expected: map[int]float64{1: 1.0, 2: 2.0}, + actual: map[int]float64{1: 1.0, 2: 5.0}, + delta: 0.1, + wantSub: "key[2]:", + }, + { + title: "non-numerical value at key", + expected: map[string]interface{}{"a": "not-a-number"}, + actual: map[string]interface{}{"a": "still-not"}, + delta: 0.1, + wantSub: "key[a]: Parameters must be numerical", + }, + { + title: "actual is NaN", + expected: map[string]float64{"x": 1.0}, + actual: map[string]float64{"x": math.NaN()}, + delta: 0.1, + wantSub: "key[x]:", + }, + } { + t.Run(tc.title, func(t *testing.T) { + mockT := new(mockTestingT) + False(t, InDeltaMapValues(mockT, tc.expected, tc.actual, tc.delta), + "InDeltaMapValues should fail for %s", tc.title) + True(t, mockT.Failed(), "mockT should have recorded a failure") + Contains(t, mockT.errorString(), tc.wantSub, + "failure message should mention the offending map key") + }) + } +} + func TestInEpsilon(t *testing.T) { t.Parallel() diff --git a/require/require.go b/require/require.go index 652871f2e..4a22263f7 100644 --- a/require/require.go +++ b/require/require.go @@ -867,6 +867,8 @@ func InDelta(t TestingT, expected interface{}, actual interface{}, delta float64 } // InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() @@ -878,6 +880,8 @@ func InDeltaMapValues(t TestingT, expected interface{}, actual interface{}, delt } // InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func InDeltaMapValuesf(t TestingT, expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { if h, ok := t.(tHelper); ok { h.Helper() diff --git a/require/require_forward.go b/require/require_forward.go index edac147ef..861d76380 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -690,6 +690,8 @@ func (a *Assertions) InDelta(expected interface{}, actual interface{}, delta flo } // InDeltaMapValues is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, delta float64, msgAndArgs ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper() @@ -698,6 +700,8 @@ func (a *Assertions) InDeltaMapValues(expected interface{}, actual interface{}, } // InDeltaMapValuesf is the same as InDelta, but it compares all values between two maps. Both maps must have exactly the same keys. +// When the assertion fails for a particular key, the failure message is prefixed with key[]: +// so the offending entry can be identified at a glance. func (a *Assertions) InDeltaMapValuesf(expected interface{}, actual interface{}, delta float64, msg string, args ...interface{}) { if h, ok := a.t.(tHelper); ok { h.Helper()