// 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"
	"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, 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
	}

	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)",
	}}
}

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)),
	}}
}

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]...)
					}
				})
			}
		})
	}
}
