+++ /dev/null
-// 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 cmpopts
-
-import (
- "bytes"
- "errors"
- "fmt"
- "io"
- "math"
- "reflect"
- "strings"
- "sync"
- "testing"
- "time"
-
- "github.com/google/go-cmp/cmp"
- "golang.org/x/xerrors"
-)
-
-type (
- MyInt int
- MyInts []int
- MyFloat float32
- MyString string
- MyTime struct{ time.Time }
- MyStruct struct {
- A, B []int
- C, D map[time.Time]string
- }
-
- Foo1 struct{ Alpha, Bravo, Charlie int }
- Foo2 struct{ *Foo1 }
- Foo3 struct{ *Foo2 }
- Bar1 struct{ Foo3 }
- Bar2 struct {
- Bar1
- *Foo3
- Bravo float32
- }
- Bar3 struct {
- Bar1
- Bravo *Bar2
- Delta struct{ Echo Foo1 }
- *Foo3
- Alpha string
- }
-
- privateStruct struct{ Public, private int }
- PublicStruct struct{ Public, private int }
- ParentStruct struct {
- *privateStruct
- *PublicStruct
- Public int
- private int
- }
-
- Everything struct {
- MyInt
- MyFloat
- MyTime
- MyStruct
- Bar3
- ParentStruct
- }
-
- EmptyInterface interface{}
-)
-
-func TestOptions(t *testing.T) {
- createBar3X := func() *Bar3 {
- return &Bar3{
- Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 2}}}},
- Bravo: &Bar2{
- Bar1: Bar1{Foo3{&Foo2{&Foo1{Charlie: 7}}}},
- Foo3: &Foo3{&Foo2{&Foo1{Bravo: 5}}},
- Bravo: 4,
- },
- Delta: struct{ Echo Foo1 }{Foo1{Charlie: 3}},
- Foo3: &Foo3{&Foo2{&Foo1{Alpha: 1}}},
- Alpha: "alpha",
- }
- }
- createBar3Y := func() *Bar3 {
- return &Bar3{
- Bar1: Bar1{Foo3{&Foo2{&Foo1{Bravo: 3}}}},
- Bravo: &Bar2{
- Bar1: Bar1{Foo3{&Foo2{&Foo1{Charlie: 8}}}},
- Foo3: &Foo3{&Foo2{&Foo1{Bravo: 6}}},
- Bravo: 5,
- },
- Delta: struct{ Echo Foo1 }{Foo1{Charlie: 4}},
- Foo3: &Foo3{&Foo2{&Foo1{Alpha: 2}}},
- Alpha: "ALPHA",
- }
- }
-
- tests := []struct {
- label string // Test name
- x, y interface{} // Input values to compare
- opts []cmp.Option // Input options
- wantEqual bool // Whether the inputs are equal
- wantPanic bool // Whether Equal should panic
- reason string // The reason for the expected outcome
- }{{
- label: "EquateEmpty",
- x: []int{},
- y: []int(nil),
- wantEqual: false,
- reason: "not equal because empty non-nil and nil slice differ",
- }, {
- label: "EquateEmpty",
- x: []int{},
- y: []int(nil),
- opts: []cmp.Option{EquateEmpty()},
- wantEqual: true,
- reason: "equal because EquateEmpty equates empty slices",
- }, {
- label: "SortSlices",
- x: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
- y: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
- wantEqual: false,
- reason: "not equal because element order differs",
- }, {
- label: "SortSlices",
- x: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
- y: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
- opts: []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
- wantEqual: true,
- reason: "equal because SortSlices sorts the slices",
- }, {
- label: "SortSlices",
- x: []MyInt{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
- y: []MyInt{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
- opts: []cmp.Option{SortSlices(func(x, y int) bool { return x < y })},
- wantEqual: false,
- reason: "not equal because MyInt is not the same type as int",
- }, {
- label: "SortSlices",
- x: []float64{0, 1, 1, 2, 2, 2},
- y: []float64{2, 0, 2, 1, 2, 1},
- opts: []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
- wantEqual: true,
- reason: "equal even when sorted with duplicate elements",
- }, {
- label: "SortSlices",
- x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
- y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
- opts: []cmp.Option{SortSlices(func(x, y float64) bool { return x < y })},
- wantPanic: true,
- reason: "panics because SortSlices used with non-transitive less function",
- }, {
- label: "SortSlices",
- x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, 3, 4, 4, 4, 4},
- y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, 2},
- opts: []cmp.Option{SortSlices(func(x, y float64) bool {
- return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
- })},
- wantEqual: false,
- reason: "no panics because SortSlices used with valid less function; not equal because NaN != NaN",
- }, {
- label: "SortSlices+EquateNaNs",
- x: []float64{0, 1, 1, 2, 2, 2, math.NaN(), 3, 3, 3, math.NaN(), 3, 4, 4, 4, 4},
- y: []float64{2, 0, 4, 4, 3, math.NaN(), 4, 1, 3, 2, 3, 3, 4, 1, math.NaN(), 2},
- opts: []cmp.Option{
- EquateNaNs(),
- SortSlices(func(x, y float64) bool {
- return (!math.IsNaN(x) && math.IsNaN(y)) || x < y
- }),
- },
- wantEqual: true,
- reason: "no panics because SortSlices used with valid less function; equal because EquateNaNs is used",
- }, {
- label: "SortMaps",
- x: map[time.Time]string{
- time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
- time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
- time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
- },
- y: map[time.Time]string{
- time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
- time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
- time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
- },
- wantEqual: false,
- reason: "not equal because timezones differ",
- }, {
- label: "SortMaps",
- x: map[time.Time]string{
- time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
- time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
- time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC): "2nd birthday",
- },
- y: map[time.Time]string{
- time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
- time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
- time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "2nd birthday",
- },
- opts: []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
- wantEqual: true,
- reason: "equal because SortMaps flattens to a slice where Time.Equal can be used",
- }, {
- label: "SortMaps",
- x: map[MyTime]string{
- {time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC)}: "0th birthday",
- {time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC)}: "1st birthday",
- {time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC)}: "2nd birthday",
- },
- y: map[MyTime]string{
- {time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "0th birthday",
- {time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "1st birthday",
- {time.Date(2011, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local)}: "2nd birthday",
- },
- opts: []cmp.Option{SortMaps(func(x, y time.Time) bool { return x.Before(y) })},
- wantEqual: false,
- reason: "not equal because MyTime is not assignable to time.Time",
- }, {
- label: "SortMaps",
- x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
- // => {0, 1, 2, 3, -1, -2, -3},
- y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
- // => {0, 1, 2, 3, 100, 200, 300},
- opts: []cmp.Option{SortMaps(func(a, b int) bool {
- if -10 < a && a <= 0 {
- a *= -100
- }
- if -10 < b && b <= 0 {
- b *= -100
- }
- return a < b
- })},
- wantEqual: false,
- reason: "not equal because values differ even though SortMap provides valid ordering",
- }, {
- label: "SortMaps",
- x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
- // => {0, 1, 2, 3, -1, -2, -3},
- y: map[int]string{300: "", 200: "", 100: "", 0: "", 1: "", 2: "", 3: ""},
- // => {0, 1, 2, 3, 100, 200, 300},
- opts: []cmp.Option{
- SortMaps(func(x, y int) bool {
- if -10 < x && x <= 0 {
- x *= -100
- }
- if -10 < y && y <= 0 {
- y *= -100
- }
- return x < y
- }),
- cmp.Comparer(func(x, y int) bool {
- if -10 < x && x <= 0 {
- x *= -100
- }
- if -10 < y && y <= 0 {
- y *= -100
- }
- return x == y
- }),
- },
- wantEqual: true,
- reason: "equal because Comparer used to equate differences",
- }, {
- label: "SortMaps",
- x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
- y: map[int]string{},
- opts: []cmp.Option{SortMaps(func(x, y int) bool {
- return x < y && x >= 0 && y >= 0
- })},
- wantPanic: true,
- reason: "panics because SortMaps used with non-transitive less function",
- }, {
- label: "SortMaps",
- x: map[int]string{-3: "", -2: "", -1: "", 0: "", 1: "", 2: "", 3: ""},
- y: map[int]string{},
- opts: []cmp.Option{SortMaps(func(x, y int) bool {
- return math.Abs(float64(x)) < math.Abs(float64(y))
- })},
- wantPanic: true,
- reason: "panics because SortMaps used with partial less function",
- }, {
- label: "EquateEmpty+SortSlices+SortMaps",
- x: MyStruct{
- A: []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
- C: map[time.Time]string{
- time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC): "0th birthday",
- time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC): "1st birthday",
- },
- D: map[time.Time]string{},
- },
- y: MyStruct{
- A: []int{1, 0, 5, 2, 8, 9, 4, 3, 6, 7},
- B: []int{},
- C: map[time.Time]string{
- time.Date(2009, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "0th birthday",
- time.Date(2010, time.November, 10, 23, 0, 0, 0, time.UTC).In(time.Local): "1st birthday",
- },
- },
- opts: []cmp.Option{
- EquateEmpty(),
- SortSlices(func(x, y int) bool { return x < y }),
- SortMaps(func(x, y time.Time) bool { return x.Before(y) }),
- },
- wantEqual: true,
- reason: "no panics because EquateEmpty should compose with the sort options",
- }, {
- label: "EquateApprox",
- x: 3.09,
- y: 3.10,
- wantEqual: false,
- reason: "not equal because floats do not exactly matches",
- }, {
- label: "EquateApprox",
- x: 3.09,
- y: 3.10,
- opts: []cmp.Option{EquateApprox(0, 0)},
- wantEqual: false,
- reason: "not equal because EquateApprox(0 ,0) is equivalent to using ==",
- }, {
- label: "EquateApprox",
- x: 3.09,
- y: 3.10,
- opts: []cmp.Option{EquateApprox(0.003, 0.009)},
- wantEqual: false,
- reason: "not equal because EquateApprox is too strict",
- }, {
- label: "EquateApprox",
- x: 3.09,
- y: 3.10,
- opts: []cmp.Option{EquateApprox(0, 0.011)},
- wantEqual: true,
- reason: "equal because margin is loose enough to match",
- }, {
- label: "EquateApprox",
- x: 3.09,
- y: 3.10,
- opts: []cmp.Option{EquateApprox(0.004, 0)},
- wantEqual: true,
- reason: "equal because fraction is loose enough to match",
- }, {
- label: "EquateApprox",
- x: 3.09,
- y: 3.10,
- opts: []cmp.Option{EquateApprox(0.004, 0.011)},
- wantEqual: true,
- reason: "equal because both the margin and fraction are loose enough to match",
- }, {
- label: "EquateApprox",
- x: float32(3.09),
- y: float64(3.10),
- opts: []cmp.Option{EquateApprox(0.004, 0)},
- wantEqual: false,
- reason: "not equal because the types differ",
- }, {
- label: "EquateApprox",
- x: float32(3.09),
- y: float32(3.10),
- opts: []cmp.Option{EquateApprox(0.004, 0)},
- wantEqual: true,
- reason: "equal because EquateApprox also applies on float32s",
- }, {
- label: "EquateApprox",
- x: []float64{math.Inf(+1), math.Inf(-1)},
- y: []float64{math.Inf(+1), math.Inf(-1)},
- opts: []cmp.Option{EquateApprox(0, 1)},
- wantEqual: true,
- reason: "equal because we fall back on == which matches Inf (EquateApprox does not apply on Inf) ",
- }, {
- label: "EquateApprox",
- x: []float64{math.Inf(+1), -1e100},
- y: []float64{+1e100, math.Inf(-1)},
- opts: []cmp.Option{EquateApprox(0, 1)},
- wantEqual: false,
- reason: "not equal because we fall back on == where Inf != 1e100 (EquateApprox does not apply on Inf)",
- }, {
- label: "EquateApprox",
- x: float64(+1e100),
- y: float64(-1e100),
- opts: []cmp.Option{EquateApprox(math.Inf(+1), 0)},
- wantEqual: true,
- reason: "equal because infinite fraction matches everything",
- }, {
- label: "EquateApprox",
- x: float64(+1e100),
- y: float64(-1e100),
- opts: []cmp.Option{EquateApprox(0, math.Inf(+1))},
- wantEqual: true,
- reason: "equal because infinite margin matches everything",
- }, {
- label: "EquateApprox",
- x: math.Pi,
- y: math.Pi,
- opts: []cmp.Option{EquateApprox(0, 0)},
- wantEqual: true,
- reason: "equal because EquateApprox(0, 0) is equivalent to ==",
- }, {
- label: "EquateApprox",
- x: math.Pi,
- y: math.Nextafter(math.Pi, math.Inf(+1)),
- opts: []cmp.Option{EquateApprox(0, 0)},
- wantEqual: false,
- reason: "not equal because EquateApprox(0, 0) is equivalent to ==",
- }, {
- label: "EquateNaNs",
- x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
- y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
- wantEqual: false,
- reason: "not equal because NaN != NaN",
- }, {
- label: "EquateNaNs",
- x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
- y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1)},
- opts: []cmp.Option{EquateNaNs()},
- wantEqual: true,
- reason: "equal because EquateNaNs allows NaN == NaN",
- }, {
- label: "EquateNaNs",
- x: []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
- y: []float32{1.0, float32(math.NaN()), math.E, -0.0, +0.0},
- opts: []cmp.Option{EquateNaNs()},
- wantEqual: true,
- reason: "equal because EquateNaNs operates on float32",
- }, {
- label: "EquateApprox+EquateNaNs",
- x: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.01, 5001},
- y: []float64{1.0, math.NaN(), math.E, -0.0, +0.0, math.Inf(+1), math.Inf(-1), 1.02, 5002},
- opts: []cmp.Option{
- EquateNaNs(),
- EquateApprox(0.01, 0),
- },
- wantEqual: true,
- reason: "equal because EquateNaNs and EquateApprox compose together",
- }, {
- label: "EquateApprox+EquateNaNs",
- x: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001},
- y: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002},
- opts: []cmp.Option{
- EquateNaNs(),
- EquateApprox(0.01, 0),
- },
- wantEqual: false,
- reason: "not equal because EquateApprox and EquateNaNs do not apply on a named type",
- }, {
- label: "EquateApprox+EquateNaNs+Transform",
- x: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.01, 5001},
- y: []MyFloat{1.0, MyFloat(math.NaN()), MyFloat(math.E), -0.0, +0.0, MyFloat(math.Inf(+1)), MyFloat(math.Inf(-1)), 1.02, 5002},
- opts: []cmp.Option{
- cmp.Transformer("", func(x MyFloat) float64 { return float64(x) }),
- EquateNaNs(),
- EquateApprox(0.01, 0),
- },
- wantEqual: true,
- reason: "equal because named type is transformed to float64",
- }, {
- label: "EquateApproxTime",
- x: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
- y: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
- opts: []cmp.Option{EquateApproxTime(0)},
- wantEqual: true,
- reason: "equal because times are identical",
- }, {
- label: "EquateApproxTime",
- x: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
- y: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
- opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
- wantEqual: true,
- reason: "equal because time is exactly at the allowed margin",
- }, {
- label: "EquateApproxTime",
- x: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
- y: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
- opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
- wantEqual: true,
- reason: "equal because time is exactly at the allowed margin (negative)",
- }, {
- label: "EquateApproxTime",
- x: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
- y: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
- opts: []cmp.Option{EquateApproxTime(3*time.Second - 1)},
- wantEqual: false,
- reason: "not equal because time is outside allowed margin",
- }, {
- label: "EquateApproxTime",
- x: time.Date(2009, 11, 10, 23, 0, 0, 0, time.UTC),
- y: time.Date(2009, 11, 10, 23, 0, 3, 0, time.UTC),
- opts: []cmp.Option{EquateApproxTime(3*time.Second - 1)},
- wantEqual: false,
- reason: "not equal because time is outside allowed margin (negative)",
- }, {
- label: "EquateApproxTime",
- x: time.Time{},
- y: time.Time{},
- opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
- wantEqual: true,
- reason: "equal because both times are zero",
- }, {
- label: "EquateApproxTime",
- x: time.Time{},
- y: time.Time{}.Add(1),
- opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
- wantEqual: false,
- reason: "not equal because zero time is always not equal not non-zero",
- }, {
- label: "EquateApproxTime",
- x: time.Time{}.Add(1),
- y: time.Time{},
- opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
- wantEqual: false,
- reason: "not equal because zero time is always not equal not non-zero",
- }, {
- label: "EquateApproxTime",
- x: time.Date(2409, 11, 10, 23, 0, 0, 0, time.UTC),
- y: time.Date(2000, 11, 10, 23, 0, 3, 0, time.UTC),
- opts: []cmp.Option{EquateApproxTime(3 * time.Second)},
- wantEqual: false,
- reason: "time difference overflows time.Duration",
- }, {
- label: "EquateErrors",
- x: nil,
- y: nil,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "nil values are equal",
- }, {
- label: "EquateErrors",
- x: errors.New("EOF"),
- y: io.EOF,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: false,
- reason: "user-defined EOF is not exactly equal",
- }, {
- label: "EquateErrors",
- x: xerrors.Errorf("wrapped: %w", io.EOF),
- y: io.EOF,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "wrapped io.EOF is equal according to errors.Is",
- }, {
- label: "EquateErrors",
- x: xerrors.Errorf("wrapped: %w", io.EOF),
- y: io.EOF,
- wantEqual: false,
- reason: "wrapped io.EOF is not equal without EquateErrors option",
- }, {
- label: "EquateErrors",
- x: io.EOF,
- y: io.EOF,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "sentinel errors are equal",
- }, {
- label: "EquateErrors",
- x: io.EOF,
- y: AnyError,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "AnyError is equal to any non-nil error",
- }, {
- label: "EquateErrors",
- x: io.EOF,
- y: AnyError,
- wantEqual: false,
- reason: "AnyError is not equal to any non-nil error without EquateErrors option",
- }, {
- label: "EquateErrors",
- x: nil,
- y: AnyError,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: false,
- reason: "AnyError is not equal to nil value",
- }, {
- label: "EquateErrors",
- x: nil,
- y: nil,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "nil values are equal",
- }, {
- label: "EquateErrors",
- x: errors.New("EOF"),
- y: io.EOF,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: false,
- reason: "user-defined EOF is not exactly equal",
- }, {
- label: "EquateErrors",
- x: xerrors.Errorf("wrapped: %w", io.EOF),
- y: io.EOF,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "wrapped io.EOF is equal according to errors.Is",
- }, {
- label: "EquateErrors",
- x: xerrors.Errorf("wrapped: %w", io.EOF),
- y: io.EOF,
- wantEqual: false,
- reason: "wrapped io.EOF is not equal without EquateErrors option",
- }, {
- label: "EquateErrors",
- x: io.EOF,
- y: io.EOF,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "sentinel errors are equal",
- }, {
- label: "EquateErrors",
- x: io.EOF,
- y: AnyError,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "AnyError is equal to any non-nil error",
- }, {
- label: "EquateErrors",
- x: io.EOF,
- y: AnyError,
- wantEqual: false,
- reason: "AnyError is not equal to any non-nil error without EquateErrors option",
- }, {
- label: "EquateErrors",
- x: nil,
- y: AnyError,
- opts: []cmp.Option{EquateErrors()},
- wantEqual: false,
- reason: "AnyError is not equal to nil value",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{nil},
- y: struct{ E error }{nil},
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "nil values are equal",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{errors.New("EOF")},
- y: struct{ E error }{io.EOF},
- opts: []cmp.Option{EquateErrors()},
- wantEqual: false,
- reason: "user-defined EOF is not exactly equal",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{xerrors.Errorf("wrapped: %w", io.EOF)},
- y: struct{ E error }{io.EOF},
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "wrapped io.EOF is equal according to errors.Is",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{xerrors.Errorf("wrapped: %w", io.EOF)},
- y: struct{ E error }{io.EOF},
- wantEqual: false,
- reason: "wrapped io.EOF is not equal without EquateErrors option",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{io.EOF},
- y: struct{ E error }{io.EOF},
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "sentinel errors are equal",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{io.EOF},
- y: struct{ E error }{AnyError},
- opts: []cmp.Option{EquateErrors()},
- wantEqual: true,
- reason: "AnyError is equal to any non-nil error",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{io.EOF},
- y: struct{ E error }{AnyError},
- wantEqual: false,
- reason: "AnyError is not equal to any non-nil error without EquateErrors option",
- }, {
- label: "EquateErrors",
- x: struct{ E error }{nil},
- y: struct{ E error }{AnyError},
- opts: []cmp.Option{EquateErrors()},
- wantEqual: false,
- reason: "AnyError is not equal to nil value",
- }, {
- label: "IgnoreFields",
- x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
- y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
- wantEqual: false,
- reason: "not equal because values do not match in deeply embedded field",
- }, {
- label: "IgnoreFields",
- x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
- y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
- opts: []cmp.Option{IgnoreFields(Bar1{}, "Alpha")},
- wantEqual: true,
- reason: "equal because IgnoreField ignores deeply embedded field: Alpha",
- }, {
- label: "IgnoreFields",
- x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
- y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
- opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo1.Alpha")},
- wantEqual: true,
- reason: "equal because IgnoreField ignores deeply embedded field: Foo1.Alpha",
- }, {
- label: "IgnoreFields",
- x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
- y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
- opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo2.Alpha")},
- wantEqual: true,
- reason: "equal because IgnoreField ignores deeply embedded field: Foo2.Alpha",
- }, {
- label: "IgnoreFields",
- x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
- y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
- opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Alpha")},
- wantEqual: true,
- reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Alpha",
- }, {
- label: "IgnoreFields",
- x: Bar1{Foo3{&Foo2{&Foo1{Alpha: 5}}}},
- y: Bar1{Foo3{&Foo2{&Foo1{Alpha: 6}}}},
- opts: []cmp.Option{IgnoreFields(Bar1{}, "Foo3.Foo2.Alpha")},
- wantEqual: true,
- reason: "equal because IgnoreField ignores deeply embedded field: Foo3.Foo2.Alpha",
- }, {
- label: "IgnoreFields",
- x: createBar3X(),
- y: createBar3Y(),
- wantEqual: false,
- reason: "not equal because many deeply nested or embedded fields differ",
- }, {
- label: "IgnoreFields",
- x: createBar3X(),
- y: createBar3Y(),
- opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Foo3", "Alpha")},
- wantEqual: true,
- reason: "equal because IgnoreFields ignores fields at the highest levels",
- }, {
- label: "IgnoreFields",
- x: createBar3X(),
- y: createBar3Y(),
- opts: []cmp.Option{
- IgnoreFields(Bar3{},
- "Bar1.Foo3.Bravo",
- "Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
- "Bravo.Foo3.Foo2.Foo1.Bravo",
- "Bravo.Bravo",
- "Delta.Echo.Charlie",
- "Foo3.Foo2.Foo1.Alpha",
- "Alpha",
- ),
- },
- wantEqual: true,
- reason: "equal because IgnoreFields ignores fields using fully-qualified field",
- }, {
- label: "IgnoreFields",
- x: createBar3X(),
- y: createBar3Y(),
- opts: []cmp.Option{
- IgnoreFields(Bar3{},
- "Bar1.Foo3.Bravo",
- "Bravo.Foo3.Foo2.Foo1.Bravo",
- "Bravo.Bravo",
- "Delta.Echo.Charlie",
- "Foo3.Foo2.Foo1.Alpha",
- "Alpha",
- ),
- },
- wantEqual: false,
- reason: "not equal because one fully-qualified field is not ignored: Bravo.Bar1.Foo3.Foo2.Foo1.Charlie",
- }, {
- label: "IgnoreFields",
- x: createBar3X(),
- y: createBar3Y(),
- opts: []cmp.Option{IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha")},
- wantEqual: false,
- reason: "not equal because highest-level field is not ignored: Foo3",
- }, {
- label: "IgnoreFields",
- x: ParentStruct{
- privateStruct: &privateStruct{private: 1},
- PublicStruct: &PublicStruct{private: 2},
- private: 3,
- },
- y: ParentStruct{
- privateStruct: &privateStruct{private: 10},
- PublicStruct: &PublicStruct{private: 20},
- private: 30,
- },
- opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{})},
- wantEqual: false,
- reason: "not equal because unexported fields mismatch",
- }, {
- label: "IgnoreFields",
- x: ParentStruct{
- privateStruct: &privateStruct{private: 1},
- PublicStruct: &PublicStruct{private: 2},
- private: 3,
- },
- y: ParentStruct{
- privateStruct: &privateStruct{private: 10},
- PublicStruct: &PublicStruct{private: 20},
- private: 30,
- },
- opts: []cmp.Option{
- cmp.AllowUnexported(ParentStruct{}, PublicStruct{}, privateStruct{}),
- IgnoreFields(ParentStruct{}, "PublicStruct.private", "privateStruct.private", "private"),
- },
- wantEqual: true,
- reason: "equal because mismatching unexported fields are ignored",
- }, {
- label: "IgnoreTypes",
- x: []interface{}{5, "same"},
- y: []interface{}{6, "same"},
- wantEqual: false,
- reason: "not equal because 5 != 6",
- }, {
- label: "IgnoreTypes",
- x: []interface{}{5, "same"},
- y: []interface{}{6, "same"},
- opts: []cmp.Option{IgnoreTypes(0)},
- wantEqual: true,
- reason: "equal because ints are ignored",
- }, {
- label: "IgnoreTypes+IgnoreInterfaces",
- x: []interface{}{5, "same", new(bytes.Buffer)},
- y: []interface{}{6, "same", new(bytes.Buffer)},
- opts: []cmp.Option{IgnoreTypes(0)},
- wantPanic: true,
- reason: "panics because bytes.Buffer has unexported fields",
- }, {
- label: "IgnoreTypes+IgnoreInterfaces",
- x: []interface{}{5, "same", new(bytes.Buffer)},
- y: []interface{}{6, "diff", new(bytes.Buffer)},
- opts: []cmp.Option{
- IgnoreTypes(0, ""),
- IgnoreInterfaces(struct{ io.Reader }{}),
- },
- wantEqual: true,
- reason: "equal because bytes.Buffer is ignored by match on interface type",
- }, {
- label: "IgnoreTypes+IgnoreInterfaces",
- x: []interface{}{5, "same", new(bytes.Buffer)},
- y: []interface{}{6, "same", new(bytes.Buffer)},
- opts: []cmp.Option{
- IgnoreTypes(0, ""),
- IgnoreInterfaces(struct {
- io.Reader
- io.Writer
- fmt.Stringer
- }{}),
- },
- wantEqual: true,
- reason: "equal because bytes.Buffer is ignored by match on multiple interface types",
- }, {
- label: "IgnoreInterfaces",
- x: struct{ mu sync.Mutex }{},
- y: struct{ mu sync.Mutex }{},
- wantPanic: true,
- reason: "panics because sync.Mutex has unexported fields",
- }, {
- label: "IgnoreInterfaces",
- x: struct{ mu sync.Mutex }{},
- y: struct{ mu sync.Mutex }{},
- opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
- wantEqual: true,
- reason: "equal because IgnoreInterfaces applies on values (with pointer receiver)",
- }, {
- label: "IgnoreInterfaces",
- x: struct{ mu *sync.Mutex }{},
- y: struct{ mu *sync.Mutex }{},
- opts: []cmp.Option{IgnoreInterfaces(struct{ sync.Locker }{})},
- wantEqual: true,
- reason: "equal because IgnoreInterfaces applies on pointers",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2},
- y: ParentStruct{Public: 1, private: -2},
- opts: []cmp.Option{cmp.AllowUnexported(ParentStruct{})},
- wantEqual: false,
- reason: "not equal because ParentStruct.private differs with AllowUnexported",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2},
- y: ParentStruct{Public: 1, private: -2},
- opts: []cmp.Option{IgnoreUnexported(ParentStruct{})},
- wantEqual: true,
- reason: "equal because IgnoreUnexported ignored ParentStruct.private",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
- opts: []cmp.Option{
- cmp.AllowUnexported(PublicStruct{}),
- IgnoreUnexported(ParentStruct{}),
- },
- wantEqual: true,
- reason: "equal because ParentStruct.private is ignored",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
- opts: []cmp.Option{
- cmp.AllowUnexported(PublicStruct{}),
- IgnoreUnexported(ParentStruct{}),
- },
- wantEqual: false,
- reason: "not equal because ParentStruct.PublicStruct.private differs and not ignored by IgnoreUnexported(ParentStruct{})",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: -2, PublicStruct: &PublicStruct{Public: 3, private: -4}},
- opts: []cmp.Option{
- IgnoreUnexported(ParentStruct{}, PublicStruct{}),
- },
- wantEqual: true,
- reason: "equal because both ParentStruct.PublicStruct and ParentStruct.PublicStruct.private are ignored",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
- opts: []cmp.Option{
- cmp.AllowUnexported(privateStruct{}, PublicStruct{}, ParentStruct{}),
- },
- wantEqual: false,
- reason: "not equal since ParentStruct.privateStruct differs",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
- opts: []cmp.Option{
- cmp.AllowUnexported(privateStruct{}, PublicStruct{}),
- IgnoreUnexported(ParentStruct{}),
- },
- wantEqual: true,
- reason: "equal because ParentStruct.privateStruct ignored by IgnoreUnexported(ParentStruct{})",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: -4}},
- opts: []cmp.Option{
- cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
- IgnoreUnexported(privateStruct{}),
- },
- wantEqual: true,
- reason: "equal because privateStruct.private ignored by IgnoreUnexported(privateStruct{})",
- }, {
- label: "IgnoreUnexported",
- x: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: 3, private: 4}},
- y: ParentStruct{Public: 1, private: 2, privateStruct: &privateStruct{Public: -3, private: -4}},
- opts: []cmp.Option{
- cmp.AllowUnexported(PublicStruct{}, ParentStruct{}),
- IgnoreUnexported(privateStruct{}),
- },
- wantEqual: false,
- reason: "not equal because privateStruct.Public differs and not ignored by IgnoreUnexported(privateStruct{})",
- }, {
- label: "IgnoreFields+IgnoreTypes+IgnoreUnexported",
- x: &Everything{
- MyInt: 5,
- MyFloat: 3.3,
- MyTime: MyTime{time.Now()},
- Bar3: *createBar3X(),
- ParentStruct: ParentStruct{
- Public: 1, private: 2, PublicStruct: &PublicStruct{Public: 3, private: 4},
- },
- },
- y: &Everything{
- MyInt: -5,
- MyFloat: 3.3,
- MyTime: MyTime{time.Now()},
- Bar3: *createBar3Y(),
- ParentStruct: ParentStruct{
- Public: 1, private: -2, PublicStruct: &PublicStruct{Public: -3, private: -4},
- },
- },
- opts: []cmp.Option{
- IgnoreFields(Everything{}, "MyTime", "Bar3.Foo3"),
- IgnoreFields(Bar3{}, "Bar1", "Bravo", "Delta", "Alpha"),
- IgnoreTypes(MyInt(0), PublicStruct{}),
- IgnoreUnexported(ParentStruct{}),
- },
- wantEqual: true,
- reason: "equal because all Ignore options can be composed together",
- }, {
- label: "IgnoreSliceElements",
- x: []int{1, 0, 2, 3, 0, 4, 0, 0},
- y: []int{0, 0, 0, 0, 1, 2, 3, 4},
- opts: []cmp.Option{
- IgnoreSliceElements(func(v int) bool { return v == 0 }),
- },
- wantEqual: true,
- reason: "equal because zero elements are ignored",
- }, {
- label: "IgnoreSliceElements",
- x: []MyInt{1, 0, 2, 3, 0, 4, 0, 0},
- y: []MyInt{0, 0, 0, 0, 1, 2, 3, 4},
- opts: []cmp.Option{
- IgnoreSliceElements(func(v int) bool { return v == 0 }),
- },
- wantEqual: false,
- reason: "not equal because MyInt is not assignable to int",
- }, {
- label: "IgnoreSliceElements",
- x: MyInts{1, 0, 2, 3, 0, 4, 0, 0},
- y: MyInts{0, 0, 0, 0, 1, 2, 3, 4},
- opts: []cmp.Option{
- IgnoreSliceElements(func(v int) bool { return v == 0 }),
- },
- wantEqual: true,
- reason: "equal because the element type of MyInts is assignable to int",
- }, {
- label: "IgnoreSliceElements+EquateEmpty",
- x: []MyInt{},
- y: []MyInt{0, 0, 0, 0},
- opts: []cmp.Option{
- IgnoreSliceElements(func(v int) bool { return v == 0 }),
- EquateEmpty(),
- },
- wantEqual: false,
- reason: "not equal because ignored elements does not imply empty slice",
- }, {
- label: "IgnoreMapEntries",
- x: map[string]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
- y: map[string]int{"one": 1, "three": 3, "TEN": 10},
- opts: []cmp.Option{
- IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
- },
- wantEqual: true,
- reason: "equal because uppercase keys are ignored",
- }, {
- label: "IgnoreMapEntries",
- x: map[MyString]int{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
- y: map[MyString]int{"one": 1, "three": 3, "TEN": 10},
- opts: []cmp.Option{
- IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
- },
- wantEqual: false,
- reason: "not equal because MyString is not assignable to string",
- }, {
- label: "IgnoreMapEntries",
- x: map[string]MyInt{"one": 1, "TWO": 2, "three": 3, "FIVE": 5},
- y: map[string]MyInt{"one": 1, "three": 3, "TEN": 10},
- opts: []cmp.Option{
- IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
- },
- wantEqual: false,
- reason: "not equal because MyInt is not assignable to int",
- }, {
- label: "IgnoreMapEntries+EquateEmpty",
- x: map[string]MyInt{"ONE": 1, "TWO": 2, "THREE": 3},
- y: nil,
- opts: []cmp.Option{
- IgnoreMapEntries(func(k string, v int) bool { return strings.ToUpper(k) == k }),
- EquateEmpty(),
- },
- wantEqual: false,
- reason: "not equal because ignored entries does not imply empty map",
- }, {
- label: "AcyclicTransformer",
- x: "a\nb\nc\nd",
- y: "a\nb\nd\nd",
- opts: []cmp.Option{
- AcyclicTransformer("", func(s string) []string { return strings.Split(s, "\n") }),
- },
- wantEqual: false,
- reason: "not equal because 3rd line differs, but should not recurse infinitely",
- }, {
- label: "AcyclicTransformer",
- x: []string{"foo", "Bar", "BAZ"},
- y: []string{"Foo", "BAR", "baz"},
- opts: []cmp.Option{
- AcyclicTransformer("", strings.ToUpper),
- },
- wantEqual: true,
- reason: "equal because of strings.ToUpper; AcyclicTransformer unnecessary, but check this still works",
- }, {
- label: "AcyclicTransformer",
- x: "this is a sentence",
- y: "this is a sentence",
- opts: []cmp.Option{
- AcyclicTransformer("", strings.Fields),
- },
- wantEqual: true,
- reason: "equal because acyclic transformer splits on any contiguous whitespace",
- }}
-
- for _, tt := range tests {
- t.Run(tt.label, func(t *testing.T) {
- var gotEqual bool
- var gotPanic string
- func() {
- defer func() {
- if ex := recover(); ex != nil {
- gotPanic = fmt.Sprint(ex)
- }
- }()
- gotEqual = cmp.Equal(tt.x, tt.y, tt.opts...)
- }()
- switch {
- case tt.reason == "":
- t.Errorf("reason must be provided")
- case gotPanic == "" && tt.wantPanic:
- t.Errorf("expected Equal panic\nreason: %s", tt.reason)
- case gotPanic != "" && !tt.wantPanic:
- t.Errorf("unexpected Equal panic: got %v\nreason: %v", gotPanic, tt.reason)
- case gotEqual != tt.wantEqual:
- t.Errorf("Equal = %v, want %v\nreason: %v", gotEqual, tt.wantEqual, tt.reason)
- }
- })
- }
-}
-
-func TestPanic(t *testing.T) {
- args := func(x ...interface{}) []interface{} { return x }
- tests := []struct {
- label string // Test name
- fnc interface{} // Option function to call
- args []interface{} // Arguments to pass in
- wantPanic string // Expected panic message
- reason string // The reason for the expected outcome
- }{{
- label: "EquateApprox",
- fnc: EquateApprox,
- args: args(0.0, 0.0),
- reason: "zero margin and fraction is equivalent to exact equality",
- }, {
- label: "EquateApprox",
- fnc: EquateApprox,
- args: args(-0.1, 0.0),
- wantPanic: "margin or fraction must be a non-negative number",
- reason: "negative inputs are invalid",
- }, {
- label: "EquateApprox",
- fnc: EquateApprox,
- args: args(0.0, -0.1),
- wantPanic: "margin or fraction must be a non-negative number",
- reason: "negative inputs are invalid",
- }, {
- label: "EquateApprox",
- fnc: EquateApprox,
- args: args(math.NaN(), 0.0),
- wantPanic: "margin or fraction must be a non-negative number",
- reason: "NaN inputs are invalid",
- }, {
- label: "EquateApprox",
- fnc: EquateApprox,
- args: args(1.0, 0.0),
- reason: "fraction of 1.0 or greater is valid",
- }, {
- label: "EquateApprox",
- fnc: EquateApprox,
- args: args(0.0, math.Inf(+1)),
- reason: "margin of infinity is valid",
- }, {
- label: "EquateApproxTime",
- fnc: EquateApproxTime,
- args: args(time.Duration(-1)),
- wantPanic: "margin must be a non-negative number",
- reason: "negative duration is invalid",
- }, {
- label: "SortSlices",
- fnc: SortSlices,
- args: args(strings.Compare),
- wantPanic: "invalid less function",
- reason: "func(x, y string) int is wrong signature for less",
- }, {
- label: "SortSlices",
- fnc: SortSlices,
- args: args((func(_, _ int) bool)(nil)),
- wantPanic: "invalid less function",
- reason: "nil value is not valid",
- }, {
- label: "SortMaps",
- fnc: SortMaps,
- args: args(strings.Compare),
- wantPanic: "invalid less function",
- reason: "func(x, y string) int is wrong signature for less",
- }, {
- label: "SortMaps",
- fnc: SortMaps,
- args: args((func(_, _ int) bool)(nil)),
- wantPanic: "invalid less function",
- reason: "nil value is not valid",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, ""),
- wantPanic: "name must not be empty",
- reason: "empty selector is invalid",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, "."),
- wantPanic: "name must not be empty",
- reason: "single dot selector is invalid",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, ".Alpha"),
- reason: "dot-prefix is okay since Foo1.Alpha reads naturally",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, "Alpha."),
- wantPanic: "name must not be empty",
- reason: "dot-suffix is invalid",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, "Alpha "),
- wantPanic: "does not exist",
- reason: "identifiers must not have spaces",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, "Zulu"),
- wantPanic: "does not exist",
- reason: "name of non-existent field is invalid",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(Foo1{}, "Alpha.NoExist"),
- wantPanic: "must be a struct",
- reason: "cannot select into a non-struct",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(&Foo1{}, "Alpha"),
- wantPanic: "must be a non-pointer struct",
- reason: "the type must be a struct (not pointer to a struct)",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(struct{ privateStruct }{}, "privateStruct"),
- reason: "privateStruct field permitted since it is the default name of the embedded type",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(struct{ privateStruct }{}, "Public"),
- reason: "Public field permitted since it is a forwarded field that is exported",
- }, {
- label: "IgnoreFields",
- fnc: IgnoreFields,
- args: args(struct{ privateStruct }{}, "private"),
- wantPanic: "does not exist",
- reason: "private field not permitted since it is a forwarded field that is unexported",
- }, {
- label: "IgnoreTypes",
- fnc: IgnoreTypes,
- reason: "empty input is valid",
- }, {
- label: "IgnoreTypes",
- fnc: IgnoreTypes,
- args: args(nil),
- wantPanic: "cannot determine type",
- reason: "input must not be nil value",
- }, {
- label: "IgnoreTypes",
- fnc: IgnoreTypes,
- args: args(0, 0, 0),
- reason: "duplicate inputs of the same type is valid",
- }, {
- label: "IgnoreInterfaces",
- fnc: IgnoreInterfaces,
- args: args(nil),
- wantPanic: "input must be an anonymous struct",
- reason: "input must not be nil value",
- }, {
- label: "IgnoreInterfaces",
- fnc: IgnoreInterfaces,
- args: args(Foo1{}),
- wantPanic: "input must be an anonymous struct",
- reason: "input must not be a named struct type",
- }, {
- label: "IgnoreInterfaces",
- fnc: IgnoreInterfaces,
- args: args(struct{ _ io.Reader }{}),
- wantPanic: "struct cannot have named fields",
- reason: "input must not have named fields",
- }, {
- label: "IgnoreInterfaces",
- fnc: IgnoreInterfaces,
- args: args(struct{ Foo1 }{}),
- wantPanic: "embedded field must be an interface type",
- reason: "field types must be interfaces",
- }, {
- label: "IgnoreInterfaces",
- fnc: IgnoreInterfaces,
- args: args(struct{ EmptyInterface }{}),
- wantPanic: "cannot ignore empty interface",
- reason: "field types must not be the empty interface",
- }, {
- label: "IgnoreInterfaces",
- fnc: IgnoreInterfaces,
- args: args(struct {
- io.Reader
- io.Writer
- io.Closer
- io.ReadWriteCloser
- }{}),
- reason: "multiple interfaces may be specified, even if they overlap",
- }, {
- label: "IgnoreUnexported",
- fnc: IgnoreUnexported,
- reason: "empty input is valid",
- }, {
- label: "IgnoreUnexported",
- fnc: IgnoreUnexported,
- args: args(nil),
- wantPanic: "must be a non-pointer struct",
- reason: "input must not be nil value",
- }, {
- label: "IgnoreUnexported",
- fnc: IgnoreUnexported,
- args: args(&Foo1{}),
- wantPanic: "must be a non-pointer struct",
- reason: "input must be a struct type (not a pointer to a struct)",
- }, {
- label: "IgnoreUnexported",
- fnc: IgnoreUnexported,
- args: args(Foo1{}, struct{ x, X int }{}),
- reason: "input may be named or unnamed structs",
- }, {
- label: "AcyclicTransformer",
- fnc: AcyclicTransformer,
- args: args("", "not a func"),
- wantPanic: "invalid transformer function",
- reason: "AcyclicTransformer has same input requirements as Transformer",
- }}
-
- for _, tt := range tests {
- t.Run(tt.label, func(t *testing.T) {
- // Prepare function arguments.
- vf := reflect.ValueOf(tt.fnc)
- var vargs []reflect.Value
- for i, arg := range tt.args {
- if arg == nil {
- tf := vf.Type()
- if i == tf.NumIn()-1 && tf.IsVariadic() {
- vargs = append(vargs, reflect.Zero(tf.In(i).Elem()))
- } else {
- vargs = append(vargs, reflect.Zero(tf.In(i)))
- }
- } else {
- vargs = append(vargs, reflect.ValueOf(arg))
- }
- }
-
- // Call the function and capture any panics.
- var gotPanic string
- func() {
- defer func() {
- if ex := recover(); ex != nil {
- if s, ok := ex.(string); ok {
- gotPanic = s
- } else {
- panic(ex)
- }
- }
- }()
- vf.Call(vargs)
- }()
-
- switch {
- case tt.reason == "":
- t.Errorf("reason must be provided")
- case tt.wantPanic == "" && gotPanic != "":
- t.Errorf("unexpected panic message: %s\nreason: %s", gotPanic, tt.reason)
- case tt.wantPanic != "" && !strings.Contains(gotPanic, tt.wantPanic):
- t.Errorf("panic message:\ngot: %s\nwant: %s\nreason: %s", gotPanic, tt.wantPanic, tt.reason)
- }
- })
- }
-}