package cmp import ( "fmt" "go/ast" "io" "reflect" "regexp" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/pkg/errors" ) func TestDeepEqual(t *testing.T) { actual := DeepEqual([]string{"a", "b"}, []string{"b", "a"})() expected := ` --- result +++ exp {[]string}[0]: -: "a" +: "b" {[]string}[1]: -: "b" +: "a" ` args := []ast.Expr{&ast.Ident{Name: "result"}, &ast.Ident{Name: "exp"}} assertFailureTemplate(t, actual, args, expected) actual = DeepEqual([]string{"a"}, []string{"a"})() assertSuccess(t, actual) } type Stub struct { unx int } func TestDeepEqualeWithUnexported(t *testing.T) { result := DeepEqual(Stub{}, Stub{unx: 1})() assertFailure(t, result, `cannot handle unexported field: {cmp.Stub}.unx consider using AllowUnexported or cmpopts.IgnoreUnexported`) } func TestRegexp(t *testing.T) { var testcases = []struct { name string regex interface{} value string match bool expErr string }{ { name: "pattern string match", regex: "^[0-9]+$", value: "12123423456", match: true, }, { name: "simple pattern string no match", regex: "bob", value: "Probably", expErr: `value "Probably" does not match regexp "bob"`, }, { name: "pattern string no match", regex: "^1", value: "2123423456", expErr: `value "2123423456" does not match regexp "^1"`, }, { name: "regexp match", regex: regexp.MustCompile("^d[0-9a-f]{8}$"), value: "d1632beef", match: true, }, { name: "invalid regexp", regex: "^1(", value: "2", expErr: "error parsing regexp: missing closing ): `^1(`", }, { name: "invalid type", regex: struct{}{}, value: "some string", expErr: "invalid type struct {} for regex pattern", }, } for _, tc := range testcases { t.Run(tc.name, func(t *testing.T) { res := Regexp(tc.regex, tc.value)() if tc.match { assertSuccess(t, res) } else { assertFailure(t, res, tc.expErr) } }) } } func TestLen(t *testing.T) { var testcases = []struct { seq interface{} length int expectedSuccess bool expectedMessage string }{ { seq: []string{"A", "b", "c"}, length: 3, expectedSuccess: true, }, { seq: []string{"A", "b", "c"}, length: 2, expectedMessage: "expected [A b c] (length 3) to have length 2", }, { seq: map[string]int{"a": 1, "b": 2}, length: 2, expectedSuccess: true, }, { seq: [3]string{"a", "b", "c"}, length: 3, expectedSuccess: true, }, { seq: "abcd", length: 4, expectedSuccess: true, }, { seq: "abcd", length: 3, expectedMessage: "expected abcd (length 4) to have length 3", }, } for _, testcase := range testcases { t.Run(fmt.Sprintf("%v len=%d", testcase.seq, testcase.length), func(t *testing.T) { result := Len(testcase.seq, testcase.length)() if testcase.expectedSuccess { assertSuccess(t, result) } else { assertFailure(t, result, testcase.expectedMessage) } }) } } func TestPanics(t *testing.T) { panicker := func() { panic("AHHHHHHHHHHH") } result := Panics(panicker)() assertSuccess(t, result) result = Panics(func() {})() assertFailure(t, result, "did not panic") } type innerstub struct { num int } type stub struct { stub innerstub num int } func TestDeepEqualEquivalenceToReflectDeepEqual(t *testing.T) { var testcases = []struct { left interface{} right interface{} }{ {nil, nil}, {7, 7}, {false, false}, {stub{innerstub{1}, 2}, stub{innerstub{1}, 2}}, {[]int{1, 2, 3}, []int{1, 2, 3}}, {[]byte(nil), []byte(nil)}, {nil, []byte(nil)}, {1, uint64(1)}, {7, "7"}, } for _, testcase := range testcases { expected := reflect.DeepEqual(testcase.left, testcase.right) res := DeepEqual(testcase.left, testcase.right, cmpStub)() if res.Success() != expected { msg := res.(result).FailureMessage() t.Errorf("deepEqual(%v, %v) did not return %v (message %s)", testcase.left, testcase.right, expected, msg) } } } var cmpStub = cmp.AllowUnexported(stub{}, innerstub{}) func TestContains(t *testing.T) { var testcases = []struct { seq interface{} item interface{} expected bool expectedMsg string }{ { seq: error(nil), item: 0, expectedMsg: "nil does not contain items", }, { seq: "abcdef", item: "cde", expected: true, }, { seq: "abcdef", item: "foo", expectedMsg: `string "abcdef" does not contain "foo"`, }, { seq: "abcdef", item: 3, expectedMsg: `string may only contain strings`, }, { seq: map[rune]int{'a': 1, 'b': 2}, item: 'b', expected: true, }, { seq: map[rune]int{'a': 1}, item: 'c', expectedMsg: "map[97:1] does not contain 99", }, { seq: map[int]int{'a': 1, 'b': 2}, item: 'b', expectedMsg: "map[int]int can not contain a int32 key", }, { seq: []interface{}{"a", 1, 'a', 1.0, true}, item: 'a', expected: true, }, { seq: []interface{}{"a", 1, 'a', 1.0, true}, item: 3, expectedMsg: "[a 1 97 1 true] does not contain 3", }, { seq: [3]byte{99, 10, 100}, item: byte(99), expected: true, }, { seq: [3]byte{99, 10, 100}, item: byte(98), expectedMsg: "[99 10 100] does not contain 98", }, } for _, testcase := range testcases { name := fmt.Sprintf("%v in %v", testcase.item, testcase.seq) t.Run(name, func(t *testing.T) { result := Contains(testcase.seq, testcase.item)() if testcase.expected { assertSuccess(t, result) } else { assertFailure(t, result, testcase.expectedMsg) } }) } } func TestEqualMultiLine(t *testing.T) { result := `abcd 1234 aaaa bbbb` exp := `abcd 1111 aaaa bbbb` expected := ` --- result +++ exp @@ -1,4 +1,4 @@ abcd -1234 +1111 aaaa bbbb ` args := []ast.Expr{&ast.Ident{Name: "result"}, &ast.Ident{Name: "exp"}} res := Equal(result, exp)() assertFailureTemplate(t, res, args, expected) } func TestError(t *testing.T) { result := Error(nil, "the error message")() assertFailure(t, result, "expected an error, got nil") // A Wrapped error also includes the stack result = Error(errors.Wrap(errors.New("other"), "wrapped"), "the error message")() assertFailureHasPrefix(t, result, `expected error "the error message", got "wrapped: other" other gotest.tools/assert/cmp.TestError`) msg := "the message" result = Error(errors.New(msg), msg)() assertSuccess(t, result) } func TestErrorContains(t *testing.T) { result := ErrorContains(nil, "the error message")() assertFailure(t, result, "expected an error, got nil") result = ErrorContains(errors.New("other"), "the error")() assertFailureHasPrefix(t, result, `expected error to contain "the error", got "other"`) msg := "the full message" result = ErrorContains(errors.New(msg), "full")() assertSuccess(t, result) } func TestNil(t *testing.T) { result := Nil(nil)() assertSuccess(t, result) var s *string result = Nil(s)() assertSuccess(t, result) var closer io.Closer result = Nil(closer)() assertSuccess(t, result) result = Nil("wrong")() assertFailure(t, result, "wrong (type string) can not be nil") notnil := "notnil" result = Nil(¬nil)() assertFailure(t, result, "notnil (type *string) is not nil") result = Nil([]string{"a"})() assertFailure(t, result, "[a] (type []string) is not nil") } type testingT interface { Errorf(msg string, args ...interface{}) } type helperT interface { Helper() } func assertSuccess(t testingT, res Result) { if ht, ok := t.(helperT); ok { ht.Helper() } if !res.Success() { msg := res.(result).FailureMessage() t.Errorf("expected success, but got failure with message %q", msg) } } func assertFailure(t testingT, res Result, expected string) { if ht, ok := t.(helperT); ok { ht.Helper() } if res.Success() { t.Errorf("expected failure") } message := res.(result).FailureMessage() if message != expected { t.Errorf("expected \n%q\ngot\n%q\n", expected, message) } } func assertFailureHasPrefix(t testingT, res Result, prefix string) { if ht, ok := t.(helperT); ok { ht.Helper() } if res.Success() { t.Errorf("expected failure") } message := res.(result).FailureMessage() if !strings.HasPrefix(message, prefix) { t.Errorf("expected \n%v\nto start with\n%v\n", message, prefix) } } // nolint: unparam func assertFailureTemplate(t testingT, res Result, args []ast.Expr, expected string) { if ht, ok := t.(helperT); ok { ht.Helper() } if res.Success() { t.Errorf("expected failure") } message := res.(templatedResult).FailureMessage(args) if message != expected { t.Errorf("expected \n%q\ngot\n%q\n", expected, message) } } type stubError struct{} func (s stubError) Error() string { return "stub error" } func isErrorOfTypeStub(err error) bool { return reflect.TypeOf(err) == reflect.TypeOf(stubError{}) } type notStubError struct{} func (s notStubError) Error() string { return "not stub error" } func isErrorOfTypeNotStub(err error) bool { return reflect.TypeOf(err) == reflect.TypeOf(notStubError{}) } type specialStubIface interface { Special() } type stubPtrError struct{} func (s *stubPtrError) Error() string { return "stub ptr error" } func TestErrorTypeWithNil(t *testing.T) { var testcases = []struct { name string expType interface{} expected string }{ { name: "with struct", expType: stubError{}, expected: "error is nil, not cmp.stubError", }, { name: "with pointer to struct", expType: &stubPtrError{}, expected: "error is nil, not *cmp.stubPtrError", }, { name: "with interface", expType: (*specialStubIface)(nil), expected: "error is nil, not cmp.specialStubIface", }, { name: "with reflect.Type", expType: reflect.TypeOf(stubError{}), expected: "error is nil, not cmp.stubError", }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { result := ErrorType(nil, testcase.expType)() assertFailure(t, result, testcase.expected) }) } } func TestErrorTypeSuccess(t *testing.T) { var testcases = []struct { name string expType interface{} err error }{ { name: "with function", expType: isErrorOfTypeStub, err: stubError{}, }, { name: "with struct", expType: stubError{}, err: stubError{}, }, { name: "with pointer to struct", expType: &stubPtrError{}, err: &stubPtrError{}, }, { name: "with interface", expType: (*error)(nil), err: stubError{}, }, { name: "with reflect.Type struct", expType: reflect.TypeOf(stubError{}), err: stubError{}, }, { name: "with reflect.Type interface", expType: reflect.TypeOf((*error)(nil)).Elem(), err: stubError{}, }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { result := ErrorType(testcase.err, testcase.expType)() assertSuccess(t, result) }) } } func TestErrorTypeFailure(t *testing.T) { var testcases = []struct { name string expType interface{} expected string }{ { name: "with struct", expType: notStubError{}, expected: "error is stub error (cmp.stubError), not cmp.notStubError", }, { name: "with pointer to struct", expType: &stubPtrError{}, expected: "error is stub error (cmp.stubError), not *cmp.stubPtrError", }, { name: "with interface", expType: (*specialStubIface)(nil), expected: "error is stub error (cmp.stubError), not cmp.specialStubIface", }, { name: "with reflect.Type struct", expType: reflect.TypeOf(notStubError{}), expected: "error is stub error (cmp.stubError), not cmp.notStubError", }, { name: "with reflect.Type interface", expType: reflect.TypeOf((*specialStubIface)(nil)).Elem(), expected: "error is stub error (cmp.stubError), not cmp.specialStubIface", }, } for _, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { result := ErrorType(stubError{}, testcase.expType)() assertFailure(t, result, testcase.expected) }) } } func TestErrorTypeInvalid(t *testing.T) { result := ErrorType(stubError{}, nil)() assertFailure(t, result, "invalid type for expected: nil") result = ErrorType(stubError{}, "my type!")() assertFailure(t, result, "invalid type for expected: string") } func TestErrorTypeWithFunc(t *testing.T) { result := ErrorType(nil, isErrorOfTypeStub)() assertFailureTemplate(t, result, []ast.Expr{nil, &ast.Ident{Name: "isErrorOfTypeStub"}}, "error is nil, not isErrorOfTypeStub") result = ErrorType(stubError{}, isErrorOfTypeNotStub)() assertFailureTemplate(t, result, []ast.Expr{nil, &ast.Ident{Name: "isErrorOfTypeNotStub"}}, "error is stub error (cmp.stubError), not isErrorOfTypeNotStub") }