From 34cdc2b0c86e027447453315bcbaf23a95ac5c23 Mon Sep 17 00:00:00 2001 From: Aaron Kaswen-Wilk Date: Mon, 30 Dec 2024 10:20:42 +0100 Subject: [PATCH 1/6] add json node with customer marshaling --- assert/internal/jsonmatch/node.go | 141 +++++++++++++++++++++++++ assert/internal/jsonmatch/node_test.go | 56 ++++++++++ 2 files changed, 197 insertions(+) create mode 100644 assert/internal/jsonmatch/node.go create mode 100644 assert/internal/jsonmatch/node_test.go diff --git a/assert/internal/jsonmatch/node.go b/assert/internal/jsonmatch/node.go new file mode 100644 index 000000000..52c505b01 --- /dev/null +++ b/assert/internal/jsonmatch/node.go @@ -0,0 +1,141 @@ +package jsonmatch + +import ( + "encoding/json" + "errors" +) + +type ( + Node struct { + kind Kind + value interface{} + } + + Kind int +) + +const ( + Object Kind = iota + Array + String + Number + Bool + Null +) + +var ErrInvalidType = errors.New("invalid data type in json object") + +func (n *Node) UnmarshalJSON(data []byte) error { + var v interface{} + if err := json.Unmarshal(data, &v); err != nil { + return err + } + + newNode, err := NewNode(v) + if err != nil { + return err + } + + n.kind = newNode.kind + n.value = newNode.value + + return nil +} + +func NewNode(v interface{}) (*Node, error) { + n := &Node{} + + switch v := v.(type) { + case map[string]interface{}: + n.kind = Object + val := make(map[string]Node) + + for k, childVal := range v { + child, err := NewNode(childVal) + if err != nil { + return nil, err + } + val[k] = *child + } + + n.value = val + case []interface{}: + n.kind = Array + val := make([]Node, 0) + for _, childVal := range v { + child, err := NewNode(childVal) + if err != nil { + return nil, err + } + val = append(val, *child) + } + n.value = val + case string: + n.kind = String + n.value = v + case float64: + n.kind = Number + n.value = v + case bool: + n.kind = Bool + n.value = v + case nil: + n.kind = Null + n.value = nil + default: + return nil, ErrInvalidType + } + + return n, nil +} + +func (n Node) Matches(other Node) bool { + if n.kind != other.kind { + return false + } + + switch n.kind { + case Object: + val := n.value.(map[string]Node) + otherVal := other.value.(map[string]Node) + + nodeKeys := keys(val) + otherKeys := keys(otherVal) + if len(nodeKeys) != len(otherKeys) { + return false + } + + for i := range nodeKeys { + k := nodeKeys[i] + if _, ok := otherVal[k]; !ok { + return false + } + if !val[k].Matches(otherVal[k]) { + return false + } + } + case Array: + for i := range n.value.([]Node) { + val := n.value.([]Node)[i] + otherVal := other.value.([]Node)[i] + if !val.Matches(otherVal) { + return false + } + } + default: + match := n.value == other.value + if !match { + return false + } + } + + return true +} + +func keys(m map[string]Node) []string { + keys := make([]string, 0) + for k := range m { + keys = append(keys, k) + } + return keys +} diff --git a/assert/internal/jsonmatch/node_test.go b/assert/internal/jsonmatch/node_test.go new file mode 100644 index 000000000..b8c7231d4 --- /dev/null +++ b/assert/internal/jsonmatch/node_test.go @@ -0,0 +1,56 @@ +package jsonmatch + +import ( + "encoding/json" + "testing" +) + +func TestUnmarshalJSON(t *testing.T) { + jsonStr := `{ + "number": 1, + "float": 1.1, + "string": "hello", + "bool": true, + "null": null, + "array": [1, 2, 3], + "object": { + "key": "value" + } + }` + + n := Node{} + + err := json.Unmarshal([]byte(jsonStr), &n) + if err != nil { + t.Fatal(err) + } + + expected := Node{ + kind: Object, + value: map[string]Node{ + "number": {kind: Number, value: 1.0}, + "float": {kind: Number, value: 1.1}, + "string": {kind: String, value: "hello"}, + "bool": {kind: Bool, value: true}, + "null": {kind: Null, value: nil}, + "array": { + kind: Array, + value: []Node{ + {kind: Number, value: 1.0}, + {kind: Number, value: 2.0}, + {kind: Number, value: 3.0}, + }, + }, + "object": { + kind: Object, + value: map[string]Node{ + "key": {kind: String, value: "value"}, + }, + }, + }, + } + + if !n.Matches(expected) { + t.Fatalf("\n\nexpected %v\n\n actual %v", expected, n) + } +} From f85dd195cae5a85102fb760af9b3cf6530504642 Mon Sep 17 00:00:00 2001 From: Aaron Kaswen-Wilk Date: Mon, 30 Dec 2024 11:18:18 +0100 Subject: [PATCH 2/6] finish implementation of json matches by with diff view and tests --- assert/assertions.go | 67 ++++-- assert/assertions_test.go | 213 +++++++++++++----- .../jsonmatch/{node.go => matches_by.go} | 73 +++--- assert/internal/jsonmatch/node_test.go | 56 ----- 4 files changed, 241 insertions(+), 168 deletions(-) rename assert/internal/jsonmatch/{node.go => matches_by.go} (59%) delete mode 100644 assert/internal/jsonmatch/node_test.go diff --git a/assert/assertions.go b/assert/assertions.go index 4e91332bb..57d6d4cee 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -21,6 +21,7 @@ import ( "github.com/pmezard/go-difflib/difflib" // Wrapper around gopkg.in/yaml.v3 + "github.com/stretchr/testify/assert/internal/jsonmatch" "github.com/stretchr/testify/assert/yaml" ) @@ -210,7 +211,6 @@ the problem actually occurred in calling code.*/ // of each stack frame leading from the current test to the assert call that // failed. func CallerInfo() []string { - var pc uintptr var ok bool var file string @@ -475,7 +475,6 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) } return true - } // validateEqualArgs checks whether provided arguments can be safely used in the @@ -530,7 +529,7 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} same, ok := samePointers(expected, actual) if !ok { - //fails when the arguments are not pointers + // fails when the arguments are not pointers return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) } @@ -549,7 +548,7 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} func samePointers(first, second interface{}) (same bool, ok bool) { firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { - return false, false //not both are pointers + return false, false // not both are pointers } firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) @@ -610,7 +609,6 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa } return true - } // EqualExportedValues asserts that the types of two objects are equal and their public @@ -665,7 +663,6 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} } return Equal(t, expected, actual, msgAndArgs...) - } // NotNil asserts that the specified object is not nil. @@ -715,7 +712,6 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { - // get nil case out of the way if object == nil { return true @@ -756,7 +752,6 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { } return pass - } // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either @@ -775,7 +770,6 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { } return pass - } // getLen tries to get the length of an object. @@ -819,7 +813,6 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { } return true - } // False asserts that the specified value is false. @@ -834,7 +827,6 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { } return true - } // NotEqual asserts that the specified values are NOT equal. @@ -857,7 +849,6 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{ } return true - } // NotEqualValues asserts that two objects are not equal even when converted to the same type @@ -880,7 +871,6 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte // return (true, false) if element was not found. // return (true, true) if element was found. func containsElement(list interface{}, element interface{}) (ok, found bool) { - listValue := reflect.ValueOf(list) listType := reflect.TypeOf(list) if listType == nil { @@ -915,7 +905,6 @@ func containsElement(list interface{}, element interface{}) (ok, found bool) { } } return true, false - } // Contains asserts that the specified string, list(array, slice...) or map contains the @@ -938,7 +927,6 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo } return true - } // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the @@ -961,7 +949,6 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) } return true - } // Subset asserts that the specified list(array, slice...) or map contains all @@ -1667,7 +1654,6 @@ func matchRegexp(rx interface{}, str interface{}) bool { default: return r.MatchString(fmt.Sprint(v)) } - } // Regexp asserts that a specified regexp matches a string. @@ -1703,7 +1689,6 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } return !match - } // Zero asserts that i is the zero value for its type. @@ -1821,6 +1806,52 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...) } +type ValueMatchers = jsonmatch.Matchers + +// JSONMatchesBy asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// assert.JSONMatchesBy(t, `{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func JSONMatchesBy(t TestingT, expected string, actual string, matchers ValueMatchers, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + var expectedJSONAsInterface, actualJSONAsInterface interface{} + + if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) + } + + if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) + } + + if !jsonmatch.MatchesBy(expectedJSONAsInterface, actualJSONAsInterface, matchers) { + diff := diff(expectedJSONAsInterface, actualJSONAsInterface) + expected, actual = formatUnequalValues(expectedJSONAsInterface, actualJSONAsInterface) + return Fail(t, fmt.Sprintf("Not equal: \n"+ + "expected: %s\n"+ + "actual : %s%s", expectedJSONAsInterface, actualJSONAsInterface, diff), msgAndArgs...) + + } + + return true +} + // YAMLEq asserts that two YAML strings are equivalent. func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { if h, ok := t.(tHelper); ok { diff --git a/assert/assertions_test.go b/assert/assertions_test.go index 9f859fa8d..e55f88366 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -91,15 +91,13 @@ type AssertionTesterInterface interface { } // AssertionTesterConformingObject is an object that conforms to the AssertionTesterInterface interface -type AssertionTesterConformingObject struct { -} +type AssertionTesterConformingObject struct{} func (a *AssertionTesterConformingObject) TestMethod() { } // AssertionTesterNonConformingObject is an object that does not conform to the AssertionTesterInterface interface -type AssertionTesterNonConformingObject struct { -} +type AssertionTesterNonConformingObject struct{} func TestObjectsAreEqual(t *testing.T) { cases := []struct { @@ -132,7 +130,6 @@ func TestObjectsAreEqual(t *testing.T) { if res != c.result { t.Errorf("ObjectsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result) } - }) } } @@ -208,7 +205,6 @@ type S6 struct { } func TestObjectsExportedFieldsAreEqual(t *testing.T) { - intValue := 1 cases := []struct { @@ -277,7 +273,6 @@ func TestObjectsExportedFieldsAreEqual(t *testing.T) { if res != c.result { t.Errorf("ObjectsExportedFieldsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result) } - }) } } @@ -328,11 +323,15 @@ func TestCopyExportedFields(t *testing.T) { }}, }, { - input: S4{[]*Nested{ - {1, 2}}, + input: S4{ + []*Nested{ + {1, 2}, + }, }, - expected: S4{[]*Nested{ - {1, nil}}, + expected: S4{ + []*Nested{ + {1, nil}, + }, }, }, { @@ -516,11 +515,9 @@ func TestEqualExportedValues(t *testing.T) { } }) } - } func TestImplements(t *testing.T) { - mockT := new(testing.T) if !Implements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterConformingObject)) { @@ -532,11 +529,9 @@ func TestImplements(t *testing.T) { if Implements(mockT, (*AssertionTesterInterface)(nil), nil) { t.Error("Implements method should return false: nil does not implement AssertionTesterInterface") } - } func TestNotImplements(t *testing.T) { - mockT := new(testing.T) if !NotImplements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) { @@ -548,11 +543,9 @@ func TestNotImplements(t *testing.T) { if NotImplements(mockT, (*AssertionTesterInterface)(nil), nil) { t.Error("NotImplements method should return false: nil can't be checked to be implementing AssertionTesterInterface or not") } - } func TestIsType(t *testing.T) { - mockT := new(testing.T) if !IsType(mockT, new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) { @@ -561,7 +554,6 @@ func TestIsType(t *testing.T) { if IsType(mockT, new(AssertionTesterConformingObject), new(AssertionTesterNonConformingObject)) { t.Error("IsType should return false: AssertionTesterConformingObject is not the same type as AssertionTesterNonConformingObject") } - } func TestEqual(t *testing.T) { @@ -610,7 +602,6 @@ func ptr(i int) *int { } func TestSame(t *testing.T) { - mockT := new(testing.T) if Same(mockT, ptr(1), ptr(1)) { @@ -629,7 +620,6 @@ func TestSame(t *testing.T) { } func TestNotSame(t *testing.T) { - mockT := new(testing.T) if !NotSame(mockT, ptr(1), ptr(1)) { @@ -814,7 +804,6 @@ func TestFormatUnequalValues(t *testing.T) { } func TestNotNil(t *testing.T) { - mockT := new(testing.T) if !NotNil(mockT, new(AssertionTesterConformingObject)) { @@ -826,11 +815,9 @@ func TestNotNil(t *testing.T) { if NotNil(mockT, (*struct{})(nil)) { t.Error("NotNil should return false: object is (*struct{})(nil)") } - } func TestNil(t *testing.T) { - mockT := new(testing.T) if !Nil(mockT, nil) { @@ -842,11 +829,9 @@ func TestNil(t *testing.T) { if Nil(mockT, new(AssertionTesterConformingObject)) { t.Error("Nil should return false: object is not nil") } - } func TestTrue(t *testing.T) { - mockT := new(testing.T) if !True(mockT, true) { @@ -855,11 +840,9 @@ func TestTrue(t *testing.T) { if True(mockT, false) { t.Error("True should return false") } - } func TestFalse(t *testing.T) { - mockT := new(testing.T) if !False(mockT, false) { @@ -868,11 +851,9 @@ func TestFalse(t *testing.T) { if False(mockT, true) { t.Error("False should return false") } - } func TestExactly(t *testing.T) { - mockT := new(testing.T) a := float32(1) @@ -903,7 +884,6 @@ func TestExactly(t *testing.T) { } func TestNotEqual(t *testing.T) { - mockT := new(testing.T) cases := []struct { @@ -986,7 +966,6 @@ func TestNotEqualValues(t *testing.T) { } func TestContainsNotContains(t *testing.T) { - type A struct { Name, Value string } @@ -1167,7 +1146,6 @@ func TestSubsetNotSubset(t *testing.T) { for _, c := range cases { t.Run("SubSet: "+c.message, func(t *testing.T) { - mockT := new(mockTestingT) res := Subset(mockT, c.list, c.subset) @@ -1214,7 +1192,6 @@ func TestNotSubsetNil(t *testing.T) { } func Test_containsElement(t *testing.T) { - list1 := []string{"Foo", "Bar"} list2 := []int{1, 2} simpleMap := map[interface{}]interface{}{"Foo": "Bar"} @@ -1445,11 +1422,9 @@ func TestCondition(t *testing.T) { if Condition(mockT, func() bool { return false }, "Lie") { t.Error("Condition should return false") } - } func TestDidPanic(t *testing.T) { - const panicMsg = "Panic!" if funcDidPanic, msg, _ := didPanic(func() { @@ -1468,11 +1443,9 @@ func TestDidPanic(t *testing.T) { }); funcDidPanic { t.Error("didPanic should return false") } - } func TestPanics(t *testing.T) { - mockT := new(testing.T) if !Panics(mockT, func() { @@ -1485,11 +1458,9 @@ func TestPanics(t *testing.T) { }) { t.Error("Panics should return false") } - } func TestPanicsWithValue(t *testing.T) { - mockT := new(testing.T) if !PanicsWithValue(mockT, "Panic!", func() { @@ -1517,7 +1488,6 @@ func TestPanicsWithValue(t *testing.T) { } func TestPanicsWithError(t *testing.T) { - mockT := new(testing.T) if !PanicsWithError(mockT, "panic", func() { @@ -1545,7 +1515,6 @@ func TestPanicsWithError(t *testing.T) { } func TestNotPanics(t *testing.T) { - mockT := new(testing.T) if !NotPanics(mockT, func() { @@ -1558,11 +1527,9 @@ func TestNotPanics(t *testing.T) { }) { t.Error("NotPanics should return false") } - } func TestNoError(t *testing.T) { - mockT := new(testing.T) // start with a nil error @@ -1593,7 +1560,6 @@ type customError struct{} func (*customError) Error() string { return "fail" } func TestError(t *testing.T) { - mockT := new(testing.T) // start with a nil error @@ -1657,7 +1623,6 @@ func TestErrorContains(t *testing.T) { } func Test_isEmpty(t *testing.T) { - chWithValue := make(chan struct{}, 1) chWithValue <- struct{}{} @@ -1684,7 +1649,6 @@ func Test_isEmpty(t *testing.T) { } func TestEmpty(t *testing.T) { - mockT := new(testing.T) chWithValue := make(chan struct{}, 1) chWithValue <- struct{}{} @@ -1729,7 +1693,6 @@ func TestEmpty(t *testing.T) { } func TestNotEmpty(t *testing.T) { - mockT := new(testing.T) chWithValue := make(chan struct{}, 1) chWithValue <- struct{}{} @@ -1843,7 +1806,6 @@ func TestLen(t *testing.T) { } func TestWithinDuration(t *testing.T) { - mockT := new(testing.T) a := time.Now() b := a.Add(10 * time.Second) @@ -1862,7 +1824,6 @@ func TestWithinDuration(t *testing.T) { } func TestWithinRange(t *testing.T) { - mockT := new(testing.T) n := time.Now() s := n.Add(-time.Second) @@ -2069,7 +2030,6 @@ func TestInEpsilon(t *testing.T) { for _, tc := range cases { False(t, InEpsilon(mockT, tc.a, tc.b, tc.epsilon, "Expected %V and %V to have a relative difference of %v", tc.a, tc.b, tc.epsilon)) } - } func TestInEpsilonSlice(t *testing.T) { @@ -2375,6 +2335,153 @@ func TestJSONEq_ArraysOfDifferentOrder(t *testing.T) { False(t, JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`)) } +var notEmpty = ValueMatchers{ + "$NOT_EMPTY": func(v interface{}) bool { + switch v := v.(type) { + case string: + return v != "" + default: + return false + } + }, +} + +func TestJSONMatchesBy_EqualSONString_NoMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, + `{"hello": "world", "foo": "bar"}`, + `{"hello": "world", "foo": "bar"}`, + nil, + )) +} + +func TestJSONMatchesBy_EqualSONString_WithMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, + `{"hello": "world", "foo": "$NOT_EMPTY"}`, + `{"hello": "world", "foo": "bar"}`, + notEmpty, + )) +} + +func TestJSONMatchesBy_EqualSONString_WithNotMatchingMatchers(t *testing.T) { + mockT := new(testing.T) + False(t, JSONMatchesBy( + mockT, + `{"hello": "world", "foo": "$NOT_EMPTY"}`, + `{"hello": "world", "foo": ""}`, + notEmpty, + )) +} + +func TestJSONMatchesBy_EquivalentButNotEqual_NoMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, + `{"hello": "world", "foo": "bar"}`, + `{"foo": "bar", "hello": "world"}`, + nil, + )) +} + +func TestJSONMatchesBy_EquivalentButNotEqual_WithMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, + `{"hello": "$NOT_EMPTY", "foo": "bar"}`, + `{"foo": "bar", "hello": "world"}`, + notEmpty, + )) +} + +func TestJSONMatchesBy_MatchersOnDifferentTypes(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, + `{"hello": "$SOME_NUMBER", "foo": "bar"}`, + `{"foo": "bar", "hello": 5}`, + ValueMatchers{ + "$SOME_NUMBER": func(v interface{}) bool { + _, ok := v.(float64) + return ok + }, + }, + )) +} + +func TestJSONMatchesBy_HashOfArraysAndHashes(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy(mockT, "{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}", + "{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}", nil)) +} + +func TestJSONMatchesBy_Array_NoMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, `["foo", {"hello": "world", "nested": "hash"}]`, + `["foo", {"nested": "hash", "hello": "world"}]`, + nil, + )) +} + +func TestJSONMatchesBy_Array_WithMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, `["$NOT_EMPTY", {"hello": "world", "nested": "hash"}]`, + `["foo", {"nested": "hash", "hello": "world"}]`, + notEmpty, + )) +} + +func TestJSONMatchesBy_HashAndArrayNotEquivalent_NoMatchers(t *testing.T) { + mockT := new(testing.T) + False(t, JSONMatchesBy(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`, nil)) +} + +func TestJSONMatchesBy_HashesNotEquivalent_NoMatchers(t *testing.T) { + mockT := new(testing.T) + False(t, JSONMatchesBy(mockT, `{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, nil)) +} + +func TestJSONMatchesBy_ActualIsNotJSON(t *testing.T) { + mockT := new(testing.T) + False(t, JSONMatchesBy(mockT, `{"foo": "bar"}`, "Not JSON", nil)) +} + +func TestJSONMatchesBy_ExpectedIsNotJSON(t *testing.T) { + mockT := new(testing.T) + False(t, JSONMatchesBy(mockT, "Not JSON", `{"foo": "bar", "hello": "world"}`, nil)) +} + +func TestJSONMatchesBy_ArraysOfDifferentOrder(t *testing.T) { + mockT := new(testing.T) + False(t, JSONMatchesBy(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`, nil)) +} + +func TestJSONMatchesBy_DeeplyNestedMatchers(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy( + mockT, + `["$THREE", {"foo": {"bar": {"baz": "$NOT_EMPTY"}}}, {"nested": { "array": ["$NOT_EMPTY", "foo"]}}]`, + `[3, {"foo": {"bar": {"baz": "qux"}}}, {"nested": { "array": ["quux", "foo"]}}]`, + ValueMatchers{ + "$THREE": func(v interface{}) bool { return v == 3.0 }, + "$NOT_EMPTY": func(v interface{}) bool { return v != "" }, + })) +} + +func TestJSONMatchesBy_EmptyObject(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy(mockT, "{}", "{}", nil)) +} + +func TestJSONMatchesBy_EmptyArray(t *testing.T) { + mockT := new(testing.T) + True(t, JSONMatchesBy(mockT, "[]", "[]", nil)) +} + func TestYAMLEq_EqualYAMLString(t *testing.T) { mockT := new(testing.T) True(t, YAMLEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)) @@ -2671,8 +2778,7 @@ func TestFailNowWithPlainTestingT(t *testing.T) { }, "should panic since mockT is missing FailNow()") } -type mockFailNowTestingT struct { -} +type mockFailNowTestingT struct{} func (m *mockFailNowTestingT) Errorf(format string, args ...interface{}) {} @@ -2687,7 +2793,7 @@ func TestFailNowWithFullTestingT(t *testing.T) { } func TestBytesEqual(t *testing.T) { - var cases = []struct { + cases := []struct { a, b []byte }{ {make([]byte, 2), make([]byte, 2)}, @@ -3123,7 +3229,6 @@ func Test_validateEqualArgs(t *testing.T) { } func Test_truncatingFormat(t *testing.T) { - original := strings.Repeat("a", bufio.MaxScanTokenSize-102) result := truncatingFormat(original) Equal(t, fmt.Sprintf("%#v", original), result, "string should not be truncated") diff --git a/assert/internal/jsonmatch/node.go b/assert/internal/jsonmatch/matches_by.go similarity index 59% rename from assert/internal/jsonmatch/node.go rename to assert/internal/jsonmatch/matches_by.go index 52c505b01..2400ac06f 100644 --- a/assert/internal/jsonmatch/node.go +++ b/assert/internal/jsonmatch/matches_by.go @@ -1,17 +1,23 @@ package jsonmatch import ( - "encoding/json" "errors" ) type ( + // reprepsents a node with a value in a json object Node struct { kind Kind value interface{} } + // an enum of the kinds of nodes in a json object Kind int + + // a map of matchers with a function to determine if a value matches + // the key of the matchers corresponds to the value passed into + // the function + Matchers map[string]func(interface{}) bool ) const ( @@ -25,25 +31,15 @@ const ( var ErrInvalidType = errors.New("invalid data type in json object") -func (n *Node) UnmarshalJSON(data []byte) error { - var v interface{} - if err := json.Unmarshal(data, &v); err != nil { - return err - } - - newNode, err := NewNode(v) - if err != nil { - return err - } - - n.kind = newNode.kind - n.value = newNode.value +func MatchesBy(expected, actual interface{}, matchers Matchers) bool { + expectedNode := New(expected) + actualNode := New(actual) - return nil + return expectedNode.matchesBy(actualNode, matchers) } -func NewNode(v interface{}) (*Node, error) { - n := &Node{} +func New(v interface{}) Node { + n := Node{} switch v := v.(type) { case map[string]interface{}: @@ -51,11 +47,7 @@ func NewNode(v interface{}) (*Node, error) { val := make(map[string]Node) for k, childVal := range v { - child, err := NewNode(childVal) - if err != nil { - return nil, err - } - val[k] = *child + val[k] = New(childVal) } n.value = val @@ -63,11 +55,7 @@ func NewNode(v interface{}) (*Node, error) { n.kind = Array val := make([]Node, 0) for _, childVal := range v { - child, err := NewNode(childVal) - if err != nil { - return nil, err - } - val = append(val, *child) + val = append(val, New(childVal)) } n.value = val case string: @@ -82,20 +70,18 @@ func NewNode(v interface{}) (*Node, error) { case nil: n.kind = Null n.value = nil - default: - return nil, ErrInvalidType } - return n, nil + return n } -func (n Node) Matches(other Node) bool { - if n.kind != other.kind { - return false - } - +func (n Node) matchesBy(other Node, matchers Matchers) bool { switch n.kind { case Object: + if other.kind != Object { + return false + } + val := n.value.(map[string]Node) otherVal := other.value.(map[string]Node) @@ -110,23 +96,30 @@ func (n Node) Matches(other Node) bool { if _, ok := otherVal[k]; !ok { return false } - if !val[k].Matches(otherVal[k]) { + if !val[k].matchesBy(otherVal[k], matchers) { return false } } case Array: + if other.kind != Array { + return false + } + for i := range n.value.([]Node) { val := n.value.([]Node)[i] otherVal := other.value.([]Node)[i] - if !val.Matches(otherVal) { + if !val.matchesBy(otherVal, matchers) { return false } } default: - match := n.value == other.value - if !match { - return false + for val, f := range matchers { + if val == n.value { + return f(other.value) + } } + + return n.value == other.value } return true diff --git a/assert/internal/jsonmatch/node_test.go b/assert/internal/jsonmatch/node_test.go deleted file mode 100644 index b8c7231d4..000000000 --- a/assert/internal/jsonmatch/node_test.go +++ /dev/null @@ -1,56 +0,0 @@ -package jsonmatch - -import ( - "encoding/json" - "testing" -) - -func TestUnmarshalJSON(t *testing.T) { - jsonStr := `{ - "number": 1, - "float": 1.1, - "string": "hello", - "bool": true, - "null": null, - "array": [1, 2, 3], - "object": { - "key": "value" - } - }` - - n := Node{} - - err := json.Unmarshal([]byte(jsonStr), &n) - if err != nil { - t.Fatal(err) - } - - expected := Node{ - kind: Object, - value: map[string]Node{ - "number": {kind: Number, value: 1.0}, - "float": {kind: Number, value: 1.1}, - "string": {kind: String, value: "hello"}, - "bool": {kind: Bool, value: true}, - "null": {kind: Null, value: nil}, - "array": { - kind: Array, - value: []Node{ - {kind: Number, value: 1.0}, - {kind: Number, value: 2.0}, - {kind: Number, value: 3.0}, - }, - }, - "object": { - kind: Object, - value: map[string]Node{ - "key": {kind: String, value: "value"}, - }, - }, - }, - } - - if !n.Matches(expected) { - t.Fatalf("\n\nexpected %v\n\n actual %v", expected, n) - } -} From 688992aa87ada0fd603ffa73e3dfb615d691a82d Mon Sep 17 00:00:00 2001 From: Aaron Kaswen-Wilk Date: Mon, 30 Dec 2024 13:30:11 +0100 Subject: [PATCH 3/6] fix ci failures --- assert/assertion_format.go | 25 +++++++++ assert/assertion_forward.go | 49 +++++++++++++++++ assert/assertions.go | 4 +- assert/{internal => }/jsonmatch/matches_by.go | 0 require/require.go | 55 +++++++++++++++++++ require/require_forward.go | 49 +++++++++++++++++ 6 files changed, 180 insertions(+), 2 deletions(-) rename assert/{internal => }/jsonmatch/matches_by.go (100%) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 190634165..5afc612d3 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -3,6 +3,7 @@ package assert import ( + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" @@ -456,6 +457,30 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) } +// JSONMatchesByf asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// assert.JSONMatchesByf(t, `{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func JSONMatchesByf(t TestingT, expected string, actual string, matchers jsonmatch.Matchers, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return JSONMatchesBy(t, expected, actual, matchers, append([]interface{}{msg}, args...)...) +} + // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 21629087b..72ce059ff 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -3,6 +3,7 @@ package assert import ( + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" @@ -904,6 +905,54 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. return JSONEqf(a.t, expected, actual, msg, args...) } +// JSONMatchesBy asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// a.JSONMatchesBy(`{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func (a *Assertions) JSONMatchesBy(expected string, actual string, matchers jsonmatch.Matchers, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return JSONMatchesBy(a.t, expected, actual, matchers, msgAndArgs...) +} + +// JSONMatchesByf asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// a.JSONMatchesByf(`{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func (a *Assertions) JSONMatchesByf(expected string, actual string, matchers jsonmatch.Matchers, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return JSONMatchesByf(a.t, expected, actual, matchers, msg, args...) +} + // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // diff --git a/assert/assertions.go b/assert/assertions.go index 57d6d4cee..b2bdcc1f7 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -20,8 +20,8 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" + "github.com/stretchr/testify/assert/jsonmatch" // Wrapper around gopkg.in/yaml.v3 - "github.com/stretchr/testify/assert/internal/jsonmatch" "github.com/stretchr/testify/assert/yaml" ) @@ -1819,7 +1819,7 @@ type ValueMatchers = jsonmatch.Matchers // // Can use nil for matchers to test equality of pure json. // -// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, // regardless of the output of the matcher function. This should be fixed in a future release. // // assert.JSONMatchesBy(t, `{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ diff --git a/assert/internal/jsonmatch/matches_by.go b/assert/jsonmatch/matches_by.go similarity index 100% rename from assert/internal/jsonmatch/matches_by.go rename to assert/jsonmatch/matches_by.go diff --git a/require/require.go b/require/require.go index d8921950d..bc8468230 100644 --- a/require/require.go +++ b/require/require.go @@ -4,6 +4,7 @@ package require import ( assert "github.com/stretchr/testify/assert" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" @@ -1145,6 +1146,60 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int t.FailNow() } +// JSONMatchesBy asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// require.JSONMatchesBy(t, `{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, require.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func JSONMatchesBy(t TestingT, expected string, actual string, matchers jsonmatch.Matchers, msgAndArgs ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONMatchesBy(t, expected, actual, matchers, msgAndArgs...) { + return + } + t.FailNow() +} + +// JSONMatchesByf asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// require.JSONMatchesByf(t, `{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, require.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func JSONMatchesByf(t TestingT, expected string, actual string, matchers jsonmatch.Matchers, msg string, args ...interface{}) { + if h, ok := t.(tHelper); ok { + h.Helper() + } + if assert.JSONMatchesByf(t, expected, actual, matchers, msg, args...) { + return + } + t.FailNow() +} + // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // diff --git a/require/require_forward.go b/require/require_forward.go index 1bd87304f..554d440cc 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -4,6 +4,7 @@ package require import ( assert "github.com/stretchr/testify/assert" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" @@ -905,6 +906,54 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. JSONEqf(a.t, expected, actual, msg, args...) } +// JSONMatchesBy asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// a.JSONMatchesBy(`{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func (a *Assertions) JSONMatchesBy(expected string, actual string, matchers jsonmatch.Matchers, msgAndArgs ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONMatchesBy(a.t, expected, actual, matchers, msgAndArgs...) +} + +// JSONMatchesByf asserts that two JSON strings are equivalent using one or more custom JSON matchers. +// the value passed into the matcher will be on of the following types: +// - string +// - float64 +// - bool +// - nil +// - []interface{} +// - map[string]interface{} +// +// Can use nil for matchers to test equality of pure json. +// +// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal, +// regardless of the output of the matcher function. This should be fixed in a future release. +// +// a.JSONMatchesByf(`{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{ +// "$NOT_EMPTY": func(v interface) bool { return v != "" }, +// }) +func (a *Assertions) JSONMatchesByf(expected string, actual string, matchers jsonmatch.Matchers, msg string, args ...interface{}) { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + JSONMatchesByf(a.t, expected, actual, matchers, msg, args...) +} + // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // From bdff01450338f98f2c043d0c08dda4de1ddc9fda Mon Sep 17 00:00:00 2001 From: Aaron Kaswen-Wilk Date: Mon, 30 Dec 2024 14:51:11 +0100 Subject: [PATCH 4/6] formatting fixes --- assert/assertion_format.go | 2 +- assert/assertion_forward.go | 2 +- assert/assertions.go | 20 +++++++++-- assert/assertions_test.go | 66 ++++++++++++++++++++++++++++++------- require/require.go | 2 +- require/require_forward.go | 2 +- 6 files changed, 76 insertions(+), 18 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 5afc612d3..9c731a2c9 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -3,8 +3,8 @@ package assert import ( - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" ) diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 72ce059ff..b9cca2384 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -3,8 +3,8 @@ package assert import ( - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" ) diff --git a/assert/assertions.go b/assert/assertions.go index b2bdcc1f7..14aa0c4f2 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -211,6 +211,7 @@ the problem actually occurred in calling code.*/ // of each stack frame leading from the current test to the assert call that // failed. func CallerInfo() []string { + var pc uintptr var ok bool var file string @@ -475,6 +476,7 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) } return true + } // validateEqualArgs checks whether provided arguments can be safely used in the @@ -529,7 +531,7 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} same, ok := samePointers(expected, actual) if !ok { - // fails when the arguments are not pointers + //fails when the arguments are not pointers return !(Fail(t, "Both arguments must be pointers", msgAndArgs...)) } @@ -548,7 +550,7 @@ func NotSame(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} func samePointers(first, second interface{}) (same bool, ok bool) { firstPtr, secondPtr := reflect.ValueOf(first), reflect.ValueOf(second) if firstPtr.Kind() != reflect.Ptr || secondPtr.Kind() != reflect.Ptr { - return false, false // not both are pointers + return false, false //not both are pointers } firstType, secondType := reflect.TypeOf(first), reflect.TypeOf(second) @@ -609,6 +611,7 @@ func EqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...interfa } return true + } // EqualExportedValues asserts that the types of two objects are equal and their public @@ -663,6 +666,7 @@ func Exactly(t TestingT, expected, actual interface{}, msgAndArgs ...interface{} } return Equal(t, expected, actual, msgAndArgs...) + } // NotNil asserts that the specified object is not nil. @@ -712,6 +716,7 @@ func Nil(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { // isEmpty gets whether the specified object is considered empty or not. func isEmpty(object interface{}) bool { + // get nil case out of the way if object == nil { return true @@ -752,6 +757,7 @@ func Empty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { } return pass + } // NotEmpty asserts that the specified object is NOT empty. I.e. not nil, "", false, 0 or either @@ -770,6 +776,7 @@ func NotEmpty(t TestingT, object interface{}, msgAndArgs ...interface{}) bool { } return pass + } // getLen tries to get the length of an object. @@ -813,6 +820,7 @@ func True(t TestingT, value bool, msgAndArgs ...interface{}) bool { } return true + } // False asserts that the specified value is false. @@ -827,6 +835,7 @@ func False(t TestingT, value bool, msgAndArgs ...interface{}) bool { } return true + } // NotEqual asserts that the specified values are NOT equal. @@ -849,6 +858,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{ } return true + } // NotEqualValues asserts that two objects are not equal even when converted to the same type @@ -871,6 +881,7 @@ func NotEqualValues(t TestingT, expected, actual interface{}, msgAndArgs ...inte // return (true, false) if element was not found. // return (true, true) if element was found. func containsElement(list interface{}, element interface{}) (ok, found bool) { + listValue := reflect.ValueOf(list) listType := reflect.TypeOf(list) if listType == nil { @@ -905,6 +916,7 @@ func containsElement(list interface{}, element interface{}) (ok, found bool) { } } return true, false + } // Contains asserts that the specified string, list(array, slice...) or map contains the @@ -927,6 +939,7 @@ func Contains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) bo } return true + } // NotContains asserts that the specified string, list(array, slice...) or map does NOT contain the @@ -949,6 +962,7 @@ func NotContains(t TestingT, s, contains interface{}, msgAndArgs ...interface{}) } return true + } // Subset asserts that the specified list(array, slice...) or map contains all @@ -1654,6 +1668,7 @@ func matchRegexp(rx interface{}, str interface{}) bool { default: return r.MatchString(fmt.Sprint(v)) } + } // Regexp asserts that a specified regexp matches a string. @@ -1689,6 +1704,7 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } return !match + } // Zero asserts that i is the zero value for its type. diff --git a/assert/assertions_test.go b/assert/assertions_test.go index e55f88366..ba8cbfd05 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -91,13 +91,15 @@ type AssertionTesterInterface interface { } // AssertionTesterConformingObject is an object that conforms to the AssertionTesterInterface interface -type AssertionTesterConformingObject struct{} +type AssertionTesterConformingObject struct { +} func (a *AssertionTesterConformingObject) TestMethod() { } // AssertionTesterNonConformingObject is an object that does not conform to the AssertionTesterInterface interface -type AssertionTesterNonConformingObject struct{} +type AssertionTesterNonConformingObject struct { +} func TestObjectsAreEqual(t *testing.T) { cases := []struct { @@ -130,6 +132,7 @@ func TestObjectsAreEqual(t *testing.T) { if res != c.result { t.Errorf("ObjectsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result) } + }) } } @@ -205,6 +208,7 @@ type S6 struct { } func TestObjectsExportedFieldsAreEqual(t *testing.T) { + intValue := 1 cases := []struct { @@ -273,6 +277,7 @@ func TestObjectsExportedFieldsAreEqual(t *testing.T) { if res != c.result { t.Errorf("ObjectsExportedFieldsAreEqual(%#v, %#v) should return %#v", c.expected, c.actual, c.result) } + }) } } @@ -323,15 +328,11 @@ func TestCopyExportedFields(t *testing.T) { }}, }, { - input: S4{ - []*Nested{ - {1, 2}, - }, + input: S4{[]*Nested{ + {1, 2}}, }, - expected: S4{ - []*Nested{ - {1, nil}, - }, + expected: S4{[]*Nested{ + {1, nil}}, }, }, { @@ -515,9 +516,11 @@ func TestEqualExportedValues(t *testing.T) { } }) } + } func TestImplements(t *testing.T) { + mockT := new(testing.T) if !Implements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterConformingObject)) { @@ -529,9 +532,11 @@ func TestImplements(t *testing.T) { if Implements(mockT, (*AssertionTesterInterface)(nil), nil) { t.Error("Implements method should return false: nil does not implement AssertionTesterInterface") } + } func TestNotImplements(t *testing.T) { + mockT := new(testing.T) if !NotImplements(mockT, (*AssertionTesterInterface)(nil), new(AssertionTesterNonConformingObject)) { @@ -543,9 +548,11 @@ func TestNotImplements(t *testing.T) { if NotImplements(mockT, (*AssertionTesterInterface)(nil), nil) { t.Error("NotImplements method should return false: nil can't be checked to be implementing AssertionTesterInterface or not") } + } func TestIsType(t *testing.T) { + mockT := new(testing.T) if !IsType(mockT, new(AssertionTesterConformingObject), new(AssertionTesterConformingObject)) { @@ -554,6 +561,7 @@ func TestIsType(t *testing.T) { if IsType(mockT, new(AssertionTesterConformingObject), new(AssertionTesterNonConformingObject)) { t.Error("IsType should return false: AssertionTesterConformingObject is not the same type as AssertionTesterNonConformingObject") } + } func TestEqual(t *testing.T) { @@ -602,6 +610,7 @@ func ptr(i int) *int { } func TestSame(t *testing.T) { + mockT := new(testing.T) if Same(mockT, ptr(1), ptr(1)) { @@ -620,6 +629,7 @@ func TestSame(t *testing.T) { } func TestNotSame(t *testing.T) { + mockT := new(testing.T) if !NotSame(mockT, ptr(1), ptr(1)) { @@ -804,6 +814,7 @@ func TestFormatUnequalValues(t *testing.T) { } func TestNotNil(t *testing.T) { + mockT := new(testing.T) if !NotNil(mockT, new(AssertionTesterConformingObject)) { @@ -815,9 +826,11 @@ func TestNotNil(t *testing.T) { if NotNil(mockT, (*struct{})(nil)) { t.Error("NotNil should return false: object is (*struct{})(nil)") } + } func TestNil(t *testing.T) { + mockT := new(testing.T) if !Nil(mockT, nil) { @@ -829,9 +842,11 @@ func TestNil(t *testing.T) { if Nil(mockT, new(AssertionTesterConformingObject)) { t.Error("Nil should return false: object is not nil") } + } func TestTrue(t *testing.T) { + mockT := new(testing.T) if !True(mockT, true) { @@ -840,9 +855,11 @@ func TestTrue(t *testing.T) { if True(mockT, false) { t.Error("True should return false") } + } func TestFalse(t *testing.T) { + mockT := new(testing.T) if !False(mockT, false) { @@ -851,9 +868,11 @@ func TestFalse(t *testing.T) { if False(mockT, true) { t.Error("False should return false") } + } func TestExactly(t *testing.T) { + mockT := new(testing.T) a := float32(1) @@ -884,6 +903,7 @@ func TestExactly(t *testing.T) { } func TestNotEqual(t *testing.T) { + mockT := new(testing.T) cases := []struct { @@ -966,6 +986,7 @@ func TestNotEqualValues(t *testing.T) { } func TestContainsNotContains(t *testing.T) { + type A struct { Name, Value string } @@ -1146,6 +1167,7 @@ func TestSubsetNotSubset(t *testing.T) { for _, c := range cases { t.Run("SubSet: "+c.message, func(t *testing.T) { + mockT := new(mockTestingT) res := Subset(mockT, c.list, c.subset) @@ -1192,6 +1214,7 @@ func TestNotSubsetNil(t *testing.T) { } func Test_containsElement(t *testing.T) { + list1 := []string{"Foo", "Bar"} list2 := []int{1, 2} simpleMap := map[interface{}]interface{}{"Foo": "Bar"} @@ -1422,9 +1445,11 @@ func TestCondition(t *testing.T) { if Condition(mockT, func() bool { return false }, "Lie") { t.Error("Condition should return false") } + } func TestDidPanic(t *testing.T) { + const panicMsg = "Panic!" if funcDidPanic, msg, _ := didPanic(func() { @@ -1443,9 +1468,11 @@ func TestDidPanic(t *testing.T) { }); funcDidPanic { t.Error("didPanic should return false") } + } func TestPanics(t *testing.T) { + mockT := new(testing.T) if !Panics(mockT, func() { @@ -1458,9 +1485,11 @@ func TestPanics(t *testing.T) { }) { t.Error("Panics should return false") } + } func TestPanicsWithValue(t *testing.T) { + mockT := new(testing.T) if !PanicsWithValue(mockT, "Panic!", func() { @@ -1488,6 +1517,7 @@ func TestPanicsWithValue(t *testing.T) { } func TestPanicsWithError(t *testing.T) { + mockT := new(testing.T) if !PanicsWithError(mockT, "panic", func() { @@ -1515,6 +1545,7 @@ func TestPanicsWithError(t *testing.T) { } func TestNotPanics(t *testing.T) { + mockT := new(testing.T) if !NotPanics(mockT, func() { @@ -1527,9 +1558,11 @@ func TestNotPanics(t *testing.T) { }) { t.Error("NotPanics should return false") } + } func TestNoError(t *testing.T) { + mockT := new(testing.T) // start with a nil error @@ -1560,6 +1593,7 @@ type customError struct{} func (*customError) Error() string { return "fail" } func TestError(t *testing.T) { + mockT := new(testing.T) // start with a nil error @@ -1623,6 +1657,7 @@ func TestErrorContains(t *testing.T) { } func Test_isEmpty(t *testing.T) { + chWithValue := make(chan struct{}, 1) chWithValue <- struct{}{} @@ -1649,6 +1684,7 @@ func Test_isEmpty(t *testing.T) { } func TestEmpty(t *testing.T) { + mockT := new(testing.T) chWithValue := make(chan struct{}, 1) chWithValue <- struct{}{} @@ -1693,6 +1729,7 @@ func TestEmpty(t *testing.T) { } func TestNotEmpty(t *testing.T) { + mockT := new(testing.T) chWithValue := make(chan struct{}, 1) chWithValue <- struct{}{} @@ -1806,6 +1843,7 @@ func TestLen(t *testing.T) { } func TestWithinDuration(t *testing.T) { + mockT := new(testing.T) a := time.Now() b := a.Add(10 * time.Second) @@ -1824,6 +1862,7 @@ func TestWithinDuration(t *testing.T) { } func TestWithinRange(t *testing.T) { + mockT := new(testing.T) n := time.Now() s := n.Add(-time.Second) @@ -2030,6 +2069,7 @@ func TestInEpsilon(t *testing.T) { for _, tc := range cases { False(t, InEpsilon(mockT, tc.a, tc.b, tc.epsilon, "Expected %V and %V to have a relative difference of %v", tc.a, tc.b, tc.epsilon)) } + } func TestInEpsilonSlice(t *testing.T) { @@ -2778,7 +2818,8 @@ func TestFailNowWithPlainTestingT(t *testing.T) { }, "should panic since mockT is missing FailNow()") } -type mockFailNowTestingT struct{} +type mockFailNowTestingT struct { +} func (m *mockFailNowTestingT) Errorf(format string, args ...interface{}) {} @@ -2793,7 +2834,7 @@ func TestFailNowWithFullTestingT(t *testing.T) { } func TestBytesEqual(t *testing.T) { - cases := []struct { + var cases = []struct { a, b []byte }{ {make([]byte, 2), make([]byte, 2)}, @@ -3229,6 +3270,7 @@ func Test_validateEqualArgs(t *testing.T) { } func Test_truncatingFormat(t *testing.T) { + original := strings.Repeat("a", bufio.MaxScanTokenSize-102) result := truncatingFormat(original) Equal(t, fmt.Sprintf("%#v", original), result, "string should not be truncated") diff --git a/require/require.go b/require/require.go index bc8468230..e6c22dfaa 100644 --- a/require/require.go +++ b/require/require.go @@ -4,8 +4,8 @@ package require import ( assert "github.com/stretchr/testify/assert" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" ) diff --git a/require/require_forward.go b/require/require_forward.go index 554d440cc..b05e10550 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -4,8 +4,8 @@ package require import ( assert "github.com/stretchr/testify/assert" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" ) From ecbed8034a2c14bd76c12e6fe804398339ce7b93 Mon Sep 17 00:00:00 2001 From: Aaron Kaswen-Wilk Date: Mon, 30 Dec 2024 14:51:11 +0100 Subject: [PATCH 5/6] formatting fixes --- assert/assertion_format.go | 3 ++- assert/assertion_forward.go | 3 ++- require/require.go | 5 +++-- require/require_forward.go | 5 +++-- 4 files changed, 10 insertions(+), 6 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 9c731a2c9..1940f02d7 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -4,9 +4,10 @@ package assert import ( http "net/http" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" + + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Conditionf uses a Comparison to assert a complex condition. diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index b9cca2384..0110e435a 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -4,9 +4,10 @@ package assert import ( http "net/http" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" + + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Condition uses a Comparison to assert a complex condition. diff --git a/require/require.go b/require/require.go index e6c22dfaa..7f70b9fb6 100644 --- a/require/require.go +++ b/require/require.go @@ -3,11 +3,12 @@ package require import ( - assert "github.com/stretchr/testify/assert" http "net/http" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" + + assert "github.com/stretchr/testify/assert" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Condition uses a Comparison to assert a complex condition. diff --git a/require/require_forward.go b/require/require_forward.go index b05e10550..4cd1da685 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -3,11 +3,12 @@ package require import ( - assert "github.com/stretchr/testify/assert" http "net/http" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" url "net/url" time "time" + + assert "github.com/stretchr/testify/assert" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Condition uses a Comparison to assert a complex condition. From dd85eac746d21f960f8a5d70238306bdbdb7b7c3 Mon Sep 17 00:00:00 2001 From: Aaron Kaswen-Wilk Date: Tue, 31 Dec 2024 14:44:01 +0100 Subject: [PATCH 6/6] undo import ordering --- assert/assertion_format.go | 3 +-- assert/assertion_forward.go | 3 +-- require/require.go | 5 ++--- require/require_forward.go | 5 ++--- 4 files changed, 6 insertions(+), 10 deletions(-) diff --git a/assert/assertion_format.go b/assert/assertion_format.go index 1940f02d7..5afc612d3 100644 --- a/assert/assertion_format.go +++ b/assert/assertion_format.go @@ -3,11 +3,10 @@ package assert import ( + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" - - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Conditionf uses a Comparison to assert a complex condition. diff --git a/assert/assertion_forward.go b/assert/assertion_forward.go index 0110e435a..72ce059ff 100644 --- a/assert/assertion_forward.go +++ b/assert/assertion_forward.go @@ -3,11 +3,10 @@ package assert import ( + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" - - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Condition uses a Comparison to assert a complex condition. diff --git a/require/require.go b/require/require.go index 7f70b9fb6..bc8468230 100644 --- a/require/require.go +++ b/require/require.go @@ -3,12 +3,11 @@ package require import ( + assert "github.com/stretchr/testify/assert" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" - - assert "github.com/stretchr/testify/assert" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Condition uses a Comparison to assert a complex condition. diff --git a/require/require_forward.go b/require/require_forward.go index 4cd1da685..554d440cc 100644 --- a/require/require_forward.go +++ b/require/require_forward.go @@ -3,12 +3,11 @@ package require import ( + assert "github.com/stretchr/testify/assert" + jsonmatch "github.com/stretchr/testify/assert/jsonmatch" http "net/http" url "net/url" time "time" - - assert "github.com/stretchr/testify/assert" - jsonmatch "github.com/stretchr/testify/assert/jsonmatch" ) // Condition uses a Comparison to assert a complex condition.