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  }