// Copyright 2017, The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE.md file.

package cmp_test

import (
	"bytes"
	"crypto/md5"
	"encoding/json"
	"fmt"
	"io"
	"math"
	"math/rand"
	"reflect"
	"regexp"
	"sort"
	"strconv"
	"strings"
	"sync"
	"testing"
	"time"

	"github.com/google/go-cmp/cmp"
	"github.com/google/go-cmp/cmp/cmpopts"
	"github.com/google/go-cmp/cmp/internal/flags"

	pb "github.com/google/go-cmp/cmp/internal/testprotos"
	ts "github.com/google/go-cmp/cmp/internal/teststructs"
)

func init() {
	flags.Deterministic = true
}

var now = time.Date(2009, time.November, 10, 23, 00, 00, 00, time.UTC)

func intPtr(n int) *int { return &n }

type test struct {
	label     string       // Test name
	x, y      interface{}  // Input values to compare
	opts      []cmp.Option // Input options
	wantDiff  string       // The exact difference string
	wantPanic string       // Sub-string of an expected panic message
	reason    string       // The reason for the expected outcome
}

func TestDiff(t *testing.T) {
	var tests []test
	tests = append(tests, comparerTests()...)
	tests = append(tests, transformerTests()...)
	tests = append(tests, embeddedTests()...)
	tests = append(tests, methodTests()...)
	tests = append(tests, cycleTests()...)
	tests = append(tests, project1Tests()...)
	tests = append(tests, project2Tests()...)
	tests = append(tests, project3Tests()...)
	tests = append(tests, project4Tests()...)

	for _, tt := range tests {
		tt := tt
		t.Run(tt.label, func(t *testing.T) {
			t.Parallel()
			var gotDiff, gotPanic string
			func() {
				defer func() {
					if ex := recover(); ex != nil {
						if s, ok := ex.(string); ok {
							gotPanic = s
						} else {
							panic(ex)
						}
					}
				}()
				gotDiff = cmp.Diff(tt.x, tt.y, tt.opts...)
			}()
			// TODO: Require every test case to provide a reason.
			if tt.wantPanic == "" {
				if gotPanic != "" {
					t.Fatalf("unexpected panic message: %s\nreason: %v", gotPanic, tt.reason)
				}
				tt.wantDiff = strings.TrimPrefix(tt.wantDiff, "\n")
				if gotDiff != tt.wantDiff {
					t.Fatalf("difference message:\ngot:\n%s\nwant:\n%s\nreason: %v", gotDiff, tt.wantDiff, tt.reason)
				}
			} else {
				if !strings.Contains(gotPanic, tt.wantPanic) {
					t.Fatalf("panic message:\ngot:  %s\nwant: %s\nreason: %v", gotPanic, tt.wantPanic, tt.reason)
				}
			}
		})
	}
}

func comparerTests() []test {
	const label = "Comparer"

	type Iface1 interface {
		Method()
	}
	type Iface2 interface {
		Method()
	}

	type tarHeader struct {
		Name       string
		Mode       int64
		Uid        int
		Gid        int
		Size       int64
		ModTime    time.Time
		Typeflag   byte
		Linkname   string
		Uname      string
		Gname      string
		Devmajor   int64
		Devminor   int64
		AccessTime time.Time
		ChangeTime time.Time
		Xattrs     map[string]string
	}

	type namedWithUnexported struct {
		unexported string
	}

	makeTarHeaders := func(tf byte) (hs []tarHeader) {
		for i := 0; i < 5; i++ {
			hs = append(hs, tarHeader{
				Name: fmt.Sprintf("some/dummy/test/file%d", i),
				Mode: 0664, Uid: i * 1000, Gid: i * 1000, Size: 1 << uint(i),
				ModTime: now.Add(time.Duration(i) * time.Hour),
				Uname:   "user", Gname: "group",
				Typeflag: tf,
			})
		}
		return hs
	}

	return []test{{
		label: label,
		x:     nil,
		y:     nil,
	}, {
		label: label,
		x:     1,
		y:     1,
	}, {
		label:     label,
		x:         1,
		y:         1,
		opts:      []cmp.Option{cmp.Ignore()},
		wantPanic: "cannot use an unfiltered option",
	}, {
		label:     label,
		x:         1,
		y:         1,
		opts:      []cmp.Option{cmp.Comparer(func(_, _ interface{}) bool { return true })},
		wantPanic: "cannot use an unfiltered option",
	}, {
		label:     label,
		x:         1,
		y:         1,
		opts:      []cmp.Option{cmp.Transformer("λ", func(x interface{}) interface{} { return x })},
		wantPanic: "cannot use an unfiltered option",
	}, {
		label: label,
		x:     1,
		y:     1,
		opts: []cmp.Option{
			cmp.Comparer(func(x, y int) bool { return true }),
			cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
		},
		wantPanic: "ambiguous set of applicable options",
	}, {
		label: label,
		x:     1,
		y:     1,
		opts: []cmp.Option{
			cmp.FilterPath(func(p cmp.Path) bool {
				return len(p) > 0 && p[len(p)-1].Type().Kind() == reflect.Int
			}, cmp.Options{cmp.Ignore(), cmp.Ignore(), cmp.Ignore()}),
			cmp.Comparer(func(x, y int) bool { return true }),
			cmp.Transformer("λ", func(x int) float64 { return float64(x) }),
		},
	}, {
		label:     label,
		opts:      []cmp.Option{struct{ cmp.Option }{}},
		wantPanic: "unknown option",
	}, {
		label: label,
		x:     struct{ A, B, C int }{1, 2, 3},
		y:     struct{ A, B, C int }{1, 2, 3},
	}, {
		label: label,
		x:     struct{ A, B, C int }{1, 2, 3},
		y:     struct{ A, B, C int }{1, 2, 4},
		wantDiff: `
  struct{ A int; B int; C int }{
  	A: 1,
  	B: 2,
- 	C: 3,
+ 	C: 4,
  }
`,
	}, {
		label:     label,
		x:         struct{ a, b, c int }{1, 2, 3},
		y:         struct{ a, b, c int }{1, 2, 4},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     &struct{ A *int }{intPtr(4)},
		y:     &struct{ A *int }{intPtr(4)},
	}, {
		label: label,
		x:     &struct{ A *int }{intPtr(4)},
		y:     &struct{ A *int }{intPtr(5)},
		wantDiff: `
  &struct{ A *int }{
- 	A: &4,
+ 	A: &5,
  }
`,
	}, {
		label: label,
		x:     &struct{ A *int }{intPtr(4)},
		y:     &struct{ A *int }{intPtr(5)},
		opts: []cmp.Option{
			cmp.Comparer(func(x, y int) bool { return true }),
		},
	}, {
		label: label,
		x:     &struct{ A *int }{intPtr(4)},
		y:     &struct{ A *int }{intPtr(5)},
		opts: []cmp.Option{
			cmp.Comparer(func(x, y *int) bool { return x != nil && y != nil }),
		},
	}, {
		label: label,
		x:     &struct{ R *bytes.Buffer }{},
		y:     &struct{ R *bytes.Buffer }{},
	}, {
		label: label,
		x:     &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
		y:     &struct{ R *bytes.Buffer }{},
		wantDiff: `
  &struct{ R *bytes.Buffer }{
- 	R: s"",
+ 	R: nil,
  }
`,
	}, {
		label: label,
		x:     &struct{ R *bytes.Buffer }{new(bytes.Buffer)},
		y:     &struct{ R *bytes.Buffer }{},
		opts: []cmp.Option{
			cmp.Comparer(func(x, y io.Reader) bool { return true }),
		},
	}, {
		label:     label,
		x:         &struct{ R bytes.Buffer }{},
		y:         &struct{ R bytes.Buffer }{},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     &struct{ R bytes.Buffer }{},
		y:     &struct{ R bytes.Buffer }{},
		opts: []cmp.Option{
			cmp.Comparer(func(x, y io.Reader) bool { return true }),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     &struct{ R bytes.Buffer }{},
		y:     &struct{ R bytes.Buffer }{},
		opts: []cmp.Option{
			cmp.Transformer("Ref", func(x bytes.Buffer) *bytes.Buffer { return &x }),
			cmp.Comparer(func(x, y io.Reader) bool { return true }),
		},
	}, {
		label:     label,
		x:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
		y:         []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
		y:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
		opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
			if x == nil || y == nil {
				return x == nil && y == nil
			}
			return x.String() == y.String()
		})},
	}, {
		label: label,
		x:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*c*")},
		y:     []*regexp.Regexp{nil, regexp.MustCompile("a*b*d*")},
		opts: []cmp.Option{cmp.Comparer(func(x, y *regexp.Regexp) bool {
			if x == nil || y == nil {
				return x == nil && y == nil
			}
			return x.String() == y.String()
		})},
		wantDiff: `
  []*regexp.Regexp{
  	nil,
- 	s"a*b*c*",
+ 	s"a*b*d*",
  }
`,
	}, {
		label: label,
		x: func() ***int {
			a := 0
			b := &a
			c := &b
			return &c
		}(),
		y: func() ***int {
			a := 0
			b := &a
			c := &b
			return &c
		}(),
	}, {
		label: label,
		x: func() ***int {
			a := 0
			b := &a
			c := &b
			return &c
		}(),
		y: func() ***int {
			a := 1
			b := &a
			c := &b
			return &c
		}(),
		wantDiff: `
  &&&int(
- 	0,
+ 	1,
  )
`,
	}, {
		label: label,
		x:     []int{1, 2, 3, 4, 5}[:3],
		y:     []int{1, 2, 3},
	}, {
		label: label,
		x:     struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
		y:     struct{ fmt.Stringer }{regexp.MustCompile("hello")},
		opts:  []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
	}, {
		label: label,
		x:     struct{ fmt.Stringer }{bytes.NewBufferString("hello")},
		y:     struct{ fmt.Stringer }{regexp.MustCompile("hello2")},
		opts:  []cmp.Option{cmp.Comparer(func(x, y fmt.Stringer) bool { return x.String() == y.String() })},
		wantDiff: `
  struct{ fmt.Stringer }(
- 	s"hello",
+ 	s"hello2",
  )
`,
	}, {
		label: label,
		x:     md5.Sum([]byte{'a'}),
		y:     md5.Sum([]byte{'b'}),
		wantDiff: `
  [16]uint8{
- 	0x0c, 0xc1, 0x75, 0xb9, 0xc0, 0xf1, 0xb6, 0xa8, 0x31, 0xc3, 0x99, 0xe2, 0x69, 0x77, 0x26, 0x61,
+ 	0x92, 0xeb, 0x5f, 0xfe, 0xe6, 0xae, 0x2f, 0xec, 0x3a, 0xd7, 0x1c, 0x77, 0x75, 0x31, 0x57, 0x8f,
  }
`,
	}, {
		label: label,
		x:     new(fmt.Stringer),
		y:     nil,
		wantDiff: `
  interface{}(
- 	&fmt.Stringer(nil),
  )
`,
	}, {
		label: label,
		x:     makeTarHeaders('0'),
		y:     makeTarHeaders('\x00'),
		wantDiff: `
  []cmp_test.tarHeader{
  	{
  		... // 4 identical fields
  		Size:     1,
  		ModTime:  s"2009-11-10 23:00:00 +0000 UTC",
- 		Typeflag: 0x30,
+ 		Typeflag: 0x00,
  		Linkname: "",
  		Uname:    "user",
  		... // 6 identical fields
  	},
  	{
  		... // 4 identical fields
  		Size:     2,
  		ModTime:  s"2009-11-11 00:00:00 +0000 UTC",
- 		Typeflag: 0x30,
+ 		Typeflag: 0x00,
  		Linkname: "",
  		Uname:    "user",
  		... // 6 identical fields
  	},
  	{
  		... // 4 identical fields
  		Size:     4,
  		ModTime:  s"2009-11-11 01:00:00 +0000 UTC",
- 		Typeflag: 0x30,
+ 		Typeflag: 0x00,
  		Linkname: "",
  		Uname:    "user",
  		... // 6 identical fields
  	},
  	{
  		... // 4 identical fields
  		Size:     8,
  		ModTime:  s"2009-11-11 02:00:00 +0000 UTC",
- 		Typeflag: 0x30,
+ 		Typeflag: 0x00,
  		Linkname: "",
  		Uname:    "user",
  		... // 6 identical fields
  	},
  	{
  		... // 4 identical fields
  		Size:     16,
  		ModTime:  s"2009-11-11 03:00:00 +0000 UTC",
- 		Typeflag: 0x30,
+ 		Typeflag: 0x00,
  		Linkname: "",
  		Uname:    "user",
  		... // 6 identical fields
  	},
  }
`,
	}, {
		label: label,
		x:     make([]int, 1000),
		y:     make([]int, 1000),
		opts: []cmp.Option{
			cmp.Comparer(func(_, _ int) bool {
				return rand.Intn(2) == 0
			}),
		},
		wantPanic: "non-deterministic or non-symmetric function detected",
	}, {
		label: label,
		x:     make([]int, 1000),
		y:     make([]int, 1000),
		opts: []cmp.Option{
			cmp.FilterValues(func(_, _ int) bool {
				return rand.Intn(2) == 0
			}, cmp.Ignore()),
		},
		wantPanic: "non-deterministic or non-symmetric function detected",
	}, {
		label: label,
		x:     []int{1, 2, 3, 4, 5, 6, 7, 8, 9, 10},
		y:     []int{10, 9, 8, 7, 6, 5, 4, 3, 2, 1},
		opts: []cmp.Option{
			cmp.Comparer(func(x, y int) bool {
				return x < y
			}),
		},
		wantPanic: "non-deterministic or non-symmetric function detected",
	}, {
		label: label,
		x:     make([]string, 1000),
		y:     make([]string, 1000),
		opts: []cmp.Option{
			cmp.Transformer("λ", func(x string) int {
				return rand.Int()
			}),
		},
		wantPanic: "non-deterministic function detected",
	}, {
		// Make sure the dynamic checks don't raise a false positive for
		// non-reflexive comparisons.
		label: label,
		x:     make([]int, 10),
		y:     make([]int, 10),
		opts: []cmp.Option{
			cmp.Transformer("λ", func(x int) float64 {
				return math.NaN()
			}),
		},
		wantDiff: `
  []int{
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
- 	Inverse(λ, float64(NaN)),
+ 	Inverse(λ, float64(NaN)),
  }
`,
	}, {
		// Ensure reasonable Stringer formatting of map keys.
		label: label,
		x:     map[*pb.Stringer]*pb.Stringer{{"hello"}: {"world"}},
		y:     map[*pb.Stringer]*pb.Stringer(nil),
		wantDiff: `
  map[*testprotos.Stringer]*testprotos.Stringer(
- 	{s"hello": s"world"},
+ 	nil,
  )
`,
	}, {
		// Ensure Stringer avoids double-quote escaping if possible.
		label: label,
		x:     []*pb.Stringer{{`multi\nline\nline\nline`}},
		wantDiff: strings.Replace(`
  interface{}(
- 	[]*testprotos.Stringer{s'multi\nline\nline\nline'},
  )
`, "'", "`", -1),
	}, {
		label: label,
		x:     struct{ I Iface2 }{},
		y:     struct{ I Iface2 }{},
		opts: []cmp.Option{
			cmp.Comparer(func(x, y Iface1) bool {
				return x == nil && y == nil
			}),
		},
	}, {
		label: label,
		x:     struct{ I Iface2 }{},
		y:     struct{ I Iface2 }{},
		opts: []cmp.Option{
			cmp.Transformer("λ", func(v Iface1) bool {
				return v == nil
			}),
		},
	}, {
		label: label,
		x:     struct{ I Iface2 }{},
		y:     struct{ I Iface2 }{},
		opts: []cmp.Option{
			cmp.FilterValues(func(x, y Iface1) bool {
				return x == nil && y == nil
			}, cmp.Ignore()),
		},
	}, {
		label: label,
		x:     []interface{}{map[string]interface{}{"avg": 0.278, "hr": 65, "name": "Mark McGwire"}, map[string]interface{}{"avg": 0.288, "hr": 63, "name": "Sammy Sosa"}},
		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"}},
		wantDiff: `
  []interface{}{
  	map[string]interface{}{
  		"avg":  float64(0.278),
- 		"hr":   int(65),
+ 		"hr":   float64(65),
  		"name": string("Mark McGwire"),
  	},
  	map[string]interface{}{
  		"avg":  float64(0.288),
- 		"hr":   int(63),
+ 		"hr":   float64(63),
  		"name": string("Sammy Sosa"),
  	},
  }
`,
	}, {
		label: label,
		x: map[*int]string{
			new(int): "hello",
		},
		y: map[*int]string{
			new(int): "world",
		},
		wantDiff: `
  map[*int]string{
- 	⟪0xdeadf00f⟫: "hello",
+ 	⟪0xdeadf00f⟫: "world",
  }
`,
	}, {
		label: label,
		x:     intPtr(0),
		y:     intPtr(0),
		opts: []cmp.Option{
			cmp.Comparer(func(x, y *int) bool { return x == y }),
		},
		// TODO: This output is unhelpful and should show the address.
		wantDiff: `
  (*int)(
- 	&0,
+ 	&0,
  )
`,
	}, {
		label: label,
		x: [2][]int{
			{0, 0, 0, 1, 2, 3, 0, 0, 4, 5, 6, 7, 8, 0, 9, 0, 0},
			{0, 1, 0, 0, 0, 20},
		},
		y: [2][]int{
			{1, 2, 3, 0, 4, 5, 6, 7, 0, 8, 9, 0, 0, 0},
			{0, 0, 1, 2, 0, 0, 0},
		},
		opts: []cmp.Option{
			cmp.FilterPath(func(p cmp.Path) bool {
				vx, vy := p.Last().Values()
				if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
					return true
				}
				if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
					return true
				}
				return false
			}, cmp.Ignore()),
		},
		wantDiff: `
  [2][]int{
  	{..., 1, 2, 3, ..., 4, 5, 6, 7, ..., 8, ..., 9, ...},
  	{
  		... // 6 ignored and 1 identical elements
- 		20,
+ 		2,
  		... // 3 ignored elements
  	},
  }
`,
		reason: "all zero slice elements are ignored (even if missing)",
	}, {
		label: label,
		x: [2]map[string]int{
			{"ignore1": 0, "ignore2": 0, "keep1": 1, "keep2": 2, "KEEP3": 3, "IGNORE3": 0},
			{"keep1": 1, "ignore1": 0},
		},
		y: [2]map[string]int{
			{"ignore1": 0, "ignore3": 0, "ignore4": 0, "keep1": 1, "keep2": 2, "KEEP3": 3},
			{"keep1": 1, "keep2": 2, "ignore2": 0},
		},
		opts: []cmp.Option{
			cmp.FilterPath(func(p cmp.Path) bool {
				vx, vy := p.Last().Values()
				if vx.IsValid() && vx.Kind() == reflect.Int && vx.Int() == 0 {
					return true
				}
				if vy.IsValid() && vy.Kind() == reflect.Int && vy.Int() == 0 {
					return true
				}
				return false
			}, cmp.Ignore()),
		},
		wantDiff: `
  [2]map[string]int{
  	{"KEEP3": 3, "keep1": 1, "keep2": 2, ...},
  	{
  		... // 2 ignored entries
  		"keep1": 1,
+ 		"keep2": 2,
  	},
  }
`,
		reason: "all zero map entries are ignored (even if missing)",
	}, {
		label:     label,
		x:         namedWithUnexported{},
		y:         namedWithUnexported{},
		wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".namedWithUnexported",
		reason:    "panic on named struct type with unexported field",
	}, {
		label:     label,
		x:         struct{ a int }{},
		y:         struct{ a int }{},
		wantPanic: strconv.Quote(reflect.TypeOf(namedWithUnexported{}).PkgPath()) + ".(struct { a int })",
		reason:    "panic on unnamed struct type with unexported field",
	}}
}

func transformerTests() []test {
	type StringBytes struct {
		String string
		Bytes  []byte
	}

	const label = "Transformer"

	transformOnce := func(name string, f interface{}) cmp.Option {
		xform := cmp.Transformer(name, f)
		return cmp.FilterPath(func(p cmp.Path) bool {
			for _, ps := range p {
				if tr, ok := ps.(cmp.Transform); ok && tr.Option() == xform {
					return false
				}
			}
			return true
		}, xform)
	}

	return []test{{
		label: label,
		x:     uint8(0),
		y:     uint8(1),
		opts: []cmp.Option{
			cmp.Transformer("λ", func(in uint8) uint16 { return uint16(in) }),
			cmp.Transformer("λ", func(in uint16) uint32 { return uint32(in) }),
			cmp.Transformer("λ", func(in uint32) uint64 { return uint64(in) }),
		},
		wantDiff: `
  uint8(Inverse(λ, uint16(Inverse(λ, uint32(Inverse(λ, uint64(
- 	0x00,
+ 	0x01,
  )))))))
`,
	}, {
		label: label,
		x:     0,
		y:     1,
		opts: []cmp.Option{
			cmp.Transformer("λ", func(in int) int { return in / 2 }),
			cmp.Transformer("λ", func(in int) int { return in }),
		},
		wantPanic: "ambiguous set of applicable options",
	}, {
		label: label,
		x:     []int{0, -5, 0, -1},
		y:     []int{1, 3, 0, -5},
		opts: []cmp.Option{
			cmp.FilterValues(
				func(x, y int) bool { return x+y >= 0 },
				cmp.Transformer("λ", func(in int) int64 { return int64(in / 2) }),
			),
			cmp.FilterValues(
				func(x, y int) bool { return x+y < 0 },
				cmp.Transformer("λ", func(in int) int64 { return int64(in) }),
			),
		},
		wantDiff: `
  []int{
  	Inverse(λ, int64(0)),
- 	Inverse(λ, int64(-5)),
+ 	Inverse(λ, int64(3)),
  	Inverse(λ, int64(0)),
- 	Inverse(λ, int64(-1)),
+ 	Inverse(λ, int64(-5)),
  }
`,
	}, {
		label: label,
		x:     0,
		y:     1,
		opts: []cmp.Option{
			cmp.Transformer("λ", func(in int) interface{} {
				if in == 0 {
					return "zero"
				}
				return float64(in)
			}),
		},
		wantDiff: `
  int(Inverse(λ, interface{}(
- 	string("zero"),
+ 	float64(1),
  )))
`,
	}, {
		label: label,
		x: `{
		  "firstName": "John",
		  "lastName": "Smith",
		  "age": 25,
		  "isAlive": true,
		  "address": {
		    "city": "Los Angeles",
		    "postalCode": "10021-3100",
		    "state": "CA",
		    "streetAddress": "21 2nd Street"
		  },
		  "phoneNumbers": [{
		    "type": "home",
		    "number": "212 555-4321"
		  },{
		    "type": "office",
		    "number": "646 555-4567"
		  },{
		    "number": "123 456-7890",
		    "type": "mobile"
		  }],
		  "children": []
		}`,
		y: `{"firstName":"John","lastName":"Smith","isAlive":true,"age":25,
			"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}`,
		opts: []cmp.Option{
			transformOnce("ParseJSON", func(s string) (m map[string]interface{}) {
				if err := json.Unmarshal([]byte(s), &m); err != nil {
					panic(err)
				}
				return m
			}),
		},
		wantDiff: `
  string(Inverse(ParseJSON, map[string]interface{}{
  	"address": map[string]interface{}{
- 		"city":          string("Los Angeles"),
+ 		"city":          string("New York"),
  		"postalCode":    string("10021-3100"),
- 		"state":         string("CA"),
+ 		"state":         string("NY"),
  		"streetAddress": string("21 2nd Street"),
  	},
  	"age":       float64(25),
  	"children":  []interface{}{},
  	"firstName": string("John"),
  	"isAlive":   bool(true),
  	"lastName":  string("Smith"),
  	"phoneNumbers": []interface{}{
  		map[string]interface{}{
- 			"number": string("212 555-4321"),
+ 			"number": string("212 555-1234"),
  			"type":   string("home"),
  		},
  		map[string]interface{}{"number": string("646 555-4567"), "type": string("office")},
  		map[string]interface{}{"number": string("123 456-7890"), "type": string("mobile")},
  	},
+ 	"spouse": nil,
  }))
`,
	}, {
		label: label,
		x:     StringBytes{String: "some\nmulti\nLine\nstring", Bytes: []byte("some\nmulti\nline\nbytes")},
		y:     StringBytes{String: "some\nmulti\nline\nstring", Bytes: []byte("some\nmulti\nline\nBytes")},
		opts: []cmp.Option{
			transformOnce("SplitString", func(s string) []string { return strings.Split(s, "\n") }),
			transformOnce("SplitBytes", func(b []byte) [][]byte { return bytes.Split(b, []byte("\n")) }),
		},
		wantDiff: `
  cmp_test.StringBytes{
  	String: Inverse(SplitString, []string{
  		"some",
  		"multi",
- 		"Line",
+ 		"line",
  		"string",
  	}),
  	Bytes: []uint8(Inverse(SplitBytes, [][]uint8{
  		{0x73, 0x6f, 0x6d, 0x65},
  		{0x6d, 0x75, 0x6c, 0x74, 0x69},
  		{0x6c, 0x69, 0x6e, 0x65},
  		{
- 			0x62,
+ 			0x42,
  			0x79,
  			0x74,
  			... // 2 identical elements
  		},
  	})),
  }
`,
	}, {
		x: "a\nb\nc\n",
		y: "a\nb\nc\n",
		opts: []cmp.Option{
			cmp.Transformer("SplitLines", func(s string) []string { return strings.Split(s, "\n") }),
		},
		wantPanic: "recursive set of Transformers detected",
	}, {
		x: complex64(0),
		y: complex64(0),
		opts: []cmp.Option{
			cmp.Transformer("T1", func(x complex64) complex128 { return complex128(x) }),
			cmp.Transformer("T2", func(x complex128) [2]float64 { return [2]float64{real(x), imag(x)} }),
			cmp.Transformer("T3", func(x float64) complex64 { return complex64(complex(x, 0)) }),
		},
		wantPanic: "recursive set of Transformers detected",
	}}
}

func reporterTests() []test {
	const label = "Reporter"

	type (
		MyString    string
		MyByte      byte
		MyBytes     []byte
		MyInt       int8
		MyInts      []int8
		MyUint      int16
		MyUints     []int16
		MyFloat     float32
		MyFloats    []float32
		MyComposite struct {
			StringA string
			StringB MyString
			BytesA  []byte
			BytesB  []MyByte
			BytesC  MyBytes
			IntsA   []int8
			IntsB   []MyInt
			IntsC   MyInts
			UintsA  []uint16
			UintsB  []MyUint
			UintsC  MyUints
			FloatsA []float32
			FloatsB []MyFloat
			FloatsC MyFloats
		}
	)

	return []test{{
		label: label,
		x:     MyComposite{IntsA: []int8{11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
		y:     MyComposite{IntsA: []int8{10, 11, 21, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
		wantDiff: `
  cmp_test.MyComposite{
  	... // 3 identical fields
  	BytesB: nil,
  	BytesC: nil,
  	IntsA: []int8{
+ 		10,
  		11,
- 		12,
+ 		21,
  		13,
  		14,
  		... // 15 identical elements
  	},
  	IntsB: nil,
  	IntsC: nil,
  	... // 6 identical fields
  }
`,
		reason: "unbatched diffing desired since few elements differ",
	}, {
		label: label,
		x:     MyComposite{IntsA: []int8{10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29}},
		y:     MyComposite{IntsA: []int8{12, 29, 13, 27, 22, 23, 17, 18, 19, 20, 21, 10, 26, 16, 25, 28, 11, 15, 24, 14}},
		wantDiff: `
  cmp_test.MyComposite{
  	... // 3 identical fields
  	BytesB: nil,
  	BytesC: nil,
  	IntsA: []int8{
- 		10, 11, 12, 13, 14, 15, 16,
+ 		12, 29, 13, 27, 22, 23,
  		17, 18, 19, 20, 21,
- 		22, 23, 24, 25, 26, 27, 28, 29,
+ 		10, 26, 16, 25, 28, 11, 15, 24, 14,
  	},
  	IntsB: nil,
  	IntsC: nil,
  	... // 6 identical fields
  }
`,
		reason: "batched diffing desired since many elements differ",
	}, {
		label: label,
		x: MyComposite{
			BytesA:  []byte{1, 2, 3},
			BytesB:  []MyByte{4, 5, 6},
			BytesC:  MyBytes{7, 8, 9},
			IntsA:   []int8{-1, -2, -3},
			IntsB:   []MyInt{-4, -5, -6},
			IntsC:   MyInts{-7, -8, -9},
			UintsA:  []uint16{1000, 2000, 3000},
			UintsB:  []MyUint{4000, 5000, 6000},
			UintsC:  MyUints{7000, 8000, 9000},
			FloatsA: []float32{1.5, 2.5, 3.5},
			FloatsB: []MyFloat{4.5, 5.5, 6.5},
			FloatsC: MyFloats{7.5, 8.5, 9.5},
		},
		y: MyComposite{
			BytesA:  []byte{3, 2, 1},
			BytesB:  []MyByte{6, 5, 4},
			BytesC:  MyBytes{9, 8, 7},
			IntsA:   []int8{-3, -2, -1},
			IntsB:   []MyInt{-6, -5, -4},
			IntsC:   MyInts{-9, -8, -7},
			UintsA:  []uint16{3000, 2000, 1000},
			UintsB:  []MyUint{6000, 5000, 4000},
			UintsC:  MyUints{9000, 8000, 7000},
			FloatsA: []float32{3.5, 2.5, 1.5},
			FloatsB: []MyFloat{6.5, 5.5, 4.5},
			FloatsC: MyFloats{9.5, 8.5, 7.5},
		},
		wantDiff: `
  cmp_test.MyComposite{
  	StringA: "",
  	StringB: "",
  	BytesA: []uint8{
- 		0x01, 0x02, 0x03, // -|...|
+ 		0x03, 0x02, 0x01, // +|...|
  	},
  	BytesB: []cmp_test.MyByte{
- 		0x04, 0x05, 0x06,
+ 		0x06, 0x05, 0x04,
  	},
  	BytesC: cmp_test.MyBytes{
- 		0x07, 0x08, 0x09, // -|...|
+ 		0x09, 0x08, 0x07, // +|...|
  	},
  	IntsA: []int8{
- 		-1, -2, -3,
+ 		-3, -2, -1,
  	},
  	IntsB: []cmp_test.MyInt{
- 		-4, -5, -6,
+ 		-6, -5, -4,
  	},
  	IntsC: cmp_test.MyInts{
- 		-7, -8, -9,
+ 		-9, -8, -7,
  	},
  	UintsA: []uint16{
- 		0x03e8, 0x07d0, 0x0bb8,
+ 		0x0bb8, 0x07d0, 0x03e8,
  	},
  	UintsB: []cmp_test.MyUint{
- 		4000, 5000, 6000,
+ 		6000, 5000, 4000,
  	},
  	UintsC: cmp_test.MyUints{
- 		7000, 8000, 9000,
+ 		9000, 8000, 7000,
  	},
  	FloatsA: []float32{
- 		1.5, 2.5, 3.5,
+ 		3.5, 2.5, 1.5,
  	},
  	FloatsB: []cmp_test.MyFloat{
- 		4.5, 5.5, 6.5,
+ 		6.5, 5.5, 4.5,
  	},
  	FloatsC: cmp_test.MyFloats{
- 		7.5, 8.5, 9.5,
+ 		9.5, 8.5, 7.5,
  	},
  }
`,
		reason: "batched diffing available for both named and unnamed slices",
	}, {
		label: label,
		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]")},
		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]")},
		wantDiff: `
  cmp_test.MyComposite{
  	StringA: "",
  	StringB: "",
  	BytesA: []uint8{
  		0xf3, 0x0f, 0x8a, 0xa4, 0xd3, 0x12, 0x52, 0x09, 0x24, 0xbe,                                     //  |......R.$.|
- 		0x58, 0x95, 0x41, 0xfd, 0x24, 0x66, 0x58, 0x8b, 0x79,                                           // -|X.A.$fX.y|
  		0x54, 0xac, 0x0d, 0xd8, 0x71, 0x77, 0x70, 0x20, 0x6a, 0x5c, 0x73, 0x7f, 0x8c, 0x17, 0x55, 0xc0, //  |T...qwp j\s...U.|
  		0x34, 0xce, 0x6e, 0xf7, 0xaa, 0x47, 0xee, 0x32, 0x9d, 0xc5, 0xca, 0x1e, 0x58, 0xaf, 0x8f, 0x27, //  |4.n..G.2....X..'|
  		0xf3, 0x02, 0x4a, 0x90, 0xed, 0x69, 0x2e, 0x70, 0x32, 0xb4, 0xab, 0x30, 0x20, 0xb6, 0xbd, 0x5c, //  |..J..i.p2..0 ..\|
  		0x62, 0x34, 0x17, 0xb0, 0x00, 0xbb, 0x4f, 0x7e, 0x27, 0x47, 0x06, 0xf4, 0x2e, 0x66, 0xfd, 0x63, //  |b4....O~'G...f.c|
  		0xd7, 0x04, 0xdd, 0xb7, 0x30, 0xb7, 0xd1,                                                       //  |....0..|
- 		0x55, 0x7e, 0x7b, 0xf6, 0xb3, 0x7e, 0x1d, 0x57, 0x69,                                           // -|U~{..~.Wi|
+ 		0x75, 0x2d, 0x5b, 0x5d, 0x5d, 0xf6, 0xb3, 0x68, 0x61, 0x68, 0x61, 0x7e, 0x1d, 0x57, 0x49,       // +|u-[]]..haha~.WI|
  		0x20, 0x9e, 0xbc, 0xdf, 0xe1, 0x4d, 0xa9, 0xef, 0xa2, 0xd2, 0xed, 0xb4, 0x47, 0x78, 0xc9, 0xc9, //  | ....M......Gx..|
  		0x27, 0xa4, 0xc6, 0xce, 0xec, 0x44, 0x70, 0x5d,                                                 //  |'....Dp]|
  	},
  	BytesB: nil,
  	BytesC: nil,
  	... // 9 identical fields
  }
`,
		reason: "binary diff in hexdump form since data is binary data",
	}, {
		label: label,
		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")},
		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")},
		wantDiff: `
  cmp_test.MyComposite{
  	StringA: "",
  	StringB: cmp_test.MyString{
- 		0x72, 0x65, 0x61, 0x64, 0x6d, 0x65,                                                             // -|readme|
+ 		0x67, 0x6f, 0x70, 0x68, 0x65, 0x72,                                                             // +|gopher|
  		0x2e, 0x74, 0x78, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  |.txt............|
  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  |................|
  		... // 64 identical bytes
  		0x30, 0x30, 0x36, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, //  |00600.0000000.00|
  		0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, //  |00000.0000000004|
- 		0x36,                                                                                           // -|6|
+ 		0x33,                                                                                           // +|3|
  		0x00, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x00, 0x30, 0x31, 0x31, //  |.00000000000.011|
- 		0x31, 0x37, 0x33,                                                                               // -|173|
+ 		0x32, 0x31, 0x37,                                                                               // +|217|
  		0x00, 0x20, 0x30, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  |. 0.............|
  		0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, //  |................|
  		... // 326 identical bytes
  	},
  	BytesA: nil,
  	BytesB: nil,
  	... // 10 identical fields
  }
`,
		reason: "binary diff desired since string looks like binary data",
	}, {
		label: label,
		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}`)},
		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}`)},
		wantDiff: strings.Replace(`
  cmp_test.MyComposite{
  	StringA: "",
  	StringB: "",
  	BytesA: bytes.Join({
  		'{"firstName":"John","lastName":"Smith","isAlive":true,"age":27,"',
  		'address":{"streetAddress":"',
- 		"314 54th Avenue",
+ 		"21 2nd Street",
  		'","city":"New York","state":"NY","postalCode":"10021-3100"},"pho',
  		'neNumbers":[{"type":"home","number":"212 555-1234"},{"type":"off',
  		... // 101 identical bytes
  	}, ""),
  	BytesB: nil,
  	BytesC: nil,
  	... // 9 identical fields
  }
`, "'", "`", -1),
		reason: "batched textual diff desired since bytes looks like textual data",
	}, {
		label: label,
		x: MyComposite{
			StringA: strings.TrimPrefix(`
Package cmp determines equality of values.

This package is intended to be a more powerful and safer alternative to
reflect.DeepEqual for comparing whether two values are semantically equal.

The primary features of cmp are:

• When the default behavior of equality does not suit the needs of the test,
custom equality functions can override the equality operation.
For example, an equality function may report floats as equal so long as they
are within some tolerance of each other.

• Types that have an Equal method may use that method to determine equality.
This allows package authors to determine the equality operation for the types
that they define.

• If no custom equality functions are used and no Equal method is defined,
equality is determined by recursively comparing the primitive kinds on both
values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
fields are not compared by default; they result in panics unless suppressed
by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
using the AllowUnexported option.
`, "\n"),
		},
		y: MyComposite{
			StringA: strings.TrimPrefix(`
Package cmp determines equality of value.

This package is intended to be a more powerful and safer alternative to
reflect.DeepEqual for comparing whether two values are semantically equal.

The primary features of cmp are:

• When the default behavior of equality does not suit the needs of the test,
custom equality functions can override the equality operation.
For example, an equality function may report floats as equal so long as they
are within some tolerance of each other.

• If no custom equality functions are used and no Equal method is defined,
equality is determined by recursively comparing the primitive kinds on both
values, much like reflect.DeepEqual. Unlike reflect.DeepEqual, unexported
fields are not compared by default; they result in panics unless suppressed
by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared
using the AllowUnexported option.`, "\n"),
		},
		wantDiff: `
  cmp_test.MyComposite{
  	StringA: strings.Join({
- 		"Package cmp determines equality of values.",
+ 		"Package cmp determines equality of value.",
  		"",
  		"This package is intended to be a more powerful and safer alternative to",
  		... // 6 identical lines
  		"For example, an equality function may report floats as equal so long as they",
  		"are within some tolerance of each other.",
- 		"",
- 		"• Types that have an Equal method may use that method to determine equality.",
- 		"This allows package authors to determine the equality operation for the types",
- 		"that they define.",
  		"",
  		"• If no custom equality functions are used and no Equal method is defined,",
  		... // 3 identical lines
  		"by using an Ignore option (see cmpopts.IgnoreUnexported) or explicitly compared",
  		"using the AllowUnexported option.",
- 		"",
  	}, "\n"),
  	StringB: "",
  	BytesA:  nil,
  	... // 11 identical fields
  }
`,
		reason: "batched per-line diff desired since string looks like multi-line textual data",
	}}
}

func embeddedTests() []test {
	const label = "EmbeddedStruct/"

	privateStruct := *new(ts.ParentStructA).PrivateStruct()

	createStructA := func(i int) ts.ParentStructA {
		s := ts.ParentStructA{}
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		return s
	}

	createStructB := func(i int) ts.ParentStructB {
		s := ts.ParentStructB{}
		s.PublicStruct.Public = 1 + i
		s.PublicStruct.SetPrivate(2 + i)
		return s
	}

	createStructC := func(i int) ts.ParentStructC {
		s := ts.ParentStructC{}
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		s.Public = 3 + i
		s.SetPrivate(4 + i)
		return s
	}

	createStructD := func(i int) ts.ParentStructD {
		s := ts.ParentStructD{}
		s.PublicStruct.Public = 1 + i
		s.PublicStruct.SetPrivate(2 + i)
		s.Public = 3 + i
		s.SetPrivate(4 + i)
		return s
	}

	createStructE := func(i int) ts.ParentStructE {
		s := ts.ParentStructE{}
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		s.PublicStruct.Public = 3 + i
		s.PublicStruct.SetPrivate(4 + i)
		return s
	}

	createStructF := func(i int) ts.ParentStructF {
		s := ts.ParentStructF{}
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		s.PublicStruct.Public = 3 + i
		s.PublicStruct.SetPrivate(4 + i)
		s.Public = 5 + i
		s.SetPrivate(6 + i)
		return s
	}

	createStructG := func(i int) *ts.ParentStructG {
		s := ts.NewParentStructG()
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		return s
	}

	createStructH := func(i int) *ts.ParentStructH {
		s := ts.NewParentStructH()
		s.PublicStruct.Public = 1 + i
		s.PublicStruct.SetPrivate(2 + i)
		return s
	}

	createStructI := func(i int) *ts.ParentStructI {
		s := ts.NewParentStructI()
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		s.PublicStruct.Public = 3 + i
		s.PublicStruct.SetPrivate(4 + i)
		return s
	}

	createStructJ := func(i int) *ts.ParentStructJ {
		s := ts.NewParentStructJ()
		s.PrivateStruct().Public = 1 + i
		s.PrivateStruct().SetPrivate(2 + i)
		s.PublicStruct.Public = 3 + i
		s.PublicStruct.SetPrivate(4 + i)
		s.Private().Public = 5 + i
		s.Private().SetPrivate(6 + i)
		s.Public.Public = 7 + i
		s.Public.SetPrivate(8 + i)
		return s
	}

	// TODO(dsnet): Workaround for reflect bug (https://golang.org/issue/21122).
	wantPanicNotGo110 := func(s string) string {
		if !flags.AtLeastGo110 {
			return ""
		}
		return s
	}

	return []test{{
		label:     label + "ParentStructA",
		x:         ts.ParentStructA{},
		y:         ts.ParentStructA{},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructA",
		x:     ts.ParentStructA{},
		y:     ts.ParentStructA{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructA{}),
		},
	}, {
		label: label + "ParentStructA",
		x:     createStructA(0),
		y:     createStructA(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructA{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructA",
		x:     createStructA(0),
		y:     createStructA(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
		},
	}, {
		label: label + "ParentStructA",
		x:     createStructA(0),
		y:     createStructA(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructA{}, privateStruct),
		},
		wantDiff: `
  teststructs.ParentStructA{
  	privateStruct: teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  }
`,
	}, {
		label: label + "ParentStructB",
		x:     ts.ParentStructB{},
		y:     ts.ParentStructB{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructB{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructB",
		x:     ts.ParentStructB{},
		y:     ts.ParentStructB{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructB{}),
			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructB",
		x:     createStructB(0),
		y:     createStructB(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructB{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructB",
		x:     createStructB(0),
		y:     createStructB(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructB",
		x:     createStructB(0),
		y:     createStructB(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructB{}, ts.PublicStruct{}),
		},
		wantDiff: `
  teststructs.ParentStructB{
  	PublicStruct: teststructs.PublicStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  }
`,
	}, {
		label:     label + "ParentStructC",
		x:         ts.ParentStructC{},
		y:         ts.ParentStructC{},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructC",
		x:     ts.ParentStructC{},
		y:     ts.ParentStructC{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructC{}),
		},
	}, {
		label: label + "ParentStructC",
		x:     createStructC(0),
		y:     createStructC(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructC{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructC",
		x:     createStructC(0),
		y:     createStructC(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
		},
	}, {
		label: label + "ParentStructC",
		x:     createStructC(0),
		y:     createStructC(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructC{}, privateStruct),
		},
		wantDiff: `
  teststructs.ParentStructC{
  	privateStruct: teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
- 	Public:  3,
+ 	Public:  4,
- 	private: 4,
+ 	private: 5,
  }
`,
	}, {
		label: label + "ParentStructD",
		x:     ts.ParentStructD{},
		y:     ts.ParentStructD{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructD{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructD",
		x:     ts.ParentStructD{},
		y:     ts.ParentStructD{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructD{}),
			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructD",
		x:     createStructD(0),
		y:     createStructD(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructD{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructD",
		x:     createStructD(0),
		y:     createStructD(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructD",
		x:     createStructD(0),
		y:     createStructD(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructD{}, ts.PublicStruct{}),
		},
		wantDiff: `
  teststructs.ParentStructD{
  	PublicStruct: teststructs.PublicStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
- 	Public:  3,
+ 	Public:  4,
- 	private: 4,
+ 	private: 5,
  }
`,
	}, {
		label: label + "ParentStructE",
		x:     ts.ParentStructE{},
		y:     ts.ParentStructE{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructE{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructE",
		x:     ts.ParentStructE{},
		y:     ts.ParentStructE{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructE{}),
			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructE",
		x:     createStructE(0),
		y:     createStructE(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructE{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructE",
		x:     createStructE(0),
		y:     createStructE(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructE",
		x:     createStructE(0),
		y:     createStructE(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
		},
	}, {
		label: label + "ParentStructE",
		x:     createStructE(0),
		y:     createStructE(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructE{}, ts.PublicStruct{}, privateStruct),
		},
		wantDiff: `
  teststructs.ParentStructE{
  	privateStruct: teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  	PublicStruct: teststructs.PublicStruct{
- 		Public:  3,
+ 		Public:  4,
- 		private: 4,
+ 		private: 5,
  	},
  }
`,
	}, {
		label: label + "ParentStructF",
		x:     ts.ParentStructF{},
		y:     ts.ParentStructF{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructF{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructF",
		x:     ts.ParentStructF{},
		y:     ts.ParentStructF{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructF{}),
			cmpopts.IgnoreUnexported(ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructF",
		x:     createStructF(0),
		y:     createStructF(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructF{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructF",
		x:     createStructF(0),
		y:     createStructF(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructF",
		x:     createStructF(0),
		y:     createStructF(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
		},
	}, {
		label: label + "ParentStructF",
		x:     createStructF(0),
		y:     createStructF(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructF{}, ts.PublicStruct{}, privateStruct),
		},
		wantDiff: `
  teststructs.ParentStructF{
  	privateStruct: teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  	PublicStruct: teststructs.PublicStruct{
- 		Public:  3,
+ 		Public:  4,
- 		private: 4,
+ 		private: 5,
  	},
- 	Public:  5,
+ 	Public:  6,
- 	private: 6,
+ 	private: 7,
  }
`,
	}, {
		label:     label + "ParentStructG",
		x:         ts.ParentStructG{},
		y:         ts.ParentStructG{},
		wantPanic: wantPanicNotGo110("cannot handle unexported field"),
	}, {
		label: label + "ParentStructG",
		x:     ts.ParentStructG{},
		y:     ts.ParentStructG{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructG{}),
		},
	}, {
		label: label + "ParentStructG",
		x:     createStructG(0),
		y:     createStructG(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructG{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructG",
		x:     createStructG(0),
		y:     createStructG(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
		},
	}, {
		label: label + "ParentStructG",
		x:     createStructG(0),
		y:     createStructG(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructG{}, privateStruct),
		},
		wantDiff: `
  &teststructs.ParentStructG{
  	privateStruct: &teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  }
`,
	}, {
		label: label + "ParentStructH",
		x:     ts.ParentStructH{},
		y:     ts.ParentStructH{},
	}, {
		label:     label + "ParentStructH",
		x:         createStructH(0),
		y:         createStructH(0),
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructH",
		x:     ts.ParentStructH{},
		y:     ts.ParentStructH{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructH{}),
		},
	}, {
		label: label + "ParentStructH",
		x:     createStructH(0),
		y:     createStructH(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructH{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructH",
		x:     createStructH(0),
		y:     createStructH(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructH",
		x:     createStructH(0),
		y:     createStructH(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructH{}, ts.PublicStruct{}),
		},
		wantDiff: `
  &teststructs.ParentStructH{
  	PublicStruct: &teststructs.PublicStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  }
`,
	}, {
		label:     label + "ParentStructI",
		x:         ts.ParentStructI{},
		y:         ts.ParentStructI{},
		wantPanic: wantPanicNotGo110("cannot handle unexported field"),
	}, {
		label: label + "ParentStructI",
		x:     ts.ParentStructI{},
		y:     ts.ParentStructI{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructI{}),
		},
	}, {
		label: label + "ParentStructI",
		x:     createStructI(0),
		y:     createStructI(0),
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructI{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructI",
		x:     createStructI(0),
		y:     createStructI(0),
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructI{}, ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructI",
		x:     createStructI(0),
		y:     createStructI(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructI{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructI",
		x:     createStructI(0),
		y:     createStructI(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
		},
	}, {
		label: label + "ParentStructI",
		x:     createStructI(0),
		y:     createStructI(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructI{}, ts.PublicStruct{}, privateStruct),
		},
		wantDiff: `
  &teststructs.ParentStructI{
  	privateStruct: &teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  	PublicStruct: &teststructs.PublicStruct{
- 		Public:  3,
+ 		Public:  4,
- 		private: 4,
+ 		private: 5,
  	},
  }
`,
	}, {
		label:     label + "ParentStructJ",
		x:         ts.ParentStructJ{},
		y:         ts.ParentStructJ{},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructJ",
		x:     ts.ParentStructJ{},
		y:     ts.ParentStructJ{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructJ{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructJ",
		x:     ts.ParentStructJ{},
		y:     ts.ParentStructJ{},
		opts: []cmp.Option{
			cmpopts.IgnoreUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
		},
	}, {
		label: label + "ParentStructJ",
		x:     createStructJ(0),
		y:     createStructJ(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}),
		},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label + "ParentStructJ",
		x:     createStructJ(0),
		y:     createStructJ(0),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
		},
	}, {
		label: label + "ParentStructJ",
		x:     createStructJ(0),
		y:     createStructJ(1),
		opts: []cmp.Option{
			cmp.AllowUnexported(ts.ParentStructJ{}, ts.PublicStruct{}, privateStruct),
		},
		wantDiff: `
  &teststructs.ParentStructJ{
  	privateStruct: &teststructs.privateStruct{
- 		Public:  1,
+ 		Public:  2,
- 		private: 2,
+ 		private: 3,
  	},
  	PublicStruct: &teststructs.PublicStruct{
- 		Public:  3,
+ 		Public:  4,
- 		private: 4,
+ 		private: 5,
  	},
  	Public: teststructs.PublicStruct{
- 		Public:  7,
+ 		Public:  8,
- 		private: 8,
+ 		private: 9,
  	},
  	private: teststructs.privateStruct{
- 		Public:  5,
+ 		Public:  6,
- 		private: 6,
+ 		private: 7,
  	},
  }
`,
	}}
}

func methodTests() []test {
	const label = "EqualMethod/"

	// A common mistake that the Equal method is on a pointer receiver,
	// but only a non-pointer value is present in the struct.
	// A transform can be used to forcibly reference the value.
	derefTransform := cmp.FilterPath(func(p cmp.Path) bool {
		if len(p) == 0 {
			return false
		}
		t := p[len(p)-1].Type()
		if _, ok := t.MethodByName("Equal"); ok || t.Kind() == reflect.Ptr {
			return false
		}
		if m, ok := reflect.PtrTo(t).MethodByName("Equal"); ok {
			tf := m.Func.Type()
			return !tf.IsVariadic() && tf.NumIn() == 2 && tf.NumOut() == 1 &&
				tf.In(0).AssignableTo(tf.In(1)) && tf.Out(0) == reflect.TypeOf(true)
		}
		return false
	}, cmp.Transformer("Ref", func(x interface{}) interface{} {
		v := reflect.ValueOf(x)
		vp := reflect.New(v.Type())
		vp.Elem().Set(v)
		return vp.Interface()
	}))

	// For each of these types, there is an Equal method defined, which always
	// returns true, while the underlying data are fundamentally different.
	// Since the method should be called, these are expected to be equal.
	return []test{{
		label: label + "StructA",
		x:     ts.StructA{X: "NotEqual"},
		y:     ts.StructA{X: "not_equal"},
	}, {
		label: label + "StructA",
		x:     &ts.StructA{X: "NotEqual"},
		y:     &ts.StructA{X: "not_equal"},
	}, {
		label: label + "StructB",
		x:     ts.StructB{X: "NotEqual"},
		y:     ts.StructB{X: "not_equal"},
		wantDiff: `
  teststructs.StructB{
- 	X: "NotEqual",
+ 	X: "not_equal",
  }
`,
	}, {
		label: label + "StructB",
		x:     ts.StructB{X: "NotEqual"},
		y:     ts.StructB{X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructB",
		x:     &ts.StructB{X: "NotEqual"},
		y:     &ts.StructB{X: "not_equal"},
	}, {
		label: label + "StructC",
		x:     ts.StructC{X: "NotEqual"},
		y:     ts.StructC{X: "not_equal"},
	}, {
		label: label + "StructC",
		x:     &ts.StructC{X: "NotEqual"},
		y:     &ts.StructC{X: "not_equal"},
	}, {
		label: label + "StructD",
		x:     ts.StructD{X: "NotEqual"},
		y:     ts.StructD{X: "not_equal"},
		wantDiff: `
  teststructs.StructD{
- 	X: "NotEqual",
+ 	X: "not_equal",
  }
`,
	}, {
		label: label + "StructD",
		x:     ts.StructD{X: "NotEqual"},
		y:     ts.StructD{X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructD",
		x:     &ts.StructD{X: "NotEqual"},
		y:     &ts.StructD{X: "not_equal"},
	}, {
		label: label + "StructE",
		x:     ts.StructE{X: "NotEqual"},
		y:     ts.StructE{X: "not_equal"},
		wantDiff: `
  teststructs.StructE{
- 	X: "NotEqual",
+ 	X: "not_equal",
  }
`,
	}, {
		label: label + "StructE",
		x:     ts.StructE{X: "NotEqual"},
		y:     ts.StructE{X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructE",
		x:     &ts.StructE{X: "NotEqual"},
		y:     &ts.StructE{X: "not_equal"},
	}, {
		label: label + "StructF",
		x:     ts.StructF{X: "NotEqual"},
		y:     ts.StructF{X: "not_equal"},
		wantDiff: `
  teststructs.StructF{
- 	X: "NotEqual",
+ 	X: "not_equal",
  }
`,
	}, {
		label: label + "StructF",
		x:     &ts.StructF{X: "NotEqual"},
		y:     &ts.StructF{X: "not_equal"},
	}, {
		label: label + "StructA1",
		x:     ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
		y:     ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
	}, {
		label: label + "StructA1",
		x:     ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  teststructs.StructA1{
  	StructA: teststructs.StructA{X: "NotEqual"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructA1",
		x:     &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "equal"},
		y:     &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "equal"},
	}, {
		label: label + "StructA1",
		x:     &ts.StructA1{StructA: ts.StructA{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructA1{StructA: ts.StructA{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  &teststructs.StructA1{
  	StructA: teststructs.StructA{X: "NotEqual"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructB1",
		x:     ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
		y:     ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructB1",
		x:     ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
		wantDiff: `
  teststructs.StructB1{
  	StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructB1",
		x:     &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "equal"},
		y:     &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructB1",
		x:     &ts.StructB1{StructB: ts.StructB{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructB1{StructB: ts.StructB{X: "not_equal"}, X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
		wantDiff: `
  &teststructs.StructB1{
  	StructB: teststructs.StructB(Inverse(Ref, &teststructs.StructB{X: "NotEqual"})),
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructC1",
		x:     ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructC1",
		x:     &ts.StructC1{StructC: ts.StructC{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructC1{StructC: ts.StructC{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructD1",
		x:     ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  teststructs.StructD1{
- 	StructD: teststructs.StructD{X: "NotEqual"},
+ 	StructD: teststructs.StructD{X: "not_equal"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructD1",
		x:     ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructD1",
		x:     &ts.StructD1{StructD: ts.StructD{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructD1{StructD: ts.StructD{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructE1",
		x:     ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  teststructs.StructE1{
- 	StructE: teststructs.StructE{X: "NotEqual"},
+ 	StructE: teststructs.StructE{X: "not_equal"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructE1",
		x:     ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
		opts:  []cmp.Option{derefTransform},
	}, {
		label: label + "StructE1",
		x:     &ts.StructE1{StructE: ts.StructE{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructE1{StructE: ts.StructE{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructF1",
		x:     ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  teststructs.StructF1{
- 	StructF: teststructs.StructF{X: "NotEqual"},
+ 	StructF: teststructs.StructF{X: "not_equal"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructF1",
		x:     &ts.StructF1{StructF: ts.StructF{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructF1{StructF: ts.StructF{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructA2",
		x:     ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
		y:     ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
	}, {
		label: label + "StructA2",
		x:     ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  teststructs.StructA2{
  	StructA: &teststructs.StructA{X: "NotEqual"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructA2",
		x:     &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "equal"},
		y:     &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "equal"},
	}, {
		label: label + "StructA2",
		x:     &ts.StructA2{StructA: &ts.StructA{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructA2{StructA: &ts.StructA{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  &teststructs.StructA2{
  	StructA: &teststructs.StructA{X: "NotEqual"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructB2",
		x:     ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
		y:     ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
	}, {
		label: label + "StructB2",
		x:     ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  teststructs.StructB2{
  	StructB: &teststructs.StructB{X: "NotEqual"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructB2",
		x:     &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "equal"},
		y:     &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "equal"},
	}, {
		label: label + "StructB2",
		x:     &ts.StructB2{StructB: &ts.StructB{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructB2{StructB: &ts.StructB{X: "not_equal"}, X: "not_equal"},
		wantDiff: `
  &teststructs.StructB2{
  	StructB: &teststructs.StructB{X: "NotEqual"},
- 	X:       "NotEqual",
+ 	X:       "not_equal",
  }
`,
	}, {
		label: label + "StructC2",
		x:     ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructC2",
		x:     &ts.StructC2{StructC: &ts.StructC{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructC2{StructC: &ts.StructC{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructD2",
		x:     ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructD2",
		x:     &ts.StructD2{StructD: &ts.StructD{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructD2{StructD: &ts.StructD{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructE2",
		x:     ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructE2",
		x:     &ts.StructE2{StructE: &ts.StructE{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructE2{StructE: &ts.StructE{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructF2",
		x:     ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
		y:     ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructF2",
		x:     &ts.StructF2{StructF: &ts.StructF{X: "NotEqual"}, X: "NotEqual"},
		y:     &ts.StructF2{StructF: &ts.StructF{X: "not_equal"}, X: "not_equal"},
	}, {
		label: label + "StructNo",
		x:     ts.StructNo{X: "NotEqual"},
		y:     ts.StructNo{X: "not_equal"},
		wantDiff: `
  teststructs.StructNo{
- 	X: "NotEqual",
+ 	X: "not_equal",
  }
`,
	}, {
		label: label + "AssignA",
		x:     ts.AssignA(func() int { return 0 }),
		y:     ts.AssignA(func() int { return 1 }),
	}, {
		label: label + "AssignB",
		x:     ts.AssignB(struct{ A int }{0}),
		y:     ts.AssignB(struct{ A int }{1}),
	}, {
		label: label + "AssignC",
		x:     ts.AssignC(make(chan bool)),
		y:     ts.AssignC(make(chan bool)),
	}, {
		label: label + "AssignD",
		x:     ts.AssignD(make(chan bool)),
		y:     ts.AssignD(make(chan bool)),
	}}
}

type (
	CycleAlpha struct {
		Name   string
		Bravos map[string]*CycleBravo
	}
	CycleBravo struct {
		ID     int
		Name   string
		Mods   int
		Alphas map[string]*CycleAlpha
	}
)

func cycleTests() []test {
	const label = "Cycle"

	type (
		P *P
		S []S
		M map[int]M
	)

	makeGraph := func() map[string]*CycleAlpha {
		v := map[string]*CycleAlpha{
			"Foo": &CycleAlpha{
				Name: "Foo",
				Bravos: map[string]*CycleBravo{
					"FooBravo": &CycleBravo{
						Name: "FooBravo",
						ID:   101,
						Mods: 100,
						Alphas: map[string]*CycleAlpha{
							"Foo": nil, // cyclic reference
						},
					},
				},
			},
			"Bar": &CycleAlpha{
				Name: "Bar",
				Bravos: map[string]*CycleBravo{
					"BarBuzzBravo": &CycleBravo{
						Name: "BarBuzzBravo",
						ID:   102,
						Mods: 2,
						Alphas: map[string]*CycleAlpha{
							"Bar":  nil, // cyclic reference
							"Buzz": nil, // cyclic reference
						},
					},
					"BuzzBarBravo": &CycleBravo{
						Name: "BuzzBarBravo",
						ID:   103,
						Mods: 0,
						Alphas: map[string]*CycleAlpha{
							"Bar":  nil, // cyclic reference
							"Buzz": nil, // cyclic reference
						},
					},
				},
			},
			"Buzz": &CycleAlpha{
				Name: "Buzz",
				Bravos: map[string]*CycleBravo{
					"BarBuzzBravo": nil, // cyclic reference
					"BuzzBarBravo": nil, // cyclic reference
				},
			},
		}
		v["Foo"].Bravos["FooBravo"].Alphas["Foo"] = v["Foo"]
		v["Bar"].Bravos["BarBuzzBravo"].Alphas["Bar"] = v["Bar"]
		v["Bar"].Bravos["BarBuzzBravo"].Alphas["Buzz"] = v["Buzz"]
		v["Bar"].Bravos["BuzzBarBravo"].Alphas["Bar"] = v["Bar"]
		v["Bar"].Bravos["BuzzBarBravo"].Alphas["Buzz"] = v["Buzz"]
		v["Buzz"].Bravos["BarBuzzBravo"] = v["Bar"].Bravos["BarBuzzBravo"]
		v["Buzz"].Bravos["BuzzBarBravo"] = v["Bar"].Bravos["BuzzBarBravo"]
		return v
	}

	var tests []test
	type XY struct{ x, y interface{} }
	for _, tt := range []struct {
		in       XY
		wantDiff string
		reason   string
	}{{
		in: func() XY {
			x := new(P)
			*x = x
			y := new(P)
			*y = y
			return XY{x, y}
		}(),
	}, {
		in: func() XY {
			x := new(P)
			*x = x
			y1, y2 := new(P), new(P)
			*y1 = y2
			*y2 = y1
			return XY{x, y1}
		}(),
		wantDiff: `
  &&cmp_test.P(
- 	&⟪0xdeadf00f⟫,
+ 	&&⟪0xdeadf00f⟫,
  )
`,
	}, {
		in: func() XY {
			x := S{nil}
			x[0] = x
			y := S{nil}
			y[0] = y
			return XY{x, y}
		}(),
	}, {
		in: func() XY {
			x := S{nil}
			x[0] = x
			y1, y2 := S{nil}, S{nil}
			y1[0] = y2
			y2[0] = y1
			return XY{x, y1}
		}(),
		wantDiff: `
  cmp_test.S{
- 	{{{*(*cmp_test.S)(⟪0xdeadf00f⟫)}}},
+ 	{{{{*(*cmp_test.S)(⟪0xdeadf00f⟫)}}}},
  }
`,
	}, {
		in: func() XY {
			x := M{0: nil}
			x[0] = x
			y := M{0: nil}
			y[0] = y
			return XY{x, y}
		}(),
	}, {
		in: func() XY {
			x := M{0: nil}
			x[0] = x
			y1, y2 := M{0: nil}, M{0: nil}
			y1[0] = y2
			y2[0] = y1
			return XY{x, y1}
		}(),
		wantDiff: `
  cmp_test.M{
- 	0: {0: ⟪0xdeadf00f⟫},
+ 	0: {0: {0: ⟪0xdeadf00f⟫}},
  }
`,
	}, {
		in: XY{makeGraph(), makeGraph()},
	}, {
		in: func() XY {
			x := makeGraph()
			y := makeGraph()
			y["Foo"].Bravos["FooBravo"].ID = 0
			y["Bar"].Bravos["BarBuzzBravo"].ID = 0
			y["Bar"].Bravos["BuzzBarBravo"].ID = 0
			return XY{x, y}
		}(),
		wantDiff: `
  map[string]*cmp_test.CycleAlpha{
  	"Bar": &{
  		Name: "Bar",
  		Bravos: map[string]*cmp_test.CycleBravo{
  			"BarBuzzBravo": &{
- 				ID:   102,
+ 				ID:   0,
  				Name: "BarBuzzBravo",
  				Mods: 2,
  				Alphas: map[string]*cmp_test.CycleAlpha{
  					"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
  					"Buzz": &{
  						Name: "Buzz",
  						Bravos: map[string]*cmp_test.CycleBravo{
  							"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
  							"BuzzBarBravo": &{
- 								ID:     103,
+ 								ID:     0,
  								Name:   "BuzzBarBravo",
  								Mods:   0,
  								Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
  							},
  						},
  					},
  				},
  			},
  			"BuzzBarBravo": &{
- 				ID:   103,
+ 				ID:   0,
  				Name: "BuzzBarBravo",
  				Mods: 0,
  				Alphas: map[string]*cmp_test.CycleAlpha{
  					"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
  					"Buzz": &{
  						Name: "Buzz",
  						Bravos: map[string]*cmp_test.CycleBravo{
  							"BarBuzzBravo": &{
- 								ID:     102,
+ 								ID:     0,
  								Name:   "BarBuzzBravo",
  								Mods:   2,
  								Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
  							},
  							"BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": ⟪0xdeadf00f⟫}},
  						},
  					},
  				},
  			},
  		},
  	},
  	"Buzz": &{
  		Name: "Buzz",
  		Bravos: map[string]*cmp_test.CycleBravo{
  			"BarBuzzBravo": &{
- 				ID:   102,
+ 				ID:   0,
  				Name: "BarBuzzBravo",
  				Mods: 2,
  				Alphas: map[string]*cmp_test.CycleAlpha{
  					"Bar": &{
  						Name: "Bar",
  						Bravos: map[string]*cmp_test.CycleBravo{
  							"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
  							"BuzzBarBravo": &{
- 								ID:     103,
+ 								ID:     0,
  								Name:   "BuzzBarBravo",
  								Mods:   0,
  								Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
  							},
  						},
  					},
  					"Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
  				},
  			},
  			"BuzzBarBravo": &{
- 				ID:   103,
+ 				ID:   0,
  				Name: "BuzzBarBravo",
  				Mods: 0,
  				Alphas: map[string]*cmp_test.CycleAlpha{
  					"Bar": &{
  						Name: "Bar",
  						Bravos: map[string]*cmp_test.CycleBravo{
  							"BarBuzzBravo": &{
- 								ID:     102,
+ 								ID:     0,
  								Name:   "BarBuzzBravo",
  								Mods:   2,
  								Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}},
  							},
  							"BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": ⟪0xdeadf00f⟫}},
  						},
  					},
  					"Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
  				},
  			},
  		},
  	},
  	"Foo": &{
  		Name: "Foo",
  		Bravos: map[string]*cmp_test.CycleBravo{
  			"FooBravo": &{
- 				ID:     101,
+ 				ID:     0,
  				Name:   "FooBravo",
  				Mods:   100,
  				Alphas: map[string]*cmp_test.CycleAlpha{"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": ⟪0xdeadf00f⟫}}}}},
  			},
  		},
  	},
  }
`,
	}, {
		in: func() XY {
			x := makeGraph()
			y := makeGraph()
			x["Buzz"].Bravos["BuzzBarBravo"] = &CycleBravo{
				Name: "BuzzBarBravo",
				ID:   103,
			}
			return XY{x, y}
		}(),
		wantDiff: `
  map[string]*cmp_test.CycleAlpha{
  	"Bar": &{
  		Name: "Bar",
  		Bravos: map[string]*cmp_test.CycleBravo{
  			"BarBuzzBravo": &{
  				ID:   102,
  				Name: "BarBuzzBravo",
  				Mods: 2,
  				Alphas: map[string]*cmp_test.CycleAlpha{
  					"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
  					"Buzz": &{
  						Name: "Buzz",
  						Bravos: map[string]*cmp_test.CycleBravo{
  							"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}},
  							"BuzzBarBravo": &{
  								ID:     103,
  								Name:   "BuzzBarBravo",
  								Mods:   0,
- 								Alphas: nil,
+ 								Alphas: map[string]*cmp_test.CycleAlpha{
+ 									"Bar": &{
+ 										Name: "Bar",
+ 										Bravos: map[string]*cmp_test.CycleBravo{
+ 											"BarBuzzBravo": &{
+ 												ID:   102,
+ 												Name: "BarBuzzBravo",
+ 												Mods: 2,
+ 												Alphas: map[string]*cmp_test.CycleAlpha{
+ 													"Bar": ⟪0xdeadf00f⟫,
+ 													"Buzz": &{
+ 														Name: "Buzz",
+ 														Bravos: map[string]*cmp_test.CycleBravo{
+ 															"BarBuzzBravo": ⟪0xdeadf00f⟫,
+ 															"BuzzBarBravo": &{
+ 																ID:     103,
+ 																Name:   "BuzzBarBravo",
+ 																Alphas: map[string]*cmp_test.CycleAlpha(⟪0xdeadf00f⟫),
+ 															},
+ 														},
+ 													},
+ 												},
+ 											},
+ 											"BuzzBarBravo": ⟪0xdeadf00f⟫,
+ 										},
+ 									},
+ 									"Buzz": ⟪0xdeadf00f⟫,
+ 								},
  							},
  						},
  					},
  				},
  			},
  			"BuzzBarBravo": &{
  				ID:   103,
  				Name: "BuzzBarBravo",
  				Mods: 0,
  				Alphas: map[string]*cmp_test.CycleAlpha{
  					"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}},
  					"Buzz": &{
  						Name: "Buzz",
  						Bravos: map[string]*cmp_test.CycleBravo{
  							"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}},
- 							"BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo"},
+ 							"BuzzBarBravo": &{
+ 								ID:   103,
+ 								Name: "BuzzBarBravo",
+ 								Alphas: map[string]*cmp_test.CycleAlpha{
+ 									"Bar": &{
+ 										Name: "Bar",
+ 										Bravos: map[string]*cmp_test.CycleBravo{
+ 											"BarBuzzBravo": &{
+ 												ID:   102,
+ 												Name: "BarBuzzBravo",
+ 												Mods: 2,
+ 												Alphas: map[string]*cmp_test.CycleAlpha{
+ 													"Bar": ⟪0xdeadf00f⟫,
+ 													"Buzz": &{
+ 														Name:   "Buzz",
+ 														Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫},
+ 													},
+ 												},
+ 											},
+ 											"BuzzBarBravo": ⟪0xdeadf00f⟫,
+ 										},
+ 									},
+ 									"Buzz": ⟪0xdeadf00f⟫,
+ 								},
+ 							},
  						},
  					},
  				},
  			},
  		},
  	},
  	"Buzz": &{
  		Name: "Buzz",
  		Bravos: map[string]*cmp_test.CycleBravo{
  			"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}}}}, "Buzz": &{Name: "Buzz", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": &{ID: 102, Name: "BarBuzzBravo", Mods: 2, Alphas: map[string]*cmp_test.CycleAlpha{"Bar": &{Name: "Bar", Bravos: map[string]*cmp_test.CycleBravo{"BarBuzzBravo": ⟪0xdeadf00f⟫, "BuzzBarBravo": &{ID: 103, Name: "BuzzBarBravo", Alphas: map[string]*cmp_test.CycleAlpha{"Bar": ⟪0xdeadf00f⟫, "Buzz": ⟪0xdeadf00f⟫}}}}, "Buzz": ⟪0xdeadf00f⟫}}, "BuzzBarBravo": ⟪0xdeadf00f⟫}}}},
  			"BuzzBarBravo": &{
  				ID:     103,
  				Name:   "BuzzBarBravo",
  				Mods:   0,
- 				Alphas: nil,
+ 				Alphas: map[string]*cmp_test.CycleAlpha{
+ 					"Bar": &{
+ 						Name: "Bar",
+ 						Bravos: map[string]*cmp_test.CycleBravo{
+ 							"BarBuzzBravo": &{
+ 								ID:   102,
+ 								Name: "BarBuzzBravo",
+ 								Mods: 2,
+ 								Alphas: map[string]*cmp_test.CycleAlpha{
+ 									"Bar": ⟪0xdeadf00f⟫,
+ 									"Buzz": &{
+ 										Name: "Buzz",
+ 										Bravos: map[string]*cmp_test.CycleBravo{
+ 											"BarBuzzBravo": ⟪0xdeadf00f⟫,
+ 											"BuzzBarBravo": &{
+ 												ID:     103,
+ 												Name:   "BuzzBarBravo",
+ 												Alphas: map[string]*cmp_test.CycleAlpha(⟪0xdeadf00f⟫),
+ 											},
+ 										},
+ 									},
+ 								},
+ 							},
+ 							"BuzzBarBravo": ⟪0xdeadf00f⟫,
+ 						},
+ 					},
+ 					"Buzz": ⟪0xdeadf00f⟫,
+ 				},
  			},
  		},
  	},
  	"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": &{Name: "Foo", Bravos: map[string]*cmp_test.CycleBravo{"FooBravo": &{ID: 101, Name: "FooBravo", Mods: 100, Alphas: map[string]*cmp_test.CycleAlpha{"Foo": ⟪0xdeadf00f⟫}}}}}}}},
  }
`,
	}} {
		tests = append(tests, test{
			label:    label,
			x:        tt.in.x,
			y:        tt.in.y,
			wantDiff: tt.wantDiff,
			reason:   tt.reason,
		})
	}
	return tests
}

func project1Tests() []test {
	const label = "Project1"

	ignoreUnexported := cmpopts.IgnoreUnexported(
		ts.EagleImmutable{},
		ts.DreamerImmutable{},
		ts.SlapImmutable{},
		ts.GoatImmutable{},
		ts.DonkeyImmutable{},
		ts.LoveRadius{},
		ts.SummerLove{},
		ts.SummerLoveSummary{},
	)

	createEagle := func() ts.Eagle {
		return ts.Eagle{
			Name:   "eagle",
			Hounds: []string{"buford", "tannen"},
			Desc:   "some description",
			Dreamers: []ts.Dreamer{{}, {
				Name: "dreamer2",
				Animal: []interface{}{
					ts.Goat{
						Target: "corporation",
						Immutable: &ts.GoatImmutable{
							ID:      "southbay",
							State:   (*pb.Goat_States)(intPtr(5)),
							Started: now,
						},
					},
					ts.Donkey{},
				},
				Amoeba: 53,
			}},
			Slaps: []ts.Slap{{
				Name: "slapID",
				Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
				Immutable: &ts.SlapImmutable{
					ID:       "immutableSlap",
					MildSlap: true,
					Started:  now,
					LoveRadius: &ts.LoveRadius{
						Summer: &ts.SummerLove{
							Summary: &ts.SummerLoveSummary{
								Devices:    []string{"foo", "bar", "baz"},
								ChangeType: []pb.SummerType{1, 2, 3},
							},
						},
					},
				},
			}},
			Immutable: &ts.EagleImmutable{
				ID:          "eagleID",
				Birthday:    now,
				MissingCall: (*pb.Eagle_MissingCalls)(intPtr(55)),
			},
		}
	}

	return []test{{
		label: label,
		x: ts.Eagle{Slaps: []ts.Slap{{
			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
		}}},
		y: ts.Eagle{Slaps: []ts.Slap{{
			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
		}}},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x: ts.Eagle{Slaps: []ts.Slap{{
			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
		}}},
		y: ts.Eagle{Slaps: []ts.Slap{{
			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
		}}},
		opts: []cmp.Option{cmp.Comparer(pb.Equal)},
	}, {
		label: label,
		x: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata"}},
		}}},
		y: ts.Eagle{Slaps: []ts.Slap{{}, {}, {}, {}, {
			Args: &pb.MetaData{Stringer: pb.Stringer{X: "metadata2"}},
		}}},
		opts: []cmp.Option{cmp.Comparer(pb.Equal)},
		wantDiff: `
  teststructs.Eagle{
  	... // 4 identical fields
  	Dreamers: nil,
  	Prong:    0,
  	Slaps: []teststructs.Slap{
  		... // 2 identical elements
  		{},
  		{},
  		{
  			Name:     "",
  			Desc:     "",
  			DescLong: "",
- 			Args:     s"metadata",
+ 			Args:     s"metadata2",
  			Tense:    0,
  			Interval: 0,
  			... // 3 identical fields
  		},
  	},
  	StateGoverner: "",
  	PrankRating:   "",
  	... // 2 identical fields
  }
`,
	}, {
		label: label,
		x:     createEagle(),
		y:     createEagle(),
		opts:  []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
	}, {
		label: label,
		x: func() ts.Eagle {
			eg := createEagle()
			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.ID = "southbay2"
			eg.Dreamers[1].Animal[0].(ts.Goat).Immutable.State = (*pb.Goat_States)(intPtr(6))
			eg.Slaps[0].Immutable.MildSlap = false
			return eg
		}(),
		y: func() ts.Eagle {
			eg := createEagle()
			devs := eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices
			eg.Slaps[0].Immutable.LoveRadius.Summer.Summary.Devices = devs[:1]
			return eg
		}(),
		opts: []cmp.Option{ignoreUnexported, cmp.Comparer(pb.Equal)},
		wantDiff: `
  teststructs.Eagle{
  	... // 2 identical fields
  	Desc:     "some description",
  	DescLong: "",
  	Dreamers: []teststructs.Dreamer{
  		{},
  		{
  			... // 4 identical fields
  			ContSlaps:         nil,
  			ContSlapsInterval: 0,
  			Animal: []interface{}{
  				teststructs.Goat{
  					Target:     "corporation",
  					Slaps:      nil,
  					FunnyPrank: "",
  					Immutable: &teststructs.GoatImmutable{
- 						ID:      "southbay2",
+ 						ID:      "southbay",
- 						State:   &6,
+ 						State:   &5,
  						Started: s"2009-11-10 23:00:00 +0000 UTC",
  						Stopped: s"0001-01-01 00:00:00 +0000 UTC",
  						... // 1 ignored and 1 identical fields
  					},
  				},
  				teststructs.Donkey{},
  			},
  			Ornamental: false,
  			Amoeba:     53,
  			... // 5 identical fields
  		},
  	},
  	Prong: 0,
  	Slaps: []teststructs.Slap{
  		{
  			... // 6 identical fields
  			Homeland:   0x00,
  			FunnyPrank: "",
  			Immutable: &teststructs.SlapImmutable{
  				ID:          "immutableSlap",
  				Out:         nil,
- 				MildSlap:    false,
+ 				MildSlap:    true,
  				PrettyPrint: "",
  				State:       nil,
  				Started:     s"2009-11-10 23:00:00 +0000 UTC",
  				Stopped:     s"0001-01-01 00:00:00 +0000 UTC",
  				LastUpdate:  s"0001-01-01 00:00:00 +0000 UTC",
  				LoveRadius: &teststructs.LoveRadius{
  					Summer: &teststructs.SummerLove{
  						Summary: &teststructs.SummerLoveSummary{
  							Devices: []string{
  								"foo",
- 								"bar",
- 								"baz",
  							},
  							ChangeType: []testprotos.SummerType{1, 2, 3},
  							... // 1 ignored field
  						},
  						... // 1 ignored field
  					},
  					... // 1 ignored field
  				},
  				... // 1 ignored field
  			},
  		},
  	},
  	StateGoverner: "",
  	PrankRating:   "",
  	... // 2 identical fields
  }
`,
	}}
}

type germSorter []*pb.Germ

func (gs germSorter) Len() int           { return len(gs) }
func (gs germSorter) Less(i, j int) bool { return gs[i].String() < gs[j].String() }
func (gs germSorter) Swap(i, j int)      { gs[i], gs[j] = gs[j], gs[i] }

func project2Tests() []test {
	const label = "Project2"

	sortGerms := cmp.Transformer("Sort", func(in []*pb.Germ) []*pb.Germ {
		out := append([]*pb.Germ(nil), in...) // Make copy
		sort.Sort(germSorter(out))
		return out
	})

	equalDish := cmp.Comparer(func(x, y *ts.Dish) bool {
		if x == nil || y == nil {
			return x == nil && y == nil
		}
		px, err1 := x.Proto()
		py, err2 := y.Proto()
		if err1 != nil || err2 != nil {
			return err1 == err2
		}
		return pb.Equal(px, py)
	})

	createBatch := func() ts.GermBatch {
		return ts.GermBatch{
			DirtyGerms: map[int32][]*pb.Germ{
				17: {
					{Stringer: pb.Stringer{X: "germ1"}},
				},
				18: {
					{Stringer: pb.Stringer{X: "germ2"}},
					{Stringer: pb.Stringer{X: "germ3"}},
					{Stringer: pb.Stringer{X: "germ4"}},
				},
			},
			GermMap: map[int32]*pb.Germ{
				13: {Stringer: pb.Stringer{X: "germ13"}},
				21: {Stringer: pb.Stringer{X: "germ21"}},
			},
			DishMap: map[int32]*ts.Dish{
				0: ts.CreateDish(nil, io.EOF),
				1: ts.CreateDish(nil, io.ErrUnexpectedEOF),
				2: ts.CreateDish(&pb.Dish{Stringer: pb.Stringer{X: "dish"}}, nil),
			},
			HasPreviousResult: true,
			DirtyID:           10,
			GermStrain:        421,
			InfectedAt:        now,
		}
	}

	return []test{{
		label:     label,
		x:         createBatch(),
		y:         createBatch(),
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     createBatch(),
		y:     createBatch(),
		opts:  []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
	}, {
		label: label,
		x:     createBatch(),
		y: func() ts.GermBatch {
			gb := createBatch()
			s := gb.DirtyGerms[18]
			s[0], s[1], s[2] = s[1], s[2], s[0]
			return gb
		}(),
		opts: []cmp.Option{cmp.Comparer(pb.Equal), equalDish},
		wantDiff: `
  teststructs.GermBatch{
  	DirtyGerms: map[int32][]*testprotos.Germ{
  		17: {s"germ1"},
  		18: {
- 			s"germ2",
  			s"germ3",
  			s"germ4",
+ 			s"germ2",
  		},
  	},
  	CleanGerms: nil,
  	GermMap:    map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
  	... // 7 identical fields
  }
`,
	}, {
		label: label,
		x:     createBatch(),
		y: func() ts.GermBatch {
			gb := createBatch()
			s := gb.DirtyGerms[18]
			s[0], s[1], s[2] = s[1], s[2], s[0]
			return gb
		}(),
		opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
	}, {
		label: label,
		x: func() ts.GermBatch {
			gb := createBatch()
			delete(gb.DirtyGerms, 17)
			gb.DishMap[1] = nil
			return gb
		}(),
		y: func() ts.GermBatch {
			gb := createBatch()
			gb.DirtyGerms[18] = gb.DirtyGerms[18][:2]
			gb.GermStrain = 22
			return gb
		}(),
		opts: []cmp.Option{cmp.Comparer(pb.Equal), sortGerms, equalDish},
		wantDiff: `
  teststructs.GermBatch{
  	DirtyGerms: map[int32][]*testprotos.Germ{
+ 		17: {s"germ1"},
  		18: Inverse(Sort, []*testprotos.Germ{
  			s"germ2",
  			s"germ3",
- 			s"germ4",
  		}),
  	},
  	CleanGerms: nil,
  	GermMap:    map[int32]*testprotos.Germ{13: s"germ13", 21: s"germ21"},
  	DishMap: map[int32]*teststructs.Dish{
  		0: &{err: &errors.errorString{s: "EOF"}},
- 		1: nil,
+ 		1: &{err: &errors.errorString{s: "unexpected EOF"}},
  		2: &{pb: &testprotos.Dish{Stringer: testprotos.Stringer{X: "dish"}}},
  	},
  	HasPreviousResult: true,
  	DirtyID:           10,
  	CleanID:           0,
- 	GermStrain:        421,
+ 	GermStrain:        22,
  	TotalDirtyGerms:   0,
  	InfectedAt:        s"2009-11-10 23:00:00 +0000 UTC",
  }
`,
	}}
}

func project3Tests() []test {
	const label = "Project3"

	allowVisibility := cmp.AllowUnexported(ts.Dirt{})

	ignoreLocker := cmpopts.IgnoreInterfaces(struct{ sync.Locker }{})

	transformProtos := cmp.Transformer("λ", func(x pb.Dirt) *pb.Dirt {
		return &x
	})

	equalTable := cmp.Comparer(func(x, y ts.Table) bool {
		tx, ok1 := x.(*ts.MockTable)
		ty, ok2 := y.(*ts.MockTable)
		if !ok1 || !ok2 {
			panic("table type must be MockTable")
		}
		return cmp.Equal(tx.State(), ty.State())
	})

	createDirt := func() (d ts.Dirt) {
		d.SetTable(ts.CreateMockTable([]string{"a", "b", "c"}))
		d.SetTimestamp(12345)
		d.Discord = 554
		d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "proto"}}
		d.SetWizard(map[string]*pb.Wizard{
			"harry": {Stringer: pb.Stringer{X: "potter"}},
			"albus": {Stringer: pb.Stringer{X: "dumbledore"}},
		})
		d.SetLastTime(54321)
		return d
	}

	return []test{{
		label:     label,
		x:         createDirt(),
		y:         createDirt(),
		wantPanic: "cannot handle unexported field",
	}, {
		label:     label,
		x:         createDirt(),
		y:         createDirt(),
		opts:      []cmp.Option{allowVisibility, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     createDirt(),
		y:     createDirt(),
		opts:  []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
	}, {
		label: label,
		x: func() ts.Dirt {
			d := createDirt()
			d.SetTable(ts.CreateMockTable([]string{"a", "c"}))
			d.Proto = pb.Dirt{Stringer: pb.Stringer{X: "blah"}}
			return d
		}(),
		y: func() ts.Dirt {
			d := createDirt()
			d.Discord = 500
			d.SetWizard(map[string]*pb.Wizard{
				"harry": {Stringer: pb.Stringer{X: "otter"}},
			})
			return d
		}(),
		opts: []cmp.Option{allowVisibility, transformProtos, ignoreLocker, cmp.Comparer(pb.Equal), equalTable},
		wantDiff: `
  teststructs.Dirt{
- 	table:   &teststructs.MockTable{state: []string{"a", "c"}},
+ 	table:   &teststructs.MockTable{state: []string{"a", "b", "c"}},
  	ts:      12345,
- 	Discord: 554,
+ 	Discord: 500,
- 	Proto:   testprotos.Dirt(Inverse(λ, s"blah")),
+ 	Proto:   testprotos.Dirt(Inverse(λ, s"proto")),
  	wizard: map[string]*testprotos.Wizard{
- 		"albus": s"dumbledore",
- 		"harry": s"potter",
+ 		"harry": s"otter",
  	},
  	sadistic: nil,
  	lastTime: 54321,
  	... // 1 ignored field
  }
`,
	}}
}

func project4Tests() []test {
	const label = "Project4"

	allowVisibility := cmp.AllowUnexported(
		ts.Cartel{},
		ts.Headquarter{},
		ts.Poison{},
	)

	transformProtos := cmp.Transformer("λ", func(x pb.Restrictions) *pb.Restrictions {
		return &x
	})

	createCartel := func() ts.Cartel {
		var p ts.Poison
		p.SetPoisonType(5)
		p.SetExpiration(now)
		p.SetManufacturer("acme")

		var hq ts.Headquarter
		hq.SetID(5)
		hq.SetLocation("moon")
		hq.SetSubDivisions([]string{"alpha", "bravo", "charlie"})
		hq.SetMetaData(&pb.MetaData{Stringer: pb.Stringer{X: "metadata"}})
		hq.SetPublicMessage([]byte{1, 2, 3, 4, 5})
		hq.SetHorseBack("abcdef")
		hq.SetStatus(44)

		var c ts.Cartel
		c.Headquarter = hq
		c.SetSource("mars")
		c.SetCreationTime(now)
		c.SetBoss("al capone")
		c.SetPoisons([]*ts.Poison{&p})

		return c
	}

	return []test{{
		label:     label,
		x:         createCartel(),
		y:         createCartel(),
		wantPanic: "cannot handle unexported field",
	}, {
		label:     label,
		x:         createCartel(),
		y:         createCartel(),
		opts:      []cmp.Option{allowVisibility, cmp.Comparer(pb.Equal)},
		wantPanic: "cannot handle unexported field",
	}, {
		label: label,
		x:     createCartel(),
		y:     createCartel(),
		opts:  []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
	}, {
		label: label,
		x: func() ts.Cartel {
			d := createCartel()
			var p1, p2 ts.Poison
			p1.SetPoisonType(1)
			p1.SetExpiration(now)
			p1.SetManufacturer("acme")
			p2.SetPoisonType(2)
			p2.SetManufacturer("acme2")
			d.SetPoisons([]*ts.Poison{&p1, &p2})
			return d
		}(),
		y: func() ts.Cartel {
			d := createCartel()
			d.SetSubDivisions([]string{"bravo", "charlie"})
			d.SetPublicMessage([]byte{1, 2, 4, 3, 5})
			return d
		}(),
		opts: []cmp.Option{allowVisibility, transformProtos, cmp.Comparer(pb.Equal)},
		wantDiff: `
  teststructs.Cartel{
  	Headquarter: teststructs.Headquarter{
  		id:       0x05,
  		location: "moon",
  		subDivisions: []string{
- 			"alpha",
  			"bravo",
  			"charlie",
  		},
  		incorporatedDate: s"0001-01-01 00:00:00 +0000 UTC",
  		metaData:         s"metadata",
  		privateMessage:   nil,
  		publicMessage: []uint8{
  			0x01,
  			0x02,
- 			0x03,
+ 			0x04,
- 			0x04,
+ 			0x03,
  			0x05,
  		},
  		horseBack: "abcdef",
  		rattle:    "",
  		... // 5 identical fields
  	},
  	source:        "mars",
  	creationDate:  s"0001-01-01 00:00:00 +0000 UTC",
  	boss:          "al capone",
  	lastCrimeDate: s"0001-01-01 00:00:00 +0000 UTC",
  	poisons: []*teststructs.Poison{
  		&{
- 			poisonType:   1,
+ 			poisonType:   5,
  			expiration:   s"2009-11-10 23:00:00 +0000 UTC",
  			manufacturer: "acme",
  			potency:      0,
  		},
- 		&{poisonType: 2, manufacturer: "acme2"},
  	},
  }
`,
	}}
}

// BenchmarkBytes benchmarks the performance of performing Equal or Diff on
// large slices of bytes.
func BenchmarkBytes(b *testing.B) {
	// Create a list of PathFilters that never apply, but are evaluated.
	const maxFilters = 5
	var filters cmp.Options
	errorIface := reflect.TypeOf((*error)(nil)).Elem()
	for i := 0; i <= maxFilters; i++ {
		filters = append(filters, cmp.FilterPath(func(p cmp.Path) bool {
			return p.Last().Type().AssignableTo(errorIface) // Never true
		}, cmp.Ignore()))
	}

	type benchSize struct {
		label string
		size  int64
	}
	for _, ts := range []benchSize{
		{"4KiB", 1 << 12},
		{"64KiB", 1 << 16},
		{"1MiB", 1 << 20},
		{"16MiB", 1 << 24},
	} {
		bx := append(append(make([]byte, ts.size/2), 'x'), make([]byte, ts.size/2)...)
		by := append(append(make([]byte, ts.size/2), 'y'), make([]byte, ts.size/2)...)
		b.Run(ts.label, func(b *testing.B) {
			// Iteratively add more filters that never apply, but are evaluated
			// to measure the cost of simply evaluating each filter.
			for i := 0; i <= maxFilters; i++ {
				b.Run(fmt.Sprintf("EqualFilter%d", i), func(b *testing.B) {
					b.ReportAllocs()
					b.SetBytes(2 * ts.size)
					for j := 0; j < b.N; j++ {
						cmp.Equal(bx, by, filters[:i]...)
					}
				})
			}
			for i := 0; i <= maxFilters; i++ {
				b.Run(fmt.Sprintf("DiffFilter%d", i), func(b *testing.B) {
					b.ReportAllocs()
					b.SetBytes(2 * ts.size)
					for j := 0; j < b.N; j++ {
						cmp.Diff(bx, by, filters[:i]...)
					}
				})
			}
		})
	}
}
