diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 896cc7fd0..03ab6d9b5 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -390,6 +390,14 @@ func InEpsilonSlicef(t TestingT, expected interface{}, actual interface{}, epsil return InEpsilonSlice(t, expected, actual, epsilon, append([]interface{}{msg}, args...)...) } +// InMapDeltasMapValuesf is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func InMapDeltasMapValuesf(t TestingT, expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return InMapDeltasMapValues(t, expected, actual, deltasMap, append([]interface{}{msg}, args...)...) +} + // IsDecreasingf asserts that the collection is decreasing // // assert.IsDecreasingf(t, []int{2, 1, 0}, "error message %s", "formatted") diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 8aaa25967..534438418 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -772,6 +772,22 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo return InEpsilonf(a.t, expected, actual, epsilon, msg, args...) } +// InMapDeltasMapValues is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func (a *Assertions) InMapDeltasMapValues(expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InMapDeltasMapValues(a.t, expected, actual, deltasMap, msgAndArgs...) +} + +// InMapDeltasMapValuesf is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func (a *Assertions) InMapDeltasMapValuesf(expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return InMapDeltasMapValuesf(a.t, expected, actual, deltasMap, msg, args...) +} + // IsDecreasing asserts that the collection is decreasing // // a.IsDecreasing([]int{2, 1, 0}) diff --git a/assert/assertions.go b/assert/assertions.go index bc15101b0..b622287fa 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -1424,6 +1424,55 @@ func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, m return true } +// InMapDeltasMapValues is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func InMapDeltasMapValues(t TestingT, expected, actual interface{}, deltasMap map[interface{}]float64, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if expected == nil || actual == nil || + reflect.TypeOf(actual).Kind() != reflect.Map || + reflect.TypeOf(expected).Kind() != reflect.Map { + return Fail(t, "Arguments must be maps", msgAndArgs...) + } + + expectedMap := reflect.ValueOf(expected) + actualMap := reflect.ValueOf(actual) + + if expectedMap.Len() != actualMap.Len() && expectedMap.Len() != len(deltasMap) { + return Fail(t, "Arguments must have the same number of keys", msgAndArgs...) + } + + for _, k := range expectedMap.MapKeys() { + ev := expectedMap.MapIndex(k) + av := actualMap.MapIndex(k) + + delta, ok := deltasMap[k.Interface()] + if !ok { + return Fail(t, fmt.Sprintf("missing key %q in delta map", k), msgAndArgs...) + } + + if !ev.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in expected map", k), msgAndArgs...) + } + + if !av.IsValid() { + return Fail(t, fmt.Sprintf("missing key %q in actual map", k), msgAndArgs...) + } + + if !InDelta( + t, + ev.Interface(), + av.Interface(), + delta, + msgAndArgs..., + ) { + return false + } + } + + return true +} + func calcRelativeError(expected, actual interface{}) (float64, error) { af, aok := toFloat(expected) bf, bok := toFloat(actual) diff --git a/assert/assertions_test.go b/assert/assertions_test.go index d2a25c245..81d9fdde3 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -1865,6 +1865,116 @@ func TestInDeltaMapValues(t *testing.T) { } } +func TestInMapDeltasMapValues(t *testing.T) { + mockT := new(testing.T) + + for _, tc := range []struct { + title string + expect interface{} + actual interface{} + f func(TestingT, bool, ...interface{}) bool + deltasMap map[interface{}]float64 + }{ + { + title: "Within deltas", + expect: map[string]float64{ + "foo": 1.0, + "bar": 7, + "baz": math.NaN(), + }, + actual: map[string]float64{ + "foo": 1.01, + "bar": 7.99, + "baz": math.NaN(), + }, + deltasMap: map[interface{}]float64{ + "foo": 0.1, + "bar": 1, + "baz": 0.1, + }, + f: True, + }, + { + title: "Within deltas", + expect: map[int]float64{ + 1: 1.0, + 2: 2.0, + }, + actual: map[int]float64{ + 1: 1.0, + 2: 1.99, + }, + deltasMap: map[interface{}]float64{ + 1: 0.1, + 2: 0.1, + }, + f: True, + }, + { + title: "Different number of keys", + expect: map[int]float64{ + 1: 1.0, + 2: 2.0, + }, + actual: map[int]float64{ + 1: 1.0, + }, + deltasMap: map[interface{}]float64{ + 1: 0.1, + 2: 0.1, + }, + f: False, + }, + { + title: "Within delta with zero value", + expect: map[string]float64{ + "zero": 0, + }, + actual: map[string]float64{ + "zero": 0, + }, + deltasMap: map[interface{}]float64{ + "zero": 0.1, + }, + f: True, + }, + { + title: "With missing key with zero value", + expect: map[string]float64{ + "zero": 0, + "foo": 0, + }, + actual: map[string]float64{ + "zero": 0, + "bar": 0, + }, + deltasMap: map[interface{}]float64{ + "zero": 0, + "bar": 0, + }, + f: False, + }, + { + title: "With missing key with zero value in deltas map", + expect: map[string]float64{ + "zero": 0, + "foo": 0, + }, + actual: map[string]float64{ + "zero": 0, + "foo": 0, + }, + deltasMap: map[interface{}]float64{ + "zero": 0, + "bar": 0, + }, + f: False, + }, + } { + tc.f(t, InMapDeltasMapValues(mockT, tc.expect, tc.actual, tc.deltasMap), tc.title+"\n"+diff(tc.expect, tc.actual)) + } +} + func TestInEpsilon(t *testing.T) { mockT := new(testing.T) diff --git a/require/require.go b/require/require.go index fde08337c..a5590c7ab 100644 --- a/require/require.go +++ b/require/require.go @@ -977,6 +977,28 @@ func InEpsilonf(t TestingT, expected interface{}, actual interface{}, epsilon fl t.FailNow() } +// InMapDeltasMapValues is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func InMapDeltasMapValues(t TestingT, expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InMapDeltasMapValues(t, expected, actual, deltasMap, msgAndArgs...) { + return + } + t.FailNow() +} + +// InMapDeltasMapValuesf is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func InMapDeltasMapValuesf(t TestingT, expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.InMapDeltasMapValuesf(t, expected, actual, deltasMap, msg, args...) { + return + } + t.FailNow() +} + // IsDecreasing asserts that the collection is decreasing // // assert.IsDecreasing(t, []int{2, 1, 0}) diff --git a/require/require_forward.go b/require/require_forward.go index 8fddd1f7a..f8a6b476e 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -773,6 +773,22 @@ func (a *Assertions) InEpsilonf(expected interface{}, actual interface{}, epsilo InEpsilonf(a.t, expected, actual, epsilon, msg, args...) } +// InMapDeltasMapValues is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func (a *Assertions) InMapDeltasMapValues(expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InMapDeltasMapValues(a.t, expected, actual, deltasMap, msgAndArgs...) +} + +// InMapDeltasMapValuesf is the same as InDelta, but it compares all values between two maps with the matching delta on the delta's map. All maps must have exactly the same keys. +func (a *Assertions) InMapDeltasMapValuesf(expected interface{}, actual interface{}, deltasMap map[interface{}]float64, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + InMapDeltasMapValuesf(a.t, expected, actual, deltasMap, msg, args...) +} + // IsDecreasing asserts that the collection is decreasing // // a.IsDecreasing([]int{2, 1, 0})