// 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 import ( "io" "reflect" "strings" "testing" ts "github.com/google/go-cmp/cmp/internal/teststructs" ) // Test that the creation of Option values with non-sensible inputs produces // a run-time panic with a decent error message func TestOptionPanic(t *testing.T) { type myBool bool tests := []struct { label string // Test description fnc interface{} // Option function to call args []interface{} // Arguments to pass in wantPanic string // Expected panic message }{{ label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{}, }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{1}, wantPanic: "invalid struct type", }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{ts.StructA{}}, }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{ts.StructA{}, ts.StructB{}, ts.StructA{}}, }, { label: "AllowUnexported", fnc: AllowUnexported, args: []interface{}{ts.StructA{}, &ts.StructB{}, ts.StructA{}}, wantPanic: "invalid struct type", }, { label: "Comparer", fnc: Comparer, args: []interface{}{5}, wantPanic: "invalid comparer function", }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x, y interface{}) bool { return true }}, }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x, y io.Reader) bool { return true }}, }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x, y io.Reader) myBool { return true }}, wantPanic: "invalid comparer function", }, { label: "Comparer", fnc: Comparer, args: []interface{}{func(x string, y interface{}) bool { return true }}, wantPanic: "invalid comparer function", }, { label: "Comparer", fnc: Comparer, args: []interface{}{(func(int, int) bool)(nil)}, wantPanic: "invalid comparer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", 0}, wantPanic: "invalid transformer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(int) int { return 0 }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(bool) bool { return true }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(int) bool { return true }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", func(int, int) bool { return true }}, wantPanic: "invalid transformer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"", (func(int) uint)(nil)}, wantPanic: "invalid transformer function", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"Func", func(Path) Path { return nil }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"世界", func(int) bool { return true }}, }, { label: "Transformer", fnc: Transformer, args: []interface{}{"/*", func(int) bool { return true }}, wantPanic: "invalid name", }, { label: "Transformer", fnc: Transformer, args: []interface{}{"_", func(int) bool { return true }}, }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{(func(Path) bool)(nil), Ignore()}, wantPanic: "invalid path filter function", }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Ignore()}, }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Reporter(&defaultReporter{})}, wantPanic: "invalid option type", }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Ignore()}}, }, { label: "FilterPath", fnc: FilterPath, args: []interface{}{func(Path) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}}, wantPanic: "invalid option type", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{0, Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x, y int) bool { return true }, Ignore()}, }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x, y interface{}) bool { return true }, Ignore()}, }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x, y interface{}) myBool { return true }, Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(x io.Reader, y interface{}) bool { return true }, Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{(func(int, int) bool)(nil), Ignore()}, wantPanic: "invalid values filter function", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(int, int) bool { return true }, Reporter(&defaultReporter{})}, wantPanic: "invalid option type", }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Ignore()}}, }, { label: "FilterValues", fnc: FilterValues, args: []interface{}{func(int, int) bool { return true }, Options{Ignore(), Reporter(&defaultReporter{})}}, wantPanic: "invalid option type", }} for _, tt := range tests { t.Run(tt.label, func(t *testing.T) { var gotPanic string func() { defer func() { if ex := recover(); ex != nil { if s, ok := ex.(string); ok { gotPanic = s } else { panic(ex) } } }() var vargs []reflect.Value for _, arg := range tt.args { vargs = append(vargs, reflect.ValueOf(arg)) } reflect.ValueOf(tt.fnc).Call(vargs) }() if tt.wantPanic == "" { if gotPanic != "" { t.Fatalf("unexpected panic message: %s", gotPanic) } } else { if !strings.Contains(gotPanic, tt.wantPanic) { t.Fatalf("panic message:\ngot: %s\nwant: %s", gotPanic, tt.wantPanic) } } }) } }