package diff import ( "fmt" "testing" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) func TestDiff(t *testing.T) { type pair struct { left interface{} right interface{} } tests := []struct { name string in pair expecteds []string }{ // Plain {"plain - left nil, right nil", pair{nil, nil}, nil, }, {"plain - left nil, right set", pair{nil, &plainStruct{}}, []string{" => &{0} (*diff.plainStruct) (added)"}, }, {"plain - left set, right nil", pair{&plainStruct{}, nil}, []string{"&{0} (*diff.plainStruct) => (removed)"}, }, {"plain - equal", pair{&plainStruct{}, &plainStruct{}}, nil, }, {"plain - diff", pair{&plainStruct{Field: 1}, &plainStruct{Field: 2}}, []string{"1 (int) => 2 (int) (changed @ Field)"}, }, // Slice {"slice - equal both nil", pair{[]string(nil), []string(nil)}, nil, }, {"slice - equal both length 0", pair{[]string{}, []string{}}, nil, }, {"slice - diff both length 1", pair{[]string{"a"}, []string{"b"}}, []string{ "a (string) => (removed @ [0])", " => b (string) (added @ [0])", }, }, {"slice - diff both length 2 re-ordered", pair{[]string{"a", "b"}, []string{"b", "a"}}, []string{ "a (string) => (removed @ [0])", " => b (string) (added @ [0])", "b (string) => (removed @ [1])", " => a (string) (added @ [1])", }, }, {"slice - diff left is longer than right, all different", pair{[]string{"a", "b"}, []string{"c"}}, []string{ "a (string) => (removed @ [0])", " => c (string) (added @ [0])", "b (string) => (removed @ [1->?])", }, }, {"slice - diff left is longer than right, some equals", pair{[]string{"a", "b"}, []string{"a"}}, []string{ "b (string) => (removed @ [1->?])", }, }, {"slice - diff left is smaller than right, all different", pair{[]string{"a"}, []string{"b", "c"}}, []string{ "a (string) => (removed @ [0])", " => b (string) (added @ [0])", " => c (string) (added @ [?->1])", }, }, {"slice - diff left is smaller than right, some equals", pair{[]string{"a"}, []string{"a", "b"}}, []string{ " => b (string) (added @ [?->1])", }, }, // Full {"full - everything diff", pair{ &topStruct{ Literal: "x", Pointer: &plainStruct{Field: 1}, Array: []string{"a", "b"}, Child: &childStruct{Literal: "1", Pointer: &plainStruct{Field: 10}, Array: []string{"1", "2"}}, }, &topStruct{ Literal: "y", Pointer: &plainStruct{Field: 2}, Array: []string{"b", "c"}, Child: &childStruct{Literal: "2", Pointer: &plainStruct{Field: 20}, Array: []string{"2"}}, }, }, []string{ "x (string) => y (string) (changed @ Literal)", "1 (int) => 2 (int) (changed @ Pointer.Field)", "a (string) => (removed @ Array[0])", " => b (string) (added @ Array[0])", "b (string) => (removed @ Array[1])", " => c (string) (added @ Array[1])", "1 (string) => 2 (string) (changed @ Child.Literal)", "10 (int) => 20 (int) (changed @ Child.Pointer.Field)", "1 (string) => (removed @ Child.Array[0->?])", }, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { actuals := accumulateDiffStrings(test.in.left, test.in.right) assert.Equal(t, test.expecteds, actuals) }) } } func eventToString(event *Event) string { path := "" if len(event.Path) > 1 { path = " @ " + event.Path.String() } return fmt.Sprintf("%s => %s (%s%s)", reflectValueToString(event.Old), reflectValueToString(event.New), event.Kind, path) } func TestDiff_EventMatch(t *testing.T) { tests := []struct { name string left interface{} right interface{} pattern string expectedMatch bool expectedGroups []string }{ { "deep array added one", &topStruct{Child: &childStruct{Array: []string{"1"}}}, &topStruct{Child: &childStruct{Array: []string{"1", "2"}}}, "Child.Array[#]", true, []string{"?->1"}, }, } for _, test := range tests { t.Run(test.name, func(t *testing.T) { events := accumulateDiff(test.left, test.right) require.Len(t, events, 1) match, groups := events[0].Match(test.pattern) if test.expectedMatch { assert.True(t, match, "Expected pattern %q to match diff path %q", test.pattern, events[0].Path) assert.Equal(t, test.expectedGroups, groups) } else { assert.False(t, match, "Expected pattern %q to NOT match diff path %q", test.pattern, events[0].Path) } }) } } // There is something inherently broken with this, the accumulation seems to broke leading to incorrect // results. I assume it's a Golang thing related to slice and struct as value versus pointers. It works // only single event but starts to act weirdly when there > 1, like the Event's Path is all wrong. It's // better to try to avoid it when possible. func accumulateDiff(left, right interface{}) (out []Event) { Diff(left, right, OnEvent(func(event Event) { out = append(out, event) })) return } func accumulateDiffStrings(left, right interface{}) (out []string) { Diff(left, right, OnEvent(func(event Event) { out = append(out, eventToString(&event)) })) return } type topStruct struct { Literal string Pointer *plainStruct Array []string Child *childStruct } type childStruct struct { Literal string Pointer *plainStruct Array []string } type plainStruct struct { Field int }