github.com/integration-system/go-cmp@v0.0.0-20190131081942-ac5582987a2f/cmp/compare_test.go (about)

     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.md file.
     4  
     5  package cmp_test
     6  
     7  import (
     8  	"bytes"
     9  	"crypto/md5"
    10  	"encoding/json"
    11  	"fmt"
    12  	"io"
    13  	"math"
    14  	"math/rand"
    15  	"reflect"
    16  	"regexp"
    17  	"sort"
    18  	"strings"
    19  	"sync"
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/integration-system/go-cmp/cmp"
    24  	"github.com/integration-system/go-cmp/cmp/cmpopts"
    25  	pb "github.com/integration-system/go-cmp/cmp/internal/testprotos"
    26  	ts "github.com/integration-system/go-cmp/cmp/internal/teststructs"
    27  )
    28  
    29  var now = time.Now()
    30  
    31  func intPtr(n int) *int { return &n }
    32  
    33  type test struct {
    34  	label     string       // Test description
    35  	x, y      interface{}  // Input values to compare
    36  	opts      []cmp.Option // Input options
    37  	wantDiff  string       // The exact difference string
    38  	wantPanic string       // Sub-string of an expected panic message
    39  }
    40  
    41  func TestDiff(t *testing.T) {
    42  	var tests []test
    43  	tests = append(tests, comparerTests()...)
    44  	tests = append(tests, transformerTests()...)
    45  	tests = append(tests, embeddedTests()...)
    46  	tests = append(tests, methodTests()...)
    47  	tests = append(tests, project1Tests()...)
    48  	tests = append(tests, project2Tests()...)
    49  	tests = append(tests, project3Tests()...)
    50  	tests = append(tests, project4Tests()...)
    51  
    52  	for _, tt := range tests {
    53  		tt := tt
    54  		t.Run(tt.label, func(t *testing.T) {
    55  			t.Parallel()
    56  			var gotDiff, gotPanic string
    57  			func() {
    58  				defer func() {
    59  					if ex := recover(); ex != nil {
    60  						if s, ok := ex.(string); ok {
    61  							gotPanic = s
    62  						} else {
    63  							panic(ex)
    64  						}
    65  					}
    66  				}()
    67  				gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
    68  			}()
    69  			if tt.wantPanic == "" {
    70  				if gotPanic != "" {
    71  					t.Fatalf("unexpected panic message: %s", gotPanic)
    72  				}
    73  				if got, want := strings.TrimSpace(gotDiff), strings.TrimSpace(tt.wantDiff); got != want {
    74  					t.Fatalf("difference message:\ngot:\n%s\n\nwant:\n%s", got, want)
    75  				}
    76  			} else {
    77  				if !strings.Contains(gotPanic, tt.wantPanic) {
    78  					t.Fatalf("panic message:\ngot:  %s\nwant: %s", gotPanic, tt.wantPanic)
    79  				}
    80  			}
    81  		})
    82  	}
    83  }
    84  
    85  func comparerTests() []test {
    86  	const label = "Comparer"
    87  
    88  	type Iface1 interface {
    89  		Method()
    90  	}
    91  	type Iface2 interface {
    92  		Method()
    93  	}
    94  
    95  	type tarHeader struct {
    96  		Name       string
    97  		Mode       int64
    98  		Uid        int
    99  		Gid        int
   100  		Size       int64
   101  		ModTime    time.Time
   102  		Typeflag   byte
   103  		Linkname   string
   104  		Uname      string
   105  		Gname      string
   106  		Devmajor   int64
   107  		Devminor   int64
   108  		AccessTime time.Time
   109  		ChangeTime time.Time
   110  		Xattrs     map[string]string
   111  	}
   112  
   113  	makeTarHeaders := func(tf byte) (hs []tarHeader) {
   114  		for i := 0; i < 5; i++ {
   115  			hs = append(hs, tarHeader{
   116  				Name: fmt.Sprintf("some/dummy/test/file%d", i),
   117  				Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i),
   118  				ModTime: now.Add(time.Duration(i) * time.Hour),
   119  				Uname:   "user", Gname: "group",
   120  				Typeflag: tf,
   121  			})
   122  		}
   123  		return hs
   124  	}
   125  
   126  	return []test{{
   127  		label: label,
   128  		x:     1,
   129  		y:     1,
   130  	}, {
   131  		label:     label,
   132  		x:         1,
   133  		y:         1,
   134  		opts:      []cmp.Option{cmp.Ignore()},
   135  		wantPanic: "cannot use an unfiltered option",
   136  	}, {
   137  		label:     label,
   138  		x:         1,
   139  		y:         1,
   140  		opts:      []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
   141  		wantPanic: "cannot use an unfiltered option",
   142  	}, {
   143  		label:     label,
   144  		x:         1,
   145  		y:         1,
   146  		opts:      []cmp.Option{cmp.Transformer("", func(x interface{}) interface{} { return x })},
   147  		wantPanic: "cannot use an unfiltered option",
   148  	}, {
   149  		label: label,
   150  		x:     1,
   151  		y:     1,
   152  		opts: []cmp.Option{
   153  			cmp.Comparer(func(x, y int) bool { return true }),
   154  			cmp.Transformer("", func(x int) float64 { return float64(x) }),
   155  		},
   156  		wantPanic: "ambiguous set of applicable options",
   157  	}, {
   158  		label: label,
   159  		x:     1,
   160  		y:     1,
   161  		opts: []cmp.Option{
   162  			cmp.FilterPath(func(p cmp.Path) bool {
   163  				return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
   164  			}, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
   165  			cmp.Comparer(func(x, y int) bool { return true }),
   166  			cmp.Transformer("", func(x int) float64 { return float64(x) }),
   167  		},
   168  	}, {
   169  		label:     label,
   170  		opts:      []cmp.Option{struct{ cmp.Option }{}},
   171  		wantPanic: "unknown option",
   172  	}, {
   173  		label: label,
   174  		x:     struct{ A, B, C int }{1, 2, 3},
   175  		y:     struct{ A, B, C int }{1, 2, 3},
   176  	}, {
   177  		label:    label,
   178  		x:        struct{ A, B, C int }{1, 2, 3},
   179  		y:        struct{ A, B, C int }{1, 2, 4},
   180  		wantDiff: "root.C:\n\t-: 3\n\t+: 4\n",
   181  	}, {
   182  		label:     label,
   183  		x:         struct{ a, b, c int }{1, 2, 3},
   184  		y:         struct{ a, b, c int }{1, 2, 4},
   185  		wantPanic: "cannot handle unexported field",
   186  	}, {
   187  		label: label,
   188  		x:     &struct{ A *int }{intPtr(4)},
   189  		y:     &struct{ A *int }{intPtr(4)},
   190  	}, {
   191  		label:    label,
   192  		x:        &struct{ A *int }{intPtr(4)},
   193  		y:        &struct{ A *int }{intPtr(5)},
   194  		wantDiff: "*root.A:\n\t-: 4\n\t+: 5\n",
   195  	}, {
   196  		label: label,
   197  		x:     &struct{ A *int }{intPtr(4)},
   198  		y:     &struct{ A *int }{intPtr(5)},
   199  		opts: []cmp.Option{
   200  			cmp.Comparer(func(x, y int) bool { return true }),
   201  		},
   202  	}, {
   203  		label: label,
   204  		x:     &struct{ A *int }{intPtr(4)},
   205  		y:     &struct{ A *int }{intPtr(5)},
   206  		opts: []cmp.Option{
   207  			cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
   208  		},
   209  	}, {
   210  		label: label,
   211  		x:     &struct{ R *bytes.Buffer }{},
   212  		y:     &struct{ R *bytes.Buffer }{},
   213  	}, {
   214  		label:    label,
   215  		x:        &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
   216  		y:        &struct{ R *bytes.Buffer }{},
   217  		wantDiff: "root.R:\n\t-: s\"\"\n\t+: <nil>\n",
   218  	}, {
   219  		label: label,
   220  		x:     &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
   221  		y:     &struct{ R *bytes.Buffer }{},
   222  		opts: []cmp.Option{
   223  			cmp.Comparer(func(x, y io.Reader) bool { return true }),
   224  		},
   225  	}, {
   226  		label:     label,
   227  		x:         &struct{ R bytes.Buffer }{},
   228  		y:         &struct{ R bytes.Buffer }{},
   229  		wantPanic: "cannot handle unexported field",
   230  	}, {
   231  		label: label,
   232  		x:     &struct{ R bytes.Buffer }{},
   233  		y:     &struct{ R bytes.Buffer }{},
   234  		opts: []cmp.Option{
   235  			cmp.Comparer(func(x, y io.Reader) bool { return true }),
   236  		},
   237  		wantPanic: "cannot handle unexported field",
   238  	}, {
   239  		label: label,
   240  		x:     &struct{ R bytes.Buffer }{},
   241  		y:     &struct{ R bytes.Buffer }{},
   242  		opts: []cmp.Option{
   243  			cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
   244  			cmp.Comparer(func(x, y io.Reader) bool { return true }),
   245  		},
   246  	}, {
   247  		label:     label,
   248  		x:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   249  		y:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   250  		wantPanic: "cannot handle unexported field",
   251  	}, {
   252  		label: label,
   253  		x:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   254  		y:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   255  		opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
   256  			if x == nil || y == nil {
   257  				return x == nil && y == nil
   258  			}
   259  			return x.String() == y.String()
   260  		})},
   261  	}, {
   262  		label: label,
   263  		x:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
   264  		y:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
   265  		opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
   266  			if x == nil || y == nil {
   267  				return x == nil && y == nil
   268  			}
   269  			return x.String() == y.String()
   270  		})},
   271  		wantDiff: `
   272  {[]*regexp.Regexp}[1]:
   273  	-: s"a*b*c*"
   274  	+: s"a*b*d*"`,
   275  	}, {
   276  		label: label,
   277  		x: func() ***int {
   278  			a := 0
   279  			b := &a
   280  			c := &b
   281  			return &c
   282  		}(),
   283  		y: func() ***int {
   284  			a := 0
   285  			b := &a
   286  			c := &b
   287  			return &c
   288  		}(),
   289  	}, {
   290  		label: label,
   291  		x: func() ***int {
   292  			a := 0
   293  			b := &a
   294  			c := &b
   295  			return &c
   296  		}(),
   297  		y: func() ***int {
   298  			a := 1
   299  			b := &a
   300  			c := &b
   301  			return &c
   302  		}(),
   303  		wantDiff: `
   304  ***{***int}:
   305  	-: 0
   306  	+: 1`,
   307  	}, {
   308  		label: label,
   309  		x:     []int{1, 2, 3, 4, 5}[:3],
   310  		y:     []int{1, 2, 3},
   311  	}, {
   312  		label: label,
   313  		x:     struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
   314  		y:     struct{ fmt.Stringer }{regexp.MustCompile("hello")},
   315  		opts:  []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
   316  	}, {
   317  		label: label,
   318  		x:     struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
   319  		y:     struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
   320  		opts:  []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
   321  		wantDiff: `
   322  root:
   323  	-: s"hello"
   324  	+: s"hello2"`,
   325  	}, {
   326  		label: label,
   327  		x:     md5.Sum([]byte{'a'}),
   328  		y:     md5.Sum([]byte{'b'}),
   329  		wantDiff: `
   330  {[16]uint8}:
   331  	-: [16]uint8{0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61}
   332  	+: [16]uint8{0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f}`,
   333  	}, {
   334  		label: label,
   335  		x:     new(fmt.Stringer),
   336  		y:     nil,
   337  		wantDiff: `
   338  :
   339  	-: &<nil>
   340  	+: <non-existent>`,
   341  	}, {
   342  		label: label,
   343  		x:     makeTarHeaders('0'),
   344  		y:     makeTarHeaders('\x00'),
   345  		wantDiff: `
   346  {[]cmp_test.tarHeader}[0].Typeflag:
   347  	-: 0x30
   348  	+: 0x00
   349  {[]cmp_test.tarHeader}[1].Typeflag:
   350  	-: 0x30
   351  	+: 0x00
   352  {[]cmp_test.tarHeader}[2].Typeflag:
   353  	-: 0x30
   354  	+: 0x00
   355  {[]cmp_test.tarHeader}[3].Typeflag:
   356  	-: 0x30
   357  	+: 0x00
   358  {[]cmp_test.tarHeader}[4].Typeflag:
   359  	-: 0x30
   360  	+: 0x00`,
   361  	}, {
   362  		label: label,
   363  		x:     make([]int, 1000),
   364  		y:     make([]int, 1000),
   365  		opts: []cmp.Option{
   366  			cmp.Comparer(func(_, _ int) bool {
   367  				return rand.Intn(2) == 0
   368  			}),
   369  		},
   370  		wantPanic: "non-deterministic or non-symmetric function detected",
   371  	}, {
   372  		label: label,
   373  		x:     make([]int, 1000),
   374  		y:     make([]int, 1000),
   375  		opts: []cmp.Option{
   376  			cmp.FilterValues(func(_, _ int) bool {
   377  				return rand.Intn(2) == 0
   378  			}, cmp.Ignore()),
   379  		},
   380  		wantPanic: "non-deterministic or non-symmetric function detected",
   381  	}, {
   382  		label: label,
   383  		x:     []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
   384  		y:     []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
   385  		opts: []cmp.Option{
   386  			cmp.Comparer(func(x, y int) bool {
   387  				return x < y
   388  			}),
   389  		},
   390  		wantPanic: "non-deterministic or non-symmetric function detected",
   391  	}, {
   392  		label: label,
   393  		x:     make([]string, 1000),
   394  		y:     make([]string, 1000),
   395  		opts: []cmp.Option{
   396  			cmp.Transformer("", func(x string) int {
   397  				return rand.Int()
   398  			}),
   399  		},
   400  		wantPanic: "non-deterministic function detected",
   401  	}, {
   402  		// Make sure the dynamic checks don't raise a false positive for
   403  		// non-reflexive comparisons.
   404  		label: label,
   405  		x:     make([]int, 10),
   406  		y:     make([]int, 10),
   407  		opts: []cmp.Option{
   408  			cmp.Transformer("", func(x int) float64 {
   409  				return math.NaN()
   410  			}),
   411  		},
   412  		wantDiff: `
   413  {[]int}:
   414  	-: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}
   415  	+: []int{0, 0, 0, 0, 0, 0, 0, 0, 0, 0}`,
   416  	}, {
   417  		// Ensure reasonable Stringer formatting of map keys.
   418  		label: label,
   419  		x:     map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
   420  		y:     map[*pb.Stringer]*pb.Stringer(nil),
   421  		wantDiff: `
   422  {map[*testprotos.Stringer]*testprotos.Stringer}:
   423  	-: map[*testprotos.Stringer]*testprotos.Stringer{s"hello": s"world"}
   424  	+: map[*testprotos.Stringer]*testprotos.Stringer(nil)`,
   425  	}, {
   426  		// Ensure Stringer avoids double-quote escaping if possible.
   427  		label:    label,
   428  		x:        []*pb.Stringer{{`multi\nline\nline\nline`}},
   429  		wantDiff: ":\n\t-: []*testprotos.Stringer{s`multi\\nline\\nline\\nline`}\n\t+: <non-existent>",
   430  	}, {
   431  		label: label,
   432  		x:     struct{ I Iface2 }{},
   433  		y:     struct{ I Iface2 }{},
   434  		opts: []cmp.Option{
   435  			cmp.Comparer(func(x, y Iface1) bool {
   436  				return x == nil && y == nil
   437  			}),
   438  		},
   439  	}, {
   440  		label: label,
   441  		x:     struct{ I Iface2 }{},
   442  		y:     struct{ I Iface2 }{},
   443  		opts: []cmp.Option{
   444  			cmp.Transformer("", func(v Iface1) bool {
   445  				return v == nil
   446  			}),
   447  		},
   448  	}, {
   449  		label: label,
   450  		x:     struct{ I Iface2 }{},
   451  		y:     struct{ I Iface2 }{},
   452  		opts: []cmp.Option{
   453  			cmp.FilterValues(func(x, y Iface1) bool {
   454  				return x == nil && y == nil
   455  			}, cmp.Ignore()),
   456  		},
   457  	}, {
   458  		label: label,
   459  		x:     []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
   460  		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"}},
   461  		wantDiff: `
   462  root[0]["hr"]:
   463  	-: int(65)
   464  	+: float64(65)
   465  root[1]["hr"]:
   466  	-: int(63)
   467  	+: float64(63)`,
   468  	}}
   469  }
   470  
   471  func transformerTests() []test {
   472  	type StringBytes struct {
   473  		String string
   474  		Bytes  []byte
   475  	}
   476  
   477  	const label = "Transformer"
   478  
   479  	transformOnce := func(name string, f interface{}) cmp.Option {
   480  		xform := cmp.Transformer(name, f)
   481  		return cmp.FilterPath(func(p cmp.Path) bool {
   482  			for _, ps := range p {
   483  				if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
   484  					return false
   485  				}
   486  			}
   487  			return true
   488  		}, xform)
   489  	}
   490  
   491  	return []test{{
   492  		label: label,
   493  		x:     uint8(0),
   494  		y:     uint8(1),
   495  		opts: []cmp.Option{
   496  			cmp.Transformer("", func(in uint8) uint16 { return uint16(in) }),
   497  			cmp.Transformer("", func(in uint16) uint32 { return uint32(in) }),
   498  			cmp.Transformer("", func(in uint32) uint64 { return uint64(in) }),
   499  		},
   500  		wantDiff: `
   501  λ(λ(λ({uint8}))):
   502  	-: 0x00
   503  	+: 0x01`,
   504  	}, {
   505  		label: label,
   506  		x:     0,
   507  		y:     1,
   508  		opts: []cmp.Option{
   509  			cmp.Transformer("", func(in int) int { return in / 2 }),
   510  			cmp.Transformer("", func(in int) int { return in }),
   511  		},
   512  		wantPanic: "ambiguous set of applicable options",
   513  	}, {
   514  		label: label,
   515  		x:     []int{0, -5, 0, -1},
   516  		y:     []int{1, 3, 0, -5},
   517  		opts: []cmp.Option{
   518  			cmp.FilterValues(
   519  				func(x, y int) bool { return x+y >= 0 },
   520  				cmp.Transformer("", func(in int) int64 { return int64(in / 2) }),
   521  			),
   522  			cmp.FilterValues(
   523  				func(x, y int) bool { return x+y < 0 },
   524  				cmp.Transformer("", func(in int) int64 { return int64(in) }),
   525  			),
   526  		},
   527  		wantDiff: `
   528  λ({[]int}[1]):
   529  	-: -5
   530  	+: 3
   531  λ({[]int}[3]):
   532  	-: -1
   533  	+: -5`,
   534  	}, {
   535  		label: label,
   536  		x:     0,
   537  		y:     1,
   538  		opts: []cmp.Option{
   539  			cmp.Transformer("", func(in int) interface{} {
   540  				if in == 0 {
   541  					return "string"
   542  				}
   543  				return float64(in)
   544  			}),
   545  		},
   546  		wantDiff: `
   547  λ({int}):
   548  	-: "string"
   549  	+: 1`,
   550  	}, {
   551  		label: label,
   552  		x: `{
   553  		  "firstName": "John",
   554  		  "lastName": "Smith",
   555  		  "age": 25,
   556  		  "isAlive": true,
   557  		  "address": {
   558  		    "city": "Los Angeles",
   559  		    "postalCode": "10021-3100",
   560  		    "state": "CA",
   561  		    "streetAddress": "21 2nd Street"
   562  		  },
   563  		  "phoneNumbers": [{
   564  		    "type": "home",
   565  		    "number": "212 555-4321"
   566  		  },{
   567  		    "type": "office",
   568  		    "number": "646 555-4567"
   569  		  },{
   570  		    "number": "123 456-7890",
   571  		    "type": "mobile"
   572  		  }],
   573  		  "children": []
   574  		}`,
   575  		y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
   576  			"address":{"streetAddress":"21 2nd Street","city":"New York",
   577  			"state":"NY","postalCode":"10021-3100"},"phoneNumbers":[{"type":"home",
   578  			"number":"212 555-1234"},{"type":"office","number":"646 555-4567"},{
   579  			"type":"mobile","number":"123 456-7890"}],"children":[],"spouse":null}`,
   580  		opts: []cmp.Option{
   581  			transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
   582  				if err := json.Unmarshal([]byte(s), &m); err != nil {
   583  					panic(err)
   584  				}
   585  				return m
   586  			}),
   587  		},
   588  		wantDiff: `
   589  ParseJSON({string})["address"]["city"]:
   590  	-: "Los Angeles"
   591  	+: "New York"
   592  ParseJSON({string})["address"]["state"]:
   593  	-: "CA"
   594  	+: "NY"
   595  ParseJSON({string})["phoneNumbers"][0]["number"]:
   596  	-: "212 555-4321"
   597  	+: "212 555-1234"
   598  ParseJSON({string})["spouse"]:
   599  	-: <non-existent>
   600  	+: interface {}(nil)`,
   601  	}, {
   602  		label: label,
   603  		x:     StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
   604  		y:     StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
   605  		opts: []cmp.Option{
   606  			transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
   607  			transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
   608  		},
   609  		wantDiff: `
   610  SplitString({cmp_test.StringBytes}.String)[2]:
   611  	-: "Line"
   612  	+: "line"
   613  SplitBytes({cmp_test.StringBytes}.Bytes)[3][0]:
   614  	-: 0x62
   615  	+: 0x42`,
   616  	}, {
   617  		x: "a\nb\nc\n",
   618  		y: "a\nb\nc\n",
   619  		opts: []cmp.Option{
   620  			cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
   621  		},
   622  		wantPanic: "recursive set of Transformers detected",
   623  	}, {
   624  		x: complex64(0),
   625  		y: complex64(0),
   626  		opts: []cmp.Option{
   627  			cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
   628  			cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
   629  			cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
   630  		},
   631  		wantPanic: "recursive set of Transformers detected",
   632  	}}
   633  }
   634  
   635  func embeddedTests() []test {
   636  	const label = "EmbeddedStruct/"
   637  
   638  	privateStruct := *new(ts.ParentStructA).PrivateStruct()
   639  
   640  	createStructA := func(i int) ts.ParentStructA {
   641  		s := ts.ParentStructA{}
   642  		s.PrivateStruct().Public = 1 + i
   643  		s.PrivateStruct().SetPrivate(2 + i)
   644  		return s
   645  	}
   646  
   647  	createStructB := func(i int) ts.ParentStructB {
   648  		s := ts.ParentStructB{}
   649  		s.PublicStruct.Public = 1 + i
   650  		s.PublicStruct.SetPrivate(2 + i)
   651  		return s
   652  	}
   653  
   654  	createStructC := func(i int) ts.ParentStructC {
   655  		s := ts.ParentStructC{}
   656  		s.PrivateStruct().Public = 1 + i
   657  		s.PrivateStruct().SetPrivate(2 + i)
   658  		s.Public = 3 + i
   659  		s.SetPrivate(4 + i)
   660  		return s
   661  	}
   662  
   663  	createStructD := func(i int) ts.ParentStructD {
   664  		s := ts.ParentStructD{}
   665  		s.PublicStruct.Public = 1 + i
   666  		s.PublicStruct.SetPrivate(2 + i)
   667  		s.Public = 3 + i
   668  		s.SetPrivate(4 + i)
   669  		return s
   670  	}
   671  
   672  	createStructE := func(i int) ts.ParentStructE {
   673  		s := ts.ParentStructE{}
   674  		s.PrivateStruct().Public = 1 + i
   675  		s.PrivateStruct().SetPrivate(2 + i)
   676  		s.PublicStruct.Public = 3 + i
   677  		s.PublicStruct.SetPrivate(4 + i)
   678  		return s
   679  	}
   680  
   681  	createStructF := func(i int) ts.ParentStructF {
   682  		s := ts.ParentStructF{}
   683  		s.PrivateStruct().Public = 1 + i
   684  		s.PrivateStruct().SetPrivate(2 + i)
   685  		s.PublicStruct.Public = 3 + i
   686  		s.PublicStruct.SetPrivate(4 + i)
   687  		s.Public = 5 + i
   688  		s.SetPrivate(6 + i)
   689  		return s
   690  	}
   691  
   692  	createStructG := func(i int) *ts.ParentStructG {
   693  		s := ts.NewParentStructG()
   694  		s.PrivateStruct().Public = 1 + i
   695  		s.PrivateStruct().SetPrivate(2 + i)
   696  		return s
   697  	}
   698  
   699  	createStructH := func(i int) *ts.ParentStructH {
   700  		s := ts.NewParentStructH()
   701  		s.PublicStruct.Public = 1 + i
   702  		s.PublicStruct.SetPrivate(2 + i)
   703  		return s
   704  	}
   705  
   706  	createStructI := func(i int) *ts.ParentStructI {
   707  		s := ts.NewParentStructI()
   708  		s.PrivateStruct().Public = 1 + i
   709  		s.PrivateStruct().SetPrivate(2 + i)
   710  		s.PublicStruct.Public = 3 + i
   711  		s.PublicStruct.SetPrivate(4 + i)
   712  		return s
   713  	}
   714  
   715  	createStructJ := func(i int) *ts.ParentStructJ {
   716  		s := ts.NewParentStructJ()
   717  		s.PrivateStruct().Public = 1 + i
   718  		s.PrivateStruct().SetPrivate(2 + i)
   719  		s.PublicStruct.Public = 3 + i
   720  		s.PublicStruct.SetPrivate(4 + i)
   721  		s.Private().Public = 5 + i
   722  		s.Private().SetPrivate(6 + i)
   723  		s.Public.Public = 7 + i
   724  		s.Public.SetPrivate(8 + i)
   725  		return s
   726  	}
   727  
   728  	return []test{{
   729  		label:     label + "ParentStructA",
   730  		x:         ts.ParentStructA{},
   731  		y:         ts.ParentStructA{},
   732  		wantPanic: "cannot handle unexported field",
   733  	}, {
   734  		label: label + "ParentStructA",
   735  		x:     ts.ParentStructA{},
   736  		y:     ts.ParentStructA{},
   737  		opts: []cmp.Option{
   738  			cmpopts.IgnoreUnexported(ts.ParentStructA{}),
   739  		},
   740  	}, {
   741  		label: label + "ParentStructA",
   742  		x:     createStructA(0),
   743  		y:     createStructA(0),
   744  		opts: []cmp.Option{
   745  			cmp.AllowUnexported(ts.ParentStructA{}),
   746  		},
   747  		wantPanic: "cannot handle unexported field",
   748  	}, {
   749  		label: label + "ParentStructA",
   750  		x:     createStructA(0),
   751  		y:     createStructA(0),
   752  		opts: []cmp.Option{
   753  			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
   754  		},
   755  	}, {
   756  		label: label + "ParentStructA",
   757  		x:     createStructA(0),
   758  		y:     createStructA(1),
   759  		opts: []cmp.Option{
   760  			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
   761  		},
   762  		wantDiff: `
   763  {teststructs.ParentStructA}.privateStruct.Public:
   764  	-: 1
   765  	+: 2
   766  {teststructs.ParentStructA}.privateStruct.private:
   767  	-: 2
   768  	+: 3`,
   769  	}, {
   770  		label: label + "ParentStructB",
   771  		x:     ts.ParentStructB{},
   772  		y:     ts.ParentStructB{},
   773  		opts: []cmp.Option{
   774  			cmpopts.IgnoreUnexported(ts.ParentStructB{}),
   775  		},
   776  		wantPanic: "cannot handle unexported field",
   777  	}, {
   778  		label: label + "ParentStructB",
   779  		x:     ts.ParentStructB{},
   780  		y:     ts.ParentStructB{},
   781  		opts: []cmp.Option{
   782  			cmpopts.IgnoreUnexported(ts.ParentStructB{}),
   783  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
   784  		},
   785  	}, {
   786  		label: label + "ParentStructB",
   787  		x:     createStructB(0),
   788  		y:     createStructB(0),
   789  		opts: []cmp.Option{
   790  			cmp.AllowUnexported(ts.ParentStructB{}),
   791  		},
   792  		wantPanic: "cannot handle unexported field",
   793  	}, {
   794  		label: label + "ParentStructB",
   795  		x:     createStructB(0),
   796  		y:     createStructB(0),
   797  		opts: []cmp.Option{
   798  			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
   799  		},
   800  	}, {
   801  		label: label + "ParentStructB",
   802  		x:     createStructB(0),
   803  		y:     createStructB(1),
   804  		opts: []cmp.Option{
   805  			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
   806  		},
   807  		wantDiff: `
   808  {teststructs.ParentStructB}.PublicStruct.Public:
   809  	-: 1
   810  	+: 2
   811  {teststructs.ParentStructB}.PublicStruct.private:
   812  	-: 2
   813  	+: 3`,
   814  	}, {
   815  		label:     label + "ParentStructC",
   816  		x:         ts.ParentStructC{},
   817  		y:         ts.ParentStructC{},
   818  		wantPanic: "cannot handle unexported field",
   819  	}, {
   820  		label: label + "ParentStructC",
   821  		x:     ts.ParentStructC{},
   822  		y:     ts.ParentStructC{},
   823  		opts: []cmp.Option{
   824  			cmpopts.IgnoreUnexported(ts.ParentStructC{}),
   825  		},
   826  	}, {
   827  		label: label + "ParentStructC",
   828  		x:     createStructC(0),
   829  		y:     createStructC(0),
   830  		opts: []cmp.Option{
   831  			cmp.AllowUnexported(ts.ParentStructC{}),
   832  		},
   833  		wantPanic: "cannot handle unexported field",
   834  	}, {
   835  		label: label + "ParentStructC",
   836  		x:     createStructC(0),
   837  		y:     createStructC(0),
   838  		opts: []cmp.Option{
   839  			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
   840  		},
   841  	}, {
   842  		label: label + "ParentStructC",
   843  		x:     createStructC(0),
   844  		y:     createStructC(1),
   845  		opts: []cmp.Option{
   846  			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
   847  		},
   848  		wantDiff: `
   849  {teststructs.ParentStructC}.privateStruct.Public:
   850  	-: 1
   851  	+: 2
   852  {teststructs.ParentStructC}.privateStruct.private:
   853  	-: 2
   854  	+: 3
   855  {teststructs.ParentStructC}.Public:
   856  	-: 3
   857  	+: 4
   858  {teststructs.ParentStructC}.private:
   859  	-: 4
   860  	+: 5`,
   861  	}, {
   862  		label: label + "ParentStructD",
   863  		x:     ts.ParentStructD{},
   864  		y:     ts.ParentStructD{},
   865  		opts: []cmp.Option{
   866  			cmpopts.IgnoreUnexported(ts.ParentStructD{}),
   867  		},
   868  		wantPanic: "cannot handle unexported field",
   869  	}, {
   870  		label: label + "ParentStructD",
   871  		x:     ts.ParentStructD{},
   872  		y:     ts.ParentStructD{},
   873  		opts: []cmp.Option{
   874  			cmpopts.IgnoreUnexported(ts.ParentStructD{}),
   875  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
   876  		},
   877  	}, {
   878  		label: label + "ParentStructD",
   879  		x:     createStructD(0),
   880  		y:     createStructD(0),
   881  		opts: []cmp.Option{
   882  			cmp.AllowUnexported(ts.ParentStructD{}),
   883  		},
   884  		wantPanic: "cannot handle unexported field",
   885  	}, {
   886  		label: label + "ParentStructD",
   887  		x:     createStructD(0),
   888  		y:     createStructD(0),
   889  		opts: []cmp.Option{
   890  			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
   891  		},
   892  	}, {
   893  		label: label + "ParentStructD",
   894  		x:     createStructD(0),
   895  		y:     createStructD(1),
   896  		opts: []cmp.Option{
   897  			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
   898  		},
   899  		wantDiff: `
   900  {teststructs.ParentStructD}.PublicStruct.Public:
   901  	-: 1
   902  	+: 2
   903  {teststructs.ParentStructD}.PublicStruct.private:
   904  	-: 2
   905  	+: 3
   906  {teststructs.ParentStructD}.Public:
   907  	-: 3
   908  	+: 4
   909  {teststructs.ParentStructD}.private:
   910  	-: 4
   911  	+: 5`,
   912  	}, {
   913  		label: label + "ParentStructE",
   914  		x:     ts.ParentStructE{},
   915  		y:     ts.ParentStructE{},
   916  		opts: []cmp.Option{
   917  			cmpopts.IgnoreUnexported(ts.ParentStructE{}),
   918  		},
   919  		wantPanic: "cannot handle unexported field",
   920  	}, {
   921  		label: label + "ParentStructE",
   922  		x:     ts.ParentStructE{},
   923  		y:     ts.ParentStructE{},
   924  		opts: []cmp.Option{
   925  			cmpopts.IgnoreUnexported(ts.ParentStructE{}),
   926  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
   927  		},
   928  	}, {
   929  		label: label + "ParentStructE",
   930  		x:     createStructE(0),
   931  		y:     createStructE(0),
   932  		opts: []cmp.Option{
   933  			cmp.AllowUnexported(ts.ParentStructE{}),
   934  		},
   935  		wantPanic: "cannot handle unexported field",
   936  	}, {
   937  		label: label + "ParentStructE",
   938  		x:     createStructE(0),
   939  		y:     createStructE(0),
   940  		opts: []cmp.Option{
   941  			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
   942  		},
   943  		wantPanic: "cannot handle unexported field",
   944  	}, {
   945  		label: label + "ParentStructE",
   946  		x:     createStructE(0),
   947  		y:     createStructE(0),
   948  		opts: []cmp.Option{
   949  			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
   950  		},
   951  	}, {
   952  		label: label + "ParentStructE",
   953  		x:     createStructE(0),
   954  		y:     createStructE(1),
   955  		opts: []cmp.Option{
   956  			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
   957  		},
   958  		wantDiff: `
   959  {teststructs.ParentStructE}.privateStruct.Public:
   960  	-: 1
   961  	+: 2
   962  {teststructs.ParentStructE}.privateStruct.private:
   963  	-: 2
   964  	+: 3
   965  {teststructs.ParentStructE}.PublicStruct.Public:
   966  	-: 3
   967  	+: 4
   968  {teststructs.ParentStructE}.PublicStruct.private:
   969  	-: 4
   970  	+: 5`,
   971  	}, {
   972  		label: label + "ParentStructF",
   973  		x:     ts.ParentStructF{},
   974  		y:     ts.ParentStructF{},
   975  		opts: []cmp.Option{
   976  			cmpopts.IgnoreUnexported(ts.ParentStructF{}),
   977  		},
   978  		wantPanic: "cannot handle unexported field",
   979  	}, {
   980  		label: label + "ParentStructF",
   981  		x:     ts.ParentStructF{},
   982  		y:     ts.ParentStructF{},
   983  		opts: []cmp.Option{
   984  			cmpopts.IgnoreUnexported(ts.ParentStructF{}),
   985  			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
   986  		},
   987  	}, {
   988  		label: label + "ParentStructF",
   989  		x:     createStructF(0),
   990  		y:     createStructF(0),
   991  		opts: []cmp.Option{
   992  			cmp.AllowUnexported(ts.ParentStructF{}),
   993  		},
   994  		wantPanic: "cannot handle unexported field",
   995  	}, {
   996  		label: label + "ParentStructF",
   997  		x:     createStructF(0),
   998  		y:     createStructF(0),
   999  		opts: []cmp.Option{
  1000  			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
  1001  		},
  1002  		wantPanic: "cannot handle unexported field",
  1003  	}, {
  1004  		label: label + "ParentStructF",
  1005  		x:     createStructF(0),
  1006  		y:     createStructF(0),
  1007  		opts: []cmp.Option{
  1008  			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
  1009  		},
  1010  	}, {
  1011  		label: label + "ParentStructF",
  1012  		x:     createStructF(0),
  1013  		y:     createStructF(1),
  1014  		opts: []cmp.Option{
  1015  			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
  1016  		},
  1017  		wantDiff: `
  1018  {teststructs.ParentStructF}.privateStruct.Public:
  1019  	-: 1
  1020  	+: 2
  1021  {teststructs.ParentStructF}.privateStruct.private:
  1022  	-: 2
  1023  	+: 3
  1024  {teststructs.ParentStructF}.PublicStruct.Public:
  1025  	-: 3
  1026  	+: 4
  1027  {teststructs.ParentStructF}.PublicStruct.private:
  1028  	-: 4
  1029  	+: 5
  1030  {teststructs.ParentStructF}.Public:
  1031  	-: 5
  1032  	+: 6
  1033  {teststructs.ParentStructF}.private:
  1034  	-: 6
  1035  	+: 7`,
  1036  	}, {
  1037  		label:     label + "ParentStructG",
  1038  		x:         ts.ParentStructG{},
  1039  		y:         ts.ParentStructG{},
  1040  		wantPanic: "cannot handle unexported field",
  1041  	}, {
  1042  		label: label + "ParentStructG",
  1043  		x:     ts.ParentStructG{},
  1044  		y:     ts.ParentStructG{},
  1045  		opts: []cmp.Option{
  1046  			cmpopts.IgnoreUnexported(ts.ParentStructG{}),
  1047  		},
  1048  	}, {
  1049  		label: label + "ParentStructG",
  1050  		x:     createStructG(0),
  1051  		y:     createStructG(0),
  1052  		opts: []cmp.Option{
  1053  			cmp.AllowUnexported(ts.ParentStructG{}),
  1054  		},
  1055  		wantPanic: "cannot handle unexported field",
  1056  	}, {
  1057  		label: label + "ParentStructG",
  1058  		x:     createStructG(0),
  1059  		y:     createStructG(0),
  1060  		opts: []cmp.Option{
  1061  			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
  1062  		},
  1063  	}, {
  1064  		label: label + "ParentStructG",
  1065  		x:     createStructG(0),
  1066  		y:     createStructG(1),
  1067  		opts: []cmp.Option{
  1068  			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
  1069  		},
  1070  		wantDiff: `
  1071  {*teststructs.ParentStructG}.privateStruct.Public:
  1072  	-: 1
  1073  	+: 2
  1074  {*teststructs.ParentStructG}.privateStruct.private:
  1075  	-: 2
  1076  	+: 3`,
  1077  	}, {
  1078  		label: label + "ParentStructH",
  1079  		x:     ts.ParentStructH{},
  1080  		y:     ts.ParentStructH{},
  1081  	}, {
  1082  		label:     label + "ParentStructH",
  1083  		x:         createStructH(0),
  1084  		y:         createStructH(0),
  1085  		wantPanic: "cannot handle unexported field",
  1086  	}, {
  1087  		label: label + "ParentStructH",
  1088  		x:     ts.ParentStructH{},
  1089  		y:     ts.ParentStructH{},
  1090  		opts: []cmp.Option{
  1091  			cmpopts.IgnoreUnexported(ts.ParentStructH{}),
  1092  		},
  1093  	}, {
  1094  		label: label + "ParentStructH",
  1095  		x:     createStructH(0),
  1096  		y:     createStructH(0),
  1097  		opts: []cmp.Option{
  1098  			cmp.AllowUnexported(ts.ParentStructH{}),
  1099  		},
  1100  		wantPanic: "cannot handle unexported field",
  1101  	}, {
  1102  		label: label + "ParentStructH",
  1103  		x:     createStructH(0),
  1104  		y:     createStructH(0),
  1105  		opts: []cmp.Option{
  1106  			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
  1107  		},
  1108  	}, {
  1109  		label: label + "ParentStructH",
  1110  		x:     createStructH(0),
  1111  		y:     createStructH(1),
  1112  		opts: []cmp.Option{
  1113  			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
  1114  		},
  1115  		wantDiff: `
  1116  {*teststructs.ParentStructH}.PublicStruct.Public:
  1117  	-: 1
  1118  	+: 2
  1119  {*teststructs.ParentStructH}.PublicStruct.private:
  1120  	-: 2
  1121  	+: 3`,
  1122  	}, {
  1123  		label:     label + "ParentStructI",
  1124  		x:         ts.ParentStructI{},
  1125  		y:         ts.ParentStructI{},
  1126  		wantPanic: "cannot handle unexported field",
  1127  	}, {
  1128  		label: label + "ParentStructI",
  1129  		x:     ts.ParentStructI{},
  1130  		y:     ts.ParentStructI{},
  1131  		opts: []cmp.Option{
  1132  			cmpopts.IgnoreUnexported(ts.ParentStructI{}),
  1133  		},
  1134  	}, {
  1135  		label: label + "ParentStructI",
  1136  		x:     createStructI(0),
  1137  		y:     createStructI(0),
  1138  		opts: []cmp.Option{
  1139  			cmpopts.IgnoreUnexported(ts.ParentStructI{}),
  1140  		},
  1141  		wantPanic: "cannot handle unexported field",
  1142  	}, {
  1143  		label: label + "ParentStructI",
  1144  		x:     createStructI(0),
  1145  		y:     createStructI(0),
  1146  		opts: []cmp.Option{
  1147  			cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
  1148  		},
  1149  	}, {
  1150  		label: label + "ParentStructI",
  1151  		x:     createStructI(0),
  1152  		y:     createStructI(0),
  1153  		opts: []cmp.Option{
  1154  			cmp.AllowUnexported(ts.ParentStructI{}),
  1155  		},
  1156  		wantPanic: "cannot handle unexported field",
  1157  	}, {
  1158  		label: label + "ParentStructI",
  1159  		x:     createStructI(0),
  1160  		y:     createStructI(0),
  1161  		opts: []cmp.Option{
  1162  			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
  1163  		},
  1164  	}, {
  1165  		label: label + "ParentStructI",
  1166  		x:     createStructI(0),
  1167  		y:     createStructI(1),
  1168  		opts: []cmp.Option{
  1169  			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
  1170  		},
  1171  		wantDiff: `
  1172  {*teststructs.ParentStructI}.privateStruct.Public:
  1173  	-: 1
  1174  	+: 2
  1175  {*teststructs.ParentStructI}.privateStruct.private:
  1176  	-: 2
  1177  	+: 3
  1178  {*teststructs.ParentStructI}.PublicStruct.Public:
  1179  	-: 3
  1180  	+: 4
  1181  {*teststructs.ParentStructI}.PublicStruct.private:
  1182  	-: 4
  1183  	+: 5`,
  1184  	}, {
  1185  		label:     label + "ParentStructJ",
  1186  		x:         ts.ParentStructJ{},
  1187  		y:         ts.ParentStructJ{},
  1188  		wantPanic: "cannot handle unexported field",
  1189  	}, {
  1190  		label: label + "ParentStructJ",
  1191  		x:     ts.ParentStructJ{},
  1192  		y:     ts.ParentStructJ{},
  1193  		opts: []cmp.Option{
  1194  			cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
  1195  		},
  1196  		wantPanic: "cannot handle unexported field",
  1197  	}, {
  1198  		label: label + "ParentStructJ",
  1199  		x:     ts.ParentStructJ{},
  1200  		y:     ts.ParentStructJ{},
  1201  		opts: []cmp.Option{
  1202  			cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
  1203  		},
  1204  	}, {
  1205  		label: label + "ParentStructJ",
  1206  		x:     createStructJ(0),
  1207  		y:     createStructJ(0),
  1208  		opts: []cmp.Option{
  1209  			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
  1210  		},
  1211  		wantPanic: "cannot handle unexported field",
  1212  	}, {
  1213  		label: label + "ParentStructJ",
  1214  		x:     createStructJ(0),
  1215  		y:     createStructJ(0),
  1216  		opts: []cmp.Option{
  1217  			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
  1218  		},
  1219  	}, {
  1220  		label: label + "ParentStructJ",
  1221  		x:     createStructJ(0),
  1222  		y:     createStructJ(1),
  1223  		opts: []cmp.Option{
  1224  			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
  1225  		},
  1226  		wantDiff: `
  1227  {*teststructs.ParentStructJ}.privateStruct.Public:
  1228  	-: 1
  1229  	+: 2
  1230  {*teststructs.ParentStructJ}.privateStruct.private:
  1231  	-: 2
  1232  	+: 3
  1233  {*teststructs.ParentStructJ}.PublicStruct.Public:
  1234  	-: 3
  1235  	+: 4
  1236  {*teststructs.ParentStructJ}.PublicStruct.private:
  1237  	-: 4
  1238  	+: 5
  1239  {*teststructs.ParentStructJ}.Public.Public:
  1240  	-: 7
  1241  	+: 8
  1242  {*teststructs.ParentStructJ}.Public.private:
  1243  	-: 8
  1244  	+: 9
  1245  {*teststructs.ParentStructJ}.private.Public:
  1246  	-: 5
  1247  	+: 6
  1248  {*teststructs.ParentStructJ}.private.private:
  1249  	-: 6
  1250  	+: 7`,
  1251  	}}
  1252  }
  1253  
  1254  func methodTests() []test {
  1255  	const label = "EqualMethod/"
  1256  
  1257  	// A common mistake that the Equal method is on a pointer receiver,
  1258  	// but only a non-pointer value is present in the struct.
  1259  	// A transform can be used to forcibly reference the value.
  1260  	derefTransform := cmp.FilterPath(func(p cmp.Path) bool {
  1261  		if len(p) == 0 {
  1262  			return false
  1263  		}
  1264  		t := p[len(p)-1].Type()
  1265  		if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
  1266  			return false
  1267  		}
  1268  		if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
  1269  			tf := m.Func.Type()
  1270  			return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
  1271  				tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
  1272  		}
  1273  		return false
  1274  	}, cmp.Transformer("Ref", func(x interface{}) interface{} {
  1275  		v := reflect.ValueOf(x)
  1276  		vp := reflect.New(v.Type())
  1277  		vp.Elem().Set(v)
  1278  		return vp.Interface()
  1279  	}))
  1280  
  1281  	// For each of these types, there is an Equal method defined, which always
  1282  	// returns true, while the underlying data are fundamentally different.
  1283  	// Since the method should be called, these are expected to be equal.
  1284  	return []test{{
  1285  		label: label + "StructA",
  1286  		x:     ts.StructA{X: "NotEqual"},
  1287  		y:     ts.StructA{X: "not_equal"},
  1288  	}, {
  1289  		label: label + "StructA",
  1290  		x:     &ts.StructA{X: "NotEqual"},
  1291  		y:     &ts.StructA{X: "not_equal"},
  1292  	}, {
  1293  		label: label + "StructB",
  1294  		x:     ts.StructB{X: "NotEqual"},
  1295  		y:     ts.StructB{X: "not_equal"},
  1296  		wantDiff: `
  1297  {teststructs.StructB}.X:
  1298  	-: "NotEqual"
  1299  	+: "not_equal"`,
  1300  	}, {
  1301  		label: label + "StructB",
  1302  		x:     ts.StructB{X: "NotEqual"},
  1303  		y:     ts.StructB{X: "not_equal"},
  1304  		opts:  []cmp.Option{derefTransform},
  1305  	}, {
  1306  		label: label + "StructB",
  1307  		x:     &ts.StructB{X: "NotEqual"},
  1308  		y:     &ts.StructB{X: "not_equal"},
  1309  	}, {
  1310  		label: label + "StructC",
  1311  		x:     ts.StructC{X: "NotEqual"},
  1312  		y:     ts.StructC{X: "not_equal"},
  1313  	}, {
  1314  		label: label + "StructC",
  1315  		x:     &ts.StructC{X: "NotEqual"},
  1316  		y:     &ts.StructC{X: "not_equal"},
  1317  	}, {
  1318  		label: label + "StructD",
  1319  		x:     ts.StructD{X: "NotEqual"},
  1320  		y:     ts.StructD{X: "not_equal"},
  1321  		wantDiff: `
  1322  {teststructs.StructD}.X:
  1323  	-: "NotEqual"
  1324  	+: "not_equal"`,
  1325  	}, {
  1326  		label: label + "StructD",
  1327  		x:     ts.StructD{X: "NotEqual"},
  1328  		y:     ts.StructD{X: "not_equal"},
  1329  		opts:  []cmp.Option{derefTransform},
  1330  	}, {
  1331  		label: label + "StructD",
  1332  		x:     &ts.StructD{X: "NotEqual"},
  1333  		y:     &ts.StructD{X: "not_equal"},
  1334  	}, {
  1335  		label: label + "StructE",
  1336  		x:     ts.StructE{X: "NotEqual"},
  1337  		y:     ts.StructE{X: "not_equal"},
  1338  		wantDiff: `
  1339  {teststructs.StructE}.X:
  1340  	-: "NotEqual"
  1341  	+: "not_equal"`,
  1342  	}, {
  1343  		label: label + "StructE",
  1344  		x:     ts.StructE{X: "NotEqual"},
  1345  		y:     ts.StructE{X: "not_equal"},
  1346  		opts:  []cmp.Option{derefTransform},
  1347  	}, {
  1348  		label: label + "StructE",
  1349  		x:     &ts.StructE{X: "NotEqual"},
  1350  		y:     &ts.StructE{X: "not_equal"},
  1351  	}, {
  1352  		label: label + "StructF",
  1353  		x:     ts.StructF{X: "NotEqual"},
  1354  		y:     ts.StructF{X: "not_equal"},
  1355  		wantDiff: `
  1356  {teststructs.StructF}.X:
  1357  	-: "NotEqual"
  1358  	+: "not_equal"`,
  1359  	}, {
  1360  		label: label + "StructF",
  1361  		x:     &ts.StructF{X: "NotEqual"},
  1362  		y:     &ts.StructF{X: "not_equal"},
  1363  	}, {
  1364  		label: label + "StructA1",
  1365  		x:     ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
  1366  		y:     ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
  1367  	}, {
  1368  		label:    label + "StructA1",
  1369  		x:        ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  1370  		y:        ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
  1371  		wantDiff: "{teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1372  	}, {
  1373  		label: label + "StructA1",
  1374  		x:     &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
  1375  		y:     &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
  1376  	}, {
  1377  		label:    label + "StructA1",
  1378  		x:        &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  1379  		y:        &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
  1380  		wantDiff: "{*teststructs.StructA1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1381  	}, {
  1382  		label: label + "StructB1",
  1383  		x:     ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
  1384  		y:     ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
  1385  		opts:  []cmp.Option{derefTransform},
  1386  	}, {
  1387  		label:    label + "StructB1",
  1388  		x:        ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  1389  		y:        ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
  1390  		opts:     []cmp.Option{derefTransform},
  1391  		wantDiff: "{teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1392  	}, {
  1393  		label: label + "StructB1",
  1394  		x:     &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
  1395  		y:     &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
  1396  		opts:  []cmp.Option{derefTransform},
  1397  	}, {
  1398  		label:    label + "StructB1",
  1399  		x:        &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  1400  		y:        &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
  1401  		opts:     []cmp.Option{derefTransform},
  1402  		wantDiff: "{*teststructs.StructB1}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1403  	}, {
  1404  		label: label + "StructC1",
  1405  		x:     ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  1406  		y:     ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
  1407  	}, {
  1408  		label: label + "StructC1",
  1409  		x:     &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  1410  		y:     &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
  1411  	}, {
  1412  		label: label + "StructD1",
  1413  		x:     ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  1414  		y:     ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  1415  		wantDiff: `
  1416  {teststructs.StructD1}.StructD.X:
  1417  	-: "NotEqual"
  1418  	+: "not_equal"
  1419  {teststructs.StructD1}.X:
  1420  	-: "NotEqual"
  1421  	+: "not_equal"`,
  1422  	}, {
  1423  		label: label + "StructD1",
  1424  		x:     ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  1425  		y:     ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  1426  		opts:  []cmp.Option{derefTransform},
  1427  	}, {
  1428  		label: label + "StructD1",
  1429  		x:     &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  1430  		y:     &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
  1431  	}, {
  1432  		label: label + "StructE1",
  1433  		x:     ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  1434  		y:     ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  1435  		wantDiff: `
  1436  {teststructs.StructE1}.StructE.X:
  1437  	-: "NotEqual"
  1438  	+: "not_equal"
  1439  {teststructs.StructE1}.X:
  1440  	-: "NotEqual"
  1441  	+: "not_equal"`,
  1442  	}, {
  1443  		label: label + "StructE1",
  1444  		x:     ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  1445  		y:     ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  1446  		opts:  []cmp.Option{derefTransform},
  1447  	}, {
  1448  		label: label + "StructE1",
  1449  		x:     &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  1450  		y:     &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
  1451  	}, {
  1452  		label: label + "StructF1",
  1453  		x:     ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  1454  		y:     ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
  1455  		wantDiff: `
  1456  {teststructs.StructF1}.StructF.X:
  1457  	-: "NotEqual"
  1458  	+: "not_equal"
  1459  {teststructs.StructF1}.X:
  1460  	-: "NotEqual"
  1461  	+: "not_equal"`,
  1462  	}, {
  1463  		label: label + "StructF1",
  1464  		x:     &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  1465  		y:     &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
  1466  	}, {
  1467  		label: label + "StructA2",
  1468  		x:     ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
  1469  		y:     ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
  1470  	}, {
  1471  		label:    label + "StructA2",
  1472  		x:        ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  1473  		y:        ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
  1474  		wantDiff: "{teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1475  	}, {
  1476  		label: label + "StructA2",
  1477  		x:     &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
  1478  		y:     &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
  1479  	}, {
  1480  		label:    label + "StructA2",
  1481  		x:        &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
  1482  		y:        &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
  1483  		wantDiff: "{*teststructs.StructA2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1484  	}, {
  1485  		label: label + "StructB2",
  1486  		x:     ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
  1487  		y:     ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
  1488  	}, {
  1489  		label:    label + "StructB2",
  1490  		x:        ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  1491  		y:        ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
  1492  		wantDiff: "{teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1493  	}, {
  1494  		label: label + "StructB2",
  1495  		x:     &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
  1496  		y:     &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
  1497  	}, {
  1498  		label:    label + "StructB2",
  1499  		x:        &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
  1500  		y:        &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
  1501  		wantDiff: "{*teststructs.StructB2}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1502  	}, {
  1503  		label: label + "StructC2",
  1504  		x:     ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  1505  		y:     ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
  1506  	}, {
  1507  		label: label + "StructC2",
  1508  		x:     &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
  1509  		y:     &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
  1510  	}, {
  1511  		label: label + "StructD2",
  1512  		x:     ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  1513  		y:     ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
  1514  	}, {
  1515  		label: label + "StructD2",
  1516  		x:     &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
  1517  		y:     &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
  1518  	}, {
  1519  		label: label + "StructE2",
  1520  		x:     ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  1521  		y:     ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
  1522  	}, {
  1523  		label: label + "StructE2",
  1524  		x:     &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
  1525  		y:     &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
  1526  	}, {
  1527  		label: label + "StructF2",
  1528  		x:     ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  1529  		y:     ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
  1530  	}, {
  1531  		label: label + "StructF2",
  1532  		x:     &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
  1533  		y:     &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
  1534  	}, {
  1535  		label:    label + "StructNo",
  1536  		x:        ts.StructNo{X: "NotEqual"},
  1537  		y:        ts.StructNo{X: "not_equal"},
  1538  		wantDiff: "{teststructs.StructNo}.X:\n\t-: \"NotEqual\"\n\t+: \"not_equal\"\n",
  1539  	}, {
  1540  		label: label + "AssignA",
  1541  		x:     ts.AssignA(func() int { return 0 }),
  1542  		y:     ts.AssignA(func() int { return 1 }),
  1543  	}, {
  1544  		label: label + "AssignB",
  1545  		x:     ts.AssignB(struct{ A int }{0}),
  1546  		y:     ts.AssignB(struct{ A int }{1}),
  1547  	}, {
  1548  		label: label + "AssignC",
  1549  		x:     ts.AssignC(make(chan bool)),
  1550  		y:     ts.AssignC(make(chan bool)),
  1551  	}, {
  1552  		label: label + "AssignD",
  1553  		x:     ts.AssignD(make(chan bool)),
  1554  		y:     ts.AssignD(make(chan bool)),
  1555  	}}
  1556  }
  1557  
  1558  func project1Tests() []test {
  1559  	const label = "Project1"
  1560  
  1561  	ignoreUnexported := cmpopts.IgnoreUnexported(
  1562  		ts.EagleImmutable{},
  1563  		ts.DreamerImmutable{},
  1564  		ts.SlapImmutable{},
  1565  		ts.GoatImmutable{},
  1566  		ts.DonkeyImmutable{},
  1567  		ts.LoveRadius{},
  1568  		ts.SummerLove{},
  1569  		ts.SummerLoveSummary{},
  1570  	)
  1571  
  1572  	createEagle := func() ts.Eagle {
  1573  		return ts.Eagle{
  1574  			Name:   "eagle",
  1575  			Hounds: []string{"buford", "tannen"},
  1576  			Desc:   "some description",
  1577  			Dreamers: []ts.Dreamer{{}, {
  1578  				Name: "dreamer2",
  1579  				Animal: []interface{}{
  1580  					ts.Goat{
  1581  						Target: "corporation",
  1582  						Immutable: &ts.GoatImmutable{
  1583  							ID:      "southbay",
  1584  							State:   (*pb.Goat_States)(intPtr(5)),
  1585  							Started: now,
  1586  						},
  1587  					},
  1588  					ts.Donkey{},
  1589  				},
  1590  				Amoeba: 53,
  1591  			}},
  1592  			Slaps: []ts.Slap{{
  1593  				Name: "slapID",
  1594  				Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  1595  				Immutable: &ts.SlapImmutable{
  1596  					ID:       "immutableSlap",
  1597  					MildSlap: true,
  1598  					Started:  now,
  1599  					LoveRadius: &ts.LoveRadius{
  1600  						Summer: &ts.SummerLove{
  1601  							Summary: &ts.SummerLoveSummary{
  1602  								Devices:    []string{"foo", "bar", "baz"},
  1603  								ChangeType: []pb.SummerType{1, 2, 3},
  1604  							},
  1605  						},
  1606  					},
  1607  				},
  1608  			}},
  1609  			Immutable: &ts.EagleImmutable{
  1610  				ID:          "eagleID",
  1611  				Birthday:    now,
  1612  				MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)),
  1613  			},
  1614  		}
  1615  	}
  1616  
  1617  	return []test{{
  1618  		label: label,
  1619  		x: ts.Eagle{Slaps: []ts.Slap{{
  1620  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  1621  		}}},
  1622  		y: ts.Eagle{Slaps: []ts.Slap{{
  1623  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  1624  		}}},
  1625  		wantPanic: "cannot handle unexported field",
  1626  	}, {
  1627  		label: label,
  1628  		x: ts.Eagle{Slaps: []ts.Slap{{
  1629  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  1630  		}}},
  1631  		y: ts.Eagle{Slaps: []ts.Slap{{
  1632  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  1633  		}}},
  1634  		opts: []cmp.Option{cmp.Comparer(pb.Equal)},
  1635  	}, {
  1636  		label: label,
  1637  		x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
  1638  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
  1639  		}}},
  1640  		y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
  1641  			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
  1642  		}}},
  1643  		opts:     []cmp.Option{cmp.Comparer(pb.Equal)},
  1644  		wantDiff: "{teststructs.Eagle}.Slaps[4].Args:\n\t-: s\"metadata\"\n\t+: s\"metadata2\"\n",
  1645  	}, {
  1646  		label: label,
  1647  		x:     createEagle(),
  1648  		y:     createEagle(),
  1649  		opts:  []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
  1650  	}, {
  1651  		label: label,
  1652  		x: func() ts.Eagle {
  1653  			eg := createEagle()
  1654  			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
  1655  			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6))
  1656  			eg.Slaps[0].Immutable.MildSlap = false
  1657  			return eg
  1658  		}(),
  1659  		y: func() ts.Eagle {
  1660  			eg := createEagle()
  1661  			devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
  1662  			eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
  1663  			return eg
  1664  		}(),
  1665  		opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
  1666  		wantDiff: `
  1667  {teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.ID:
  1668  	-: "southbay2"
  1669  	+: "southbay"
  1670  *{teststructs.Eagle}.Dreamers[1].Animal[0].(teststructs.Goat).Immutable.State:
  1671  	-: testprotos.Goat_States(6)
  1672  	+: testprotos.Goat_States(5)
  1673  {teststructs.Eagle}.Slaps[0].Immutable.MildSlap:
  1674  	-: false
  1675  	+: true
  1676  {teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[1->?]:
  1677  	-: "bar"
  1678  	+: <non-existent>
  1679  {teststructs.Eagle}.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices[2->?]:
  1680  	-: "baz"
  1681  	+: <non-existent>`,
  1682  	}}
  1683  }
  1684  
  1685  type germSorter []*pb.Germ
  1686  
  1687  func (gs germSorter) Len() int           { return len(gs) }
  1688  func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
  1689  func (gs germSorter) Swap(i, j int)      { gs[i], gs[j] = gs[j], gs[i] }
  1690  
  1691  func project2Tests() []test {
  1692  	const label = "Project2"
  1693  
  1694  	sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
  1695  		out := append([]*pb.Germ(nil), in...) // Make copy
  1696  		sort.Sort(germSorter(out))
  1697  		return out
  1698  	})
  1699  
  1700  	equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
  1701  		if x == nil || y == nil {
  1702  			return x == nil && y == nil
  1703  		}
  1704  		px, err1 := x.Proto()
  1705  		py, err2 := y.Proto()
  1706  		if err1 != nil || err2 != nil {
  1707  			return err1 == err2
  1708  		}
  1709  		return pb.Equal(px, py)
  1710  	})
  1711  
  1712  	createBatch := func() ts.GermBatch {
  1713  		return ts.GermBatch{
  1714  			DirtyGerms: map[int32][]*pb.Germ{
  1715  				17: {
  1716  					{Stringer: pb.Stringer{X: "germ1"}},
  1717  				},
  1718  				18: {
  1719  					{Stringer: pb.Stringer{X: "germ2"}},
  1720  					{Stringer: pb.Stringer{X: "germ3"}},
  1721  					{Stringer: pb.Stringer{X: "germ4"}},
  1722  				},
  1723  			},
  1724  			GermMap: map[int32]*pb.Germ{
  1725  				13: {Stringer: pb.Stringer{X: "germ13"}},
  1726  				21: {Stringer: pb.Stringer{X: "germ21"}},
  1727  			},
  1728  			DishMap: map[int32]*ts.Dish{
  1729  				0: ts.CreateDish(nil, io.EOF),
  1730  				1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
  1731  				2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
  1732  			},
  1733  			HasPreviousResult: true,
  1734  			DirtyID:           10,
  1735  			GermStrain:        421,
  1736  			InfectedAt:        now,
  1737  		}
  1738  	}
  1739  
  1740  	return []test{{
  1741  		label:     label,
  1742  		x:         createBatch(),
  1743  		y:         createBatch(),
  1744  		wantPanic: "cannot handle unexported field",
  1745  	}, {
  1746  		label: label,
  1747  		x:     createBatch(),
  1748  		y:     createBatch(),
  1749  		opts:  []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  1750  	}, {
  1751  		label: label,
  1752  		x:     createBatch(),
  1753  		y: func() ts.GermBatch {
  1754  			gb := createBatch()
  1755  			s := gb.DirtyGerms[18]
  1756  			s[0], s[1], s[2] = s[1], s[2], s[0]
  1757  			return gb
  1758  		}(),
  1759  		opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
  1760  		wantDiff: `
  1761  {teststructs.GermBatch}.DirtyGerms[18][0->?]:
  1762  	-: s"germ2"
  1763  	+: <non-existent>
  1764  {teststructs.GermBatch}.DirtyGerms[18][?->2]:
  1765  	-: <non-existent>
  1766  	+: s"germ2"`,
  1767  	}, {
  1768  		label: label,
  1769  		x:     createBatch(),
  1770  		y: func() ts.GermBatch {
  1771  			gb := createBatch()
  1772  			s := gb.DirtyGerms[18]
  1773  			s[0], s[1], s[2] = s[1], s[2], s[0]
  1774  			return gb
  1775  		}(),
  1776  		opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  1777  	}, {
  1778  		label: label,
  1779  		x: func() ts.GermBatch {
  1780  			gb := createBatch()
  1781  			delete(gb.DirtyGerms, 17)
  1782  			gb.DishMap[1] = nil
  1783  			return gb
  1784  		}(),
  1785  		y: func() ts.GermBatch {
  1786  			gb := createBatch()
  1787  			gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
  1788  			gb.GermStrain = 22
  1789  			return gb
  1790  		}(),
  1791  		opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
  1792  		wantDiff: `
  1793  {teststructs.GermBatch}.DirtyGerms[17]:
  1794  	-: <non-existent>
  1795  	+: []*testprotos.Germ{s"germ1"}
  1796  Sort({teststructs.GermBatch}.DirtyGerms[18])[2->?]:
  1797  	-: s"germ4"
  1798  	+: <non-existent>
  1799  {teststructs.GermBatch}.DishMap[1]:
  1800  	-: (*teststructs.Dish)(nil)
  1801  	+: &teststructs.Dish{err: &errors.errorString{s: "unexpected EOF"}}
  1802  {teststructs.GermBatch}.GermStrain:
  1803  	-: 421
  1804  	+: 22`,
  1805  	}}
  1806  }
  1807  
  1808  func project3Tests() []test {
  1809  	const label = "Project3"
  1810  
  1811  	allowVisibility := cmp.AllowUnexported(ts.Dirt{})
  1812  
  1813  	ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})
  1814  
  1815  	transformProtos := cmp.Transformer("", func(x pb.Dirt) *pb.Dirt {
  1816  		return &x
  1817  	})
  1818  
  1819  	equalTable := cmp.Comparer(func(x, y ts.Table) bool {
  1820  		tx, ok1 := x.(*ts.MockTable)
  1821  		ty, ok2 := y.(*ts.MockTable)
  1822  		if !ok1 || !ok2 {
  1823  			panic("table type must be MockTable")
  1824  		}
  1825  		return cmp.Equal(tx.State(), ty.State())
  1826  	})
  1827  
  1828  	createDirt := func() (d ts.Dirt) {
  1829  		d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
  1830  		d.SetTimestamp(12345)
  1831  		d.Discord = 554
  1832  		d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
  1833  		d.SetWizard(map[string]*pb.Wizard{
  1834  			"harry": {Stringer: pb.Stringer{X: "potter"}},
  1835  			"albus": {Stringer: pb.Stringer{X: "dumbledore"}},
  1836  		})
  1837  		d.SetLastTime(54321)
  1838  		return d
  1839  	}
  1840  
  1841  	return []test{{
  1842  		label:     label,
  1843  		x:         createDirt(),
  1844  		y:         createDirt(),
  1845  		wantPanic: "cannot handle unexported field",
  1846  	}, {
  1847  		label:     label,
  1848  		x:         createDirt(),
  1849  		y:         createDirt(),
  1850  		opts:      []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  1851  		wantPanic: "cannot handle unexported field",
  1852  	}, {
  1853  		label: label,
  1854  		x:     createDirt(),
  1855  		y:     createDirt(),
  1856  		opts:  []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  1857  	}, {
  1858  		label: label,
  1859  		x: func() ts.Dirt {
  1860  			d := createDirt()
  1861  			d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
  1862  			d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
  1863  			return d
  1864  		}(),
  1865  		y: func() ts.Dirt {
  1866  			d := createDirt()
  1867  			d.Discord = 500
  1868  			d.SetWizard(map[string]*pb.Wizard{
  1869  				"harry": {Stringer: pb.Stringer{X: "otter"}},
  1870  			})
  1871  			return d
  1872  		}(),
  1873  		opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
  1874  		wantDiff: `
  1875  {teststructs.Dirt}.table:
  1876  	-: &teststructs.MockTable{state: []string{"a", "c"}}
  1877  	+: &teststructs.MockTable{state: []string{"a", "b", "c"}}
  1878  {teststructs.Dirt}.Discord:
  1879  	-: teststructs.DiscordState(554)
  1880  	+: teststructs.DiscordState(500)
  1881  λ({teststructs.Dirt}.Proto):
  1882  	-: s"blah"
  1883  	+: s"proto"
  1884  {teststructs.Dirt}.wizard["albus"]:
  1885  	-: s"dumbledore"
  1886  	+: <non-existent>
  1887  {teststructs.Dirt}.wizard["harry"]:
  1888  	-: s"potter"
  1889  	+: s"otter"`,
  1890  	}}
  1891  }
  1892  
  1893  func project4Tests() []test {
  1894  	const label = "Project4"
  1895  
  1896  	allowVisibility := cmp.AllowUnexported(
  1897  		ts.Cartel{},
  1898  		ts.Headquarter{},
  1899  		ts.Poison{},
  1900  	)
  1901  
  1902  	transformProtos := cmp.Transformer("", func(x pb.Restrictions) *pb.Restrictions {
  1903  		return &x
  1904  	})
  1905  
  1906  	createCartel := func() ts.Cartel {
  1907  		var p ts.Poison
  1908  		p.SetPoisonType(5)
  1909  		p.SetExpiration(now)
  1910  		p.SetManufacturer("acme")
  1911  
  1912  		var hq ts.Headquarter
  1913  		hq.SetID(5)
  1914  		hq.SetLocation("moon")
  1915  		hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
  1916  		hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
  1917  		hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
  1918  		hq.SetHorseBack("abcdef")
  1919  		hq.SetStatus(44)
  1920  
  1921  		var c ts.Cartel
  1922  		c.Headquarter = hq
  1923  		c.SetSource("mars")
  1924  		c.SetCreationTime(now)
  1925  		c.SetBoss("al capone")
  1926  		c.SetPoisons([]*ts.Poison{&p})
  1927  
  1928  		return c
  1929  	}
  1930  
  1931  	return []test{{
  1932  		label:     label,
  1933  		x:         createCartel(),
  1934  		y:         createCartel(),
  1935  		wantPanic: "cannot handle unexported field",
  1936  	}, {
  1937  		label:     label,
  1938  		x:         createCartel(),
  1939  		y:         createCartel(),
  1940  		opts:      []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
  1941  		wantPanic: "cannot handle unexported field",
  1942  	}, {
  1943  		label: label,
  1944  		x:     createCartel(),
  1945  		y:     createCartel(),
  1946  		opts:  []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
  1947  	}, {
  1948  		label: label,
  1949  		x: func() ts.Cartel {
  1950  			d := createCartel()
  1951  			var p1, p2 ts.Poison
  1952  			p1.SetPoisonType(1)
  1953  			p1.SetExpiration(now)
  1954  			p1.SetManufacturer("acme")
  1955  			p2.SetPoisonType(2)
  1956  			p2.SetManufacturer("acme2")
  1957  			d.SetPoisons([]*ts.Poison{&p1, &p2})
  1958  			return d
  1959  		}(),
  1960  		y: func() ts.Cartel {
  1961  			d := createCartel()
  1962  			d.SetSubDivisions([]string{"bravo", "charlie"})
  1963  			d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
  1964  			return d
  1965  		}(),
  1966  		opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
  1967  		wantDiff: `
  1968  {teststructs.Cartel}.Headquarter.subDivisions[0->?]:
  1969  	-: "alpha"
  1970  	+: <non-existent>
  1971  {teststructs.Cartel}.Headquarter.publicMessage[2]:
  1972  	-: 0x03
  1973  	+: 0x04
  1974  {teststructs.Cartel}.Headquarter.publicMessage[3]:
  1975  	-: 0x04
  1976  	+: 0x03
  1977  {teststructs.Cartel}.poisons[0].poisonType:
  1978  	-: testprotos.PoisonType(1)
  1979  	+: testprotos.PoisonType(5)
  1980  {teststructs.Cartel}.poisons[1->?]:
  1981  	-: &teststructs.Poison{poisonType: testprotos.PoisonType(2), manufacturer: "acme2"}
  1982  	+: <non-existent>`,
  1983  	}}
  1984  }