vitess.io/vitess@v0.16.2/go/test/utils/diff.go (about) 1 /* 2 Copyright 2020 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package utils 18 19 import ( 20 "os" 21 "reflect" 22 "testing" 23 24 "github.com/stretchr/testify/require" 25 26 "google.golang.org/protobuf/encoding/prototext" 27 28 "google.golang.org/protobuf/proto" 29 30 "github.com/google/go-cmp/cmp" 31 ) 32 33 // MustMatchFn is used to create a common diff function for a test file 34 // Usage in *_test.go file: 35 // 36 // Top declaration: 37 // 38 // var mustMatch = testutils.MustMatchFn( 39 // 40 // []any{ // types with unexported fields 41 // type1{}, 42 // type2{}, 43 // ... 44 // typeN{}, 45 // }, 46 // []string{ // ignored fields 47 // ".id", // id numbers are unstable 48 // ".createAt", // created dates might not be interesting to compare 49 // }, 50 // 51 // ) 52 // 53 // In Test*() function: 54 // 55 // mustMatch(t, want, got, "something doesn't match") 56 func MustMatchFn(ignoredFields ...string) func(t *testing.T, want, got any, errMsg ...string) { 57 diffOpts := []cmp.Option{ 58 cmp.Comparer(func(a, b proto.Message) bool { 59 return proto.Equal(a, b) 60 }), 61 cmp.Exporter(func(reflect.Type) bool { 62 return true 63 }), 64 cmpIgnoreFields(ignoredFields...), 65 } 66 // Diffs want/got and fails with errMsg on any failure. 67 return func(t *testing.T, want, got any, errMsg ...string) { 68 t.Helper() 69 diff := cmp.Diff(want, got, diffOpts...) 70 if diff != "" { 71 t.Fatalf("%v: (-want +got)\n%v", errMsg, diff) 72 } 73 } 74 } 75 76 // MustMatch is a convenience version of MustMatchFn with no overrides. 77 // Usage in Test*() function: 78 // 79 // testutils.MustMatch(t, want, got, "something doesn't match") 80 var MustMatch = MustMatchFn() 81 82 // Skips fields of pathNames for cmp.Diff. 83 // Similar to standard cmpopts.IgnoreFields, but allows unexported fields. 84 func cmpIgnoreFields(pathNames ...string) cmp.Option { 85 skipFields := make(map[string]bool, len(pathNames)) 86 for _, name := range pathNames { 87 skipFields[name] = true 88 } 89 90 return cmp.FilterPath(func(path cmp.Path) bool { 91 for _, ps := range path { 92 if skipFields[ps.String()] { 93 return true 94 } 95 } 96 return false 97 }, cmp.Ignore()) 98 } 99 100 func MustMatchPB(t *testing.T, expected string, pb proto.Message) { 101 t.Helper() 102 103 expectedPb := pb.ProtoReflect().New().Interface() 104 if err := prototext.Unmarshal([]byte(expected), expectedPb); err != nil { 105 t.Fatal(err) 106 } 107 108 MustMatch(t, expectedPb, pb) 109 } 110 111 func MakeTestOutput(t *testing.T, dir, pattern string) string { 112 testOutputTempDir, err := os.MkdirTemp(dir, pattern) 113 require.NoError(t, err) 114 115 t.Cleanup(func() { 116 if !t.Failed() { 117 _ = os.RemoveAll(testOutputTempDir) 118 } else { 119 t.Logf("Errors found in plantests. If the output is correct, run `cp %s/* testdata/` to update test expectations", testOutputTempDir) 120 } 121 }) 122 123 return testOutputTempDir 124 }