...

Source file src/github.com/google/go-cmp/cmp/compare_test.go

Documentation: github.com/google/go-cmp/cmp

     1  // Copyright 2017, The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  package cmp_test
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/sha256"
    10  	"encoding/json"
    11  	"errors"
    12  	"flag"
    13  	"fmt"
    14  	"io"
    15  	"io/ioutil"
    16  	"math"
    17  	"math/rand"
    18  	"reflect"
    19  	"regexp"
    20  	"sort"
    21  	"strconv"
    22  	"strings"
    23  	"sync"
    24  	"testing"
    25  	"time"
    26  
    27  	"github.com/google/go-cmp/cmp"
    28  	"github.com/google/go-cmp/cmp/cmpopts"
    29  	"github.com/google/go-cmp/cmp/internal/flags"
    30  
    31  	pb "github.com/google/go-cmp/cmp/internal/testprotos"
    32  	ts "github.com/google/go-cmp/cmp/internal/teststructs"
    33  	foo1 "github.com/google/go-cmp/cmp/internal/teststructs/foo1"
    34  	foo2 "github.com/google/go-cmp/cmp/internal/teststructs/foo2"
    35  )
    36  
    37  func init() {
    38  	flags.Deterministic = true
    39  }
    40  
    41  var update = flag.Bool("update", false, "update golden test files")
    42  
    43  const goldenHeaderPrefix = "<<< "
    44  const goldenFooterPrefix = ">>> "
    45  
    46  // mustParseGolden parses a file as a set of key-value pairs.
    47  //
    48  // The syntax is simple and looks something like:
    49  //
    50  //	<<< Key1
    51  //	value1a
    52  //	value1b
    53  //	>>> Key1
    54  //	<<< Key2
    55  //	value2
    56  //	>>> Key2
    57  //
    58  // It is the user's responsibility to choose a sufficiently unique key name
    59  // such that it never appears in the body of the value itself.
    60  func mustParseGolden(path string) map[string]string {
    61  	b, err := ioutil.ReadFile(path)
    62  	if err != nil {
    63  		panic(err)
    64  	}
    65  	s := string(b)
    66  
    67  	out := map[string]string{}
    68  	for len(s) > 0 {
    69  		// Identify the next header.
    70  		i := strings.Index(s, "\n") + len("\n")
    71  		header := s[:i]
    72  		if !strings.HasPrefix(header, goldenHeaderPrefix) {
    73  			panic(fmt.Sprintf("invalid header: %q", header))
    74  		}
    75  
    76  		// Locate the next footer.
    77  		footer := goldenFooterPrefix + header[len(goldenHeaderPrefix):]
    78  		j := strings.Index(s, footer)
    79  		if j < 0 {
    80  			panic(fmt.Sprintf("missing footer: %q", footer))
    81  		}
    82  
    83  		// Store the name and data.
    84  		name := header[len(goldenHeaderPrefix) : len(header)-len("\n")]
    85  		if _, ok := out[name]; ok {
    86  			panic(fmt.Sprintf("duplicate name: %q", name))
    87  		}
    88  		out[name] = s[len(header):j]
    89  		s = s[j+len(footer):]
    90  	}
    91  	return out
    92  }
    93  func mustFormatGolden(path string, in []struct{ Name, Data string }) {
    94  	var b []byte
    95  	for _, v := range in {
    96  		b = append(b, goldenHeaderPrefix+v.Name+"\n"...)
    97  		b = append(b, v.Data...)
    98  		b = append(b, goldenFooterPrefix+v.Name+"\n"...)
    99  	}
   100  	if err := ioutil.WriteFile(path, b, 0664); err != nil {
   101  		panic(err)
   102  	}
   103  }
   104  
   105  var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)
   106  
   107  // TODO(≥go1.18): Define a generic function that boxes a value on the heap.
   108  func newInt(n int) *int { return &n }
   109  
   110  type Stringer string
   111  
   112  func newStringer(s string) fmt.Stringer { return (*Stringer)(&s) }
   113  func (s Stringer) String() string       { return string(s) }
   114  
   115  type test struct {
   116  	label     string       // Test name
   117  	x, y      interface{}  // Input values to compare
   118  	opts      []cmp.Option // Input options
   119  	wantEqual bool         // Whether any difference is expected
   120  	wantPanic string       // Sub-string of an expected panic message
   121  	reason    string       // The reason for the expected outcome
   122  }
   123  
   124  func TestDiff(t *testing.T) {
   125  	var tests []test
   126  	tests = append(tests, comparerTests()...)
   127  	tests = append(tests, transformerTests()...)
   128  	tests = append(tests, reporterTests()...)
   129  	tests = append(tests, embeddedTests()...)
   130  	tests = append(tests, methodTests()...)
   131  	tests = append(tests, cycleTests()...)
   132  	tests = append(tests, project1Tests()...)
   133  	tests = append(tests, project2Tests()...)
   134  	tests = append(tests, project3Tests()...)
   135  	tests = append(tests, project4Tests()...)
   136  
   137  	const goldenFile = "testdata/diffs"
   138  	gotDiffs := []struct{ Name, Data string }{}
   139  	wantDiffs := mustParseGolden(goldenFile)
   140  	for _, tt := range tests {
   141  		tt := tt
   142  		t.Run(tt.label, func(t *testing.T) {
   143  			if !*update {
   144  				t.Parallel()
   145  			}
   146  			var gotDiff, gotPanic string
   147  			func() {
   148  				defer func() {
   149  					if ex := recover(); ex != nil {
   150  						if s, ok := ex.(string); ok {
   151  							gotPanic = s
   152  						} else {
   153  							panic(ex)
   154  						}
   155  					}
   156  				}()
   157  				gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
   158  			}()
   159  
   160  			switch {
   161  			case strings.Contains(t.Name(), "#"):
   162  				panic("unique test name must be provided")
   163  			case tt.reason == "":
   164  				panic("reason must be provided")
   165  			case tt.wantPanic == "":
   166  				if gotPanic != "" {
   167  					t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
   168  				}
   169  				if *update {
   170  					if gotDiff != "" {
   171  						gotDiffs = append(gotDiffs, struct{ Name, Data string }{t.Name(), gotDiff})
   172  					}
   173  				} else {
   174  					wantDiff := wantDiffs[t.Name()]
   175  					if diff := cmp.Diff(wantDiff, gotDiff); diff != "" {
   176  						t.Fatalf("Diff:\ngot:\n%s\nwant:\n%s\ndiff: (-want +got)\n%s\nreason: %v", gotDiff, wantDiff, diff, tt.reason)
   177  					}
   178  				}
   179  				gotEqual := gotDiff == ""
   180  				if gotEqual != tt.wantEqual {
   181  					t.Fatalf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
   182  				}
   183  			default:
   184  				if !strings.Contains(gotPanic, tt.wantPanic) {
   185  					t.Fatalf("panic message:\ngot:  %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
   186  				}
   187  			}
   188  		})
   189  	}
   190  
   191  	if *update {
   192  		mustFormatGolden(goldenFile, gotDiffs)
   193  	}
   194  }
   195  
   196  func comparerTests() []test {
   197  	const label = "Comparer"
   198  
   199  	type Iface1 interface {
   200  		Method()
   201  	}
   202  	type Iface2 interface {
   203  		Method()
   204  	}
   205  
   206  	type tarHeader struct {
   207  		Name       string
   208  		Mode       int64
   209  		Uid        int
   210  		Gid        int
   211  		Size       int64
   212  		ModTime    time.Time
   213  		Typeflag   byte
   214  		Linkname   string
   215  		Uname      string
   216  		Gname      string
   217  		Devmajor   int64
   218  		Devminor   int64
   219  		AccessTime time.Time
   220  		ChangeTime time.Time
   221  		Xattrs     map[string]string
   222  	}
   223  
   224  	type namedWithUnexported struct {
   225  		unexported string
   226  	}
   227  
   228  	makeTarHeaders := func(tf byte) (hs []tarHeader) {
   229  		for i := 0; i < 5; i++ {
   230  			hs = append(hs, tarHeader{
   231  				Name: fmt.Sprintf("some/dummy/test/file%d", i),
   232  				Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i),
   233  				ModTime: now.Add(time.Duration(i) * time.Hour),
   234  				Uname:   "user", Gname: "group",
   235  				Typeflag: tf,
   236  			})
   237  		}
   238  		return hs
   239  	}
   240  
   241  	return []test{{
   242  		label:     label + "/Nil",
   243  		x:         nil,
   244  		y:         nil,
   245  		wantEqual: true,
   246  		reason:    "nils are equal",
   247  	}, {
   248  		label:     label + "/Integer",
   249  		x:         1,
   250  		y:         1,
   251  		wantEqual: true,
   252  		reason:    "identical integers are equal",
   253  	}, {
   254  		label:     label + "/UnfilteredIgnore",
   255  		x:         1,
   256  		y:         1,
   257  		opts:      []cmp.Option{cmp.Ignore()},
   258  		wantPanic: "cannot use an unfiltered option",
   259  		reason:    "unfiltered options are functionally useless",
   260  	}, {
   261  		label:     label + "/UnfilteredCompare",
   262  		x:         1,
   263  		y:         1,
   264  		opts:      []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
   265  		wantPanic: "cannot use an unfiltered option",
   266  		reason:    "unfiltered options are functionally useless",
   267  	}, {
   268  		label:     label + "/UnfilteredTransform",
   269  		x:         1,
   270  		y:         1,
   271  		opts:      []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })},
   272  		wantPanic: "cannot use an unfiltered option",
   273  		reason:    "unfiltered options are functionally useless",
   274  	}, {
   275  		label: label + "/AmbiguousOptions",
   276  		x:     1,
   277  		y:     1,
   278  		opts: []cmp.Option{
   279  			cmp.Comparer(func(x, y int) bool { return true }),
   280  			cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
   281  		},
   282  		wantPanic: "ambiguous set of applicable options",
   283  		reason:    "both options apply on int, leading to ambiguity",
   284  	}, {
   285  		label: label + "/IgnorePrecedence",
   286  		x:     1,
   287  		y:     1,
   288  		opts: []cmp.Option{
   289  			cmp.FilterPath(func(p cmp.Path) bool {
   290  				return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
   291  			}, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
   292  			cmp.Comparer(func(x, y int) bool { return true }),
   293  			cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
   294  		},
   295  		wantEqual: true,
   296  		reason:    "ignore takes precedence over other options",
   297  	}, {
   298  		label:     label + "/UnknownOption",
   299  		opts:      []cmp.Option{struct{ cmp.Option }{}},
   300  		wantPanic: "unknown option",
   301  		reason:    "use of unknown option should panic",
   302  	}, {
   303  		label:     label + "/StructEqual",
   304  		x:         struct{ A, B, C int }{1, 2, 3},
   305  		y:         struct{ A, B, C int }{1, 2, 3},
   306  		wantEqual: true,
   307  		reason:    "struct comparison with all equal fields",
   308  	}, {
   309  		label:     label + "/StructInequal",
   310  		x:         struct{ A, B, C int }{1, 2, 3},
   311  		y:         struct{ A, B, C int }{1, 2, 4},
   312  		wantEqual: false,
   313  		reason:    "struct comparison with inequal C field",
   314  	}, {
   315  		label:     label + "/StructUnexported",
   316  		x:         struct{ a, b, c int }{1, 2, 3},
   317  		y:         struct{ a, b, c int }{1, 2, 4},
   318  		wantPanic: "cannot handle unexported field",
   319  		reason:    "unexported fields result in a panic by default",
   320  	}, {
   321  		label:     label + "/PointerStructEqual",
   322  		x:         &struct{ A *int }{newInt(4)},
   323  		y:         &struct{ A *int }{newInt(4)},
   324  		wantEqual: true,
   325  		reason:    "comparison of pointer to struct with equal A field",
   326  	}, {
   327  		label:     label + "/PointerStructInequal",
   328  		x:         &struct{ A *int }{newInt(4)},
   329  		y:         &struct{ A *int }{newInt(5)},
   330  		wantEqual: false,
   331  		reason:    "comparison of pointer to struct with inequal A field",
   332  	}, {
   333  		label: label + "/PointerStructTrueComparer",
   334  		x:     &struct{ A *int }{newInt(4)},
   335  		y:     &struct{ A *int }{newInt(5)},
   336  		opts: []cmp.Option{
   337  			cmp.Comparer(func(x, y int) bool { return true }),
   338  		},
   339  		wantEqual: true,
   340  		reason:    "comparison of pointer to struct with inequal A field, but treated as equal with always equal comparer",
   341  	}, {
   342  		label: label + "/PointerStructNonNilComparer",
   343  		x:     &struct{ A *int }{newInt(4)},
   344  		y:     &struct{ A *int }{newInt(5)},
   345  		opts: []cmp.Option{
   346  			cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
   347  		},
   348  		wantEqual: true,
   349  		reason:    "comparison of pointer to struct with inequal A field, but treated as equal with comparer checking pointers for nilness",
   350  	}, {
   351  		label:     label + "/StructNestedPointerEqual",
   352  		x:         &struct{ R *bytes.Buffer }{},
   353  		y:         &struct{ R *bytes.Buffer }{},
   354  		wantEqual: true,
   355  		reason:    "equal since both pointers in R field are nil",
   356  	}, {
   357  		label:     label + "/StructNestedPointerInequal",
   358  		x:         &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
   359  		y:         &struct{ R *bytes.Buffer }{},
   360  		wantEqual: false,
   361  		reason:    "inequal since R field is inequal",
   362  	}, {
   363  		label: label + "/StructNestedPointerTrueComparer",
   364  		x:     &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
   365  		y:     &struct{ R *bytes.Buffer }{},
   366  		opts: []cmp.Option{
   367  			cmp.Comparer(func(x, y io.Reader) bool { return true }),
   368  		},
   369  		wantEqual: true,
   370  		reason:    "equal despite inequal R field values since the comparer always reports true",
   371  	}, {
   372  		label:     label + "/StructNestedValueUnexportedPanic1",
   373  		x:         &struct{ R bytes.Buffer }{},
   374  		y:         &struct{ R bytes.Buffer }{},
   375  		wantPanic: "cannot handle unexported field",
   376  		reason:    "bytes.Buffer contains unexported fields",
   377  	}, {
   378  		label: label + "/StructNestedValueUnexportedPanic2",
   379  		x:     &struct{ R bytes.Buffer }{},
   380  		y:     &struct{ R bytes.Buffer }{},
   381  		opts: []cmp.Option{
   382  			cmp.Comparer(func(x, y io.Reader) bool { return true }),
   383  		},
   384  		wantPanic: "cannot handle unexported field",
   385  		reason:    "bytes.Buffer value does not implement io.Reader",
   386  	}, {
   387  		label: label + "/StructNestedValueEqual",
   388  		x:     &struct{ R bytes.Buffer }{},
   389  		y:     &struct{ R bytes.Buffer }{},
   390  		opts: []cmp.Option{
   391  			cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
   392  			cmp.Comparer(func(x, y io.Reader) bool { return true }),
   393  		},
   394  		wantEqual: true,
   395  		reason:    "bytes.Buffer pointer due to shallow copy does implement io.Reader",
   396  	}, {
   397  		label:     label + "/RegexpUnexportedPanic",
   398  		x:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   399  		y:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   400  		wantPanic: "cannot handle unexported field",
   401  		reason:    "regexp.Regexp contains unexported fields",
   402  	}, {
   403  		label: label + "/RegexpEqual",
   404  		x:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   405  		y:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   406  		opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
   407  			if x == nil || y == nil {
   408  				return x == nil && y == nil
   409  			}
   410  			return x.String() == y.String()
   411  		})},
   412  		wantEqual: true,
   413  		reason:    "comparer for *regexp.Regexp applied with equal regexp strings",
   414  	}, {
   415  		label: label + "/RegexpInequal",
   416  		x:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   417  		y:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
   418  		opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
   419  			if x == nil || y == nil {
   420  				return x == nil && y == nil
   421  			}
   422  			return x.String() == y.String()
   423  		})},
   424  		wantEqual: false,
   425  		reason:    "comparer for *regexp.Regexp applied with inequal regexp strings",
   426  	}, {
   427  		label: label + "/TriplePointerEqual",
   428  		x: func() ***int {
   429  			a := 0
   430  			b := &a
   431  			c := &b
   432  			return &c
   433  		}(),
   434  		y: func() ***int {
   435  			a := 0
   436  			b := &a
   437  			c := &b
   438  			return &c
   439  		}(),
   440  		wantEqual: true,
   441  		reason:    "three layers of pointers to the same value",
   442  	}, {
   443  		label: label + "/TriplePointerInequal",
   444  		x: func() ***int {
   445  			a := 0
   446  			b := &a
   447  			c := &b
   448  			return &c
   449  		}(),
   450  		y: func() ***int {
   451  			a := 1
   452  			b := &a
   453  			c := &b
   454  			return &c
   455  		}(),
   456  		wantEqual: false,
   457  		reason:    "three layers of pointers to different values",
   458  	}, {
   459  		label:     label + "/SliceWithDifferingCapacity",
   460  		x:         []int{1, 2, 3, 4, 5}[:3],
   461  		y:         []int{1, 2, 3},
   462  		wantEqual: true,
   463  		reason:    "elements past the slice length are not compared",
   464  	}, {
   465  		label:     label + "/StringerEqual",
   466  		x:         struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
   467  		y:         struct{ fmt.Stringer }{regexp.MustCompile("hello")},
   468  		opts:      []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
   469  		wantEqual: true,
   470  		reason:    "comparer for fmt.Stringer used to compare differing types with same string",
   471  	}, {
   472  		label:     label + "/StringerInequal",
   473  		x:         struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
   474  		y:         struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
   475  		opts:      []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
   476  		wantEqual: false,
   477  		reason:    "comparer for fmt.Stringer used to compare differing types with different strings",
   478  	}, {
   479  		label:     label + "/DifferingHash",
   480  		x:         sha256.Sum256([]byte{'a'}),
   481  		y:         sha256.Sum256([]byte{'b'}),
   482  		wantEqual: false,
   483  		reason:    "hash differs",
   484  	}, {
   485  		label:     label + "/NilStringer",
   486  		x:         new(fmt.Stringer),
   487  		y:         nil,
   488  		wantEqual: false,
   489  		reason:    "by default differing types are always inequal",
   490  	}, {
   491  		label:     label + "/TarHeaders",
   492  		x:         makeTarHeaders('0'),
   493  		y:         makeTarHeaders('\x00'),
   494  		wantEqual: false,
   495  		reason:    "type flag differs between the headers",
   496  	}, {
   497  		label: label + "/NonDeterministicComparer",
   498  		x:     make([]int, 1000),
   499  		y:     make([]int, 1000),
   500  		opts: []cmp.Option{
   501  			cmp.Comparer(func(_, _ int) bool {
   502  				return rand.Intn(2) == 0
   503  			}),
   504  		},
   505  		wantPanic: "non-deterministic or non-symmetric function detected",
   506  		reason:    "non-deterministic comparer",
   507  	}, {
   508  		label: label + "/NonDeterministicFilter",
   509  		x:     make([]int, 1000),
   510  		y:     make([]int, 1000),
   511  		opts: []cmp.Option{
   512  			cmp.FilterValues(func(_, _ int) bool {
   513  				return rand.Intn(2) == 0
   514  			}, cmp.Ignore()),
   515  		},
   516  		wantPanic: "non-deterministic or non-symmetric function detected",
   517  		reason:    "non-deterministic filter",
   518  	}, {
   519  		label: label + "/AsymmetricComparer",
   520  		x:     []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   521  		y:     []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
   522  		opts: []cmp.Option{
   523  			cmp.Comparer(func(x, y int) bool {
   524  				return x < y
   525  			}),
   526  		},
   527  		wantPanic: "non-deterministic or non-symmetric function detected",
   528  		reason:    "asymmetric comparer",
   529  	}, {
   530  		label: label + "/NonDeterministicTransformer",
   531  		x:     make([]string, 1000),
   532  		y:     make([]string, 1000),
   533  		opts: []cmp.Option{
   534  			cmp.Transformer("λ", func(x string) int {
   535  				return rand.Int()
   536  			}),
   537  		},
   538  		wantPanic: "non-deterministic function detected",
   539  		reason:    "non-deterministic transformer",
   540  	}, {
   541  		label: label + "/IrreflexiveComparison",
   542  		x:     make([]int, 10),
   543  		y:     make([]int, 10),
   544  		opts: []cmp.Option{
   545  			cmp.Transformer("λ", func(x int) float64 {
   546  				return math.NaN()
   547  			}),
   548  		},
   549  		wantEqual: false,
   550  		reason:    "dynamic checks should not panic for non-reflexive comparisons",
   551  	}, {
   552  		label:     label + "/StringerMapKey",
   553  		x:         map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
   554  		y:         map[*pb.Stringer]*pb.Stringer(nil),
   555  		wantEqual: false,
   556  		reason:    "stringer should be used to format the map key",
   557  	}, {
   558  		label:     label + "/StringerBacktick",
   559  		x:         []*pb.Stringer{{`multi\nline\nline\nline`}},
   560  		wantEqual: false,
   561  		reason:    "stringer should use backtick quoting if more readable",
   562  	}, {
   563  		label: label + "/AvoidPanicAssignableConverter",
   564  		x:     struct{ I Iface2 }{},
   565  		y:     struct{ I Iface2 }{},
   566  		opts: []cmp.Option{
   567  			cmp.Comparer(func(x, y Iface1) bool {
   568  				return x == nil && y == nil
   569  			}),
   570  		},
   571  		wantEqual: true,
   572  		reason:    "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
   573  	}, {
   574  		label: label + "/AvoidPanicAssignableTransformer",
   575  		x:     struct{ I Iface2 }{},
   576  		y:     struct{ I Iface2 }{},
   577  		opts: []cmp.Option{
   578  			cmp.Transformer("λ", func(v Iface1) bool {
   579  				return v == nil
   580  			}),
   581  		},
   582  		wantEqual: true,
   583  		reason:    "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
   584  	}, {
   585  		label: label + "/AvoidPanicAssignableFilter",
   586  		x:     struct{ I Iface2 }{},
   587  		y:     struct{ I Iface2 }{},
   588  		opts: []cmp.Option{
   589  			cmp.FilterValues(func(x, y Iface1) bool {
   590  				return x == nil && y == nil
   591  			}, cmp.Ignore()),
   592  		},
   593  		wantEqual: true,
   594  		reason:    "function call using Go reflection should automatically convert assignable interfaces; see https://golang.org/issues/22143",
   595  	}, {
   596  		label:     label + "/DynamicMap",
   597  		x:         []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
   598  		y:         []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65.0, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63.0, "name": "Sammy Sosa"}},
   599  		wantEqual: false,
   600  		reason:    "dynamic map with differing types (but semantically equivalent values) should be inequal",
   601  	}, {
   602  		label: label + "/MapKeyPointer",
   603  		x: map[*int]string{
   604  			new(int): "hello",
   605  		},
   606  		y: map[*int]string{
   607  			new(int): "world",
   608  		},
   609  		wantEqual: false,
   610  		reason:    "map keys should use shallow (rather than deep) pointer comparison",
   611  	}, {
   612  		label: label + "/IgnoreSliceElements",
   613  		x: [2][]int{
   614  			{0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
   615  			{0, 1, 0, 0, 0, 20},
   616  		},
   617  		y: [2][]int{
   618  			{1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0},
   619  			{0, 0, 1, 2, 0, 0, 0},
   620  		},
   621  		opts: []cmp.Option{
   622  			cmp.FilterPath(func(p cmp.Path) bool {
   623  				vx, vy := p.Last().Values()
   624  				if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
   625  					return true
   626  				}
   627  				if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
   628  					return true
   629  				}
   630  				return false
   631  			}, cmp.Ignore()),
   632  		},
   633  		wantEqual: false,
   634  		reason:    "all zero slice elements are ignored (even if missing)",
   635  	}, {
   636  		label: label + "/IgnoreMapEntries",
   637  		x: [2]map[string]int{
   638  			{"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0},
   639  			{"keep1": 1, "ignore1": 0},
   640  		},
   641  		y: [2]map[string]int{
   642  			{"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3},
   643  			{"keep1": 1, "keep2": 2, "ignore2": 0},
   644  		},
   645  		opts: []cmp.Option{
   646  			cmp.FilterPath(func(p cmp.Path) bool {
   647  				vx, vy := p.Last().Values()
   648  				if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
   649  					return true
   650  				}
   651  				if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
   652  					return true
   653  				}
   654  				return false
   655  			}, cmp.Ignore()),
   656  		},
   657  		wantEqual: false,
   658  		reason:    "all zero map entries are ignored (even if missing)",
   659  	}, {
   660  		label:     label + "/PanicUnexportedNamed",
   661  		x:         namedWithUnexported{unexported: "x"},
   662  		y:         namedWithUnexported{unexported: "y"},
   663  		wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".namedWithUnexported",
   664  		reason:    "panic on named struct type with unexported field",
   665  	}, {
   666  		label:     label + "/PanicUnexportedUnnamed",
   667  		x:         struct{ a int }{},
   668  		y:         struct{ a int }{},
   669  		wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".(struct { a int })",
   670  		reason:    "panic on unnamed struct type with unexported field",
   671  	}, {
   672  		label: label + "/UnaddressableStruct",
   673  		x:     struct{ s fmt.Stringer }{new(bytes.Buffer)},
   674  		y:     struct{ s fmt.Stringer }{nil},
   675  		opts: []cmp.Option{
   676  			cmp.AllowUnexported(struct{ s fmt.Stringer }{}),
   677  			cmp.FilterPath(func(p cmp.Path) bool {
   678  				if _, ok := p.Last().(cmp.StructField); !ok {
   679  					return false
   680  				}
   681  
   682  				t := p.Index(-1).Type()
   683  				vx, vy := p.Index(-1).Values()
   684  				pvx, pvy := p.Index(-2).Values()
   685  				switch {
   686  				case vx.Type() != t:
   687  					panic(fmt.Sprintf("inconsistent type: %v != %v", vx.Type(), t))
   688  				case vy.Type() != t:
   689  					panic(fmt.Sprintf("inconsistent type: %v != %v", vy.Type(), t))
   690  				case vx.CanAddr() != pvx.CanAddr():
   691  					panic(fmt.Sprintf("inconsistent addressability: %v != %v", vx.CanAddr(), pvx.CanAddr()))
   692  				case vy.CanAddr() != pvy.CanAddr():
   693  					panic(fmt.Sprintf("inconsistent addressability: %v != %v", vy.CanAddr(), pvy.CanAddr()))
   694  				}
   695  				return true
   696  			}, cmp.Ignore()),
   697  		},
   698  		wantEqual: true,
   699  		reason:    "verify that exporter does not leak implementation details",
   700  	}, {
   701  		label:     label + "/ErrorPanic",
   702  		x:         io.EOF,
   703  		y:         io.EOF,
   704  		wantPanic: "consider using cmpopts.EquateErrors",
   705  		reason:    "suggest cmpopts.EquateErrors when accessing unexported fields of error types",
   706  	}, {
   707  		label:     label + "/ErrorEqual",
   708  		x:         io.EOF,
   709  		y:         io.EOF,
   710  		opts:      []cmp.Option{cmpopts.EquateErrors()},
   711  		wantEqual: true,
   712  		reason:    "cmpopts.EquateErrors should equate these two errors as sentinel values",
   713  	}}
   714  }
   715  
   716  func transformerTests() []test {
   717  	type StringBytes struct {
   718  		String string
   719  		Bytes  []byte
   720  	}
   721  
   722  	const label = "Transformer"
   723  
   724  	transformOnce := func(name string, f interface{}) cmp.Option {
   725  		xform := cmp.Transformer(name, f)
   726  		return cmp.FilterPath(func(p cmp.Path) bool {
   727  			for _, ps := range p {
   728  				if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
   729  					return false
   730  				}
   731  			}
   732  			return true
   733  		}, xform)
   734  	}
   735  
   736  	return []test{{
   737  		label: label + "/Uints",
   738  		x:     uint8(0),
   739  		y:     uint8(1),
   740  		opts: []cmp.Option{
   741  			cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }),
   742  			cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }),
   743  			cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }),
   744  		},
   745  		wantEqual: false,
   746  		reason:    "transform uint8 -> uint16 -> uint32 -> uint64",
   747  	}, {
   748  		label: label + "/Ambiguous",
   749  		x:     0,
   750  		y:     1,
   751  		opts: []cmp.Option{
   752  			cmp.Transformer("λ", func(in int) int { return in / 2 }),
   753  			cmp.Transformer("λ", func(in int) int { return in }),
   754  		},
   755  		wantPanic: "ambiguous set of applicable options",
   756  		reason:    "both transformers apply on int",
   757  	}, {
   758  		label: label + "/Filtered",
   759  		x:     []int{0, -5, 0, -1},
   760  		y:     []int{1, 3, 0, -5},
   761  		opts: []cmp.Option{
   762  			cmp.FilterValues(
   763  				func(x, y int) bool { return x+y >= 0 },
   764  				cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }),
   765  			),
   766  			cmp.FilterValues(
   767  				func(x, y int) bool { return x+y < 0 },
   768  				cmp.Transformer("λ", func(in int) int64 { return int64(in) }),
   769  			),
   770  		},
   771  		wantEqual: false,
   772  		reason:    "disjoint transformers filtered based on the values",
   773  	}, {
   774  		label: label + "/DisjointOutput",
   775  		x:     0,
   776  		y:     1,
   777  		opts: []cmp.Option{
   778  			cmp.Transformer("λ", func(in int) interface{} {
   779  				if in == 0 {
   780  					return "zero"
   781  				}
   782  				return float64(in)
   783  			}),
   784  		},
   785  		wantEqual: false,
   786  		reason:    "output type differs based on input value",
   787  	}, {
   788  		label: label + "/JSON",
   789  		x: `{
   790  		  "firstName": "John",
   791  		  "lastName": "Smith",
   792  		  "age": 25,
   793  		  "isAlive": true,
   794  		  "address": {
   795  		    "city": "Los Angeles",
   796  		    "postalCode": "10021-3100",
   797  		    "state": "CA",
   798  		    "streetAddress": "21 2nd Street"
   799  		  },
   800  		  "phoneNumbers": [{
   801  		    "type": "home",
   802  		    "number": "212 555-4321"
   803  		  },{
   804  		    "type": "office",
   805  		    "number": "646 555-4567"
   806  		  },{
   807  		    "number": "123 456-7890",
   808  		    "type": "mobile"
   809  		  }],
   810  		  "children": []
   811  		}`,
   812  		y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
   813  			"address":{"streetAddress":"21 2nd Street","city":"New York",
   814  			"state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home",
   815  			"number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{
   816  			"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
   817  		opts: []cmp.Option{
   818  			transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
   819  				if err := json.Unmarshal([]byte(s), &m); err != nil {
   820  					panic(err)
   821  				}
   822  				return m
   823  			}),
   824  		},
   825  		wantEqual: false,
   826  		reason:    "transformer used to parse JSON input",
   827  	}, {
   828  		label: label + "/AcyclicString",
   829  		x:     StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
   830  		y:     StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
   831  		opts: []cmp.Option{
   832  			transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
   833  			transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
   834  		},
   835  		wantEqual: false,
   836  		reason:    "string -> []string and []byte -> [][]byte transformer only applied once",
   837  	}, {
   838  		label: label + "/CyclicString",
   839  		x:     "a\nb\nc\n",
   840  		y:     "a\nb\nc\n",
   841  		opts: []cmp.Option{
   842  			cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
   843  		},
   844  		wantPanic: "recursive set of Transformers detected",
   845  		reason:    "cyclic transformation from string -> []string -> string",
   846  	}, {
   847  		label: label + "/CyclicComplex",
   848  		x:     complex64(0),
   849  		y:     complex64(0),
   850  		opts: []cmp.Option{
   851  			cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
   852  			cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
   853  			cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
   854  		},
   855  		wantPanic: "recursive set of Transformers detected",
   856  		reason:    "cyclic transformation from complex64 -> complex128 -> [2]float64 -> complex64",
   857  	}}
   858  }
   859  
   860  func reporterTests() []test {
   861  	const label = "Reporter"
   862  
   863  	type (
   864  		MyString    string
   865  		MyByte      byte
   866  		MyBytes     []byte
   867  		MyInt       int8
   868  		MyInts      []int8
   869  		MyUint      int16
   870  		MyUints     []int16
   871  		MyFloat     float32
   872  		MyFloats    []float32
   873  		MyComposite struct {
   874  			StringA string
   875  			StringB MyString
   876  			BytesA  []byte
   877  			BytesB  []MyByte
   878  			BytesC  MyBytes
   879  			IntsA   []int8
   880  			IntsB   []MyInt
   881  			IntsC   MyInts
   882  			UintsA  []uint16
   883  			UintsB  []MyUint
   884  			UintsC  MyUints
   885  			FloatsA []float32
   886  			FloatsB []MyFloat
   887  			FloatsC MyFloats
   888  		}
   889  		PointerString *string
   890  	)
   891  
   892  	return []test{{
   893  		label:     label + "/PanicStringer",
   894  		x:         struct{ X fmt.Stringer }{struct{ fmt.Stringer }{nil}},
   895  		y:         struct{ X fmt.Stringer }{bytes.NewBuffer(nil)},
   896  		wantEqual: false,
   897  		reason:    "panic from fmt.Stringer should not crash the reporter",
   898  	}, {
   899  		label:     label + "/PanicError",
   900  		x:         struct{ X error }{struct{ error }{nil}},
   901  		y:         struct{ X error }{errors.New("")},
   902  		wantEqual: false,
   903  		reason:    "panic from error should not crash the reporter",
   904  	}, {
   905  		label:     label + "/AmbiguousType",
   906  		x:         foo1.Bar{},
   907  		y:         foo2.Bar{},
   908  		wantEqual: false,
   909  		reason:    "reporter should display the qualified type name to disambiguate between the two values",
   910  	}, {
   911  		label: label + "/AmbiguousPointer",
   912  		x:     newInt(0),
   913  		y:     newInt(0),
   914  		opts: []cmp.Option{
   915  			cmp.Comparer(func(x, y *int) bool { return x == y }),
   916  		},
   917  		wantEqual: false,
   918  		reason:    "reporter should display the address to disambiguate between the two values",
   919  	}, {
   920  		label: label + "/AmbiguousPointerStruct",
   921  		x:     struct{ I *int }{newInt(0)},
   922  		y:     struct{ I *int }{newInt(0)},
   923  		opts: []cmp.Option{
   924  			cmp.Comparer(func(x, y *int) bool { return x == y }),
   925  		},
   926  		wantEqual: false,
   927  		reason:    "reporter should display the address to disambiguate between the two struct fields",
   928  	}, {
   929  		label: label + "/AmbiguousPointerSlice",
   930  		x:     []*int{newInt(0)},
   931  		y:     []*int{newInt(0)},
   932  		opts: []cmp.Option{
   933  			cmp.Comparer(func(x, y *int) bool { return x == y }),
   934  		},
   935  		wantEqual: false,
   936  		reason:    "reporter should display the address to disambiguate between the two slice elements",
   937  	}, {
   938  		label: label + "/AmbiguousPointerMap",
   939  		x:     map[string]*int{"zero": newInt(0)},
   940  		y:     map[string]*int{"zero": newInt(0)},
   941  		opts: []cmp.Option{
   942  			cmp.Comparer(func(x, y *int) bool { return x == y }),
   943  		},
   944  		wantEqual: false,
   945  		reason:    "reporter should display the address to disambiguate between the two map values",
   946  	}, {
   947  		label:     label + "/AmbiguousStringer",
   948  		x:         Stringer("hello"),
   949  		y:         newStringer("hello"),
   950  		wantEqual: false,
   951  		reason:    "reporter should avoid calling String to disambiguate between the two values",
   952  	}, {
   953  		label:     label + "/AmbiguousStringerStruct",
   954  		x:         struct{ S fmt.Stringer }{Stringer("hello")},
   955  		y:         struct{ S fmt.Stringer }{newStringer("hello")},
   956  		wantEqual: false,
   957  		reason:    "reporter should avoid calling String to disambiguate between the two struct fields",
   958  	}, {
   959  		label:     label + "/AmbiguousStringerSlice",
   960  		x:         []fmt.Stringer{Stringer("hello")},
   961  		y:         []fmt.Stringer{newStringer("hello")},
   962  		wantEqual: false,
   963  		reason:    "reporter should avoid calling String to disambiguate between the two slice elements",
   964  	}, {
   965  		label:     label + "/AmbiguousStringerMap",
   966  		x:         map[string]fmt.Stringer{"zero": Stringer("hello")},
   967  		y:         map[string]fmt.Stringer{"zero": newStringer("hello")},
   968  		wantEqual: false,
   969  		reason:    "reporter should avoid calling String to disambiguate between the two map values",
   970  	}, {
   971  		label: label + "/AmbiguousSliceHeader",
   972  		x:     make([]int, 0, 5),
   973  		y:     make([]int, 0, 1000),
   974  		opts: []cmp.Option{
   975  			cmp.Comparer(func(x, y []int) bool { return cap(x) == cap(y) }),
   976  		},
   977  		wantEqual: false,
   978  		reason:    "reporter should display the slice header to disambiguate between the two slice values",
   979  	}, {
   980  		label: label + "/AmbiguousStringerMapKey",
   981  		x: map[interface{}]string{
   982  			nil:               "nil",
   983  			Stringer("hello"): "goodbye",
   984  			foo1.Bar{"fizz"}:  "buzz",
   985  		},
   986  		y: map[interface{}]string{
   987  			newStringer("hello"): "goodbye",
   988  			foo2.Bar{"fizz"}:     "buzz",
   989  		},
   990  		wantEqual: false,
   991  		reason:    "reporter should avoid calling String to disambiguate between the two map keys",
   992  	}, {
   993  		label:     label + "/NonAmbiguousStringerMapKey",
   994  		x:         map[interface{}]string{Stringer("hello"): "goodbye"},
   995  		y:         map[interface{}]string{newStringer("fizz"): "buzz"},
   996  		wantEqual: false,
   997  		reason:    "reporter should call String as there is no ambiguity between the two map keys",
   998  	}, {
   999  		label:     label + "/InvalidUTF8",
  1000  		x:         MyString("\xed\xa0\x80"),
  1001  		wantEqual: false,
  1002  		reason:    "invalid UTF-8 should format as quoted string",
  1003  	}, {
  1004  		label:     label + "/UnbatchedSlice",
  1005  		x:         MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  1006  		y:         MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  1007  		wantEqual: false,
  1008  		reason:    "unbatched diffing desired since few elements differ",
  1009  	}, {
  1010  		label:     label + "/BatchedSlice",
  1011  		x:         MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  1012  		y:         MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
  1013  		wantEqual: false,
  1014  		reason:    "batched diffing desired since many elements differ",
  1015  	}, {
  1016  		label:     label + "/BatchedWithComparer",
  1017  		x:         MyComposite{BytesA: []byte{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
  1018  		y:         MyComposite{BytesA: []byte{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
  1019  		wantEqual: false,
  1020  		opts: []cmp.Option{
  1021  			cmp.Comparer(bytes.Equal),
  1022  		},
  1023  		reason: "batched diffing desired since many elements differ",
  1024  	}, {
  1025  		label:     label + "/BatchedLong",
  1026  		x:         MyComposite{IntsA: []int8{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 102, 103, 104, 105, 106, 107, 108, 109, 110, 111, 112, 113, 114, 115, 116, 117, 118, 119, 120, 121, 122, 123, 124, 125, 126, 127}},
  1027  		wantEqual: false,
  1028  		reason:    "batched output desired for a single slice of primitives unique to one of the inputs",
  1029  	}, {
  1030  		label: label + "/BatchedNamedAndUnnamed",
  1031  		x: MyComposite{
  1032  			BytesA:  []byte{1, 2, 3},
  1033  			BytesB:  []MyByte{4, 5, 6},
  1034  			BytesC:  MyBytes{7, 8, 9},
  1035  			IntsA:   []int8{-1, -2, -3},
  1036  			IntsB:   []MyInt{-4, -5, -6},
  1037  			IntsC:   MyInts{-7, -8, -9},
  1038  			UintsA:  []uint16{1000, 2000, 3000},
  1039  			UintsB:  []MyUint{4000, 5000, 6000},
  1040  			UintsC:  MyUints{7000, 8000, 9000},
  1041  			FloatsA: []float32{1.5, 2.5, 3.5},
  1042  			FloatsB: []MyFloat{4.5, 5.5, 6.5},
  1043  			FloatsC: MyFloats{7.5, 8.5, 9.5},
  1044  		},
  1045  		y: MyComposite{
  1046  			BytesA:  []byte{3, 2, 1},
  1047  			BytesB:  []MyByte{6, 5, 4},
  1048  			BytesC:  MyBytes{9, 8, 7},
  1049  			IntsA:   []int8{-3, -2, -1},
  1050  			IntsB:   []MyInt{-6, -5, -4},
  1051  			IntsC:   MyInts{-9, -8, -7},
  1052  			UintsA:  []uint16{3000, 2000, 1000},
  1053  			UintsB:  []MyUint{6000, 5000, 4000},
  1054  			UintsC:  MyUints{9000, 8000, 7000},
  1055  			FloatsA: []float32{3.5, 2.5, 1.5},
  1056  			FloatsB: []MyFloat{6.5, 5.5, 4.5},
  1057  			FloatsC: MyFloats{9.5, 8.5, 7.5},
  1058  		},
  1059  		wantEqual: false,
  1060  		reason:    "batched diffing available for both named and unnamed slices",
  1061  	}, {
  1062  		label:     label + "/BinaryHexdump",
  1063  		x:         MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeX\x95A\xfd$fX\x8byT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1U~{\xf6\xb3~\x1dWi \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
  1064  		y:         MyComposite{BytesA: []byte("\xf3\x0f\x8a\xa4\xd3\x12R\t$\xbeT\xac\r\xd8qwp\x20j\\s\u007f\x8c\x17U\xc04\xcen\xf7\xaaG\xee2\x9d\xc5\xca\x1eX\xaf\x8f'\xf3\x02J\x90\xedi.p2\xb4\xab0 \xb6\xbd\\b4\x17\xb0\x00\xbbO~'G\x06\xf4.f\xfdc\xd7\x04ݷ0\xb7\xd1u-[]]\xf6\xb3haha~\x1dWI \x9e\xbc\xdf\xe1M\xa9\xef\xa2\xd2\xed\xb4Gx\xc9\xc9'\xa4\xc6\xce\xecDp]")},
  1065  		wantEqual: false,
  1066  		reason:    "binary diff in hexdump form since data is binary data",
  1067  	}, {
  1068  		label:     label + "/StringHexdump",
  1069  		x:         MyComposite{StringB: MyString("readme.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000046\x0000000000000\x00011173\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
  1070  		y:         MyComposite{StringB: MyString("gopher.txt\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000600\x000000000\x000000000\x0000000000043\x0000000000000\x00011217\x00 0\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00ustar\x0000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x000000000\x000000000\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00")},
  1071  		wantEqual: false,
  1072  		reason:    "binary diff desired since string looks like binary data",
  1073  	}, {
  1074  		label:     label + "/BinaryString",
  1075  		x:         MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"314 54th Avenue","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)},
  1076  		y:         MyComposite{BytesA: []byte(`{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"address":{"streetAddress":"21 2nd Street","city":"New York","state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home","number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`)},
  1077  		wantEqual: false,
  1078  		reason:    "batched textual diff desired since bytes looks like textual data",
  1079  	}, {
  1080  		label:     label + "/TripleQuote",
  1081  		x:         MyComposite{StringA: "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"},
  1082  		y:         MyComposite{StringA: "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n"},
  1083  		wantEqual: false,
  1084  		reason:    "use triple-quote syntax",
  1085  	}, {
  1086  		label: label + "/TripleQuoteSlice",
  1087  		x: []string{
  1088  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1089  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1090  		},
  1091  		y: []string{
  1092  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n",
  1093  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1094  		},
  1095  		wantEqual: false,
  1096  		reason:    "use triple-quote syntax for slices of strings",
  1097  	}, {
  1098  		label: label + "/TripleQuoteNamedTypes",
  1099  		x: MyComposite{
  1100  			StringB: MyString("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
  1101  			BytesC:  MyBytes("aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
  1102  		},
  1103  		y: MyComposite{
  1104  			StringB: MyString("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
  1105  			BytesC:  MyBytes("aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nSSS\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz"),
  1106  		},
  1107  		wantEqual: false,
  1108  		reason:    "use triple-quote syntax for named types",
  1109  	}, {
  1110  		label: label + "/TripleQuoteSliceNamedTypes",
  1111  		x: []MyString{
  1112  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1113  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1114  		},
  1115  		y: []MyString{
  1116  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\n",
  1117  			"aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1118  		},
  1119  		wantEqual: false,
  1120  		reason:    "use triple-quote syntax for slices of named strings",
  1121  	}, {
  1122  		label:     label + "/TripleQuoteEndlines",
  1123  		x:         "aaa\nbbb\nccc\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n\r",
  1124  		y:         "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\r\nhhh\n\riii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz",
  1125  		wantEqual: false,
  1126  		reason:    "use triple-quote syntax",
  1127  	}, {
  1128  		label:     label + "/AvoidTripleQuoteAmbiguousQuotes",
  1129  		x:         "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1130  		y:         "aaa\nbbb\nCCC\nddd\neee\n\"\"\"\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1131  		wantEqual: false,
  1132  		reason:    "avoid triple-quote syntax due to presence of ambiguous triple quotes",
  1133  	}, {
  1134  		label:     label + "/AvoidTripleQuoteAmbiguousEllipsis",
  1135  		x:         "aaa\nbbb\nccc\n...\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1136  		y:         "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1137  		wantEqual: false,
  1138  		reason:    "avoid triple-quote syntax due to presence of ambiguous ellipsis",
  1139  	}, {
  1140  		label:     label + "/AvoidTripleQuoteNonPrintable",
  1141  		x:         "aaa\nbbb\nccc\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1142  		y:         "aaa\nbbb\nCCC\nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\no\roo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1143  		wantEqual: false,
  1144  		reason:    "use triple-quote syntax",
  1145  	}, {
  1146  		label:     label + "/AvoidTripleQuoteIdenticalWhitespace",
  1147  		x:         "aaa\nbbb\nccc\n ddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nRRR\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1148  		y:         "aaa\nbbb\nccc \nddd\neee\nfff\nggg\nhhh\niii\njjj\nkkk\nlll\nmmm\nnnn\nooo\nppp\nqqq\nrrr\nsss\nttt\nuuu\nvvv\nwww\nxxx\nyyy\nzzz\n",
  1149  		wantEqual: false,
  1150  		reason:    "avoid triple-quote syntax due to visual equivalence of differences",
  1151  	}, {
  1152  		label: label + "/TripleQuoteStringer",
  1153  		x: []fmt.Stringer{
  1154  			bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
  1155  			bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math/rand\"\n)\n\nfunc main() {\n\tfmt.Println(\"My favorite number is\", rand.Intn(10))\n}\n")),
  1156  		},
  1157  		y: []fmt.Stringer{
  1158  			bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n)\n\nfunc main() {\n\tfmt.Println(\"Hello, playground\")\n}\n")),
  1159  			bytes.NewBuffer([]byte("package main\n\nimport (\n\t\"fmt\"\n\t\"math\"\n)\n\nfunc main() {\n\tfmt.Printf(\"Now you have %g problems.\\n\", math.Sqrt(7))\n}\n")),
  1160  		},
  1161  		opts:      []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
  1162  		wantEqual: false,
  1163  		reason:    "multi-line String output should be formatted with triple quote",
  1164  	}, {
  1165  		label:     label + "/LimitMaximumBytesDiffs",
  1166  		x:         []byte("\xcd====\x06\x1f\xc2\xcc\xc2-S=====\x1d\xdfa\xae\x98\x9fH======ǰ\xb7=======\xef====:\\\x94\xe6J\xc7=====\xb4======\n\n\xf7\x94===========\xf2\x9c\xc0f=====4\xf6\xf1\xc3\x17\x82======n\x16`\x91D\xc6\x06=======\x1cE====.===========\xc4\x18=======\x8a\x8d\x0e====\x87\xb1\xa5\x8e\xc3=====z\x0f1\xaeU======G,=======5\xe75\xee\x82\xf4\xce====\x11r===========\xaf]=======z\x05\xb3\x91\x88%\xd2====\n1\x89=====i\xb7\x055\xe6\x81\xd2=============\x883=@̾====\x14\x05\x96%^t\x04=====\xe7Ȉ\x90\x1d============="),
  1167  		y:         []byte("\\====|\x96\xe7SB\xa0\xab=====\xf0\xbd\xa5q\xab\x17;======\xabP\x00=======\xeb====\xa5\x14\xe6O(\xe4=====(======/c@?===========\xd9x\xed\x13=====J\xfc\x918B\x8d======a8A\xebs\x04\xae=======\aC====\x1c===========\x91\"=======uؾ====s\xec\x845\a=====;\xabS9t======\x1f\x1b=======\x80\xab/\xed+:;====\xeaI===========\xabl=======\xb9\xe9\xfdH\x93\x8e\u007f====ח\xe5=====Ig\x88m\xf5\x01V=============\xf7+4\xb0\x92E====\x9fj\xf8&\xd0h\xf9=====\xeeΨ\r\xbf============="),
  1168  		wantEqual: false,
  1169  		reason:    "total bytes difference output is truncated due to excessive number of differences",
  1170  	}, {
  1171  		label:     label + "/LimitMaximumStringDiffs",
  1172  		x:         "a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n",
  1173  		y:         "aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n",
  1174  		wantEqual: false,
  1175  		reason:    "total string difference output is truncated due to excessive number of differences",
  1176  	}, {
  1177  		label: label + "/LimitMaximumSliceDiffs",
  1178  		x: func() (out []struct{ S string }) {
  1179  			for _, s := range strings.Split("a\nb\nc\nd\ne\nf\ng\nh\ni\nj\nk\nl\nm\nn\no\np\nq\nr\ns\nt\nu\nv\nw\nx\ny\nz\nA\nB\nC\nD\nE\nF\nG\nH\nI\nJ\nK\nL\nM\nN\nO\nP\nQ\nR\nS\nT\nU\nV\nW\nX\nY\nZ\n", "\n") {
  1180  				out = append(out, struct{ S string }{s})
  1181  			}
  1182  			return out
  1183  		}(),
  1184  		y: func() (out []struct{ S string }) {
  1185  			for _, s := range strings.Split("aa\nb\ncc\nd\nee\nf\ngg\nh\nii\nj\nkk\nl\nmm\nn\noo\np\nqq\nr\nss\nt\nuu\nv\nww\nx\nyy\nz\nAA\nB\nCC\nD\nEE\nF\nGG\nH\nII\nJ\nKK\nL\nMM\nN\nOO\nP\nQQ\nR\nSS\nT\nUU\nV\nWW\nX\nYY\nZ\n", "\n") {
  1186  				out = append(out, struct{ S string }{s})
  1187  			}
  1188  			return out
  1189  		}(),
  1190  		wantEqual: false,
  1191  		reason:    "total slice difference output is truncated due to excessive number of differences",
  1192  	}, {
  1193  		label: label + "/MultilineString",
  1194  		x: MyComposite{
  1195  			StringA: strings.TrimPrefix(`
  1196  Package cmp determines equality of values.
  1197  
  1198  This package is intended to be a more powerful and safer alternative to
  1199  reflect.DeepEqual for comparing whether two values are semantically equal.
  1200  
  1201  The primary features of cmp are:
  1202  
  1203  • When the default behavior of equality does not suit the needs of the test,
  1204  custom equality functions can override the equality operation.
  1205  For example, an equality function may report floats as equal so long as they
  1206  are within some tolerance of each other.
  1207  
  1208  • Types that have an Equal method may use that method to determine equality.
  1209  This allows package authors to determine the equality operation for the types
  1210  that they define.
  1211  
  1212  • If no custom equality functions are used and no Equal method is defined,
  1213  equality is determined by recursively comparing the primitive kinds on both
  1214  values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
  1215  fields are not compared by default; they result in panics unless suppressed
  1216  by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
  1217  using the AllowUnexported option.
  1218  `, "\n"),
  1219  		},
  1220  		y: MyComposite{
  1221  			StringA: strings.TrimPrefix(`
  1222  Package cmp determines equality of value.
  1223  
  1224  This package is intended to be a more powerful and safer alternative to
  1225  reflect.DeepEqual for comparing whether two values are semantically equal.
  1226  
  1227  The primary features of cmp are:
  1228  
  1229  • When the default behavior of equality does not suit the needs of the test,
  1230  custom equality functions can override the equality operation.
  1231  For example, an equality function may report floats as equal so long as they
  1232  are within some tolerance of each other.
  1233  
  1234  • If no custom equality functions are used and no Equal method is defined,
  1235  equality is determined by recursively comparing the primitive kinds on both
  1236  values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
  1237  fields are not compared by default; they result in panics unless suppressed
  1238  by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
  1239  using the AllowUnexported option.`, "\n"),
  1240  		},
  1241  		wantEqual: false,
  1242  		reason:    "batched per-line diff desired since string looks like multi-line textual data",
  1243  	}, {
  1244  		label: label + "/Slices",
  1245  		x: MyComposite{
  1246  			BytesA:  []byte{1, 2, 3},
  1247  			BytesB:  []MyByte{4, 5, 6},
  1248  			BytesC:  MyBytes{7, 8, 9},
  1249  			IntsA:   []int8{-1, -2, -3},
  1250  			IntsB:   []MyInt{-4, -5, -6},
  1251  			IntsC:   MyInts{-7, -8, -9},
  1252  			UintsA:  []uint16{1000, 2000, 3000},
  1253  			UintsB:  []MyUint{4000, 5000, 6000},
  1254  			UintsC:  MyUints{7000, 8000, 9000},
  1255  			FloatsA: []float32{1.5, 2.5, 3.5},
  1256  			FloatsB: []MyFloat{4.5, 5.5, 6.5},
  1257  			FloatsC: MyFloats{7.5, 8.5, 9.5},
  1258  		},
  1259  		y:         MyComposite{},
  1260  		wantEqual: false,
  1261  		reason:    "batched diffing for non-nil slices and nil slices",
  1262  	}, {
  1263  		label: label + "/EmptySlices",
  1264  		x: MyComposite{
  1265  			BytesA:  []byte{},
  1266  			BytesB:  []MyByte{},
  1267  			BytesC:  MyBytes{},
  1268  			IntsA:   []int8{},
  1269  			IntsB:   []MyInt{},
  1270  			IntsC:   MyInts{},
  1271  			UintsA:  []uint16{},
  1272  			UintsB:  []MyUint{},
  1273  			UintsC:  MyUints{},
  1274  			FloatsA: []float32{},
  1275  			FloatsB: []MyFloat{},
  1276  			FloatsC: MyFloats{},
  1277  		},
  1278  		y:         MyComposite{},
  1279  		wantEqual: false,
  1280  		reason:    "batched diffing for empty slices and nil slices",
  1281  	}, {
  1282  		label: label + "/LargeMapKey",
  1283  		x: map[*[]byte]int{func() *[]byte {
  1284  			b := make([]byte, 1<<20)
  1285  			return &b
  1286  		}(): 0},
  1287  		y: map[*[]byte]int{func() *[]byte {
  1288  			b := make([]byte, 1<<20)
  1289  			return &b
  1290  		}(): 0},
  1291  		reason: "printing map keys should have some verbosity limit imposed",
  1292  	}, {
  1293  		label: label + "/LargeStringInInterface",
  1294  		x:     struct{ X interface{} }{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis."},
  1295  
  1296  		y:      struct{ X interface{} }{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,"},
  1297  		reason: "strings within an interface should benefit from specialized diffing",
  1298  	}, {
  1299  		label:  label + "/LargeBytesInInterface",
  1300  		x:      struct{ X interface{} }{[]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis.")},
  1301  		y:      struct{ X interface{} }{[]byte("Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,")},
  1302  		reason: "bytes slice within an interface should benefit from specialized diffing",
  1303  	}, {
  1304  		label:  label + "/LargeStandaloneString",
  1305  		x:      struct{ X interface{} }{[1]string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis."}},
  1306  		y:      struct{ X interface{} }{[1]string{"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam sit amet pretium ligula, at gravida quam. Integer iaculis, velit at sagittis ultricies, lacus metus scelerisque turpis, ornare feugiat nulla nisl ac erat. Maecenas elementum ultricies libero, sed efficitur lacus molestie non. Nulla ac pretium dolor. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Pellentesque mi lorem, consectetur id porttitor id, sollicitudin sit amet enim. Duis eu dolor magna. Nunc ut augue turpis,"}},
  1307  		reason: "printing a large standalone string that is different should print enough context to see the difference",
  1308  	}, {
  1309  		label:  label + "/SurroundingEqualElements",
  1310  		x:      "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa,#=_value _value=2 11\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb,#=_value _value=2 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc,#=_value _value=1 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd,#=_value _value=3 31\torg-4747474747474747,bucket-4242424242424242:m,tag1=c,#=_value _value=4 41\t",
  1311  		y:      "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa _value=2 11\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb _value=2 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc _value=1 21\torg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd _value=3 31\torg-4747474747474747,bucket-4242424242424242:m,tag1=c _value=4 41\t",
  1312  		reason: "leading/trailing equal spans should not appear in diff lines",
  1313  	}, {
  1314  		label:  label + "/MostlyTextString",
  1315  		x:      "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa,\xff=_value _value=2 11\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb,\xff=_value _value=2 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc,\xff=_value _value=1 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd,\xff=_value _value=3 31\norg-4747474747474747,bucket-4242424242424242:m,tag1=c,\xff=_value _value=4 41\n",
  1316  		y:      "org-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=aa _value=2 11\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=bb _value=2 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=b,tag2=cc _value=1 21\norg-4747474747474747,bucket-4242424242424242:m,tag1=a,tag2=dd _value=3 31\norg-4747474747474747,bucket-4242424242424242:m,tag1=c _value=4 41\n",
  1317  		reason: "the presence of a few invalid UTF-8 characters should not prevent printing this as text",
  1318  	}, {
  1319  		label:  label + "/AllLinesDiffer",
  1320  		x:      "d5c14bdf6bac81c27afc5429500ed750\n25483503b557c606dad4f144d27ae10b\n90bdbcdbb6ea7156068e3dcfb7459244\n978f480a6e3cced51e297fbff9a506b7\n",
  1321  		y:      "Xd5c14bdf6bac81c27afc5429500ed750\nX25483503b557c606dad4f144d27ae10b\nX90bdbcdbb6ea7156068e3dcfb7459244\nX978f480a6e3cced51e297fbff9a506b7\n",
  1322  		reason: "all lines are different, so diffing based on lines is pointless",
  1323  	}, {
  1324  		label:  label + "/StringifiedBytes",
  1325  		x:      struct{ X []byte }{[]byte("hello, world!")},
  1326  		y:      struct{ X []byte }{},
  1327  		reason: "[]byte should be printed as text since it is printable text",
  1328  	}, {
  1329  		label:  label + "/NonStringifiedBytes",
  1330  		x:      struct{ X []byte }{[]byte("\xde\xad\xbe\xef")},
  1331  		y:      struct{ X []byte }{},
  1332  		reason: "[]byte should not be printed as text since it is binary data",
  1333  	}, {
  1334  		label:  label + "/StringifiedNamedBytes",
  1335  		x:      struct{ X MyBytes }{MyBytes("hello, world!")},
  1336  		y:      struct{ X MyBytes }{},
  1337  		reason: "MyBytes should be printed as text since it is printable text",
  1338  	}, {
  1339  		label:  label + "/NonStringifiedNamedBytes",
  1340  		x:      struct{ X MyBytes }{MyBytes("\xde\xad\xbe\xef")},
  1341  		y:      struct{ X MyBytes }{},
  1342  		reason: "MyBytes should not be printed as text since it is binary data",
  1343  	}, {
  1344  		label: label + "/ShortJSON",
  1345  		x: `{
  1346  	"id": 1,
  1347  	"foo": true,
  1348  	"bar": true,
  1349  }`,
  1350  		y: `{
  1351  	"id": 1434180,
  1352  	"foo": true,
  1353  	"bar": true,
  1354  }`,
  1355  		reason: "short multiline JSON should prefer triple-quoted string diff as it is more readable",
  1356  	}, {
  1357  		label: label + "/PointerToStringOrAny",
  1358  		x: func() *string {
  1359  			var v string = "hello"
  1360  			return &v
  1361  		}(),
  1362  		y: func() *interface{} {
  1363  			var v interface{} = "hello"
  1364  			return &v
  1365  		}(),
  1366  		reason: "mismatched types between any and *any should print differently",
  1367  	}, {
  1368  		label: label + "/NamedPointer",
  1369  		x: func() *string {
  1370  			v := "hello"
  1371  			return &v
  1372  		}(),
  1373  		y: func() PointerString {
  1374  			v := "hello"
  1375  			return &v
  1376  		}(),
  1377  		reason: "mismatched pointer types should print differently",
  1378  	}, {
  1379  		label:  label + "/MapStringAny",
  1380  		x:      map[string]interface{}{"key": int(0)},
  1381  		y:      map[string]interface{}{"key": uint(0)},
  1382  		reason: "mismatched underlying value within interface",
  1383  	}, {
  1384  		label:  label + "/StructFieldAny",
  1385  		x:      struct{ X interface{} }{int(0)},
  1386  		y:      struct{ X interface{} }{uint(0)},
  1387  		reason: "mismatched underlying value within interface",
  1388  	}, {
  1389  		label: label + "/SliceOfBytesText",
  1390  		x: [][]byte{
  1391  			[]byte("hello"), []byte("foo"), []byte("barbaz"), []byte("blahdieblah"),
  1392  		},
  1393  		y: [][]byte{
  1394  			[]byte("foo"), []byte("foo"), []byte("barbaz"), []byte("added"), []byte("here"), []byte("hrmph"),
  1395  		},
  1396  		reason: "should print text byte slices as strings",
  1397  	}, {
  1398  		label: label + "/SliceOfBytesBinary",
  1399  		x: [][]byte{
  1400  			[]byte("\xde\xad\xbe\xef"), []byte("\xffoo"), []byte("barbaz"), []byte("blahdieblah"),
  1401  		},
  1402  		y: [][]byte{
  1403  			[]byte("\xffoo"), []byte("foo"), []byte("barbaz"), []byte("added"), []byte("here"), []byte("hrmph\xff"),
  1404  		},
  1405  		reason: "should print text byte slices as strings except those with binary",
  1406  	}, {
  1407  		label: label + "/ManyEscapeCharacters",
  1408  		x: `[
  1409  	{"Base32": "NA======"},
  1410  	{"Base32": "NBSQ===="},
  1411  	{"Base32": "NBSWY==="},
  1412  	{"Base32": "NBSWY3A="},
  1413  	{"Base32": "NBSWY3DP"}
  1414  ]`,
  1415  		y: `[
  1416  	{"Base32": "NB======"},
  1417  	{"Base32": "NBSQ===="},
  1418  	{"Base32": "NBSWY==="},
  1419  	{"Base32": "NBSWY3A="},
  1420  	{"Base32": "NBSWY3DP"}
  1421  ]`,
  1422  		reason: "should use line-based diffing since byte-based diffing is unreadable due to heavy amounts of escaping",
  1423  	}}
  1424  }
  1425  
  1426  func embeddedTests() []test {
  1427  	const label = "EmbeddedStruct"
  1428  
  1429  	privateStruct := *new(ts.ParentStructA).PrivateStruct()
  1430  
  1431  	createStructA := func(i int) ts.ParentStructA {
  1432  		s := ts.ParentStructA{}
  1433  		s.PrivateStruct().Public = 1 + i
  1434  		s.PrivateStruct().SetPrivate(2 + i)
  1435  		return s
  1436  	}
  1437  
  1438  	createStructB := func(i int) ts.ParentStructB {
  1439  		s := ts.ParentStructB{}
  1440  		s.PublicStruct.Public = 1 + i
  1441  		s.PublicStruct.SetPrivate(2 + i)
  1442  		return s
  1443  	}
  1444  
  1445  	createStructC := func(i int) ts.ParentStructC {
  1446  		s := ts.ParentStructC{}
  1447  		s.PrivateStruct().Public = 1 + i
  1448  		s.PrivateStruct().SetPrivate(2 + i)
  1449  		s.Public = 3 + i
  1450  		s.SetPrivate(4 + i)
  1451  		return s
  1452  	}
  1453  
  1454  	createStructD := func(i int) ts.ParentStructD {
  1455  		s := ts.ParentStructD{}
  1456  		s.PublicStruct.Public = 1 + i
  1457  		s.PublicStruct.SetPrivate(2 + i)
  1458  		s.Public = 3 + i
  1459  		s.SetPrivate(4 + i)
  1460  		return s
  1461  	}
  1462  
  1463  	createStructE := func(i int) ts.ParentStructE {
  1464  		s := ts.ParentStructE{}
  1465  		s.PrivateStruct().Public = 1 + i
  1466  		s.PrivateStruct().SetPrivate(2 + i)
  1467  		s.PublicStruct.Public = 3 + i
  1468  		s.PublicStruct.SetPrivate(4 + i)
  1469  		return s
  1470  	}
  1471  
  1472  	createStructF := func(i int) ts.ParentStructF {
  1473  		s := ts.ParentStructF{}
  1474  		s.PrivateStruct().Public = 1 + i
  1475  		s.PrivateStruct().SetPrivate(2 + i)
  1476  		s.PublicStruct.Public = 3 + i
  1477  		s.PublicStruct.SetPrivate(4 + i)
  1478  		s.Public = 5 + i
  1479  		s.SetPrivate(6 + i)
  1480  		return s
  1481  	}
  1482  
  1483  	createStructG := func(i int) *ts.ParentStructG {
  1484  		s := ts.NewParentStructG()
  1485  		s.PrivateStruct().Public = 1 + i
  1486  		s.PrivateStruct().SetPrivate(2 + i)
  1487  		return s
  1488  	}
  1489  
  1490  	createStructH := func(i int) *ts.ParentStructH {
  1491  		s := ts.NewParentStructH()
  1492  		s.PublicStruct.Public = 1 + i
  1493  		s.PublicStruct.SetPrivate(2 + i)
  1494  		return s
  1495  	}
  1496  
  1497  	createStructI := func(i int) *ts.ParentStructI {
  1498  		s := ts.NewParentStructI()
  1499  		s.PrivateStruct().Public = 1 + i
  1500  		s.PrivateStruct().SetPrivate(2 + i)
  1501  		s.PublicStruct.Public = 3 + i
  1502  		s.PublicStruct.SetPrivate(4 + i)
  1503  		return s
  1504  	}
  1505  
  1506  	createStructJ := func(i int) *ts.ParentStructJ {
  1507  		s := ts.NewParentStructJ()
  1508  		s.PrivateStruct().Public = 1 + i
  1509  		s.PrivateStruct().SetPrivate(2 + i)
  1510  		s.PublicStruct.Public = 3 + i
  1511  		s.PublicStruct.SetPrivate(4 + i)
  1512  		s.Private().Public = 5 + i
  1513  		s.Private().SetPrivate(6 + i)
  1514  		s.Public.Public = 7 + i
  1515  		s.Public.SetPrivate(8 + i)
  1516  		return s
  1517  	}
  1518  
  1519  	return []test{{
  1520  		label:     label + "/ParentStructA/PanicUnexported1",
  1521  		x:         ts.ParentStructA{},
  1522  		y:         ts.ParentStructA{},
  1523  		wantPanic: "cannot handle unexported field",
  1524  		reason:    "ParentStructA has an unexported field",
  1525  	}, {
  1526  		label: label + "/ParentStructA/Ignored",
  1527  		x:     ts.ParentStructA{},
  1528  		y:     ts.ParentStructA{},
  1529  		opts: []cmp.Option{
  1530  			cmpopts.IgnoreUnexported(ts.ParentStructA{}),
  1531  		},
  1532  		wantEqual: true,
  1533  		reason:    "the only field (which is unexported) of ParentStructA is ignored",
  1534  	}, {
  1535  		label: label + "/ParentStructA/PanicUnexported2",
  1536  		x:     createStructA(0),
  1537  		y:     createStructA(0),
  1538  		opts: []cmp.Option{
  1539  			cmp.AllowUnexported(ts.ParentStructA{}),
  1540  		},
  1541  		wantPanic: "cannot handle unexported field",
  1542  		reason:    "privateStruct also has unexported fields",
  1543  	}, {
  1544  		label: label + "/ParentStructA/Equal",
  1545  		x:     createStructA(0),
  1546  		y:     createStructA(0),
  1547  		opts: []cmp.Option{
  1548  			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
  1549  		},
  1550  		wantEqual: true,
  1551  		reason:    "unexported fields of both ParentStructA and privateStruct are allowed",
  1552  	}, {
  1553  		label: label + "/ParentStructA/Inequal",
  1554  		x:     createStructA(0),
  1555  		y:     createStructA(1),
  1556  		opts: []cmp.Option{
  1557  			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
  1558  		},
  1559  		wantEqual: false,
  1560  		reason:    "the two values differ on some fields",
  1561  	}, {
  1562  		label: label + "/ParentStructB/PanicUnexported1",
  1563  		x:     ts.ParentStructB{},
  1564  		y:     ts.ParentStructB{},
  1565  		opts: []cmp.Option{
  1566  			cmpopts.IgnoreUnexported(ts.ParentStructB{}),
  1567  		},
  1568  		wantPanic: "cannot handle unexported field",
  1569  		reason:    "PublicStruct has an unexported field",
  1570  	}, {
  1571  		label: label + "/ParentStructB/Ignored",
  1572  		x:     ts.ParentStructB{},
  1573  		y:     ts.ParentStructB{},
  1574  		opts: []cmp.Option{
  1575  			cmpopts.IgnoreUnexported(ts.ParentStructB{}),
  1576  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1577  		},
  1578  		wantEqual: true,
  1579  		reason:    "unexported fields of both ParentStructB and PublicStruct are ignored",
  1580  	}, {
  1581  		label: label + "/ParentStructB/PanicUnexported2",
  1582  		x:     createStructB(0),
  1583  		y:     createStructB(0),
  1584  		opts: []cmp.Option{
  1585  			cmp.AllowUnexported(ts.ParentStructB{}),
  1586  		},
  1587  		wantPanic: "cannot handle unexported field",
  1588  		reason:    "PublicStruct also has unexported fields",
  1589  	}, {
  1590  		label: label + "/ParentStructB/Equal",
  1591  		x:     createStructB(0),
  1592  		y:     createStructB(0),
  1593  		opts: []cmp.Option{
  1594  			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
  1595  		},
  1596  		wantEqual: true,
  1597  		reason:    "unexported fields of both ParentStructB and PublicStruct are allowed",
  1598  	}, {
  1599  		label: label + "/ParentStructB/Inequal",
  1600  		x:     createStructB(0),
  1601  		y:     createStructB(1),
  1602  		opts: []cmp.Option{
  1603  			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
  1604  		},
  1605  		wantEqual: false,
  1606  		reason:    "the two values differ on some fields",
  1607  	}, {
  1608  		label:     label + "/ParentStructC/PanicUnexported1",
  1609  		x:         ts.ParentStructC{},
  1610  		y:         ts.ParentStructC{},
  1611  		wantPanic: "cannot handle unexported field",
  1612  		reason:    "ParentStructC has unexported fields",
  1613  	}, {
  1614  		label: label + "/ParentStructC/Ignored",
  1615  		x:     ts.ParentStructC{},
  1616  		y:     ts.ParentStructC{},
  1617  		opts: []cmp.Option{
  1618  			cmpopts.IgnoreUnexported(ts.ParentStructC{}),
  1619  		},
  1620  		wantEqual: true,
  1621  		reason:    "unexported fields of ParentStructC are ignored",
  1622  	}, {
  1623  		label: label + "/ParentStructC/PanicUnexported2",
  1624  		x:     createStructC(0),
  1625  		y:     createStructC(0),
  1626  		opts: []cmp.Option{
  1627  			cmp.AllowUnexported(ts.ParentStructC{}),
  1628  		},
  1629  		wantPanic: "cannot handle unexported field",
  1630  		reason:    "privateStruct also has unexported fields",
  1631  	}, {
  1632  		label: label + "/ParentStructC/Equal",
  1633  		x:     createStructC(0),
  1634  		y:     createStructC(0),
  1635  		opts: []cmp.Option{
  1636  			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
  1637  		},
  1638  		wantEqual: true,
  1639  		reason:    "unexported fields of both ParentStructC and privateStruct are allowed",
  1640  	}, {
  1641  		label: label + "/ParentStructC/Inequal",
  1642  		x:     createStructC(0),
  1643  		y:     createStructC(1),
  1644  		opts: []cmp.Option{
  1645  			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
  1646  		},
  1647  		wantEqual: false,
  1648  		reason:    "the two values differ on some fields",
  1649  	}, {
  1650  		label: label + "/ParentStructD/PanicUnexported1",
  1651  		x:     ts.ParentStructD{},
  1652  		y:     ts.ParentStructD{},
  1653  		opts: []cmp.Option{
  1654  			cmpopts.IgnoreUnexported(ts.ParentStructD{}),
  1655  		},
  1656  		wantPanic: "cannot handle unexported field",
  1657  		reason:    "ParentStructD has unexported fields",
  1658  	}, {
  1659  		label: label + "/ParentStructD/Ignored",
  1660  		x:     ts.ParentStructD{},
  1661  		y:     ts.ParentStructD{},
  1662  		opts: []cmp.Option{
  1663  			cmpopts.IgnoreUnexported(ts.ParentStructD{}),
  1664  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1665  		},
  1666  		wantEqual: true,
  1667  		reason:    "unexported fields of ParentStructD and PublicStruct are ignored",
  1668  	}, {
  1669  		label: label + "/ParentStructD/PanicUnexported2",
  1670  		x:     createStructD(0),
  1671  		y:     createStructD(0),
  1672  		opts: []cmp.Option{
  1673  			cmp.AllowUnexported(ts.ParentStructD{}),
  1674  		},
  1675  		wantPanic: "cannot handle unexported field",
  1676  		reason:    "PublicStruct also has unexported fields",
  1677  	}, {
  1678  		label: label + "/ParentStructD/Equal",
  1679  		x:     createStructD(0),
  1680  		y:     createStructD(0),
  1681  		opts: []cmp.Option{
  1682  			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
  1683  		},
  1684  		wantEqual: true,
  1685  		reason:    "unexported fields of both ParentStructD and PublicStruct are allowed",
  1686  	}, {
  1687  		label: label + "/ParentStructD/Inequal",
  1688  		x:     createStructD(0),
  1689  		y:     createStructD(1),
  1690  		opts: []cmp.Option{
  1691  			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
  1692  		},
  1693  		wantEqual: false,
  1694  		reason:    "the two values differ on some fields",
  1695  	}, {
  1696  		label: label + "/ParentStructE/PanicUnexported1",
  1697  		x:     ts.ParentStructE{},
  1698  		y:     ts.ParentStructE{},
  1699  		opts: []cmp.Option{
  1700  			cmpopts.IgnoreUnexported(ts.ParentStructE{}),
  1701  		},
  1702  		wantPanic: "cannot handle unexported field",
  1703  		reason:    "ParentStructE has unexported fields",
  1704  	}, {
  1705  		label: label + "/ParentStructE/Ignored",
  1706  		x:     ts.ParentStructE{},
  1707  		y:     ts.ParentStructE{},
  1708  		opts: []cmp.Option{
  1709  			cmpopts.IgnoreUnexported(ts.ParentStructE{}),
  1710  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1711  		},
  1712  		wantEqual: true,
  1713  		reason:    "unexported fields of ParentStructE and PublicStruct are ignored",
  1714  	}, {
  1715  		label: label + "/ParentStructE/PanicUnexported2",
  1716  		x:     createStructE(0),
  1717  		y:     createStructE(0),
  1718  		opts: []cmp.Option{
  1719  			cmp.AllowUnexported(ts.ParentStructE{}),
  1720  		},
  1721  		wantPanic: "cannot handle unexported field",
  1722  		reason:    "PublicStruct and privateStruct also has unexported fields",
  1723  	}, {
  1724  		label: label + "/ParentStructE/PanicUnexported3",
  1725  		x:     createStructE(0),
  1726  		y:     createStructE(0),
  1727  		opts: []cmp.Option{
  1728  			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
  1729  		},
  1730  		wantPanic: "cannot handle unexported field",
  1731  		reason:    "privateStruct also has unexported fields",
  1732  	}, {
  1733  		label: label + "/ParentStructE/Equal",
  1734  		x:     createStructE(0),
  1735  		y:     createStructE(0),
  1736  		opts: []cmp.Option{
  1737  			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
  1738  		},
  1739  		wantEqual: true,
  1740  		reason:    "unexported fields of both ParentStructE, PublicStruct, and privateStruct are allowed",
  1741  	}, {
  1742  		label: label + "/ParentStructE/Inequal",
  1743  		x:     createStructE(0),
  1744  		y:     createStructE(1),
  1745  		opts: []cmp.Option{
  1746  			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
  1747  		},
  1748  		wantEqual: false,
  1749  		reason:    "the two values differ on some fields",
  1750  	}, {
  1751  		label: label + "/ParentStructF/PanicUnexported1",
  1752  		x:     ts.ParentStructF{},
  1753  		y:     ts.ParentStructF{},
  1754  		opts: []cmp.Option{
  1755  			cmpopts.IgnoreUnexported(ts.ParentStructF{}),
  1756  		},
  1757  		wantPanic: "cannot handle unexported field",
  1758  		reason:    "ParentStructF has unexported fields",
  1759  	}, {
  1760  		label: label + "/ParentStructF/Ignored",
  1761  		x:     ts.ParentStructF{},
  1762  		y:     ts.ParentStructF{},
  1763  		opts: []cmp.Option{
  1764  			cmpopts.IgnoreUnexported(ts.ParentStructF{}),
  1765  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
  1766  		},
  1767  		wantEqual: true,
  1768  		reason:    "unexported fields of ParentStructF and PublicStruct are ignored",
  1769  	}, {
  1770  		label: label + "/ParentStructF/PanicUnexported2",
  1771  		x:     createStructF(0),
  1772  		y:     createStructF(0),
  1773  		opts: []cmp.Option{
  1774  			cmp.AllowUnexported(ts.ParentStructF{}),
  1775  		},
  1776  		wantPanic: "cannot handle unexported field",
  1777  		reason:    "PublicStruct and privateStruct also has unexported fields",
  1778  	}, {
  1779  		label: label + "/ParentStructF/PanicUnexported3",
  1780  		x:     createStructF(0),
  1781  		y:     createStructF(0),
  1782  		opts: []cmp.Option{
  1783  			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
  1784  		},
  1785  		wantPanic: "cannot handle unexported field",
  1786  		reason:    "privateStruct also has unexported fields",
  1787  	}, {
  1788  		label: label + "/ParentStructF/Equal",
  1789  		x:     createStructF(0),
  1790  		y:     createStructF(0),
  1791  		opts: []cmp.Option{
  1792  			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
  1793  		},
  1794  		wantEqual: true,
  1795  		reason:    "unexported fields of both ParentStructF, PublicStruct, and privateStruct are allowed",
  1796  	}, {
  1797  		label: label + "/ParentStructF/Inequal",
  1798  		x:     createStructF(0),
  1799  		y:     createStructF(1),
  1800  		opts: []cmp.Option{
  1801  			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
  1802  		},
  1803  		wantEqual: false,
  1804  		reason:    "the two values differ on some fields",
  1805  	}, {
  1806  		label:     label + "/ParentStructG/PanicUnexported1",
  1807  		x:         ts.ParentStructG{},
  1808  		y:         ts.ParentStructG{},
  1809  		wantPanic: "cannot handle unexported field",
  1810  		reason:    "ParentStructG has unexported fields",
  1811  	}, {
  1812  		label: label + "/ParentStructG/Ignored",
  1813  		x:     ts.ParentStructG{},
  1814  		y:     ts.ParentStructG{},
  1815  		opts: []cmp.Option{
  1816  			cmpopts.IgnoreUnexported(ts.ParentStructG{}),
  1817  		},
  1818  		wantEqual: true,
  1819  		reason:    "unexported fields of ParentStructG are ignored",
  1820  	}, {
  1821  		label: label + "/ParentStructG/PanicUnexported2",
  1822  		x:     createStructG(0),
  1823  		y:     createStructG(0),
  1824  		opts: []cmp.Option{
  1825  			cmp.AllowUnexported(ts.ParentStructG{}),
  1826  		},
  1827  		wantPanic: "cannot handle unexported field",
  1828  		reason:    "privateStruct also has unexported fields",
  1829  	}, {
  1830  		label: label + "/ParentStructG/Equal",
  1831  		x:     createStructG(0),
  1832  		y:     createStructG(0),
  1833  		opts: []cmp.Option{
  1834  			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
  1835  		},
  1836  		wantEqual: true,
  1837  		reason:    "unexported fields of both ParentStructG and privateStruct are allowed",
  1838  	}, {
  1839  		label: label + "/ParentStructG/Inequal",
  1840  		x:     createStructG(0),
  1841  		y:     createStructG(1),
  1842  		opts: []cmp.Option{
  1843  			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
  1844  		},
  1845  		wantEqual: false,
  1846  		reason:    "the two values differ on some fields",
  1847  	}, {
  1848  		label:     label + "/ParentStructH/EqualNil",
  1849  		x:         ts.ParentStructH{},
  1850  		y:         ts.ParentStructH{},
  1851  		wantEqual: true,
  1852  		reason:    "PublicStruct is not compared because the pointer is nil",
  1853  	}, {
  1854  		label:     label + "/ParentStructH/PanicUnexported1",
  1855  		x:         createStructH(0),
  1856  		y:         createStructH(0),
  1857  		wantPanic: "cannot handle unexported field",
  1858  		reason:    "PublicStruct has unexported fields",
  1859  	}, {
  1860  		label: label + "/ParentStructH/Ignored",
  1861  		x:     ts.ParentStructH{},
  1862  		y:     ts.ParentStructH{},
  1863  		opts: []cmp.Option{
  1864  			cmpopts.IgnoreUnexported(ts.ParentStructH{}),
  1865  		},
  1866  		wantEqual: true,
  1867  		reason:    "unexported fields of ParentStructH are ignored (it has none)",
  1868  	}, {
  1869  		label: label + "/ParentStructH/PanicUnexported2",
  1870  		x:     createStructH(0),
  1871  		y:     createStructH(0),
  1872  		opts: []cmp.Option{
  1873  			cmp.AllowUnexported(ts.ParentStructH{}),
  1874  		},
  1875  		wantPanic: "cannot handle unexported field",
  1876  		reason:    "PublicStruct also has unexported fields",
  1877  	}, {
  1878  		label: label + "/ParentStructH/Equal",
  1879  		x:     createStructH(0),
  1880  		y:     createStructH(0),
  1881  		opts: []cmp.Option{
  1882  			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
  1883  		},
  1884  		wantEqual: true,
  1885  		reason:    "unexported fields of both ParentStructH and PublicStruct are allowed",
  1886  	}, {
  1887  		label: label + "/ParentStructH/Inequal",
  1888  		x:     createStructH(0),
  1889  		y:     createStructH(1),
  1890  		opts: []cmp.Option{
  1891  			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
  1892  		},
  1893  		wantEqual: false,
  1894  		reason:    "the two values differ on some fields",
  1895  	}, {
  1896  		label:     label + "/ParentStructI/PanicUnexported1",
  1897  		x:         ts.ParentStructI{},
  1898  		y:         ts.ParentStructI{},
  1899  		wantPanic: "cannot handle unexported field",
  1900  		reason:    "ParentStructI has unexported fields",
  1901  	}, {
  1902  		label: label + "/ParentStructI/Ignored1",
  1903  		x:     ts.ParentStructI{},
  1904  		y:     ts.ParentStructI{},
  1905  		opts: []cmp.Option{
  1906  			cmpopts.IgnoreUnexported(ts.ParentStructI{}),
  1907  		},
  1908  		wantEqual: true,
  1909  		reason:    "unexported fields of ParentStructI are ignored",
  1910  	}, {
  1911  		label: label + "/ParentStructI/PanicUnexported2",
  1912  		x:     createStructI(0),
  1913  		y:     createStructI(0),
  1914  		opts: []cmp.Option{
  1915  			cmpopts.IgnoreUnexported(ts.ParentStructI{}),
  1916  		},
  1917  		wantPanic: "cannot handle unexported field",
  1918  		reason:    "PublicStruct and privateStruct also has unexported fields",
  1919  	}, {
  1920  		label: label + "/ParentStructI/Ignored2",
  1921  		x:     createStructI(0),
  1922  		y:     createStructI(0),
  1923  		opts: []cmp.Option{
  1924  			cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
  1925  		},
  1926  		wantEqual: true,
  1927  		reason:    "unexported fields of ParentStructI and PublicStruct are ignored",
  1928  	}, {
  1929  		label: label + "/ParentStructI/PanicUnexported3",
  1930  		x:     createStructI(0),
  1931  		y:     createStructI(0),
  1932  		opts: []cmp.Option{
  1933  			cmp.AllowUnexported(ts.ParentStructI{}),
  1934  		},
  1935  		wantPanic: "cannot handle unexported field",
  1936  		reason:    "PublicStruct and privateStruct also has unexported fields",
  1937  	}, {
  1938  		label: label + "/ParentStructI/Equal",
  1939  		x:     createStructI(0),
  1940  		y:     createStructI(0),
  1941  		opts: []cmp.Option{
  1942  			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
  1943  		},
  1944  		wantEqual: true,
  1945  		reason:    "unexported fields of both ParentStructI, PublicStruct, and privateStruct are allowed",
  1946  	}, {
  1947  		label: label + "/ParentStructI/Inequal",
  1948  		x:     createStructI(0),
  1949  		y:     createStructI(1),
  1950  		opts: []cmp.Option{
  1951  			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
  1952  		},
  1953  		wantEqual: false,
  1954  		reason:    "the two values differ on some fields",
  1955  	}, {
  1956  		label:     label + "/ParentStructJ/PanicUnexported1",
  1957  		x:         ts.ParentStructJ{},
  1958  		y:         ts.ParentStructJ{},
  1959  		wantPanic: "cannot handle unexported field",
  1960  		reason:    "ParentStructJ has unexported fields",
  1961  	}, {
  1962  		label: label + "/ParentStructJ/PanicUnexported2",
  1963  		x:     ts.ParentStructJ{},
  1964  		y:     ts.ParentStructJ{},
  1965  		opts: []cmp.Option{
  1966  			cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
  1967  		},
  1968  		wantPanic: "cannot handle unexported field",
  1969  		reason:    "PublicStruct and privateStruct also has unexported fields",
  1970  	}, {
  1971  		label: label + "/ParentStructJ/Ignored",
  1972  		x:     ts.ParentStructJ{},
  1973  		y:     ts.ParentStructJ{},
  1974  		opts: []cmp.Option{
  1975  			cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
  1976  		},
  1977  		wantEqual: true,
  1978  		reason:    "unexported fields of ParentStructJ and PublicStruct are ignored",
  1979  	}, {
  1980  		label: label + "/ParentStructJ/PanicUnexported3",
  1981  		x:     createStructJ(0),
  1982  		y:     createStructJ(0),
  1983  		opts: []cmp.Option{
  1984  			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
  1985  		},
  1986  		wantPanic: "cannot handle unexported field",
  1987  		reason:    "privateStruct also has unexported fields",
  1988  	}, {
  1989  		label: label + "/ParentStructJ/Equal",
  1990  		x:     createStructJ(0),
  1991  		y:     createStructJ(0),
  1992  		opts: []cmp.Option{
  1993  			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
  1994  		},
  1995  		wantEqual: true,
  1996  		reason:    "unexported fields of both ParentStructJ, PublicStruct, and privateStruct are allowed",
  1997  	}, {
  1998  		label: label + "/ParentStructJ/Inequal",
  1999  		x:     createStructJ(0),
  2000  		y:     createStructJ(1),
  2001  		opts: []cmp.Option{
  2002  			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
  2003  		},
  2004  		wantEqual: false,
  2005  		reason:    "the two values differ on some fields",
  2006  	}}
  2007  }
  2008  
  2009  func methodTests() []test {
  2010  	const label = "EqualMethod"
  2011  
  2012  	// A common mistake that the Equal method is on a pointer receiver,
  2013  	// but only a non-pointer value is present in the struct.
  2014  	// A transform can be used to forcibly reference the value.
  2015  	addrTransform := cmp.FilterPath(func(p cmp.Path) bool {
  2016  		if len(p) == 0 {
  2017  			return false
  2018  		}
  2019  		t := p[len(p)-1].Type()
  2020  		if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
  2021  			return false
  2022  		}
  2023  		if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
  2024  			tf := m.Func.Type()
  2025  			return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
  2026  				tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
  2027  		}
  2028  		return false
  2029  	}, cmp.Transformer("Addr", func(x interface{}) interface{} {
  2030  		v := reflect.ValueOf(x)
  2031  		vp := reflect.New(v.Type())
  2032  		vp.Elem().Set(v)
  2033  		return vp.Interface()
  2034  	}))
  2035  
  2036  	// For each of these types, there is an Equal method defined, which always
  2037  	// returns true, while the underlying data are fundamentally different.
  2038  	// Since the method should be called, these are expected to be equal.
  2039  	return []test{{
  2040  		label:     label + "/StructA/ValueEqual",
  2041  		x:         ts.StructA{X: "NotEqual"},
  2042  		y:         ts.StructA{X: "not_equal"},
  2043  		wantEqual: true,
  2044  		reason:    "Equal method on StructA value called",
  2045  	}, {
  2046  		label:     label + "/StructA/PointerEqual",
  2047  		x:         &ts.StructA{X: "NotEqual"},
  2048  		y:         &ts.StructA{X: "not_equal"},
  2049  		wantEqual: true,
  2050  		reason:    "Equal method on StructA pointer called",
  2051  	}, {
  2052  		label:     label + "/StructB/ValueInequal",
  2053  		x:         ts.StructB{X: "NotEqual"},
  2054  		y:         ts.StructB{X: "not_equal"},
  2055  		wantEqual: false,
  2056  		reason:    "Equal method on StructB value not called",
  2057  	}, {
  2058  		label:     label + "/StructB/ValueAddrEqual",
  2059  		x:         ts.StructB{X: "NotEqual"},
  2060  		y:         ts.StructB{X: "not_equal"},
  2061  		opts:      []cmp.Option{addrTransform},
  2062  		wantEqual: true,
  2063  		reason:    "Equal method on StructB pointer called due to shallow copy transform",
  2064  	}, {
  2065  		label:     label + "/StructB/PointerEqual",
  2066  		x:         &ts.StructB{X: "NotEqual"},
  2067  		y:         &ts.StructB{X: "not_equal"},
  2068  		wantEqual: true,
  2069  		reason:    "Equal method on StructB pointer called",
  2070  	}, {
  2071  		label:     label + "/StructC/ValueEqual",
  2072  		x:         ts.StructC{X: "NotEqual"},
  2073  		y:         ts.StructC{X: "not_equal"},
  2074  		wantEqual: true,
  2075  		reason:    "Equal method on StructC value called",
  2076  	}, {
  2077  		label:     label + "/StructC/PointerEqual",
  2078  		x:         &ts.StructC{X: "NotEqual"},
  2079  		y:         &ts.StructC{X: "not_equal"},
  2080  		wantEqual: true,
  2081  		reason:    "Equal method on StructC pointer called",
  2082  	}, {
  2083  		label:     label + "/StructD/ValueInequal",
  2084  		x:         ts.StructD{X: "NotEqual"},
  2085  		y:         ts.StructD{X: "not_equal"},
  2086  		wantEqual: false,
  2087  		reason:    "Equal method on StructD value not called",
  2088  	}, {
  2089  		label:     label + "/StructD/ValueAddrEqual",
  2090  		x:         ts.StructD{X: "NotEqual"},
  2091  		y:         ts.StructD{X: "not_equal"},
  2092  		opts:      []cmp.Option{addrTransform},
  2093  		wantEqual: true,
  2094  		reason:    "Equal method on StructD pointer called due to shallow copy transform",
  2095  	}, {
  2096  		label:     label + "/StructD/PointerEqual",
  2097  		x:         &ts.StructD{X: "NotEqual"},
  2098  		y:         &ts.StructD{X: "not_equal"},
  2099  		wantEqual: true,
  2100  		reason:    "Equal method on StructD pointer called",
  2101  	}, {
  2102  		label:     label + "/StructE/ValueInequal",
  2103  		x:         ts.StructE{X: "NotEqual"},
  2104  		y:         ts.StructE{X: "not_equal"},
  2105  		wantEqual: false,
  2106  		reason:    "Equal method on StructE value not called",
  2107  	}, {
  2108  		label:     label + "/StructE/ValueAddrEqual",
  2109  		x:         ts.StructE{X: "NotEqual"},
  2110  		y:         ts.StructE{X: "not_equal"},
  2111  		opts:      []cmp.Option{addrTransform},
  2112  		wantEqual: true,
  2113  		reason:    "Equal method on StructE pointer called due to shallow copy transform",
  2114  	}, {
  2115  		label:     label + "/StructE/PointerEqual",
  2116  		x:         &ts.StructE{X: "NotEqual"},
  2117  		y:         &ts.StructE{X: "not_equal"},
  2118  		wantEqual: true,
  2119  		reason:    "Equal method on StructE pointer called",
  2120  	}, {
  2121  		label:     label + "/StructF/ValueInequal",
  2122  		x:         ts.StructF{X: "NotEqual"},
  2123  		y:         ts.StructF{X: "not_equal"},
  2124  		wantEqual: false,
  2125  		reason:    "Equal method on StructF value not called",
  2126  	}, {
  2127  		label:     label + "/StructF/PointerEqual",
  2128  		x:         &ts.StructF{X: "NotEqual"},
  2129  		y:         &ts.StructF{X: "not_equal"},
  2130  		wantEqual: true,
  2131  		reason:    "Equal method on StructF pointer called",
  2132  	}, {
  2133  		label:     label + "/StructA1/ValueEqual",
  2134  		x:         ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
  2135  		y:         ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
  2136  		wantEqual: true,
  2137  		reason:    "Equal method on StructA value called with equal X field",
  2138  	}, {
  2139  		label:     label + "/StructA1/ValueInequal",
  2140  		x:         ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  2141  		y:         ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
  2142  		wantEqual: false,
  2143  		reason:    "Equal method on StructA value called, but inequal X field",
  2144  	}, {
  2145  		label:     label + "/StructA1/PointerEqual",
  2146  		x:         &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
  2147  		y:         &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
  2148  		wantEqual: true,
  2149  		reason:    "Equal method on StructA value called with equal X field",
  2150  	}, {
  2151  		label:     label + "/StructA1/PointerInequal",
  2152  		x:         &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  2153  		y:         &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
  2154  		wantEqual: false,
  2155  		reason:    "Equal method on StructA value called, but inequal X field",
  2156  	}, {
  2157  		label:     label + "/StructB1/ValueEqual",
  2158  		x:         ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
  2159  		y:         ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
  2160  		opts:      []cmp.Option{addrTransform},
  2161  		wantEqual: true,
  2162  		reason:    "Equal method on StructB pointer called due to shallow copy transform with equal X field",
  2163  	}, {
  2164  		label:     label + "/StructB1/ValueInequal",
  2165  		x:         ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  2166  		y:         ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
  2167  		opts:      []cmp.Option{addrTransform},
  2168  		wantEqual: false,
  2169  		reason:    "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
  2170  	}, {
  2171  		label:     label + "/StructB1/PointerEqual",
  2172  		x:         &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
  2173  		y:         &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
  2174  		opts:      []cmp.Option{addrTransform},
  2175  		wantEqual: true,
  2176  		reason:    "Equal method on StructB pointer called due to shallow copy transform with equal X field",
  2177  	}, {
  2178  		label:     label + "/StructB1/PointerInequal",
  2179  		x:         &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  2180  		y:         &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
  2181  		opts:      []cmp.Option{addrTransform},
  2182  		wantEqual: false,
  2183  		reason:    "Equal method on StructB pointer called due to shallow copy transform, but inequal X field",
  2184  	}, {
  2185  		label:     label + "/StructC1/ValueEqual",
  2186  		x:         ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  2187  		y:         ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
  2188  		wantEqual: true,
  2189  		reason:    "Equal method on StructC1 value called",
  2190  	}, {
  2191  		label:     label + "/StructC1/PointerEqual",
  2192  		x:         &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  2193  		y:         &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
  2194  		wantEqual: true,
  2195  		reason:    "Equal method on StructC1 pointer called",
  2196  	}, {
  2197  		label:     label + "/StructD1/ValueInequal",
  2198  		x:         ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2199  		y:         ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  2200  		wantEqual: false,
  2201  		reason:    "Equal method on StructD1 value not called",
  2202  	}, {
  2203  		label:     label + "/StructD1/PointerAddrEqual",
  2204  		x:         ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2205  		y:         ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  2206  		opts:      []cmp.Option{addrTransform},
  2207  		wantEqual: true,
  2208  		reason:    "Equal method on StructD1 pointer called due to shallow copy transform",
  2209  	}, {
  2210  		label:     label + "/StructD1/PointerEqual",
  2211  		x:         &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2212  		y:         &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  2213  		wantEqual: true,
  2214  		reason:    "Equal method on StructD1 pointer called",
  2215  	}, {
  2216  		label:     label + "/StructE1/ValueInequal",
  2217  		x:         ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2218  		y:         ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  2219  		wantEqual: false,
  2220  		reason:    "Equal method on StructE1 value not called",
  2221  	}, {
  2222  		label:     label + "/StructE1/ValueAddrEqual",
  2223  		x:         ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2224  		y:         ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  2225  		opts:      []cmp.Option{addrTransform},
  2226  		wantEqual: true,
  2227  		reason:    "Equal method on StructE1 pointer called due to shallow copy transform",
  2228  	}, {
  2229  		label:     label + "/StructE1/PointerEqual",
  2230  		x:         &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2231  		y:         &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  2232  		wantEqual: true,
  2233  		reason:    "Equal method on StructE1 pointer called",
  2234  	}, {
  2235  		label:     label + "/StructF1/ValueInequal",
  2236  		x:         ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2237  		y:         ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
  2238  		wantEqual: false,
  2239  		reason:    "Equal method on StructF1 value not called",
  2240  	}, {
  2241  		label:     label + "/StructF1/PointerEqual",
  2242  		x:         &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2243  		y:         &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
  2244  		wantEqual: true,
  2245  		reason:    "Equal method on StructF1 pointer called",
  2246  	}, {
  2247  		label:     label + "/StructA2/ValueEqual",
  2248  		x:         ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
  2249  		y:         ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
  2250  		wantEqual: true,
  2251  		reason:    "Equal method on StructA pointer called with equal X field",
  2252  	}, {
  2253  		label:     label + "/StructA2/ValueInequal",
  2254  		x:         ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  2255  		y:         ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
  2256  		wantEqual: false,
  2257  		reason:    "Equal method on StructA pointer called, but inequal X field",
  2258  	}, {
  2259  		label:     label + "/StructA2/PointerEqual",
  2260  		x:         &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
  2261  		y:         &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
  2262  		wantEqual: true,
  2263  		reason:    "Equal method on StructA pointer called with equal X field",
  2264  	}, {
  2265  		label:     label + "/StructA2/PointerInequal",
  2266  		x:         &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  2267  		y:         &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
  2268  		wantEqual: false,
  2269  		reason:    "Equal method on StructA pointer called, but inequal X field",
  2270  	}, {
  2271  		label:     label + "/StructB2/ValueEqual",
  2272  		x:         ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
  2273  		y:         ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
  2274  		wantEqual: true,
  2275  		reason:    "Equal method on StructB pointer called with equal X field",
  2276  	}, {
  2277  		label:     label + "/StructB2/ValueInequal",
  2278  		x:         ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  2279  		y:         ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
  2280  		wantEqual: false,
  2281  		reason:    "Equal method on StructB pointer called, but inequal X field",
  2282  	}, {
  2283  		label:     label + "/StructB2/PointerEqual",
  2284  		x:         &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
  2285  		y:         &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
  2286  		wantEqual: true,
  2287  		reason:    "Equal method on StructB pointer called with equal X field",
  2288  	}, {
  2289  		label:     label + "/StructB2/PointerInequal",
  2290  		x:         &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  2291  		y:         &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
  2292  		wantEqual: false,
  2293  		reason:    "Equal method on StructB pointer called, but inequal X field",
  2294  	}, {
  2295  		label:     label + "/StructC2/ValueEqual",
  2296  		x:         ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  2297  		y:         ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
  2298  		wantEqual: true,
  2299  		reason:    "Equal method called on StructC2 value due to forwarded StructC pointer",
  2300  	}, {
  2301  		label:     label + "/StructC2/PointerEqual",
  2302  		x:         &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  2303  		y:         &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
  2304  		wantEqual: true,
  2305  		reason:    "Equal method called on StructC2 pointer due to forwarded StructC pointer",
  2306  	}, {
  2307  		label:     label + "/StructD2/ValueEqual",
  2308  		x:         ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2309  		y:         ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
  2310  		wantEqual: true,
  2311  		reason:    "Equal method called on StructD2 value due to forwarded StructD pointer",
  2312  	}, {
  2313  		label:     label + "/StructD2/PointerEqual",
  2314  		x:         &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  2315  		y:         &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
  2316  		wantEqual: true,
  2317  		reason:    "Equal method called on StructD2 pointer due to forwarded StructD pointer",
  2318  	}, {
  2319  		label:     label + "/StructE2/ValueEqual",
  2320  		x:         ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2321  		y:         ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
  2322  		wantEqual: true,
  2323  		reason:    "Equal method called on StructE2 value due to forwarded StructE pointer",
  2324  	}, {
  2325  		label:     label + "/StructE2/PointerEqual",
  2326  		x:         &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  2327  		y:         &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
  2328  		wantEqual: true,
  2329  		reason:    "Equal method called on StructE2 pointer due to forwarded StructE pointer",
  2330  	}, {
  2331  		label:     label + "/StructF2/ValueEqual",
  2332  		x:         ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2333  		y:         ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
  2334  		wantEqual: true,
  2335  		reason:    "Equal method called on StructF2 value due to forwarded StructF pointer",
  2336  	}, {
  2337  		label:     label + "/StructF2/PointerEqual",
  2338  		x:         &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  2339  		y:         &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
  2340  		wantEqual: true,
  2341  		reason:    "Equal method called on StructF2 pointer due to forwarded StructF pointer",
  2342  	}, {
  2343  		label:     label + "/StructNo/Inequal",
  2344  		x:         ts.StructNo{X: "NotEqual"},
  2345  		y:         ts.StructNo{X: "not_equal"},
  2346  		wantEqual: false,
  2347  		reason:    "Equal method not called since StructNo is not assignable to InterfaceA",
  2348  	}, {
  2349  		label:     label + "/AssignA/Equal",
  2350  		x:         ts.AssignA(func() int { return 0 }),
  2351  		y:         ts.AssignA(func() int { return 1 }),
  2352  		wantEqual: true,
  2353  		reason:    "Equal method called since named func is assignable to unnamed func",
  2354  	}, {
  2355  		label:     label + "/AssignB/Equal",
  2356  		x:         ts.AssignB(struct{ A int }{0}),
  2357  		y:         ts.AssignB(struct{ A int }{1}),
  2358  		wantEqual: true,
  2359  		reason:    "Equal method called since named struct is assignable to unnamed struct",
  2360  	}, {
  2361  		label:     label + "/AssignC/Equal",
  2362  		x:         ts.AssignC(make(chan bool)),
  2363  		y:         ts.AssignC(make(chan bool)),
  2364  		wantEqual: true,
  2365  		reason:    "Equal method called since named channel is assignable to unnamed channel",
  2366  	}, {
  2367  		label:     label + "/AssignD/Equal",
  2368  		x:         ts.AssignD(make(chan bool)),
  2369  		y:         ts.AssignD(make(chan bool)),
  2370  		wantEqual: true,
  2371  		reason:    "Equal method called since named channel is assignable to unnamed channel",
  2372  	}}
  2373  }
  2374  
  2375  type (
  2376  	CycleAlpha struct {
  2377  		Name   string
  2378  		Bravos map[string]*CycleBravo
  2379  	}
  2380  	CycleBravo struct {
  2381  		ID     int
  2382  		Name   string
  2383  		Mods   int
  2384  		Alphas map[string]*CycleAlpha
  2385  	}
  2386  )
  2387  
  2388  func cycleTests() []test {
  2389  	const label = "Cycle"
  2390  
  2391  	type (
  2392  		P *P
  2393  		S []S
  2394  		M map[int]M
  2395  	)
  2396  
  2397  	makeGraph := func() map[string]*CycleAlpha {
  2398  		v := map[string]*CycleAlpha{
  2399  			"Foo": &CycleAlpha{
  2400  				Name: "Foo",
  2401  				Bravos: map[string]*CycleBravo{
  2402  					"FooBravo": &CycleBravo{
  2403  						Name: "FooBravo",
  2404  						ID:   101,
  2405  						Mods: 100,
  2406  						Alphas: map[string]*CycleAlpha{
  2407  							"Foo": nil, // cyclic reference
  2408  						},
  2409  					},
  2410  				},
  2411  			},
  2412  			"Bar": &CycleAlpha{
  2413  				Name: "Bar",
  2414  				Bravos: map[string]*CycleBravo{
  2415  					"BarBuzzBravo": &CycleBravo{
  2416  						Name: "BarBuzzBravo",
  2417  						ID:   102,
  2418  						Mods: 2,
  2419  						Alphas: map[string]*CycleAlpha{
  2420  							"Bar":  nil, // cyclic reference
  2421  							"Buzz": nil, // cyclic reference
  2422  						},
  2423  					},
  2424  					"BuzzBarBravo": &CycleBravo{
  2425  						Name: "BuzzBarBravo",
  2426  						ID:   103,
  2427  						Mods: 0,
  2428  						Alphas: map[string]*CycleAlpha{
  2429  							"Bar":  nil, // cyclic reference
  2430  							"Buzz": nil, // cyclic reference
  2431  						},
  2432  					},
  2433  				},
  2434  			},
  2435  			"Buzz": &CycleAlpha{
  2436  				Name: "Buzz",
  2437  				Bravos: map[string]*CycleBravo{
  2438  					"BarBuzzBravo": nil, // cyclic reference
  2439  					"BuzzBarBravo": nil, // cyclic reference
  2440  				},
  2441  			},
  2442  		}
  2443  		v["Foo"].Bravos["FooBravo"].Alphas["Foo"] = v["Foo"]
  2444  		v["Bar"].Bravos["BarBuzzBravo"].Alphas["Bar"] = v["Bar"]
  2445  		v["Bar"].Bravos["BarBuzzBravo"].Alphas["Buzz"] = v["Buzz"]
  2446  		v["Bar"].Bravos["BuzzBarBravo"].Alphas["Bar"] = v["Bar"]
  2447  		v["Bar"].Bravos["BuzzBarBravo"].Alphas["Buzz"] = v["Buzz"]
  2448  		v["Buzz"].Bravos["BarBuzzBravo"] = v["Bar"].Bravos["BarBuzzBravo"]
  2449  		v["Buzz"].Bravos["BuzzBarBravo"] = v["Bar"].Bravos["BuzzBarBravo"]
  2450  		return v
  2451  	}
  2452  
  2453  	var tests []test
  2454  	type XY struct{ x, y interface{} }
  2455  	for _, tt := range []struct {
  2456  		label     string
  2457  		in        XY
  2458  		wantEqual bool
  2459  		reason    string
  2460  	}{{
  2461  		label: "PointersEqual",
  2462  		in: func() XY {
  2463  			x := new(P)
  2464  			*x = x
  2465  			y := new(P)
  2466  			*y = y
  2467  			return XY{x, y}
  2468  		}(),
  2469  		wantEqual: true,
  2470  		reason:    "equal pair of single-node pointers",
  2471  	}, {
  2472  		label: "PointersInequal",
  2473  		in: func() XY {
  2474  			x := new(P)
  2475  			*x = x
  2476  			y1, y2 := new(P), new(P)
  2477  			*y1 = y2
  2478  			*y2 = y1
  2479  			return XY{x, y1}
  2480  		}(),
  2481  		wantEqual: false,
  2482  		reason:    "inequal pair of single-node and double-node pointers",
  2483  	}, {
  2484  		label: "SlicesEqual",
  2485  		in: func() XY {
  2486  			x := S{nil}
  2487  			x[0] = x
  2488  			y := S{nil}
  2489  			y[0] = y
  2490  			return XY{x, y}
  2491  		}(),
  2492  		wantEqual: true,
  2493  		reason:    "equal pair of single-node slices",
  2494  	}, {
  2495  		label: "SlicesInequal",
  2496  		in: func() XY {
  2497  			x := S{nil}
  2498  			x[0] = x
  2499  			y1, y2 := S{nil}, S{nil}
  2500  			y1[0] = y2
  2501  			y2[0] = y1
  2502  			return XY{x, y1}
  2503  		}(),
  2504  		wantEqual: false,
  2505  		reason:    "inequal pair of single-node and double node slices",
  2506  	}, {
  2507  		label: "MapsEqual",
  2508  		in: func() XY {
  2509  			x := M{0: nil}
  2510  			x[0] = x
  2511  			y := M{0: nil}
  2512  			y[0] = y
  2513  			return XY{x, y}
  2514  		}(),
  2515  		wantEqual: true,
  2516  		reason:    "equal pair of single-node maps",
  2517  	}, {
  2518  		label: "MapsInequal",
  2519  		in: func() XY {
  2520  			x := M{0: nil}
  2521  			x[0] = x
  2522  			y1, y2 := M{0: nil}, M{0: nil}
  2523  			y1[0] = y2
  2524  			y2[0] = y1
  2525  			return XY{x, y1}
  2526  		}(),
  2527  		wantEqual: false,
  2528  		reason:    "inequal pair of single-node and double-node maps",
  2529  	}, {
  2530  		label:     "GraphEqual",
  2531  		in:        XY{makeGraph(), makeGraph()},
  2532  		wantEqual: true,
  2533  		reason:    "graphs are equal since they have identical forms",
  2534  	}, {
  2535  		label: "GraphInequalZeroed",
  2536  		in: func() XY {
  2537  			x := makeGraph()
  2538  			y := makeGraph()
  2539  			y["Foo"].Bravos["FooBravo"].ID = 0
  2540  			y["Bar"].Bravos["BarBuzzBravo"].ID = 0
  2541  			y["Bar"].Bravos["BuzzBarBravo"].ID = 0
  2542  			return XY{x, y}
  2543  		}(),
  2544  		wantEqual: false,
  2545  		reason:    "graphs are inequal because the ID fields are different",
  2546  	}, {
  2547  		label: "GraphInequalStruct",
  2548  		in: func() XY {
  2549  			x := makeGraph()
  2550  			y := makeGraph()
  2551  			x["Buzz"].Bravos["BuzzBarBravo"] = &CycleBravo{
  2552  				Name: "BuzzBarBravo",
  2553  				ID:   103,
  2554  			}
  2555  			return XY{x, y}
  2556  		}(),
  2557  		wantEqual: false,
  2558  		reason:    "graphs are inequal because they differ on a map element",
  2559  	}} {
  2560  		tests = append(tests, test{
  2561  			label:     label + "/" + tt.label,
  2562  			x:         tt.in.x,
  2563  			y:         tt.in.y,
  2564  			wantEqual: tt.wantEqual,
  2565  			reason:    tt.reason,
  2566  		})
  2567  	}
  2568  	return tests
  2569  }
  2570  
  2571  func project1Tests() []test {
  2572  	const label = "Project1"
  2573  
  2574  	ignoreUnexported := cmpopts.IgnoreUnexported(
  2575  		ts.EagleImmutable{},
  2576  		ts.DreamerImmutable{},
  2577  		ts.SlapImmutable{},
  2578  		ts.GoatImmutable{},
  2579  		ts.DonkeyImmutable{},
  2580  		ts.LoveRadius{},
  2581  		ts.SummerLove{},
  2582  		ts.SummerLoveSummary{},
  2583  	)
  2584  
  2585  	createEagle := func() ts.Eagle {
  2586  		return ts.Eagle{
  2587  			Name:   "eagle",
  2588  			Hounds: []string{"buford", "tannen"},
  2589  			Desc:   "some description",
  2590  			Dreamers: []ts.Dreamer{{}, {
  2591  				Name: "dreamer2",
  2592  				Animal: []interface{}{
  2593  					ts.Goat{
  2594  						Target: "corporation",
  2595  						Immutable: &ts.GoatImmutable{
  2596  							ID:      "southbay",
  2597  							State:   (*pb.Goat_States)(newInt(5)),
  2598  							Started: now,
  2599  						},
  2600  					},
  2601  					ts.Donkey{},
  2602  				},
  2603  				Amoeba: 53,
  2604  			}},
  2605  			Slaps: []ts.Slap{{
  2606  				Name: "slapID",
  2607  				Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2608  				Immutable: &ts.SlapImmutable{
  2609  					ID:       "immutableSlap",
  2610  					MildSlap: true,
  2611  					Started:  now,
  2612  					LoveRadius: &ts.LoveRadius{
  2613  						Summer: &ts.SummerLove{
  2614  							Summary: &ts.SummerLoveSummary{
  2615  								Devices:    []string{"foo", "bar", "baz"},
  2616  								ChangeType: []pb.SummerType{1, 2, 3},
  2617  							},
  2618  						},
  2619  					},
  2620  				},
  2621  			}},
  2622  			Immutable: &ts.EagleImmutable{
  2623  				ID:          "eagleID",
  2624  				Birthday:    now,
  2625  				MissingCall: (*pb.Eagle_MissingCalls)(newInt(55)),
  2626  			},
  2627  		}
  2628  	}
  2629  
  2630  	return []test{{
  2631  		label: label + "/PanicUnexported",
  2632  		x: ts.Eagle{Slaps: []ts.Slap{{
  2633  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2634  		}}},
  2635  		y: ts.Eagle{Slaps: []ts.Slap{{
  2636  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2637  		}}},
  2638  		wantPanic: "cannot handle unexported field",
  2639  		reason:    "struct contains unexported fields",
  2640  	}, {
  2641  		label: label + "/ProtoEqual",
  2642  		x: ts.Eagle{Slaps: []ts.Slap{{
  2643  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2644  		}}},
  2645  		y: ts.Eagle{Slaps: []ts.Slap{{
  2646  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2647  		}}},
  2648  		opts:      []cmp.Option{cmp.Comparer(pb.Equal)},
  2649  		wantEqual: true,
  2650  		reason:    "simulated protobuf messages contain the same values",
  2651  	}, {
  2652  		label: label + "/ProtoInequal",
  2653  		x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
  2654  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  2655  		}}},
  2656  		y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
  2657  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
  2658  		}}},
  2659  		opts:      []cmp.Option{cmp.Comparer(pb.Equal)},
  2660  		wantEqual: false,
  2661  		reason:    "simulated protobuf messages contain different values",
  2662  	}, {
  2663  		label:     label + "/Equal",
  2664  		x:         createEagle(),
  2665  		y:         createEagle(),
  2666  		opts:      []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
  2667  		wantEqual: true,
  2668  		reason:    "equal because values are the same",
  2669  	}, {
  2670  		label: label + "/Inequal",
  2671  		x: func() ts.Eagle {
  2672  			eg := createEagle()
  2673  			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
  2674  			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(newInt(6))
  2675  			eg.Slaps[0].Immutable.MildSlap = false
  2676  			return eg
  2677  		}(),
  2678  		y: func() ts.Eagle {
  2679  			eg := createEagle()
  2680  			devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
  2681  			eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
  2682  			return eg
  2683  		}(),
  2684  		opts:      []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
  2685  		wantEqual: false,
  2686  		reason:    "inequal because some values are different",
  2687  	}}
  2688  }
  2689  
  2690  type germSorter []*pb.Germ
  2691  
  2692  func (gs germSorter) Len() int           { return len(gs) }
  2693  func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
  2694  func (gs germSorter) Swap(i, j int)      { gs[i], gs[j] = gs[j], gs[i] }
  2695  
  2696  func project2Tests() []test {
  2697  	const label = "Project2"
  2698  
  2699  	sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
  2700  		out := append([]*pb.Germ(nil), in...) // Make copy
  2701  		sort.Sort(germSorter(out))
  2702  		return out
  2703  	})
  2704  
  2705  	equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
  2706  		if x == nil || y == nil {
  2707  			return x == nil && y == nil
  2708  		}
  2709  		px, err1 := x.Proto()
  2710  		py, err2 := y.Proto()
  2711  		if err1 != nil || err2 != nil {
  2712  			return err1 == err2
  2713  		}
  2714  		return pb.Equal(px, py)
  2715  	})
  2716  
  2717  	createBatch := func() ts.GermBatch {
  2718  		return ts.GermBatch{
  2719  			DirtyGerms: map[int32][]*pb.Germ{
  2720  				17: {
  2721  					{Stringer: pb.Stringer{X: "germ1"}},
  2722  				},
  2723  				18: {
  2724  					{Stringer: pb.Stringer{X: "germ2"}},
  2725  					{Stringer: pb.Stringer{X: "germ3"}},
  2726  					{Stringer: pb.Stringer{X: "germ4"}},
  2727  				},
  2728  			},
  2729  			GermMap: map[int32]*pb.Germ{
  2730  				13: {Stringer: pb.Stringer{X: "germ13"}},
  2731  				21: {Stringer: pb.Stringer{X: "germ21"}},
  2732  			},
  2733  			DishMap: map[int32]*ts.Dish{
  2734  				0: ts.CreateDish(nil, io.EOF),
  2735  				1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
  2736  				2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
  2737  			},
  2738  			HasPreviousResult: true,
  2739  			DirtyID:           10,
  2740  			GermStrain:        421,
  2741  			InfectedAt:        now,
  2742  		}
  2743  	}
  2744  
  2745  	return []test{{
  2746  		label:     label + "/PanicUnexported",
  2747  		x:         createBatch(),
  2748  		y:         createBatch(),
  2749  		wantPanic: "cannot handle unexported field",
  2750  		reason:    "struct contains unexported fields",
  2751  	}, {
  2752  		label:     label + "/Equal",
  2753  		x:         createBatch(),
  2754  		y:         createBatch(),
  2755  		opts:      []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  2756  		wantEqual: true,
  2757  		reason:    "equal because identical values are compared",
  2758  	}, {
  2759  		label: label + "/InequalOrder",
  2760  		x:     createBatch(),
  2761  		y: func() ts.GermBatch {
  2762  			gb := createBatch()
  2763  			s := gb.DirtyGerms[18]
  2764  			s[0], s[1], s[2] = s[1], s[2], s[0]
  2765  			return gb
  2766  		}(),
  2767  		opts:      []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
  2768  		wantEqual: false,
  2769  		reason:    "inequal because slice contains elements in differing order",
  2770  	}, {
  2771  		label: label + "/EqualOrder",
  2772  		x:     createBatch(),
  2773  		y: func() ts.GermBatch {
  2774  			gb := createBatch()
  2775  			s := gb.DirtyGerms[18]
  2776  			s[0], s[1], s[2] = s[1], s[2], s[0]
  2777  			return gb
  2778  		}(),
  2779  		opts:      []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  2780  		wantEqual: true,
  2781  		reason:    "equal because unordered slice is sorted using transformer",
  2782  	}, {
  2783  		label: label + "/Inequal",
  2784  		x: func() ts.GermBatch {
  2785  			gb := createBatch()
  2786  			delete(gb.DirtyGerms, 17)
  2787  			gb.DishMap[1] = nil
  2788  			return gb
  2789  		}(),
  2790  		y: func() ts.GermBatch {
  2791  			gb := createBatch()
  2792  			gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
  2793  			gb.GermStrain = 22
  2794  			return gb
  2795  		}(),
  2796  		opts:      []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  2797  		wantEqual: false,
  2798  		reason:    "inequal because some values are different",
  2799  	}}
  2800  }
  2801  
  2802  func project3Tests() []test {
  2803  	const label = "Project3"
  2804  
  2805  	allowVisibility := cmp.AllowUnexported(ts.Dirt{})
  2806  
  2807  	ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
  2808  
  2809  	transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt {
  2810  		return &x
  2811  	})
  2812  
  2813  	equalTable := cmp.Comparer(func(x, y ts.Table) bool {
  2814  		tx, ok1 := x.(*ts.MockTable)
  2815  		ty, ok2 := y.(*ts.MockTable)
  2816  		if !ok1 || !ok2 {
  2817  			panic("table type must be MockTable")
  2818  		}
  2819  		return cmp.Equal(tx.State(), ty.State())
  2820  	})
  2821  
  2822  	createDirt := func() (d ts.Dirt) {
  2823  		d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
  2824  		d.SetTimestamp(12345)
  2825  		d.Discord = 554
  2826  		d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
  2827  		d.SetWizard(map[string]*pb.Wizard{
  2828  			"harry": {Stringer: pb.Stringer{X: "potter"}},
  2829  			"albus": {Stringer: pb.Stringer{X: "dumbledore"}},
  2830  		})
  2831  		d.SetLastTime(54321)
  2832  		return d
  2833  	}
  2834  
  2835  	return []test{{
  2836  		label:     label + "/PanicUnexported1",
  2837  		x:         createDirt(),
  2838  		y:         createDirt(),
  2839  		wantPanic: "cannot handle unexported field",
  2840  		reason:    "struct contains unexported fields",
  2841  	}, {
  2842  		label:     label + "/PanicUnexported2",
  2843  		x:         createDirt(),
  2844  		y:         createDirt(),
  2845  		opts:      []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  2846  		wantPanic: "cannot handle unexported field",
  2847  		reason:    "struct contains references to simulated protobuf types with unexported fields",
  2848  	}, {
  2849  		label:     label + "/Equal",
  2850  		x:         createDirt(),
  2851  		y:         createDirt(),
  2852  		opts:      []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  2853  		wantEqual: true,
  2854  		reason:    "transformer used to create reference to protobuf message so it works with pb.Equal",
  2855  	}, {
  2856  		label: label + "/Inequal",
  2857  		x: func() ts.Dirt {
  2858  			d := createDirt()
  2859  			d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
  2860  			d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
  2861  			return d
  2862  		}(),
  2863  		y: func() ts.Dirt {
  2864  			d := createDirt()
  2865  			d.Discord = 500
  2866  			d.SetWizard(map[string]*pb.Wizard{
  2867  				"harry": {Stringer: pb.Stringer{X: "otter"}},
  2868  			})
  2869  			return d
  2870  		}(),
  2871  		opts:      []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  2872  		wantEqual: false,
  2873  		reason:    "inequal because some values are different",
  2874  	}}
  2875  }
  2876  
  2877  func project4Tests() []test {
  2878  	const label = "Project4"
  2879  
  2880  	allowVisibility := cmp.AllowUnexported(
  2881  		ts.Cartel{},
  2882  		ts.Headquarter{},
  2883  		ts.Poison{},
  2884  	)
  2885  
  2886  	transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions {
  2887  		return &x
  2888  	})
  2889  
  2890  	createCartel := func() ts.Cartel {
  2891  		var p ts.Poison
  2892  		p.SetPoisonType(5)
  2893  		p.SetExpiration(now)
  2894  		p.SetManufacturer("acme")
  2895  
  2896  		var hq ts.Headquarter
  2897  		hq.SetID(5)
  2898  		hq.SetLocation("moon")
  2899  		hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
  2900  		hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
  2901  		hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
  2902  		hq.SetHorseBack("abcdef")
  2903  		hq.SetStatus(44)
  2904  
  2905  		var c ts.Cartel
  2906  		c.Headquarter = hq
  2907  		c.SetSource("mars")
  2908  		c.SetCreationTime(now)
  2909  		c.SetBoss("al capone")
  2910  		c.SetPoisons([]*ts.Poison{&p})
  2911  
  2912  		return c
  2913  	}
  2914  
  2915  	return []test{{
  2916  		label:     label + "/PanicUnexported1",
  2917  		x:         createCartel(),
  2918  		y:         createCartel(),
  2919  		wantPanic: "cannot handle unexported field",
  2920  		reason:    "struct contains unexported fields",
  2921  	}, {
  2922  		label:     label + "/PanicUnexported2",
  2923  		x:         createCartel(),
  2924  		y:         createCartel(),
  2925  		opts:      []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
  2926  		wantPanic: "cannot handle unexported field",
  2927  		reason:    "struct contains references to simulated protobuf types with unexported fields",
  2928  	}, {
  2929  		label:     label + "/Equal",
  2930  		x:         createCartel(),
  2931  		y:         createCartel(),
  2932  		opts:      []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
  2933  		wantEqual: true,
  2934  		reason:    "transformer used to create reference to protobuf message so it works with pb.Equal",
  2935  	}, {
  2936  		label: label + "/Inequal",
  2937  		x: func() ts.Cartel {
  2938  			d := createCartel()
  2939  			var p1, p2 ts.Poison
  2940  			p1.SetPoisonType(1)
  2941  			p1.SetExpiration(now)
  2942  			p1.SetManufacturer("acme")
  2943  			p2.SetPoisonType(2)
  2944  			p2.SetManufacturer("acme2")
  2945  			d.SetPoisons([]*ts.Poison{&p1, &p2})
  2946  			return d
  2947  		}(),
  2948  		y: func() ts.Cartel {
  2949  			d := createCartel()
  2950  			d.SetSubDivisions([]string{"bravo", "charlie"})
  2951  			d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
  2952  			return d
  2953  		}(),
  2954  		opts:      []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
  2955  		wantEqual: false,
  2956  		reason:    "inequal because some values are different",
  2957  	}}
  2958  }
  2959  
  2960  // BenchmarkBytes benchmarks the performance of performing Equal or Diff on
  2961  // large slices of bytes.
  2962  func BenchmarkBytes(b *testing.B) {
  2963  	// Create a list of PathFilters that never apply, but are evaluated.
  2964  	const maxFilters = 5
  2965  	var filters cmp.Options
  2966  	errorIface := reflect.TypeOf((*error)(nil)).Elem()
  2967  	for i := 0; i <= maxFilters; i++ {
  2968  		filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool {
  2969  			return p.Last().Type().AssignableTo(errorIface) // Never true
  2970  		}, cmp.Ignore()))
  2971  	}
  2972  
  2973  	type benchSize struct {
  2974  		label string
  2975  		size  int64
  2976  	}
  2977  	for _, ts := range []benchSize{
  2978  		{"4KiB", 1 << 12},
  2979  		{"64KiB", 1 << 16},
  2980  		{"1MiB", 1 << 20},
  2981  		{"16MiB", 1 << 24},
  2982  	} {
  2983  		bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...)
  2984  		by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...)
  2985  		b.Run(ts.label, func(b *testing.B) {
  2986  			// Iteratively add more filters that never apply, but are evaluated
  2987  			// to measure the cost of simply evaluating each filter.
  2988  			for i := 0; i <= maxFilters; i++ {
  2989  				b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) {
  2990  					b.ReportAllocs()
  2991  					b.SetBytes(2 * ts.size)
  2992  					for j := 0; j < b.N; j++ {
  2993  						cmp.Equal(bx, by, filters[:i]...)
  2994  					}
  2995  				})
  2996  			}
  2997  			for i := 0; i <= maxFilters; i++ {
  2998  				b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) {
  2999  					b.ReportAllocs()
  3000  					b.SetBytes(2 * ts.size)
  3001  					for j := 0; j < b.N; j++ {
  3002  						cmp.Diff(bx, by, filters[:i]...)
  3003  					}
  3004  				})
  3005  			}
  3006  		})
  3007  	}
  3008  }
  3009  

View as plain text