pax_global_header00006660000000000000000000000064135605660030014516gustar00rootroot0000000000000052 comment=39c84631118e10c6880bf11e82eff92ce189e5ee quicktest-1.6.0/000077500000000000000000000000001356056600300135365ustar00rootroot00000000000000quicktest-1.6.0/.travis.yml000066400000000000000000000003241356056600300156460ustar00rootroot00000000000000language: go go_import_path: github.com/frankban/quicktest go: - "1.11.x" - "1.12.x" - "1.13.x" - 1.x - master script: - GO111MODULE=on go test -race ./... - GO111MODULE=on go test -v -race ./... quicktest-1.6.0/LICENSE000066400000000000000000000020571356056600300145470ustar00rootroot00000000000000MIT License Copyright (c) 2017 Canonical Ltd. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. quicktest-1.6.0/README.md000066400000000000000000000023151356056600300150160ustar00rootroot00000000000000[![GoDoc](https://godoc.org/github.com/frankban/quicktest?status.svg)](https://godoc.org/github.com/frankban/quicktest) [![Build Status](https://travis-ci.org/frankban/quicktest.svg?branch=master)](https://travis-ci.org/frankban/quicktest) # quicktest A collection of Go helpers for writing tests. ## Installation To install the package, run `go get github.com/frankban/quicktest`. ## Usage Quicktest helpers can be easily integrated inside regular Go tests, for instance: ```go import qt "github.com/frankban/quicktest" func TestFoo(t *testing.T) { t.Run("numbers", func(t *testing.T) { c := qt.New(t) numbers, err := somepackage.Numbers() c.Assert(numbers, qt.DeepEquals, []int{42, 47}) c.Assert(err, qt.ErrorMatches, "bad wolf") }) t.Run("nil", func(t *testing.T) { c := qt.New(t) got := somepackage.MaybeNil() c.Assert(got, qt.IsNil, qt.Commentf("value: %v", somepackage.Value)) }) } ``` The library provides some base checkers like Equals, DeepEquals, Matches, ErrorMatches, IsNil and others. More can be added by implementing the Checker interface. See the [go documentation](https://godoc.org/github.com/frankban/quicktest) for this library. quicktest-1.6.0/checker.go000066400000000000000000000456251356056600300155050ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "encoding/json" "errors" "fmt" "reflect" "regexp" "strings" "testing" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" "github.com/kr/pretty" ) // Checker is implemented by types used as part of Check/Assert invocations. type Checker interface { // Check checks that the obtained value (got) is correct with respect to // the checker's arguments (args). On failure, the returned error is // printed along with the checker arguments and any key-value pairs added // by calling the note function. Values are pretty-printed unless they are // of type Unquoted. // // When the check arguments are invalid, Check may return a BadCheck error, // which suppresses printing of the checker arguments. Values added with // note are still printed. // // If Check returns ErrSilent, neither the checker arguments nor the error // are printed. Again, values added with note are still printed. Check(got interface{}, args []interface{}, note func(key string, value interface{})) error // ArgNames returns the names of all required arguments, including the // mandatory got argument and any additional args. ArgNames() []string } // Equals is a Checker checking equality of two comparable values. // // For instance: // // c.Assert(answer, qt.Equals, 42) // // Note that the following will fail: // // c.Assert((*sometype)(nil), qt.Equals, nil) // // Use the IsNil checker below for this kind of nil check. var Equals Checker = &equalsChecker{ argNames: []string{"got", "want"}, } type equalsChecker struct { argNames } // Check implements Checker.Check by checking that got == args[0]. func (c *equalsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { defer func() { // A panic is raised when the provided values are not comparable. if r := recover(); r != nil { err = fmt.Errorf("%s", r) } }() if want := args[0]; got != want { if _, ok := got.(error); ok && want == nil { return errors.New("got non-nil error") } return errors.New("values are not equal") } return nil } // CmpEquals returns a Checker checking equality of two arbitrary values // according to the provided compare options. See DeepEquals as an example of // such a checker, commonly used when no compare options are required. // // Example calls: // // c.Assert(list, qt.CmpEquals(cmpopts.SortSlices), []int{42, 47}) // c.Assert(got, qt.CmpEquals(), []int{42, 47}) // Same as qt.DeepEquals. // func CmpEquals(opts ...cmp.Option) Checker { return cmpEquals(testing.Verbose, opts...) } func cmpEquals(verbose func() bool, opts ...cmp.Option) Checker { return &cmpEqualsChecker{ argNames: []string{"got", "want"}, opts: opts, verbose: verbose, } } type cmpEqualsChecker struct { argNames opts cmp.Options verbose func() bool } // Check implements Checker.Check by checking that got == args[0] according to // the compare options stored in the checker. func (c *cmpEqualsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { defer func() { // A panic is raised in some cases, for instance when trying to compare // structs with unexported fields and neither AllowUnexported nor // cmpopts.IgnoreUnexported are provided. if r := recover(); r != nil { err = fmt.Errorf("%s", r) } }() want := args[0] if diff := cmp.Diff(got, want, c.opts...); diff != "" { // Only output values when the verbose flag is set. if c.verbose() { note("diff (-got +want)", Unquoted(diff)) return errors.New("values are not deep equal") } note("error", Unquoted("values are not deep equal")) note("diff (-got +want)", Unquoted(diff)) return ErrSilent } return nil } // DeepEquals is a Checker deeply checking equality of two arbitrary values. // The comparison is done using the github.com/google/go-cmp/cmp package. // When comparing structs, by default no exported fields are allowed. CmpEquals // can be used when more customized compare options are required. // // Example call: // // c.Assert(got, qt.DeepEquals, []int{42, 47}) // var DeepEquals = CmpEquals() // ContentEquals is like DeepEquals but any slices in the compared values will // be sorted before being compared. var ContentEquals = CmpEquals(cmpopts.SortSlices(func(x, y interface{}) bool { // TODO frankban: implement a proper sort function. return pretty.Sprint(x) < pretty.Sprint(y) })) // Matches is a Checker checking that the provided string or fmt.Stringer // matches the provided regular expression pattern. // // For instance: // // c.Assert("these are the voyages", qt.Matches, "these are .*") // c.Assert(net.ParseIP("1.2.3.4"), qt.Matches, "1.*") // var Matches Checker = &matchesChecker{ argNames: []string{"got value", "regexp"}, } type matchesChecker struct { argNames } // Check implements Checker.Check by checking that got is a string or a // fmt.Stringer and that it matches args[0]. func (c *matchesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error { pattern := args[0] switch v := got.(type) { case string: return match(v, pattern, "value does not match regexp", note) case fmt.Stringer: return match(v.String(), pattern, "value.String() does not match regexp", note) } note("value", got) return BadCheckf("value is not a string or a fmt.Stringer") } // ErrorMatches is a Checker checking that the provided value is an error whose // message matches the provided regular expression pattern. // // For instance: // // c.Assert(err, qt.ErrorMatches, "bad wolf .*") // var ErrorMatches Checker = &errorMatchesChecker{ argNames: []string{"got error", "regexp"}, } type errorMatchesChecker struct { argNames } // Check implements Checker.Check by checking that got is an error whose // Error() matches args[0]. func (c *errorMatchesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error { if got == nil { return errors.New("got nil error but want non-nil") } err, ok := got.(error) if !ok { note("got", got) return BadCheckf("first argument is not an error") } return match(err.Error(), args[0], "error does not match regexp", note) } // PanicMatches is a Checker checking that the provided function panics with a // message matching the provided regular expression pattern. // // For instance: // // c.Assert(func() {panic("bad wolf ...")}, qt.PanicMatches, "bad wolf .*") // var PanicMatches Checker = &panicMatchesChecker{ argNames: []string{"function", "regexp"}, } type panicMatchesChecker struct { argNames } // Check implements Checker.Check by checking that got is a func() that panics // with a message matching args[0]. func (c *panicMatchesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { f := reflect.ValueOf(got) if f.Kind() != reflect.Func { note("got", got) return BadCheckf("first argument is not a function") } ftype := f.Type() if ftype.NumIn() != 0 { note("function", got) return BadCheckf("cannot use a function receiving arguments") } defer func() { r := recover() if r == nil { err = errors.New("function did not panic") return } msg := fmt.Sprint(r) note("panic value", msg) err = match(msg, args[0], "panic value does not match regexp", note) }() f.Call(nil) return nil } // IsNil is a Checker checking that the provided value is nil. // // For instance: // // c.Assert(got, qt.IsNil) // var IsNil Checker = &isNilChecker{ argNames: []string{"got"}, } type isNilChecker struct { argNames } // Check implements Checker.Check by checking that got is nil. func (c *isNilChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { if got == nil { return nil } value := reflect.ValueOf(got) if canBeNil(value.Kind()) && value.IsNil() { return nil } return fmt.Errorf("%#v is not nil", got) } // HasLen is a Checker checking that the provided value has the given length. // // For instance: // // c.Assert([]int{42, 47}, qt.HasLen, 2) // c.Assert(myMap, qt.HasLen, 42) // var HasLen Checker = &hasLenChecker{ argNames: []string{"got", "want length"}, } type hasLenChecker struct { argNames } // Check implements Checker.Check by checking that len(got) == args[0]. func (c *hasLenChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { v := reflect.ValueOf(got) switch v.Kind() { case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice, reflect.String: default: note("got", got) return BadCheckf("first argument has no length") } want, ok := args[0].(int) if !ok { note("length", args[0]) return BadCheckf("length is not an int") } length := v.Len() note("len(got)", length) if length != want { return fmt.Errorf("unexpected length") } return nil } // Satisfies is a Checker checking that the provided value, when used as // argument of the provided predicate function, causes the function to return // true. The function must be of type func(T) bool, having got assignable to T. // // For instance: // // // Check that an error from os.Open satisfies os.IsNotExist. // c.Assert(err, qt.Satisfies, os.IsNotExist) // // // Check that a floating point number is a not-a-number. // c.Assert(f, qt.Satisfies, math.IsNaN) // var Satisfies Checker = &satisfiesChecker{ argNames: []string{"arg", "predicate function"}, } type satisfiesChecker struct { argNames } // Check implements Checker.Check by checking that args[0](got) == true. func (c *satisfiesChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { // Original code at // . // Copyright 2011 Canonical Ltd. // Licensed under the LGPLv3, see LICENCE file for details. predicate := args[0] f := reflect.ValueOf(predicate) ftype := f.Type() if ftype.Kind() != reflect.Func || ftype.NumIn() != 1 || ftype.NumOut() != 1 || ftype.Out(0).Kind() != reflect.Bool { note("predicate function", predicate) return BadCheckf("predicate function is not a func(T) bool") } v, t := reflect.ValueOf(got), ftype.In(0) if !v.IsValid() { if !canBeNil(t.Kind()) { note("predicate function", predicate) return BadCheckf("cannot use nil as type %v in argument to predicate function", t) } v = reflect.Zero(t) } else if !v.Type().AssignableTo(t) { note("arg", got) note("predicate function", predicate) return BadCheckf("cannot use value of type %v as type %v in argument to predicate function", v.Type(), t) } if f.Call([]reflect.Value{v})[0].Interface().(bool) { return nil } return fmt.Errorf("value does not satisfy predicate function") } // Not returns a Checker negating the given Checker. // // For instance: // // c.Assert(got, qt.Not(qt.IsNil)) // c.Assert(answer, qt.Not(qt.Equals), 42) // func Not(checker Checker) Checker { return ¬Checker{ Checker: checker, } } type notChecker struct { Checker } // Check implements Checker.Check by checking that the stored checker fails. func (c *notChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) (err error) { if nc, ok := c.Checker.(*notChecker); ok { return nc.Checker.Check(got, args, note) } err = c.Checker.Check(got, args, note) if IsBadCheck(err) { return err } if err != nil { return nil } return errors.New("unexpected success") } // Contains is a checker that checks that a map, slice, array // or string contains a value. It's the same as using // Any(Equals), except that it has a special case // for strings - if the first argument is a string, // the second argument must also be a string // and strings.Contains will be used. // // For example: // // c.Assert("hello world", qt.Contains, "world") // c.Assert([]int{3,5,7,99}, qt.Contains, 7) // var Contains Checker = &containsChecker{ argNames: []string{"got", "want"}, } type containsChecker struct { argNames } // Check implements Checker.Check by checking that got contains args[0]. func (c *containsChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error { if got, ok := got.(string); ok { want, ok := args[0].(string) if !ok { return BadCheckf("strings can only contain strings, not %T", args[0]) } if strings.Contains(got, want) { return nil } return errors.New("no substring match found") } return Any(Equals).Check(got, args, note) } // Any returns a Checker that uses the given checker to check elements // of a slice or array or the values from a map. It succeeds if any element // passes the check. // // For example: // // c.Assert([]int{3,5,7,99}, qt.Any(qt.Equals), 7) // c.Assert([][]string{{"a", "b"}, {"c", "d"}}, qt.Any(qt.DeepEquals), []string{"c", "d"}) // // See also All and Contains. func Any(c Checker) Checker { return &anyChecker{ argNames: append([]string{"container"}, c.ArgNames()[1:]...), elemChecker: c, } } type anyChecker struct { argNames elemChecker Checker } // Check implements Checker.Check by checking that one of the elements of // got passes the c.elemChecker check. func (c *anyChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error { iter, err := newIter(got) if err != nil { return BadCheckf("%v", err) } for iter.next() { // For the time being, discard the notes added by the sub-checker, // because it's not clear what a good behaviour would be. // Should we print all the failed check for all elements? If there's only // one element in the container, the answer is probably yes, // but let's leave it for now. err := c.elemChecker.Check( iter.value().Interface(), args, func(key string, value interface{}) {}, ) if err == nil { return nil } if IsBadCheck(err) { return BadCheckf("at %s: %v", iter.key(), err) } } return errors.New("no matching element found") } // All returns a Checker that uses the given checker to check elements // of slice or array or the values of a map. It succeeds if all elements // pass the check. // On failure it prints the error from the first index that failed. // // For example: // // c.Assert([]int{3, 5, 8}, qt.All(qt.Not(qt.Equals)), 0) // c.Assert([][]string{{"a", "b"}, {"a", "b"}}, qt.All(qt.DeepEquals), []string{"c", "d"}) // // See also Any and Contains. func All(c Checker) Checker { return &allChecker{ argNames: append([]string{"container"}, c.ArgNames()[1:]...), elemChecker: c, } } type allChecker struct { argNames elemChecker Checker } // Check implement Checker.Check by checking that all the elements of got // pass the c.elemChecker check. func (c *allChecker) Check(got interface{}, args []interface{}, notef func(key string, value interface{})) error { iter, err := newIter(got) if err != nil { return BadCheckf("%v", err) } for iter.next() { // Store any notes added by the checker so // we can add our own note at the start // to say which element failed. var notes []note err := c.elemChecker.Check( iter.value().Interface(), args, func(key string, val interface{}) { notes = append(notes, note{key, val}) }, ) if err == nil { continue } if IsBadCheck(err) { return BadCheckf("at %s: %v", iter.key(), err) } notef("error", Unquoted("mismatch at "+iter.key())) // TODO should we print the whole container value in // verbose mode? if err != ErrSilent { // If the error's not silent, the checker is expecting // the caller to print the error and the value that failed. notef("error", Unquoted(err.Error())) notef("first mismatched element", iter.value().Interface()) } for _, n := range notes { notef(n.key, n.value) } return ErrSilent } return nil } // JSONEquals is a checker that checks whether a byte slice // or string is JSON-equivalent to a Go value. See CodecEquals for // more information. // // It uses DeepEquals to do the comparison. If a more sophisticated // comparison is required, use CodecEquals directly. // // For instance: // // c.Assert(`{"First": 47.11}`, qt.JSONEquals, &MyStruct{First: 47.11}) // var JSONEquals = CodecEquals(json.Marshal, json.Unmarshal) type codecEqualChecker struct { argNames marshal func(interface{}) ([]byte, error) unmarshal func([]byte, interface{}) error deepEquals Checker } // CodecEquals returns a checker that checks for codec value equivalence. // // It expects two arguments: a byte slice or a string containing some // codec-marshaled data, and a Go value. // // It uses unmarshal to unmarshal the data into an interface{} value. // It marshals the Go value using marshal, then unmarshals the result into // an interface{} value. // // It then checks that the two interface{} values are deep-equal to one // another, using CmpEquals(opts) to perform the check. // // See JSONEquals for an example of this in use. func CodecEquals( marshal func(interface{}) ([]byte, error), unmarshal func([]byte, interface{}) error, opts ...cmp.Option, ) Checker { return &codecEqualChecker{ argNames: argNames{"got", "want"}, marshal: marshal, unmarshal: unmarshal, deepEquals: CmpEquals(opts...), } } func (c *codecEqualChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error { var gotContent []byte switch got := got.(type) { case string: gotContent = []byte(got) case []byte: gotContent = got default: return BadCheckf("expected string or byte, got %T", got) } wantContent := args[0] wantContentBytes, err := c.marshal(wantContent) if err != nil { return BadCheckf("cannot marshal expected contents: %v", err) } var wantContentVal interface{} if err := c.unmarshal(wantContentBytes, &wantContentVal); err != nil { return BadCheckf("cannot unmarshal expected contents: %v", err) } var gotContentVal interface{} if err := c.unmarshal([]byte(gotContent), &gotContentVal); err != nil { return fmt.Errorf("cannot unmarshal obtained contents: %v; %q", err, gotContent) } return c.deepEquals.Check(gotContentVal, []interface{}{wantContentVal}, note) } // argNames helps implementing Checker.ArgNames. type argNames []string // ArgNames implements Checker.ArgNames by returning the argument names. func (a argNames) ArgNames() []string { return a } // match checks that the given error message matches the given pattern. func match(got string, pattern interface{}, msg string, note func(key string, value interface{})) error { regex, ok := pattern.(string) if !ok { note("regexp", pattern) return BadCheckf("regexp is not a string") } matches, err := regexp.MatchString("^("+regex+")$", got) if err != nil { note("regexp", regex) return BadCheckf("cannot compile regexp: %s", err) } if matches { return nil } return errors.New(msg) } // canBeNil reports whether a value or type of the given kind can be nil. func canBeNil(k reflect.Kind) bool { switch k { case reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Ptr, reflect.Slice: return true } return false } quicktest-1.6.0/checker_test.go000066400000000000000000001371601356056600300165400ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "bytes" "encoding/json" "fmt" "strings" "testing" "time" "github.com/google/go-cmp/cmp" "github.com/google/go-cmp/cmp/cmpopts" qt "github.com/frankban/quicktest" ) var ( goTime = time.Date(2012, 3, 28, 0, 0, 0, 0, time.UTC) chInt = func() chan int { ch := make(chan int, 4) ch <- 42 ch <- 47 return ch }() sameInts = cmpopts.SortSlices(func(x, y int) bool { return x < y }) cmpEqualsGot = struct { Strings []interface{} Ints []int }{ Strings: []interface{}{"who", "dalek"}, Ints: []int{42, 47}, } cmpEqualsWant = struct { Strings []interface{} Ints []int }{ Strings: []interface{}{"who", "dalek"}, Ints: []int{42}, } ) type InnerJSON struct { First string Second int `json:",omitempty" yaml:",omitempty"` Third map[string]bool `json:",omitempty" yaml:",omitempty"` } type OuterJSON struct { First float64 Second []*InnerJSON `json:"Last,omitempty" yaml:"last,omitempty"` } var checkerTests = []struct { about string checker qt.Checker got interface{} args []interface{} verbose bool expectedCheckFailure string expectedNegateFailure string }{{ about: "Equals: same values", checker: qt.Equals, got: 42, args: []interface{}{42}, expectedNegateFailure: ` error: unexpected success got: int(42) want: `, }, { about: "Equals: different values", checker: qt.Equals, got: "42", args: []interface{}{"47"}, expectedCheckFailure: ` error: values are not equal got: "42" want: "47" `, }, { about: "Equals: different strings with quotes", checker: qt.Equals, got: `string "foo"`, args: []interface{}{`string "bar"`}, expectedCheckFailure: tilde2bq(` error: values are not equal got: ~string "foo"~ want: ~string "bar"~ `), }, { about: "Equals: different types", checker: qt.Equals, got: 42, args: []interface{}{"42"}, expectedCheckFailure: ` error: values are not equal got: int(42) want: "42" `, }, { about: "Equals: nil and nil", checker: qt.Equals, got: nil, args: []interface{}{nil}, expectedNegateFailure: ` error: unexpected success got: nil want: `, }, { about: "Equals: error is not nil", checker: qt.Equals, got: errBadWolf, args: []interface{}{nil}, expectedCheckFailure: ` error: got non-nil error got: bad wolf file:line want: nil `, }, { about: "Equals: error is not nil: not formatted", checker: qt.Equals, got: &errTest{ msg: "bad wolf", }, args: []interface{}{nil}, expectedCheckFailure: ` error: got non-nil error got: e"bad wolf" want: nil `, }, { about: "Equals: error does not guard against nil", checker: qt.Equals, got: (*errTest)(nil), args: []interface{}{nil}, expectedCheckFailure: ` error: got non-nil error got: e want: nil `, }, { about: "Equals: error is not nil: not formatted and with quotes", checker: qt.Equals, got: &errTest{ msg: `failure: "bad wolf"`, }, args: []interface{}{nil}, expectedCheckFailure: tilde2bq(` error: got non-nil error got: e~failure: "bad wolf"~ want: nil `), }, { about: "Equals: nil struct", checker: qt.Equals, got: (*struct{})(nil), args: []interface{}{nil}, expectedCheckFailure: ` error: values are not equal got: (*struct {})(nil) want: nil `, }, { about: "Equals: uncomparable types", checker: qt.Equals, got: struct { Ints []int }{ Ints: []int{42, 47}, }, args: []interface{}{struct { Ints []int }{ Ints: []int{42, 47}, }}, expectedCheckFailure: ` error: runtime error: comparing uncomparable type struct { Ints []int } got: struct { Ints []int }{ Ints: {42, 47}, } want: `, }, { about: "Equals: not enough arguments", checker: qt.Equals, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, }, { about: "Equals: too many arguments", checker: qt.Equals, args: []interface{}{nil, 47}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ nil, int(47), } want args: want `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ nil, int(47), } want args: want `, }, { about: "CmpEquals: same values", checker: qt.CmpEquals(), got: cmpEqualsGot, args: []interface{}{cmpEqualsGot}, expectedNegateFailure: ` error: unexpected success got: struct { Strings []interface {}; Ints []int }{ Strings: { "who", "dalek", }, Ints: {42, 47}, } want: `, }, { about: "CmpEquals: different values", checker: qt.CmpEquals(), got: cmpEqualsGot, args: []interface{}{cmpEqualsWant}, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s `, diff(cmpEqualsGot, cmpEqualsWant)), }, { about: "CmpEquals: different values: verbose", checker: qt.CmpEquals(), got: cmpEqualsGot, args: []interface{}{cmpEqualsWant}, verbose: true, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s got: struct { Strings []interface {}; Ints []int }{ Strings: { "who", "dalek", }, Ints: {42, 47}, } want: struct { Strings []interface {}; Ints []int }{ Strings: { "who", "dalek", }, Ints: {42}, } `, diff(cmpEqualsGot, cmpEqualsWant)), }, { about: "CmpEquals: same values with options", checker: qt.CmpEquals(sameInts), got: []int{1, 2, 3}, args: []interface{}{ []int{3, 2, 1}, }, expectedNegateFailure: ` error: unexpected success got: []int{1, 2, 3} want: []int{3, 2, 1} `, }, { about: "CmpEquals: different values with options", checker: qt.CmpEquals(sameInts), got: []int{1, 2, 4}, args: []interface{}{ []int{3, 2, 1}, }, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s `, diff([]int{1, 2, 4}, []int{3, 2, 1}, sameInts)), }, { about: "CmpEquals: different values with options: verbose", checker: qt.CmpEquals(sameInts), got: []int{1, 2, 4}, args: []interface{}{ []int{3, 2, 1}, }, verbose: true, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s got: []int{1, 2, 4} want: []int{3, 2, 1} `, diff([]int{1, 2, 4}, []int{3, 2, 1}, sameInts)), }, { about: "CmpEquals: structs with unexported fields not allowed", checker: qt.CmpEquals(), got: struct{ answer int }{ answer: 42, }, args: []interface{}{ struct{ answer int }{ answer: 42, }, }, expectedCheckFailure: ` error: cannot handle unexported field: root.answer consider using a custom Comparer; if you control the implementation of type, you can also consider AllowUnexported or cmpopts.IgnoreUnexported got: struct { answer int }{answer:42} want: `, }, { about: "CmpEquals: structs with unexported fields ignored", checker: qt.CmpEquals(cmpopts.IgnoreUnexported(struct{ answer int }{})), got: struct{ answer int }{ answer: 42, }, args: []interface{}{ struct{ answer int }{ answer: 42, }, }, expectedNegateFailure: ` error: unexpected success got: struct { answer int }{answer:42} want: `, }, { about: "CmpEquals: same times", checker: qt.CmpEquals(), got: goTime, args: []interface{}{ goTime, }, expectedNegateFailure: ` error: unexpected success got: s"2012-03-28 00:00:00 +0000 UTC" want: `, }, { about: "CmpEquals: different times: verbose", checker: qt.CmpEquals(), got: goTime.Add(24 * time.Hour), args: []interface{}{ goTime, }, verbose: true, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s got: s"2012-03-29 00:00:00 +0000 UTC" want: s"2012-03-28 00:00:00 +0000 UTC" `, diff(goTime.Add(24*time.Hour), goTime)), }, { about: "CmpEquals: not enough arguments", checker: qt.CmpEquals(), expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, }, { about: "CmpEquals: too many arguments", checker: qt.CmpEquals(), got: []int{42}, args: []interface{}{[]int{42}, "bad wolf"}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ []int{42}, "bad wolf", } want args: want `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ []int{42}, "bad wolf", } want args: want `, }, { about: "DeepEquals: different values", checker: qt.DeepEquals, got: cmpEqualsGot, args: []interface{}{cmpEqualsWant}, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s `, diff(cmpEqualsGot, cmpEqualsWant)), }, { about: "DeepEquals: different values: verbose", checker: qt.DeepEquals, got: cmpEqualsGot, args: []interface{}{cmpEqualsWant}, verbose: true, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s got: struct { Strings []interface {}; Ints []int }{ Strings: { "who", "dalek", }, Ints: {42, 47}, } want: struct { Strings []interface {}; Ints []int }{ Strings: { "who", "dalek", }, Ints: {42}, } `, diff(cmpEqualsGot, cmpEqualsWant)), }, { about: "ContentEquals: same values", checker: qt.ContentEquals, got: []string{"these", "are", "the", "voyages"}, args: []interface{}{ []string{"these", "are", "the", "voyages"}, }, expectedNegateFailure: ` error: unexpected success got: []string{"these", "are", "the", "voyages"} want: `, }, { about: "ContentEquals: same contents", checker: qt.ContentEquals, got: []int{1, 2, 3}, args: []interface{}{ []int{3, 2, 1}, }, expectedNegateFailure: ` error: unexpected success got: []int{1, 2, 3} want: []int{3, 2, 1} `, }, { about: "ContentEquals: same contents on complex slice", checker: qt.ContentEquals, got: []struct { Strings []interface{} Ints []int }{cmpEqualsGot, cmpEqualsGot, cmpEqualsWant}, args: []interface{}{ []struct { Strings []interface{} Ints []int }{cmpEqualsWant, cmpEqualsGot, cmpEqualsGot}, }, expectedNegateFailure: ` error: unexpected success got: []struct { Strings []interface {}; Ints []int }{ { Strings: { "who", "dalek", }, Ints: {42, 47}, }, { Strings: { "who", "dalek", }, Ints: {42, 47}, }, { Strings: { "who", "dalek", }, Ints: {42}, }, } want: []struct { Strings []interface {}; Ints []int }{ { Strings: { "who", "dalek", }, Ints: {42}, }, { Strings: { "who", "dalek", }, Ints: {42, 47}, }, { Strings: { "who", "dalek", }, Ints: {42, 47}, }, } `, }, { about: "ContentEquals: same contents on a nested slice", checker: qt.ContentEquals, got: struct { Nums []int }{ Nums: []int{1, 2, 3, 4}, }, args: []interface{}{ struct { Nums []int }{ Nums: []int{4, 3, 2, 1}, }, }, expectedNegateFailure: ` error: unexpected success got: struct { Nums []int }{ Nums: {1, 2, 3, 4}, } want: struct { Nums []int }{ Nums: {4, 3, 2, 1}, } `, }, { about: "ContentEquals: slices of different type", checker: qt.ContentEquals, got: []string{"bad", "wolf"}, args: []interface{}{ []interface{}{"bad", "wolf"}, }, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s `, diff([]string{"bad", "wolf"}, []interface{}{"bad", "wolf"})), }, { about: "ContentEquals: not enough arguments", checker: qt.ContentEquals, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, }, { about: "ContentEquals: too many arguments", checker: qt.ContentEquals, args: []interface{}{nil, nil}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ nil, nil, } want args: want `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ nil, nil, } want args: want `, }, { about: "Matches: perfect match", checker: qt.Matches, got: "exterminate", args: []interface{}{"exterminate"}, expectedNegateFailure: ` error: unexpected success got value: "exterminate" regexp: `, }, { about: "Matches: match", checker: qt.Matches, got: "these are the voyages", args: []interface{}{"these are the .*"}, expectedNegateFailure: ` error: unexpected success got value: "these are the voyages" regexp: "these are the .*" `, }, { about: "Matches: match with stringer", checker: qt.Matches, got: bytes.NewBufferString("resistance is futile"), args: []interface{}{"resistance is (futile|useful)"}, expectedNegateFailure: ` error: unexpected success got value: s"resistance is futile" regexp: "resistance is (futile|useful)" `, }, { about: "Matches: mismatch", checker: qt.Matches, got: "voyages", args: []interface{}{"these are the voyages"}, expectedCheckFailure: ` error: value does not match regexp got value: "voyages" regexp: "these are the voyages" `, }, { about: "Matches: mismatch with stringer", checker: qt.Matches, got: bytes.NewBufferString("voyages"), args: []interface{}{"these are the voyages"}, expectedCheckFailure: ` error: value.String() does not match regexp got value: s"voyages" regexp: "these are the voyages" `, }, { about: "Matches: empty pattern", checker: qt.Matches, got: "these are the voyages", args: []interface{}{""}, expectedCheckFailure: ` error: value does not match regexp got value: "these are the voyages" regexp: "" `, }, { about: "Matches: complex pattern", checker: qt.Matches, got: "end of the universe", args: []interface{}{"bad wolf|end of the .*"}, expectedNegateFailure: ` error: unexpected success got value: "end of the universe" regexp: "bad wolf|end of the .*" `, }, { about: "Matches: invalid pattern", checker: qt.Matches, got: "voyages", args: []interface{}{"("}, expectedCheckFailure: ` error: bad check: cannot compile regexp: error parsing regexp: missing closing ): ` + "`^(()$`" + ` regexp: "(" `, expectedNegateFailure: ` error: bad check: cannot compile regexp: error parsing regexp: missing closing ): ` + "`^(()$`" + ` regexp: "(" `, }, { about: "Matches: pattern not a string", checker: qt.Matches, got: "", args: []interface{}{[]int{42}}, expectedCheckFailure: ` error: bad check: regexp is not a string regexp: []int{42} `, expectedNegateFailure: ` error: bad check: regexp is not a string regexp: []int{42} `, }, { about: "Matches: not a string or as stringer", checker: qt.Matches, got: 42, args: []interface{}{".*"}, expectedCheckFailure: ` error: bad check: value is not a string or a fmt.Stringer value: int(42) `, expectedNegateFailure: ` error: bad check: value is not a string or a fmt.Stringer value: int(42) `, }, { about: "Matches: not enough arguments", checker: qt.Matches, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, }, { about: "Matches: too many arguments", checker: qt.Matches, got: "these are the voyages", args: []interface{}{"these are the .*", nil}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ "these are the .*", nil, } want args: regexp `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ "these are the .*", nil, } want args: regexp `, }, { about: "ErrorMatches: perfect match", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{"bad wolf"}, expectedNegateFailure: ` error: unexpected success got error: bad wolf file:line regexp: "bad wolf" `, }, { about: "ErrorMatches: match", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{"bad .*"}, expectedNegateFailure: ` error: unexpected success got error: bad wolf file:line regexp: "bad .*" `, }, { about: "ErrorMatches: mismatch", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{"exterminate"}, expectedCheckFailure: ` error: error does not match regexp got error: bad wolf file:line regexp: "exterminate" `, }, { about: "ErrorMatches: empty pattern", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{""}, expectedCheckFailure: ` error: error does not match regexp got error: bad wolf file:line regexp: "" `, }, { about: "ErrorMatches: complex pattern", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{"bad wolf|end of the universe"}, expectedNegateFailure: ` error: unexpected success got error: bad wolf file:line regexp: "bad wolf|end of the universe" `, }, { about: "ErrorMatches: invalid pattern", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{"("}, expectedCheckFailure: ` error: bad check: cannot compile regexp: error parsing regexp: missing closing ): ` + "`^(()$`" + ` regexp: "(" `, expectedNegateFailure: ` error: bad check: cannot compile regexp: error parsing regexp: missing closing ): ` + "`^(()$`" + ` regexp: "(" `, }, { about: "ErrorMatches: pattern not a string", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{[]int{42}}, expectedCheckFailure: ` error: bad check: regexp is not a string regexp: []int{42} `, expectedNegateFailure: ` error: bad check: regexp is not a string regexp: []int{42} `, }, { about: "ErrorMatches: not an error", checker: qt.ErrorMatches, got: 42, args: []interface{}{".*"}, expectedCheckFailure: ` error: bad check: first argument is not an error got: int(42) `, expectedNegateFailure: ` error: bad check: first argument is not an error got: int(42) `, }, { about: "ErrorMatches: nil error", checker: qt.ErrorMatches, got: nil, args: []interface{}{"some pattern"}, expectedCheckFailure: ` error: got nil error but want non-nil got error: nil regexp: "some pattern" `, }, { about: "ErrorMatches: not enough arguments", checker: qt.ErrorMatches, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, }, { about: "ErrorMatches: too many arguments", checker: qt.ErrorMatches, got: errBadWolf, args: []interface{}{"bad wolf", []string{"bad", "wolf"}}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ "bad wolf", []string{"bad", "wolf"}, } want args: regexp `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ "bad wolf", []string{"bad", "wolf"}, } want args: regexp `, }, { about: "PanicMatches: perfect match", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{"error: bad wolf"}, expectedNegateFailure: ` error: unexpected success panic value: "error: bad wolf" function: func() {...} regexp: `, }, { about: "PanicMatches: match", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{"error: .*"}, expectedNegateFailure: ` error: unexpected success panic value: "error: bad wolf" function: func() {...} regexp: "error: .*" `, }, { about: "PanicMatches: mismatch", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{"error: exterminate"}, expectedCheckFailure: ` error: panic value does not match regexp panic value: "error: bad wolf" function: func() {...} regexp: "error: exterminate" `, }, { about: "PanicMatches: empty pattern", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{""}, expectedCheckFailure: ` error: panic value does not match regexp panic value: "error: bad wolf" function: func() {...} regexp: "" `, }, { about: "PanicMatches: complex pattern", checker: qt.PanicMatches, got: func() { panic("bad wolf") }, args: []interface{}{"bad wolf|end of the universe"}, expectedNegateFailure: ` error: unexpected success panic value: "bad wolf" function: func() {...} regexp: "bad wolf|end of the universe" `, }, { about: "PanicMatches: invalid pattern", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{"("}, expectedCheckFailure: ` error: bad check: cannot compile regexp: error parsing regexp: missing closing ): ` + "`^(()$`" + ` panic value: "error: bad wolf" regexp: "(" `, expectedNegateFailure: ` error: bad check: cannot compile regexp: error parsing regexp: missing closing ): ` + "`^(()$`" + ` panic value: "error: bad wolf" regexp: "(" `, }, { about: "PanicMatches: pattern not a string", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{nil}, expectedCheckFailure: ` error: bad check: regexp is not a string panic value: "error: bad wolf" regexp: nil `, expectedNegateFailure: ` error: bad check: regexp is not a string panic value: "error: bad wolf" regexp: nil `, }, { about: "PanicMatches: not a function", checker: qt.PanicMatches, got: map[string]int{"answer": 42}, args: []interface{}{".*"}, expectedCheckFailure: ` error: bad check: first argument is not a function got: map[string]int{"answer":42} `, expectedNegateFailure: ` error: bad check: first argument is not a function got: map[string]int{"answer":42} `, }, { about: "PanicMatches: not a proper function", checker: qt.PanicMatches, got: func(int) { panic("error: bad wolf") }, args: []interface{}{".*"}, expectedCheckFailure: ` error: bad check: cannot use a function receiving arguments function: func(int) {...} `, expectedNegateFailure: ` error: bad check: cannot use a function receiving arguments function: func(int) {...} `, }, { about: "PanicMatches: function returning something", checker: qt.PanicMatches, got: func() error { panic("error: bad wolf") }, args: []interface{}{".*"}, expectedNegateFailure: ` error: unexpected success panic value: "error: bad wolf" function: func() error {...} regexp: ".*" `, }, { about: "PanicMatches: no panic", checker: qt.PanicMatches, got: func() {}, args: []interface{}{".*"}, expectedCheckFailure: ` error: function did not panic function: func() {...} regexp: ".*" `, }, { about: "PanicMatches: not enough arguments", checker: qt.PanicMatches, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, }, { about: "PanicMatches: too many arguments", checker: qt.PanicMatches, got: func() { panic("error: bad wolf") }, args: []interface{}{"error: bad wolf", 42}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ "error: bad wolf", int(42), } want args: regexp `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ "error: bad wolf", int(42), } want args: regexp `, }, { about: "IsNil: nil", checker: qt.IsNil, got: nil, expectedNegateFailure: ` error: unexpected success got: nil `, }, { about: "IsNil: nil struct", checker: qt.IsNil, got: (*struct{})(nil), expectedNegateFailure: ` error: unexpected success got: (*struct {})(nil) `, }, { about: "IsNil: nil func", checker: qt.IsNil, got: (func())(nil), expectedNegateFailure: ` error: unexpected success got: func() {...} `, }, { about: "IsNil: nil map", checker: qt.IsNil, got: (map[string]string)(nil), expectedNegateFailure: ` error: unexpected success got: map[string]string{} `, }, { about: "IsNil: nil slice", checker: qt.IsNil, got: ([]int)(nil), expectedNegateFailure: ` error: unexpected success got: []int(nil) `, }, { about: "IsNil: error does not guard against nil", checker: qt.IsNil, got: (*errTest)(nil), expectedNegateFailure: ` error: unexpected success got: e `, }, { about: "IsNil: not nil", checker: qt.IsNil, got: 42, expectedCheckFailure: ` error: 42 is not nil got: int(42) `, }, { about: "IsNil: too many arguments", checker: qt.IsNil, args: []interface{}{"not nil"}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 got args: []interface {}{ "not nil", } `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 got args: []interface {}{ "not nil", } `, }, { about: "HasLen: arrays with the same length", checker: qt.HasLen, got: [4]string{"these", "are", "the", "voyages"}, args: []interface{}{4}, expectedNegateFailure: ` error: unexpected success len(got): int(4) got: [4]string{"these", "are", "the", "voyages"} want length: `, }, { about: "HasLen: channels with the same length", checker: qt.HasLen, got: chInt, args: []interface{}{2}, expectedNegateFailure: fmt.Sprintf(` error: unexpected success len(got): int(2) got: (chan int)(%v) want length: `, chInt), }, { about: "HasLen: maps with the same length", checker: qt.HasLen, got: map[string]bool{"true": true}, args: []interface{}{1}, expectedNegateFailure: ` error: unexpected success len(got): int(1) got: map[string]bool{"true":true} want length: `, }, { about: "HasLen: slices with the same length", checker: qt.HasLen, got: []int{}, args: []interface{}{0}, expectedNegateFailure: ` error: unexpected success len(got): int(0) got: []int{} want length: `, }, { about: "HasLen: strings with the same length", checker: qt.HasLen, got: "these are the voyages", args: []interface{}{21}, expectedNegateFailure: ` error: unexpected success len(got): int(21) got: "these are the voyages" want length: `, }, { about: "HasLen: arrays with different lengths", checker: qt.HasLen, got: [4]string{"these", "are", "the", "voyages"}, args: []interface{}{0}, expectedCheckFailure: ` error: unexpected length len(got): int(4) got: [4]string{"these", "are", "the", "voyages"} want length: int(0) `, }, { about: "HasLen: channels with different lengths", checker: qt.HasLen, got: chInt, args: []interface{}{4}, expectedCheckFailure: fmt.Sprintf(` error: unexpected length len(got): int(2) got: (chan int)(%v) want length: int(4) `, chInt), }, { about: "HasLen: maps with different lengths", checker: qt.HasLen, got: map[string]bool{"true": true}, args: []interface{}{42}, expectedCheckFailure: ` error: unexpected length len(got): int(1) got: map[string]bool{"true":true} want length: int(42) `, }, { about: "HasLen: slices with different lengths", checker: qt.HasLen, got: []int{42, 47}, args: []interface{}{1}, expectedCheckFailure: ` error: unexpected length len(got): int(2) got: []int{42, 47} want length: int(1) `, }, { about: "HasLen: strings with different lengths", checker: qt.HasLen, got: "these are the voyages", args: []interface{}{42}, expectedCheckFailure: ` error: unexpected length len(got): int(21) got: "these are the voyages" want length: int(42) `, }, { about: "HasLen: value without a length", checker: qt.HasLen, got: 42, args: []interface{}{42}, expectedCheckFailure: ` error: bad check: first argument has no length got: int(42) `, expectedNegateFailure: ` error: bad check: first argument has no length got: int(42) `, }, { about: "HasLen: expected value not a number", checker: qt.HasLen, got: "these are the voyages", args: []interface{}{"bad wolf"}, expectedCheckFailure: ` error: bad check: length is not an int length: "bad wolf" `, expectedNegateFailure: ` error: bad check: length is not an int length: "bad wolf" `, }, { about: "HasLen: not enough arguments", checker: qt.HasLen, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want length `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want length `, }, { about: "HasLen: too many arguments", checker: qt.HasLen, got: []int{42}, args: []interface{}{42, 47}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ int(42), int(47), } want args: want length `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ int(42), int(47), } want args: want length `, }, { about: "Satisfies: success with an error", checker: qt.Satisfies, got: qt.BadCheckf("bad wolf"), args: []interface{}{qt.IsBadCheck}, expectedNegateFailure: ` error: unexpected success arg: e"bad check: bad wolf" predicate function: func(error) bool {...} `, }, { about: "Satisfies: success with an int", checker: qt.Satisfies, got: 42, args: []interface{}{ func(v int) bool { return v == 42 }, }, expectedNegateFailure: ` error: unexpected success arg: int(42) predicate function: func(int) bool {...} `, }, { about: "Satisfies: success with nil", checker: qt.Satisfies, got: nil, args: []interface{}{ func(v []int) bool { return true }, }, expectedNegateFailure: ` error: unexpected success arg: nil predicate function: func([]int) bool {...} `, }, { about: "Satisfies: failure with an error", checker: qt.Satisfies, got: nil, args: []interface{}{qt.IsBadCheck}, expectedCheckFailure: ` error: value does not satisfy predicate function arg: nil predicate function: func(error) bool {...} `, }, { about: "Satisfies: failure with a string", checker: qt.Satisfies, got: "bad wolf", args: []interface{}{ func(string) bool { return false }, }, expectedCheckFailure: ` error: value does not satisfy predicate function arg: "bad wolf" predicate function: func(string) bool {...} `, }, { about: "Satisfies: not a function", checker: qt.Satisfies, got: 42, args: []interface{}{42}, expectedCheckFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: int(42) `, expectedNegateFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: int(42) `, }, { about: "Satisfies: function accepting no arguments", checker: qt.Satisfies, got: 42, args: []interface{}{ func() bool { return true }, }, expectedCheckFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func() bool {...} `, expectedNegateFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func() bool {...} `, }, { about: "Satisfies: function accepting too many arguments", checker: qt.Satisfies, got: 42, args: []interface{}{ func(int, string) bool { return false }, }, expectedCheckFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(int, string) bool {...} `, expectedNegateFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(int, string) bool {...} `, }, { about: "Satisfies: function returning no arguments", checker: qt.Satisfies, got: 42, args: []interface{}{ func(error) {}, }, expectedCheckFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(error) {...} `, expectedNegateFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(error) {...} `, }, { about: "Satisfies: function returning too many argments", checker: qt.Satisfies, got: 42, args: []interface{}{ func(int) (bool, error) { return true, nil }, }, expectedCheckFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(int) (bool, error) {...} `, expectedNegateFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(int) (bool, error) {...} `, }, { about: "Satisfies: function not returning a bool", checker: qt.Satisfies, got: 42, args: []interface{}{ func(int) error { return nil }, }, expectedCheckFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(int) error {...} `, expectedNegateFailure: ` error: bad check: predicate function is not a func(T) bool predicate function: func(int) error {...} `, }, { about: "Satisfies: type mismatch", checker: qt.Satisfies, got: 42, args: []interface{}{qt.IsBadCheck}, expectedCheckFailure: ` error: bad check: cannot use value of type int as type error in argument to predicate function arg: int(42) predicate function: func(error) bool {...} `, expectedNegateFailure: ` error: bad check: cannot use value of type int as type error in argument to predicate function arg: int(42) predicate function: func(error) bool {...} `, }, { about: "Satisfies: nil value that cannot be nil", checker: qt.Satisfies, got: nil, args: []interface{}{ func(string) bool { return true }, }, expectedCheckFailure: ` error: bad check: cannot use nil as type string in argument to predicate function predicate function: func(string) bool {...} `, expectedNegateFailure: ` error: bad check: cannot use nil as type string in argument to predicate function predicate function: func(string) bool {...} `, }, { about: "Satisfies: not enough arguments", checker: qt.Satisfies, expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: predicate function `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: predicate function `, }, { about: "Satisfies: too many arguments", checker: qt.Satisfies, got: 42, args: []interface{}{func() bool { return true }, 1, 2}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 3, want 1 got args: []interface {}{ func() bool {...}, int(1), int(2), } want args: predicate function `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 3, want 1 got args: []interface {}{ func() bool {...}, int(1), int(2), } want args: predicate function `, }, { about: "Not: success", checker: qt.Not(qt.IsNil), got: 42, expectedNegateFailure: ` error: 42 is not nil got: int(42) `, }, { about: "Not: failure", checker: qt.Not(qt.IsNil), got: nil, expectedCheckFailure: ` error: unexpected success got: nil `, }, { about: "Not: not enough arguments", checker: qt.Not(qt.PanicMatches), expectedCheckFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, expectedNegateFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: regexp `, }, { about: "Not: too many arguments", checker: qt.Not(qt.Equals), args: []interface{}{42, nil}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ int(42), nil, } want args: want `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ int(42), nil, } want args: want `, }, { about: "Contains with string", checker: qt.Contains, got: "hello, world", args: []interface{}{"world"}, expectedNegateFailure: ` error: unexpected success got: "hello, world" want: "world" `, }, { about: "Contains with string no match", checker: qt.Contains, got: "hello, world", args: []interface{}{"worlds"}, expectedCheckFailure: ` error: no substring match found got: "hello, world" want: "worlds" `, }, { about: "Contains with slice", checker: qt.Contains, got: []string{"a", "b", "c"}, args: []interface{}{"a"}, expectedNegateFailure: ` error: unexpected success got: []string{"a", "b", "c"} want: "a" `, }, { about: "Contains with map", checker: qt.Contains, // Note: we can't use more than one element here because // pretty.Print output is non-deterministic. // https://github.com/kr/pretty/issues/47 got: map[string]string{"a": "d"}, args: []interface{}{"d"}, expectedNegateFailure: ` error: unexpected success got: map[string]string{"a":"d"} want: "d" `, }, { about: "Contains with non-string", checker: qt.Contains, got: "aa", args: []interface{}{5}, expectedCheckFailure: ` error: bad check: strings can only contain strings, not int `, expectedNegateFailure: ` error: bad check: strings can only contain strings, not int `, }, { about: "All slice equals", checker: qt.All(qt.Equals), got: []string{"a", "a"}, args: []interface{}{"a"}, expectedNegateFailure: ` error: unexpected success container: []string{"a", "a"} want: "a" `, }, { about: "All slice match", checker: qt.All(qt.Matches), got: []string{"red", "blue", "green"}, args: []interface{}{".*e.*"}, expectedNegateFailure: ` error: unexpected success container: []string{"red", "blue", "green"} regexp: ".*e.*" `, }, { about: "All nested match", checker: qt.All(qt.All(qt.Matches)), got: [][]string{{"hello", "goodbye"}, {"red", "blue"}, {}}, args: []interface{}{".*e.*"}, expectedNegateFailure: ` error: unexpected success container: [][]string{ {"hello", "goodbye"}, {"red", "blue"}, {}, } regexp: ".*e.*" `, }, { about: "All nested mismatch", checker: qt.All(qt.All(qt.Matches)), got: [][]string{{"hello", "goodbye"}, {"black", "blue"}, {}}, args: []interface{}{".*e.*"}, expectedCheckFailure: ` error: mismatch at index 1 error: mismatch at index 0 error: value does not match regexp first mismatched element: "black" `, }, { about: "All slice mismatch", checker: qt.All(qt.Matches), got: []string{"red", "black"}, args: []interface{}{".*e.*"}, expectedCheckFailure: ` error: mismatch at index 1 error: value does not match regexp first mismatched element: "black" `, }, { about: "All slice mismatch with DeepEqual", checker: qt.All(qt.DeepEquals), got: [][]string{{"a", "b"}, {"a", "c"}}, args: []interface{}{[]string{"a", "b"}}, expectedCheckFailure: ` error: mismatch at index 1 error: values are not deep equal diff (-got +want): ` + diff([]string{"a", "c"}, []string{"a", "b"}) + ` `, }, { about: "All bad checker args count", checker: qt.All(qt.IsNil), got: []int{}, args: []interface{}{5}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 got args: []interface {}{ int(5), } `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 got args: []interface {}{ int(5), } `, }, { about: "All bad checker args", checker: qt.All(qt.Matches), got: []string{"hello"}, args: []interface{}{5}, expectedCheckFailure: ` error: bad check: at index 0: bad check: regexp is not a string `, expectedNegateFailure: ` error: bad check: at index 0: bad check: regexp is not a string `, }, { about: "All with non-container", checker: qt.All(qt.Equals), got: 5, args: []interface{}{5}, expectedCheckFailure: ` error: bad check: map, slice or array required `, expectedNegateFailure: ` error: bad check: map, slice or array required `, }, { about: "All mismatch with map", checker: qt.All(qt.Matches), got: map[string]string{"a": "red", "b": "black"}, args: []interface{}{".*e.*"}, expectedCheckFailure: ` error: mismatch at key "b" error: value does not match regexp first mismatched element: "black" `, }, { about: "Any with non-container", checker: qt.Any(qt.Equals), got: 5, args: []interface{}{5}, expectedCheckFailure: ` error: bad check: map, slice or array required `, expectedNegateFailure: ` error: bad check: map, slice or array required `, }, { about: "Any no match", checker: qt.Any(qt.Equals), got: []int{}, args: []interface{}{5}, expectedCheckFailure: ` error: no matching element found container: []int{} want: int(5) `, }, { about: "Any bad checker arg count", checker: qt.Any(qt.IsNil), got: []int{}, args: []interface{}{5}, expectedCheckFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 got args: []interface {}{ int(5), } `, expectedNegateFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 got args: []interface {}{ int(5), } `, }, { about: "Any bad checker args", checker: qt.Any(qt.Matches), got: []string{"hello"}, args: []interface{}{5}, expectedCheckFailure: ` error: bad check: at index 0: bad check: regexp is not a string `, expectedNegateFailure: ` error: bad check: at index 0: bad check: regexp is not a string `, }, { about: "JSONEquals simple", checker: qt.JSONEquals, got: `{"First": 47.11}`, args: []interface{}{ &OuterJSON{ First: 47.11, }, }, expectedNegateFailure: tilde2bq(` error: unexpected success got: ~{"First": 47.11}~ want: &quicktest_test.OuterJSON{ First: 47.11, Second: nil, } `), }, { about: "JSONEquals nested", checker: qt.JSONEquals, got: `{"First": 47.11, "Last": [{"First": "Hello", "Second": 42}]}`, args: []interface{}{ &OuterJSON{ First: 47.11, Second: []*InnerJSON{ {First: "Hello", Second: 42}, }, }, }, expectedNegateFailure: tilde2bq(` error: unexpected success got: ~{"First": 47.11, "Last": [{"First": "Hello", "Second": 42}]}~ want: &quicktest_test.OuterJSON{ First: 47.11, Second: { &quicktest_test.InnerJSON{ First: "Hello", Second: 42, Third: {}, }, }, } `), }, { about: "JSONEquals nested with newline", checker: qt.JSONEquals, got: `{"First": 47.11, "Last": [{"First": "Hello", "Second": 42}, {"First": "World", "Third": {"F": false}}]}`, args: []interface{}{ &OuterJSON{ First: 47.11, Second: []*InnerJSON{ {First: "Hello", Second: 42}, {First: "World", Third: map[string]bool{ "F": false, }}, }, }, }, expectedNegateFailure: ` error: unexpected success got: "{\"First\": 47.11, \"Last\": [{\"First\": \"Hello\", \"Second\": 42},\n\t\t\t{\"First\": \"World\", \"Third\": {\"F\": false}}]}" want: &quicktest_test.OuterJSON{ First: 47.11, Second: { &quicktest_test.InnerJSON{ First: "Hello", Second: 42, Third: {}, }, &quicktest_test.InnerJSON{ First: "World", Second: 0, Third: {"F":false}, }, }, } `, }, { about: "JSONEquals extra field", checker: qt.JSONEquals, got: `{"NotThere": 1}`, args: []interface{}{ &OuterJSON{ First: 2, }, }, expectedCheckFailure: fmt.Sprintf(` error: values are not deep equal diff (-got +want): %s `, diff(map[string]interface{}{"NotThere": 1.0}, map[string]interface{}{"First": 2.0})), }, { about: "JSONEquals cannot unmarshal obtained value", checker: qt.JSONEquals, got: `{"NotThere": `, args: []interface{}{nil}, expectedCheckFailure: tilde2bq(` error: cannot unmarshal obtained contents: unexpected end of JSON input; "{\"NotThere\": " got: ~{"NotThere": ~ want: nil `), }, { about: "JSONEquals cannot marshal expected value", checker: qt.JSONEquals, got: `null`, args: []interface{}{ jsonErrorMarshaler{}, }, expectedCheckFailure: ` error: bad check: cannot marshal expected contents: json: error calling MarshalJSON for type quicktest_test.jsonErrorMarshaler: qt json marshal error `, expectedNegateFailure: ` error: bad check: cannot marshal expected contents: json: error calling MarshalJSON for type quicktest_test.jsonErrorMarshaler: qt json marshal error `, }, { about: "JSONEquals with []byte", checker: qt.JSONEquals, got: []byte("null"), args: []interface{}{nil}, expectedNegateFailure: ` error: unexpected success got: []uint8{0x6e, 0x75, 0x6c, 0x6c} want: nil `, }, { about: "JSONEquals with bad type", checker: qt.JSONEquals, got: 0, args: []interface{}{nil}, expectedCheckFailure: ` error: bad check: expected string or byte, got int `, expectedNegateFailure: ` error: bad check: expected string or byte, got int `, }, { about: "CodecEquals with bad marshal", checker: qt.CodecEquals( func(x interface{}) ([]byte, error) { return []byte("bad json"), nil }, json.Unmarshal, ), got: "null", args: []interface{}{nil}, expectedCheckFailure: ` error: bad check: cannot unmarshal expected contents: invalid character 'b' looking for beginning of value `, expectedNegateFailure: ` error: bad check: cannot unmarshal expected contents: invalid character 'b' looking for beginning of value `, }, { about: "CodecEquals with options", checker: qt.CodecEquals( json.Marshal, json.Unmarshal, cmpopts.SortSlices(func(x, y interface{}) bool { return x.(string) < y.(string) }), ), got: `["b", "z", "c", "a"]`, args: []interface{}{[]string{"a", "c", "z", "b"}}, expectedNegateFailure: tilde2bq(` error: unexpected success got: ~["b", "z", "c", "a"]~ want: []string{"a", "c", "z", "b"} `), }} type jsonErrorMarshaler struct{} func (jsonErrorMarshaler) MarshalJSON() ([]byte, error) { return nil, fmt.Errorf("qt json marshal error") } func TestCheckers(t *testing.T) { for _, test := range checkerTests { checker := qt.WithVerbosity(test.checker, test.verbose) t.Run(test.about, func(t *testing.T) { tt := &testingT{} c := qt.New(tt) ok := c.Check(test.got, checker, test.args...) checkResult(t, ok, tt.errorString(), test.expectedCheckFailure) }) t.Run("Not "+test.about, func(t *testing.T) { tt := &testingT{} c := qt.New(tt) ok := c.Check(test.got, qt.Not(checker), test.args...) checkResult(t, ok, tt.errorString(), test.expectedNegateFailure) }) } } func diff(x, y interface{}, opts ...cmp.Option) string { d := cmp.Diff(x, y, opts...) return strings.TrimSuffix(qt.Prefixf(" ", "%s", d), "\n") } func tilde2bq(s string) string { return strings.Replace(s, "~", "`", -1) } quicktest-1.6.0/cleanup_test.go000066400000000000000000000023051356056600300165530ustar00rootroot00000000000000// +build go1.14 package quicktest_test import ( "testing" qt "github.com/frankban/quicktest" ) // This file defines tests that are only valid since the Cleanup // method was added in Go 1.14. func TestCCleanup(t *testing.T) { c := qt.New(t) cleanups := 0 c.Run("defer", func(c *qt.C) { c.Cleanup(func() { cleanups++ }) }) c.Assert(cleanups, qt.Equals, 1) } func TestCDeferWithoutDone(t *testing.T) { c := qt.New(t) tc := &testingTWithCleanup{ TB: t, cleanup: func() {}, } c1 := qt.New(tc) c1.Defer(func() {}) c1.Defer(func() {}) c.Assert(tc.cleanup, qt.PanicMatches, `Done not called after Defer`) } func TestCDeferVsCleanupOrder(t *testing.T) { c := qt.New(t) var defers []int testDefer(c, func(c *qt.C) { c.Defer(func() { defers = append(defers, 0) }) c.Cleanup(func() { defers = append(defers, 1) }) c.Defer(func() { defers = append(defers, 2) }) c.Cleanup(func() { defers = append(defers, 3) }) }) c.Assert(defers, qt.DeepEquals, []int{3, 2, 1, 0}) } type testingTWithCleanup struct { testing.TB cleanup func() } func (t *testingTWithCleanup) Cleanup(f func()) { oldCleanup := t.cleanup t.cleanup = func() { defer oldCleanup() f() } } quicktest-1.6.0/comment.go000066400000000000000000000016141356056600300155310ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import "fmt" // Commentf returns a test comment whose output is formatted according to // the given format specifier and args. It may be provided as the last argument // to any check or assertion and will be displayed if the check or assertion // fails. For instance: // // c.Assert(a, qt.Equals, 42, qt.Commentf("answer is not %d", 42)) // func Commentf(format string, args ...interface{}) Comment { return Comment{ format: format, args: args, } } // Comment represents additional information on a check or an assertion which is // displayed when the check or assertion fails. type Comment struct { format string args []interface{} } // String outputs a string formatted according to the stored format specifier // and args. func (c Comment) String() string { return fmt.Sprintf(c.format, c.args...) } quicktest-1.6.0/comment_test.go000066400000000000000000000012201356056600300165610ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "testing" qt "github.com/frankban/quicktest" ) func TestCommentf(t *testing.T) { c := qt.Commentf("the answer is %d", 42) comment := c.String() expectedComment := "the answer is 42" if comment != expectedComment { t.Fatalf("comment error:\ngot %q\nwant %q", comment, expectedComment) } } func TestConstantCommentf(t *testing.T) { const expectedComment = "bad wolf" c := qt.Commentf(expectedComment) comment := c.String() if comment != expectedComment { t.Fatalf("constant comment error:\ngot %q\nwant %q", comment, expectedComment) } } quicktest-1.6.0/deferpanic_test.go000066400000000000000000000014121356056600300172220ustar00rootroot00000000000000// +build !go1.14 package quicktest_test import ( "testing" qt "github.com/frankban/quicktest" ) func TestCDeferCalledEvenAfterDeferPanic(t *testing.T) { // This test doesn't test anything useful under go 1.14 and // later when Cleanup is built in. c := qt.New(t) deferred1 := 0 deferred2 := 0 c.Defer(func() { deferred1++ }) c.Defer(func() { panic("scream and shout") }) c.Defer(func() { deferred2++ }) c.Defer(func() { panic("run in circles") }) func() { defer func() { c.Check(recover(), qt.Equals, "scream and shout") }() c.Done() }() c.Assert(deferred1, qt.Equals, 1) c.Assert(deferred2, qt.Equals, 1) // Check that calling Done again doesn't panic. c.Done() c.Assert(deferred1, qt.Equals, 1) c.Assert(deferred2, qt.Equals, 1) } quicktest-1.6.0/doc.go000066400000000000000000000157201356056600300146370ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. /* Package quicktest provides a collection of Go helpers for writing tests. Quicktest helpers can be easily integrated inside regular Go tests, for instance: import qt "github.com/frankban/quicktest" func TestFoo(t *testing.T) { t.Run("numbers", func(t *testing.T) { c := qt.New(t) numbers, err := somepackage.Numbers() c.Assert(numbers, qt.DeepEquals, []int{42, 47}) c.Assert(err, qt.ErrorMatches, "bad wolf") }) t.Run("nil", func(t *testing.T) { c := qt.New(t) got := somepackage.MaybeNil() c.Assert(got, qt.IsNil, qt.Commentf("value: %v", somepackage.Value)) }) } Deferred execution Quicktest provides the ability to defer the execution of functions that will be run when the test completes. This is often useful for creating OS-level resources such as temporary directories (see c.Mkdir). The functions will be run in last-in, first-out order. func (c *C) Defer(f func()) To trigger the deferred behavior, call c.Done: func (c *C) Done() If you create a *C instance at the top level, you’ll have to add a defer to trigger the cleanups at the end of the test: defer c.Done() However, if you use quicktest to create a subtest, Done will be called automatically at the end of that subtest. For example: func TestFoo(t *testing.T) { c := qt.New(t) c.Run("subtest", func(c *qt.C) { c.Setenv("HOME", c.Mkdir()) // Here $HOME is set the path to a newly created directory. // At the end of the test the directory will be removed // and HOME set back to its original value. }) } Assertions An assertion looks like this, where qt.Equals could be replaced by any available checker. If the assertion fails, the underlying Fatal method is called to describe the error and abort the test. c.Assert(someValue, qt.Equals, wantValue) If you don’t want to abort on failure, use Check instead, which calls Error instead of Fatal: c.Check(someValue, qt.Equals, wantValue) The library provides some base checkers like Equals, DeepEquals, Matches, ErrorMatches, IsNil and others. More can be added by implementing the Checker interface. Here is a list of checkers already included in the package. Equals Equals checks that two values are equal, as compared with Go's == operator. For instance: c.Assert(answer, qt.Equals, 42) Note that the following will fail: c.Assert((*sometype)(nil), qt.Equals, nil) Use the IsNil checker below for this kind of nil check. DeepEquals DeepEquals checks that two arbitrary values are deeply equal. The comparison is done using the github.com/google/go-cmp/cmp package. When comparing structs, by default no exported fields are allowed. If a more sophisticated comparison is required, use CmpEquals (see below). Example call: c.Assert(got, qt.DeepEquals, []int{42, 47}) CmpEquals CmpEquals checks equality of two arbitrary values according to the provided compare options. DeepEquals is more commonly used when no compare options are required. Example calls: c.Assert(list, qt.CmpEquals(cmpopts.SortSlices), []int{42, 47}) c.Assert(got, qt.CmpEquals(), []int{42, 47}) // Same as qt.DeepEquals. Matches Matches checks that a string or result of calling the String method (if the value implements fmt.Stringer) matches the provided regular expression. For instance: c.Assert("these are the voyages", qt.Matches, `these are .*`) c.Assert(net.ParseIP("1.2.3.4"), qt.Matches, `1.*`) ErrorMatches ErrorMatches checks that the provided value is an error whose message matches the provided regular expression. For instance: c.Assert(err, qt.ErrorMatches, `bad wolf .*`) PanicMatches PanicMatches checks that the provided function panics with a message matching the provided regular expression. For instance: c.Assert(func() {panic("bad wolf ...")}, qt.PanicMatches, `bad wolf .*`) IsNil IsNil checks that the provided value is nil. For instance: c.Assert(got, qt.IsNil) HasLen HasLen checks that the provided value has the given length. For instance: c.Assert([]int{42, 47}, qt.HasLen, 2) c.Assert(myMap, qt.HasLen, 42) Satisfies Satisfies checks that the provided value, when used as argument of the provided predicate function, causes the function to return true. The function must be of type func(T) bool, having got assignable to T. For instance: // Check that an error from os.Open satisfies os.IsNotExist. c.Assert(err, qt.Satisfies, os.IsNotExist) // Check that a floating point number is a not-a-number. c.Assert(f, qt.Satisfies, math.IsNaN) Not Not returns a Checker negating the given Checker. For instance: c.Assert(got, qt.Not(qt.IsNil)) c.Assert(answer, qt.Not(qt.Equals), 42) Contains Contains checks that a map, slice, array or string contains a value. It's the same as using Any(Equals), except that it has a special case for strings - if the first argument is a string, the second argument must also be a string and strings.Contains will be used. For example: c.Assert("hello world", qt.Contains, "world") c.Assert([]int{3,5,7,99}, qt.Contains, 7) Any Any returns a Checker that uses the given checker to check elements of a slice or array or the values from a map. It succeeds if any element passes the check. For example: c.Assert([]int{3,5,7,99}, qt.Any(qt.Equals), 7) c.Assert([][]string{{"a", "b"}, {"c", "d"}}, qt.Any(qt.DeepEquals), []string{"c", "d"}) See also All and Contains. All All returns a Checker that uses the given checker to check elements of slice or array or the values of a map. It succeeds if all elements pass the check. On failure it prints the error from the first index that failed. For example: c.Assert([]int{3, 5, 8}, qt.All(qt.Not(qt.Equals)), 0) c.Assert([][]string{{"a", "b"}, {"a", "b"}}, qt.All(qt.DeepEquals), []string{"c", "d"}) See also Any and Contains. JSONEquals JSONEquals checks whether a byte slice or string is JSON-equivalent to a Go value. See CodecEquals for more information. It uses DeepEquals to do the comparison. If a more sophisticated comparison is required, use CodecEquals directly. For instance: c.Assert(`{"First": 47.11}`, qt.JSONEquals, &MyStruct{First: 47.11}) CodecEquals CodecEquals returns a checker that checks for codec value equivalence. func CodecEquals( marshal func(interface{}) ([]byte, error), unmarshal func([]byte, interface{}) error, opts ...cmp.Option, ) Checker It expects two arguments: a byte slice or a string containing some codec-marshaled data, and a Go value. It uses unmarshal to unmarshal the data into an interface{} value. It marshals the Go value using marshal, then unmarshals the result into an interface{} value. It then checks that the two interface{} values are deep-equal to one another, using CmpEquals(opts) to perform the check. See JSONEquals for an example of this in use. */ package quicktest quicktest-1.6.0/error.go000066400000000000000000000020711356056600300152160ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "fmt" ) // BadCheckf returns an error used to report a problem with the checker // invocation or testing execution itself (like wrong number or type of // arguments) rather than a real Check or Assert failure. // This helper can be used when implementing checkers. func BadCheckf(format string, a ...interface{}) error { e := badCheck(fmt.Sprintf(format, a...)) return &e } // IsBadCheck reports whether the given error has been created by BadCheckf. // This helper can be used when implementing checkers. func IsBadCheck(err error) bool { _, ok := err.(*badCheck) return ok } type badCheck string // Error implements the error interface. func (e *badCheck) Error() string { return "bad check: " + string(*e) } // ErrSilent is the error used when there is no need to include in the failure // output the "error" and "check" keys and all the keys automatically // added for args. This helper can be used when implementing checkers. var ErrSilent = fmt.Errorf("silent failure") quicktest-1.6.0/error_test.go000066400000000000000000000021071356056600300162550ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "errors" "fmt" "testing" qt "github.com/frankban/quicktest" ) func TestBadCheckf(t *testing.T) { err := qt.BadCheckf("bad %s", "wolf") expectedMessage := "bad check: bad wolf" if err.Error() != expectedMessage { t.Fatalf("error:\ngot %q\nwant %q", err, expectedMessage) } } func TestIsBadCheck(t *testing.T) { err := qt.BadCheckf("bad wolf") assertBool(t, qt.IsBadCheck(err), true) err = errors.New("bad wolf") assertBool(t, qt.IsBadCheck(err), false) } var errBadWolf = &errTest{ msg: "bad wolf", formatted: true, } // errTest is an error type used in tests. type errTest struct { msg string formatted bool } // Error implements error. func (err *errTest) Error() string { return err.msg } // Format implements fmt.Formatter. func (err *errTest) Format(f fmt.State, c rune) { if !f.Flag('+') || c != 'v' { fmt.Fprint(f, "unexpected verb for formatting the error") } fmt.Fprint(f, err.Error()) if err.formatted { fmt.Fprint(f, "\n file:line") } } quicktest-1.6.0/export_test.go000066400000000000000000000013351356056600300164470ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest var Prefixf = prefixf // WithVerbosity returns the given checker with a verbosity level of v. // A copy of the original checker is made if mutating is required. func WithVerbosity(c Checker, v bool) Checker { switch checker := c.(type) { case *allChecker: c := *checker c.elemChecker = WithVerbosity(c.elemChecker, v) return &c case *anyChecker: c := *checker c.elemChecker = WithVerbosity(c.elemChecker, v) return &c case *cmpEqualsChecker: c := *checker c.verbose = func() bool { return v } return &c case *codecEqualChecker: c := *checker c.deepEquals = WithVerbosity(c.deepEquals, v) return &c } return c } quicktest-1.6.0/format.go000066400000000000000000000031771356056600300153650ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "fmt" "reflect" "strconv" "strings" "github.com/kr/pretty" ) // Format formats the given value as a string. It is used to print values in // test failures unless that's changed by calling C.SetFormat. func Format(v interface{}) string { switch v := v.(type) { case error: s, ok := checkStringCall(v, v.Error) if !ok { return "e" } if msg := fmt.Sprintf("%+v", v); msg != s { // The error has formatted itself with additional information. // Leave that as is. return msg } return "e" + quoteString(s) case fmt.Stringer: s, ok := checkStringCall(v, v.String) if !ok { return "s" } return "s" + quoteString(s) case string: return quoteString(v) } // The pretty.Sprint equivalent does not quote string values. return fmt.Sprintf("%# v", pretty.Formatter(v)) } func quoteString(s string) string { // TODO think more about what to do about multi-line strings. if strings.Contains(s, `"`) && !strings.Contains(s, "\n") && strconv.CanBackquote(s) { return "`" + s + "`" } return strconv.Quote(s) } // checkStringCall calls f and returns its result, and reports if the call // succeeded without panicking due to a nil pointer. // If f panics and v is a nil pointer, it returns false. func checkStringCall(v interface{}, f func() string) (s string, ok bool) { defer func() { err := recover() if err == nil { return } if val := reflect.ValueOf(v); val.Kind() == reflect.Ptr && val.IsNil() { ok = false return } panic(err) }() return f(), true } type formatFunc func(interface{}) string quicktest-1.6.0/format_test.go000066400000000000000000000042121356056600300164130ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "bytes" "testing" qt "github.com/frankban/quicktest" ) var formatTests = []struct { about string value interface{} want string }{{ about: "error value", value: errBadWolf, want: "bad wolf\n file:line", }, { about: "error value: not formatted", value: &errTest{ msg: "exterminate!", }, want: `e"exterminate!"`, }, { about: "error value: with quotes", value: &errTest{ msg: `cannot open "/no/such/file"`, }, want: "e`cannot open \"/no/such/file\"`", }, { about: "error value: multi-line", value: &errTest{ msg: `err: "these are the voyages"`, }, want: `e"err:\n\"these are the voyages\""`, }, { about: "error value: with backquotes", value: &errTest{ msg: "cannot `open` \"file\"", }, want: `e"cannot ` + "`open`" + ` \"file\""`, }, { about: "error value: not guarding against nil", value: (*errTest)(nil), want: `e`, }, { about: "stringer", value: bytes.NewBufferString("I am a stringer"), want: `s"I am a stringer"`, }, { about: "stringer: with quotes", value: bytes.NewBufferString(`I say "hello"`), want: "s`I say \"hello\"`", }, { about: "stringer: not guarding against nil", value: (*nilStringer)(nil), want: "s", }, { about: "string", value: "these are the voyages", want: `"these are the voyages"`, }, { about: "string: with quotes", value: `here is a quote: "`, want: "`here is a quote: \"`", }, { about: "string: multi-line", value: `foo "bar" `, want: `"foo\n\"bar\"\n"`, }, { about: "string: with backquotes", value: `"` + "`", want: `"\"` + "`\"", }, { about: "slice", value: []int{1, 2, 3}, want: "[]int{1, 2, 3}", }, { about: "time", value: goTime, want: `s"2012-03-28 00:00:00 +0000 UTC"`, }} func TestFormat(t *testing.T) { for _, test := range formatTests { t.Run(test.about, func(t *testing.T) { got := qt.Format(test.value) if got != test.want { t.Fatalf("format:\ngot %q\nwant %q", got, test.want) } }) } } // nilStringer is a stringer not guarding against nil. type nilStringer struct { msg string } func (s *nilStringer) String() string { return s.msg } quicktest-1.6.0/go.mod000066400000000000000000000001711356056600300146430ustar00rootroot00000000000000module github.com/frankban/quicktest require ( github.com/google/go-cmp v0.3.1 github.com/kr/pretty v0.1.0 ) go 1.13 quicktest-1.6.0/go.sum000066400000000000000000000010611356056600300146670ustar00rootroot00000000000000github.com/google/go-cmp v0.3.1 h1:Xye71clBPdm5HgqGwUkwhbynsUJZhDbS20FvLhQ2izg= github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= quicktest-1.6.0/iter.go000066400000000000000000000022161356056600300150310ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "fmt" "reflect" ) // containerIter provides an interface for iterating over a container // (map, slice or array). type containerIter interface { // next advances to the next item in the container. next() bool // key returns the current key as a string. key() string // value returns the current value. value() reflect.Value } // newIter returns an iterator over x which must be a map, slice // or array. func newIter(x interface{}) (containerIter, error) { v := reflect.ValueOf(x) switch v.Kind() { case reflect.Map: return newMapIter(v), nil case reflect.Slice, reflect.Array: return &sliceIter{ index: -1, v: v, }, nil default: return nil, fmt.Errorf("map, slice or array required") } } // sliceIter implements containerIter for slices and arrays. type sliceIter struct { v reflect.Value index int } func (i *sliceIter) next() bool { i.index++ return i.index < i.v.Len() } func (i *sliceIter) value() reflect.Value { return i.v.Index(i.index) } func (i *sliceIter) key() string { return fmt.Sprintf("index %d", i.index) } quicktest-1.6.0/mapiter.go000066400000000000000000000007771356056600300155410ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. // +build go1.12 package quicktest import ( "fmt" "reflect" ) func newMapIter(v reflect.Value) containerIter { return mapIter{v.MapRange()} } // mapIter implements containerIter for maps. type mapIter struct { iter *reflect.MapIter } func (i mapIter) next() bool { return i.iter.Next() } func (i mapIter) key() string { return fmt.Sprintf("key %#v", i.iter.Key()) } func (i mapIter) value() reflect.Value { return i.iter.Value() } quicktest-1.6.0/mapiter_go1.11.go000066400000000000000000000015611356056600300165170ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. // +build !go1.12 package quicktest import ( "fmt" "reflect" ) func newMapIter(v reflect.Value) containerIter { return &mapIter{ v: v, keys: v.MapKeys(), index: -1, } } // mapIter implements containerIter for maps prior to the // introduction of reflect.Value.MapRange in Go 1.12. type mapIter struct { v reflect.Value keys []reflect.Value index int } func (i *mapIter) next() bool { i.index++ return i.index < len(i.keys) } func (i *mapIter) value() reflect.Value { v := i.v.MapIndex(i.keys[i.index]) if !v.IsValid() { // We've probably got a NaN key; we can't // get NaN keys from maps with reflect, // so just return the zero value. return reflect.Zero(i.v.Type().Elem()) } return v } func (i *mapIter) key() string { return fmt.Sprintf("key %#v", i.keys[i.index]) } quicktest-1.6.0/patch.go000066400000000000000000000030461356056600300151670ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "io/ioutil" "os" "reflect" ) // Patch sets a variable to a temporary value for the duration of the // test (until c.Done is called). // // It sets the value pointed to by the given destination to the given // value, which must be assignable to the element type of the // destination. // // When c.Done is called, the destination is set to its original // value. func (c *C) Patch(dest, value interface{}) { destv := reflect.ValueOf(dest).Elem() oldv := reflect.New(destv.Type()).Elem() oldv.Set(destv) valuev := reflect.ValueOf(value) if !valuev.IsValid() { // This isn't quite right when the destination type is not // nilable, but it's better than the complex alternative. valuev = reflect.Zero(destv.Type()) } destv.Set(valuev) c.Defer(func() { destv.Set(oldv) }) } // Setenv sets an environment variable to a temporary value for the // duration of the test (until c.Done is called). // // When c.Done is called, the environment variable will be returned // to its original value. func (c *C) Setenv(name, val string) { oldVal := os.Getenv(name) os.Setenv(name, val) c.Defer(func() { os.Setenv(name, oldVal) }) } // Mkdir makes a temporary directory and returns its name. // // The directory and its contents will be removed when // c.Done is called. func (c *C) Mkdir() string { name, err := ioutil.TempDir("", "quicktest-") c.Assert(err, Equals, nil) c.Defer(func() { err := os.RemoveAll(name) c.Check(err, Equals, nil) }) return name } quicktest-1.6.0/patch_test.go000066400000000000000000000037401356056600300162270ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "errors" "os" "path/filepath" "testing" qt "github.com/frankban/quicktest" ) func TestPatchSetInt(t *testing.T) { c := qt.New(t) i := 99 testDefer(c, func(c *qt.C) { c.Patch(&i, 88) c.Assert(i, qt.Equals, 88) }) c.Assert(i, qt.Equals, 99) } func TestPatchSetError(t *testing.T) { c := qt.New(t) oldErr := errors.New("foo") newErr := errors.New("bar") err := oldErr testDefer(c, func(c *qt.C) { c.Patch(&err, newErr) c.Assert(err, qt.Equals, newErr) }) c.Assert(err, qt.Equals, oldErr) } func TestPatchSetErrorToNil(t *testing.T) { c := qt.New(t) oldErr := errors.New("foo") err := oldErr testDefer(c, func(c *qt.C) { c.Patch(&err, nil) c.Assert(err, qt.Equals, nil) }) c.Assert(err, qt.Equals, oldErr) } func TestPatchSetMapToNil(t *testing.T) { c := qt.New(t) oldMap := map[string]int{"foo": 1234} m := oldMap testDefer(c, func(c *qt.C) { c.Patch(&m, nil) c.Assert(m, qt.IsNil) }) c.Assert(m, qt.DeepEquals, oldMap) } func TestSetPatchPanicsWhenNotAssignable(t *testing.T) { c := qt.New(t) i := 99 type otherInt int c.Assert(func() { c.Patch(&i, otherInt(88)) }, qt.PanicMatches, `reflect\.Set: value of type quicktest_test\.otherInt is not assignable to type int`) } func TestSetenv(t *testing.T) { c := qt.New(t) const envName = "SOME_VAR" os.Setenv(envName, "initial") testDefer(c, func(c *qt.C) { c.Setenv(envName, "new value") c.Check(os.Getenv(envName), qt.Equals, "new value") }) c.Check(os.Getenv(envName), qt.Equals, "initial") } func TestMkdir(t *testing.T) { c := qt.New(t) var dir string testDefer(c, func(c *qt.C) { dir = c.Mkdir() c.Assert(dir, qt.Not(qt.Equals), "") info, err := os.Stat(dir) c.Assert(err, qt.Equals, nil) c.Assert(info.IsDir(), qt.Equals, true) f, err := os.Create(filepath.Join(dir, "hello")) c.Assert(err, qt.Equals, nil) f.Close() }) _, err := os.Stat(dir) c.Assert(err, qt.Not(qt.IsNil)) } quicktest-1.6.0/qtsuite/000077500000000000000000000000001356056600300152345ustar00rootroot00000000000000quicktest-1.6.0/qtsuite/suite.go000066400000000000000000000060641356056600300167220ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. /* Package qtsuite allows quicktest to run test suites. A test suite is a value with one or more test methods. For example, the following code defines a suite of test functions that starts an HTTP server before running each test, and tears it down afterwards: type suite struct { url string } func (s *suite) Init(c *qt.C) { hnd := func(w http.ResponseWriter, req *http.Request) { fmt.Fprintf(w, "%s %s", req.Method, req.URL.Path) } srv := httptest.NewServer(http.HandlerFunc(hnd)) c.Defer(srv.Close) s.url = srv.URL } func (s *suite) TestGet(c *qt.C) { c.Parallel() resp, err := http.Get(s.url) c.Assert(err, qt.Equals, nil) defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) c.Assert(err, qt.Equals, nil) c.Assert(string(b), qt.Equals, "GET /") } func (s *suite) TestHead(c *qt.C) { c.Parallel() resp, err := http.Head(s.url + "/path") c.Assert(err, qt.Equals, nil) defer resp.Body.Close() b, err := ioutil.ReadAll(resp.Body) c.Assert(err, qt.Equals, nil) c.Assert(string(b), qt.Equals, "") c.Assert(resp.ContentLength, qt.Equals, int64(10)) } The above code could be invoked from a test function like this: func TestHTTPMethods(t *testing.T) { qtsuite.Run(qt.New(t), &suite{"http://example.com"}) } */ package qtsuite import ( "reflect" "strings" "unicode" "unicode/utf8" qt "github.com/frankban/quicktest" ) // Run runs each test method defined on the given value as a separate // subtest. A test is a method of the form // func (T) TestXxx(*quicktest.C) // where Xxx does not start with a lowercase letter. // // If suite is a pointer, the value pointed to is copied before any // methods are invoked on it; a new copy is made for each test. This // means that it is OK for tests to modify fields in suite concurrently // if desired - it's OK to call c.Parallel(). // // If suite has a method of the form // func (T) Init(*quicktest.C) // this method will be invoked before each test run. func Run(c *qt.C, suite interface{}) { sv := reflect.ValueOf(suite) st := sv.Type() init, hasInit := st.MethodByName("Init") if hasInit && !isValidMethod(init) { c.Fatal("wrong signature for Init, must be Init(*quicktest.C)") } for i := 0; i < st.NumMethod(); i++ { m := st.Method(i) if !isTestMethod(m) { continue } c.Run(m.Name, func(c *qt.C) { if !isValidMethod(m) { c.Fatalf("wrong signature for %s, must be %s(*quicktest.C)", m.Name, m.Name) } sv := sv if st.Kind() == reflect.Ptr { sv1 := reflect.New(st.Elem()) sv1.Elem().Set(sv.Elem()) sv = sv1 } args := []reflect.Value{sv, reflect.ValueOf(c)} if hasInit { init.Func.Call(args) } m.Func.Call(args) }) } } var cType = reflect.TypeOf(&qt.C{}) func isTestMethod(m reflect.Method) bool { if !strings.HasPrefix(m.Name, "Test") { return false } r, n := utf8.DecodeRuneInString(m.Name[4:]) return n == 0 || !unicode.IsLower(r) } func isValidMethod(m reflect.Method) bool { return m.Type.NumIn() == 2 && m.Type.NumOut() == 0 && m.Type.In(1) == cType } quicktest-1.6.0/qtsuite/suite_test.go000066400000000000000000000057171356056600300177650ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package qtsuite_test import ( "bytes" "fmt" "testing" qt "github.com/frankban/quicktest" "github.com/frankban/quicktest/qtsuite" ) func TestRunSuite(t *testing.T) { c := qt.New(t) var calls []call tt := &testingT{} qtsuite.Run(qt.New(tt), testSuite{calls: &calls}) c.Assert(calls, qt.DeepEquals, []call{ {"Test1", 0}, {"Test4", 0}, }) } func TestRunSuiteEmbedded(t *testing.T) { c := qt.New(t) var calls []call tt := &testingT{} suite := struct { testSuite }{testSuite: testSuite{calls: &calls}} qtsuite.Run(qt.New(tt), suite) c.Assert(calls, qt.DeepEquals, []call{ {"Test1", 0}, {"Test4", 0}, }) } func TestRunSuitePtr(t *testing.T) { c := qt.New(t) var calls []call tt := &testingT{} qtsuite.Run(qt.New(tt), &testSuite{calls: &calls}) c.Assert(calls, qt.DeepEquals, []call{ {"Init", 0}, {"Test1", 1}, {"Init", 0}, {"Test4", 1}, }) } type testSuite struct { init int calls *[]call } func (s testSuite) addCall(name string) { *s.calls = append(*s.calls, call{Name: name, Init: s.init}) } func (s *testSuite) Init(*qt.C) { s.addCall("Init") s.init++ } func (s testSuite) Test1(*qt.C) { s.addCall("Test1") } func (s testSuite) Test2() { s.addCall("Test2") } func (s testSuite) Test3(*testing.T) { s.addCall("Test3") } func (s testSuite) Test4(*qt.C) { s.addCall("Test4") } func (s testSuite) Test5(*qt.C) bool { s.addCall("Test5") return false } func (s testSuite) Testa(*qt.C) { s.addCall("Testa") } type call struct { Name string Init int } func TestInvalidInit(t *testing.T) { c := qt.New(t) tt := &testingT{} tc := qt.New(tt) qtsuite.Run(tc, invalidTestSuite{}) c.Assert(tt.fatalString(), qt.Equals, "wrong signature for Init, must be Init(*quicktest.C)") } type invalidTestSuite struct{} func (invalidTestSuite) Init() {} // testingT can be passed to qt.New for testing purposes. type testingT struct { testing.TB errorBuf bytes.Buffer fatalBuf bytes.Buffer subTestResult bool subTestName string subTestT *testing.T } // Error overrides *testing.T.Error so that messages are collected. func (t *testingT) Error(a ...interface{}) { fmt.Fprint(&t.errorBuf, a...) } // Fatal overrides *testing.T.Fatal so that messages are collected and the // goroutine is not killed. func (t *testingT) Fatal(a ...interface{}) { fmt.Fprint(&t.fatalBuf, a...) } // Run overrides *testing.T.Run. func (t *testingT) Run(name string, f func(t *testing.T)) bool { t.subTestName, t.subTestT = name, &testing.T{} ch := make(chan struct{}) // Run the subtest in its own goroutine so that if it calls runtime.GoExit, // we can still return appropriately. go func() { defer close(ch) f(t.subTestT) }() <-ch return t.subTestResult } // errorString returns the error message. func (t *testingT) errorString() string { return t.errorBuf.String() } // fatalString returns the fatal error message. func (t *testingT) fatalString() string { return t.fatalBuf.String() } quicktest-1.6.0/quicktest.go000066400000000000000000000214141356056600300161030ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "fmt" "reflect" "strings" "sync" "testing" ) // New returns a new checker instance that uses t to fail the test when checks // fail. It only ever calls the Fatal, Error and (when available) Run methods // of t. For instance. // // func TestFoo(t *testing.T) { // t.Run("A=42", func(t *testing.T) { // c := qt.New(t) // c.Assert(a, qt.Equals, 42) // }) // } // // The library already provides some base checkers, and more can be added by // implementing the Checker interface. // // If there is a likelihood that Defer will be called, then // a call to Done should be deferred after calling New. // For example: // // func TestFoo(t *testing.T) { // c := qt.New(t) // defer c.Done() // c.Setenv("HOME", "/non-existent") // c.Assert(os.Getenv("HOME"), qt.Equals, "/non-existent") // }) // // A value of C that's has a non-nil TB field but is otherwise zero is valid. // So: // // c := &qt.C{TB: t} // // is valid a way to create a C value; it's exactly the same as: // // c := qt.New(t) // // Methods on C may be called concurrently, assuming the underlying // `testing.TB` implementation also allows that. func New(t testing.TB) *C { return &C{ TB: t, } } // C is a quicktest checker. It embeds a testing.TB value and provides // additional checking functionality. If an Assert or Check operation fails, it // uses the wrapped TB value to fail the test appropriately. type C struct { testing.TB mu sync.Mutex doneNeeded bool deferred func() format formatFunc } // cleaner is implemented by testing.TB on Go 1.14 and later. type cleaner interface { Cleanup(func()) } // Defer registers a function to be called when c.Done is // called. Deferred functions will be called in last added, first called // order. If c.Done is not called by the end of the test, the test // may panic. Note that if Cleanup is called, there is no // need to call Done. func (c *C) Defer(f func()) { c.mu.Lock() defer c.mu.Unlock() if cleaner, ok := c.TB.(cleaner); ok { // Use TB.Cleanup when available, but add a check // that Done has been called so that we don't run // into unexpected Go version incompatibilities. if c.doneNeeded { // We've already installed the wrapper func that checks for Done // so we can avoid doing it again. cleaner.Cleanup(f) return } c.doneNeeded = true cleaner.Cleanup(func() { c.mu.Lock() doneNeeded := c.doneNeeded c.mu.Unlock() if doneNeeded { panic("Done not called after Defer") } f() }) return } oldDeferred := c.deferred c.deferred = func() { if oldDeferred != nil { defer oldDeferred() } f() } } // Done calls all the functions registered by Defer in reverse // registration order. After it's called, the functions are // unregistered, so calling Done twice will only call them once. // // When a test function is called by Run, Done will be called // automatically on the C value passed into it. func (c *C) Done() { c.mu.Lock() deferred := c.deferred c.deferred = nil c.doneNeeded = false c.mu.Unlock() if deferred != nil { deferred() } } // SetFormat sets the function used to print values in test failures. // By default Format is used. // Any subsequent subtests invoked with c.Run will also use this function by // default. func (c *C) SetFormat(format func(interface{}) string) { c.mu.Lock() c.format = format c.mu.Unlock() } // getFormat returns the format function // safely acquired under lock. func (c *C) getFormat() func(interface{}) string { c.mu.Lock() defer c.mu.Unlock() return c.format } // Check runs the given check and continues execution in case of failure. // For instance: // // c.Check(answer, qt.Equals, 42) // c.Check(got, qt.IsNil, qt.Commentf("iteration %d", i)) // // Additional args (not consumed by the checker), when provided, are included // as comments in the failure output when the check fails. func (c *C) Check(got interface{}, checker Checker, args ...interface{}) bool { return c.check(c.TB.Error, checker, got, args) } // Assert runs the given check and stops execution in case of failure. // For instance: // // c.Assert(got, qt.DeepEquals, []int{42, 47}) // c.Assert(got, qt.ErrorMatches, "bad wolf .*", qt.Commentf("a comment")) // // Additional args (not consumed by the checker), when provided, are included // as comments in the failure output when the check fails. func (c *C) Assert(got interface{}, checker Checker, args ...interface{}) bool { return c.check(c.TB.Fatal, checker, got, args) } var ( stringType = reflect.TypeOf("") boolType = reflect.TypeOf(true) tbType = reflect.TypeOf(new(testing.TB)).Elem() ) // Run runs f as a subtest of t called name. It's a wrapper around // the Run method of c.TB that provides the quicktest checker to f. When // the function completes, c.Done will be called to run any // functions registered with c.Defer. // // c.TB must implement a Run method of the following form: // // Run(string, func(T)) bool // // where T is any type that is assignable to testing.TB. // Implementations include *testing.T, *testing.B and *C itself. // // The TB field in the subtest will hold the value passed // by Run to its argument function. // // func TestFoo(t *testing.T) { // c := qt.New(t) // c.Run("A=42", func(c *qt.C) { // // This assertion only stops the current subtest. // c.Assert(a, qt.Equals, 42) // }) // } // // A panic is raised when Run is called and the embedded concrete type does not // implement a Run method with a correct signature. func (c *C) Run(name string, f func(c *C)) bool { badType := func(m string) { panic(fmt.Sprintf("cannot execute Run with underlying concrete type %T (%s)", c.TB, m)) } m := reflect.ValueOf(c.TB).MethodByName("Run") if !m.IsValid() { // c.TB doesn't implement a Run method. badType("no Run method") } mt := m.Type() if mt.NumIn() != 2 || mt.In(0) != stringType || mt.NumOut() != 1 || mt.Out(0) != boolType { // The Run method doesn't have the right argument counts and types. badType("wrong argument count for Run method") } farg := mt.In(1) if farg.Kind() != reflect.Func || farg.NumIn() != 1 || farg.NumOut() != 0 || !farg.In(0).AssignableTo(tbType) { // The first argument to the Run function arg isn't right. badType("bad first argument type for Run method") } fv := reflect.MakeFunc(farg, func(args []reflect.Value) []reflect.Value { c2 := New(args[0].Interface().(testing.TB)) defer c2.Done() c2.SetFormat(c.getFormat()) f(c2) return nil }) return m.Call([]reflect.Value{reflect.ValueOf(name), fv})[0].Interface().(bool) } // Parallel signals that this test is to be run in parallel with (and only with) other parallel tests. // It's a wrapper around *testing.T.Parallel. // // A panic is raised when Parallel is called and the embedded concrete type does not // implement Parallel, for instance if TB's concrete type is a benchmark. func (c *C) Parallel() { p, ok := c.TB.(interface { Parallel() }) if !ok { panic(fmt.Sprintf("cannot execute Parallel with underlying concrete type %T", c.TB)) } p.Parallel() } // check performs the actual check and calls the provided fail function in case // of failure. func (c *C) check(fail func(...interface{}), checker Checker, got interface{}, args []interface{}) bool { // Allow checkers to annotate messages. rp := reportParams{ got: got, args: args, format: c.getFormat(), } if rp.format == nil { // No format set; use the default: Format. rp.format = Format } note := func(key string, value interface{}) { rp.notes = append(rp.notes, note{ key: key, value: value, }) } // Ensure that we have a checker. if checker == nil { fail(report(BadCheckf("nil checker provided"), rp)) return false } // Extract a comment if it has been provided. rp.argNames = checker.ArgNames() wantNumArgs := len(rp.argNames) - 1 if len(args) > 0 { if comment, ok := args[len(args)-1].(Comment); ok { rp.comment = comment rp.args = args[:len(args)-1] } } // Validate that we have the correct number of arguments. if gotNumArgs := len(rp.args); gotNumArgs != wantNumArgs { if gotNumArgs > 0 { note("got args", rp.args) } if wantNumArgs > 0 { note("want args", Unquoted(strings.Join(rp.argNames[1:], ", "))) } var prefix string if gotNumArgs > wantNumArgs { prefix = "too many arguments provided to checker" } else { prefix = "not enough arguments provided to checker" } fail(report(BadCheckf("%s: got %d, want %d", prefix, gotNumArgs, wantNumArgs), rp)) return false } // Execute the check and report the failure if necessary. if err := checker.Check(got, args, note); err != nil { fail(report(err, rp)) return false } return true } quicktest-1.6.0/quicktest_test.go000066400000000000000000000355301356056600300171460ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "bytes" "errors" "fmt" "strings" "testing" qt "github.com/frankban/quicktest" ) var _ testing.TB = (*qt.C)(nil) var cTests = []struct { about string checker qt.Checker got interface{} args []interface{} format func(interface{}) string expectedFailure string }{{ about: "success", checker: qt.Equals, got: 42, args: []interface{}{42}, }, { about: "failure", checker: qt.Equals, got: "42", args: []interface{}{"47"}, expectedFailure: ` error: values are not equal got: "42" want: "47" `, }, { about: "failure with % signs", checker: qt.Equals, got: "42%x", args: []interface{}{"47%y"}, expectedFailure: ` error: values are not equal got: "42%x" want: "47%y" `, }, { about: "failure with comment", checker: qt.Equals, got: true, args: []interface{}{false, qt.Commentf("apparently %v != %v", true, false)}, expectedFailure: ` error: values are not equal comment: apparently true != false got: bool(true) want: bool(false) `, }, { about: "another failure with comment", checker: qt.IsNil, got: 42, args: []interface{}{qt.Commentf("bad wolf: %d", 42)}, expectedFailure: ` error: 42 is not nil comment: bad wolf: 42 got: int(42) `, }, { about: "failure with constant comment", checker: qt.IsNil, got: "something", args: []interface{}{qt.Commentf("these are the voyages")}, expectedFailure: ` error: "something" is not nil comment: these are the voyages got: "something" `, }, { about: "failure with empty comment", checker: qt.IsNil, got: 47, args: []interface{}{qt.Commentf("")}, expectedFailure: ` error: 47 is not nil got: int(47) `, }, { about: "nil checker", expectedFailure: ` error: bad check: nil checker provided `, }, { about: "not enough arguments", checker: qt.Equals, got: 42, args: []interface{}{}, expectedFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 want args: want `, }, { about: "not enough arguments with comment", checker: qt.DeepEquals, got: 42, args: []interface{}{qt.Commentf("test %d", 0)}, expectedFailure: ` error: bad check: not enough arguments provided to checker: got 0, want 1 comment: test 0 want args: want `, }, { about: "too many arguments", checker: qt.Matches, got: 42, args: []interface{}{42, 47}, expectedFailure: ` error: bad check: too many arguments provided to checker: got 2, want 1 got args: []interface {}{ int(42), int(47), } want args: regexp `, }, { about: "really too many arguments", checker: qt.DeepEquals, got: 42, args: []interface{}{42, 47, nil, "stop"}, expectedFailure: ` error: bad check: too many arguments provided to checker: got 4, want 1 got args: []interface {}{ int(42), int(47), nil, "stop", } want args: want `, }, { about: "too many arguments with comment", checker: qt.IsNil, got: 42, args: []interface{}{nil, qt.Commentf("these are the voyages")}, expectedFailure: ` error: bad check: too many arguments provided to checker: got 1, want 0 comment: these are the voyages got args: []interface {}{ nil, } `, }, { about: "many arguments and notes", checker: &testingChecker{ argNames: []string{"arg1", "arg2", "arg3"}, addNotes: func(note func(key string, value interface{})) { note("note1", "these") note("note2", qt.Unquoted("are")) note("note3", "the") note("note4", "voyages") note("note5", true) }, err: errors.New("bad wolf"), }, got: 42, args: []interface{}{"val2", "val3"}, expectedFailure: ` error: bad wolf note1: "these" note2: are note3: "the" note4: "voyages" note5: bool(true) arg1: int(42) arg2: "val2" arg3: "val3" `, }, { about: "many arguments and notes with the same value", checker: &testingChecker{ argNames: []string{"arg1", "arg2", "arg3", "arg4"}, addNotes: func(note func(key string, value interface{})) { note("note1", "value1") note("note2", []int{42}) note("note3", "value1") note("note4", nil) }, err: errors.New("bad wolf"), }, got: "value1", args: []interface{}{"value1", []int{42}, nil}, expectedFailure: ` error: bad wolf note1: "value1" note2: []int{42} note3: note4: nil arg1: arg2: arg3: arg4: `, }, { about: "many arguments and notes with custom format function", checker: &testingChecker{ argNames: []string{"arg1", "arg2", "arg3"}, addNotes: func(note func(key string, value interface{})) { note("note1", "these") note("note2", qt.Unquoted("are")) note("note3", "the") note("note4", "voyages") note("note5", true) }, err: errors.New("bad wolf"), }, got: 42, args: []interface{}{"val2", "val3"}, format: func(v interface{}) string { return fmt.Sprintf("bad wolf %v", v) }, expectedFailure: ` error: bad wolf note1: bad wolf these note2: are note3: bad wolf the note4: bad wolf voyages note5: bad wolf true arg1: bad wolf 42 arg2: bad wolf val2 arg3: bad wolf val3 `, }, { about: "bad check with notes", checker: &testingChecker{ argNames: []string{"got", "want"}, addNotes: func(note func(key string, value interface{})) { note("note", 42) }, err: qt.BadCheckf("bad wolf"), }, got: 42, args: []interface{}{"want"}, expectedFailure: ` error: bad check: bad wolf note: int(42) `, }, { about: "silent failure with notes", checker: &testingChecker{ argNames: []string{"got", "want"}, addNotes: func(note func(key string, value interface{})) { note("note1", "first note") note("note2", qt.Unquoted("second note")) }, err: qt.ErrSilent, }, got: 42, args: []interface{}{"want"}, expectedFailure: ` note1: "first note" note2: second note `, }} func TestCAssertCheck(t *testing.T) { for _, test := range cTests { t.Run("Check: "+test.about, func(t *testing.T) { tt := &testingT{} c := qt.New(tt) if test.format != nil { c.SetFormat(test.format) } ok := c.Check(test.got, test.checker, test.args...) checkResult(t, ok, tt.errorString(), test.expectedFailure) if tt.fatalString() != "" { t.Fatalf("no fatal messages expected, but got %q", tt.fatalString()) } }) t.Run("Assert: "+test.about, func(t *testing.T) { tt := &testingT{} c := qt.New(tt) if test.format != nil { c.SetFormat(test.format) } ok := c.Assert(test.got, test.checker, test.args...) checkResult(t, ok, tt.fatalString(), test.expectedFailure) if tt.errorString() != "" { t.Fatalf("no error messages expected, but got %q", tt.errorString()) } }) } } func TestCRunSuccess(t *testing.T) { tt := &testingT{} c := qt.New(tt) var run bool subTestName := "my test" ok := c.Run(subTestName, func(innerC *qt.C) { run = true if innerC == c { t.Fatal("subtest C: same instance provided") } if innerC.TB != tt.subTestT { t.Fatalf("subtest testing object: got %p, want %p", innerC.TB, tt.subTestT) } if tt.subTestName != subTestName { t.Fatalf("subtest name: got %q, want %q", tt.subTestName, subTestName) } }) assertBool(t, run, true) assertBool(t, ok, false) // Simulate a test success. tt.subTestResult = true ok = c.Run(subTestName, func(innerC *qt.C) {}) assertBool(t, ok, true) } func TestCRunOnBenchmark(t *testing.T) { called := false testing.Benchmark(func(b *testing.B) { c := qt.New(b) c.Run("c", func(c *qt.C) { b1, ok := c.TB.(*testing.B) if !ok { t.Errorf("c.TB is type %T not *testing.B", c.TB) return } if b1 == b { t.Errorf("c.TB hasn't been given a new B value") return } called = true }) }) if !called { t.Fatalf("sub-benchmark was never called") } } // wrongRun1 has Run method with wrong arg count. type wrongRun1 struct { testing.TB } func (wrongRun1) Run() {} // wrongRun2 has no Run method. type wrongRun2 struct { testing.TB } // wrongRun3 has Run method that takes a type not // assignable to testing.TB. type wrongRun3 struct { testing.TB } func (wrongRun3) Run(string, func(string)) bool { return false } // wrongRun4 has Run method that doesn't return bool. type wrongRun4 struct { testing.TB } func (wrongRun4) Run(string, func(*testing.T)) {} var CRunPanicTests = []struct { tb testing.TB expectPanic string }{{ tb: wrongRun1{}, expectPanic: "wrong argument count for Run method", }, { tb: wrongRun2{}, expectPanic: "no Run method", }, { tb: wrongRun3{}, expectPanic: "bad first argument type for Run method", }, { tb: wrongRun4{}, expectPanic: "wrong argument count for Run method", }} func TestCRunPanic(t *testing.T) { for _, test := range CRunPanicTests { t.Run(fmt.Sprintf("%T", test.tb), func(t *testing.T) { c := qt.New(test.tb) defer func() { got := recover() want := fmt.Sprintf( "cannot execute Run with underlying concrete type %T (%s)", test.tb, test.expectPanic, ) if got != want { t.Fatalf("unexpected panic recover message; got %q want %q", got, want) } }() c.Run("panic", func(innerC *qt.C) {}) }) } } func TestCRunFormat(t *testing.T) { tt, innerTT := &testingT{}, &testingT{} c := qt.New(tt) c.SetFormat(func(v interface{}) string { return fmt.Sprintf("myfmt(%v)", v) }) c.Run("my test", func(innerC *qt.C) { innerC.TB = innerTT innerC.Check(42, qt.Equals, nil) }) assertPrefix(t, innerTT.errorString(), ` error: values are not equal got: myfmt(42) want: myfmt() `) } func TestCParallel(t *testing.T) { tt := &testingT{} c := qt.New(tt) c.Parallel() if !tt.parallel { t.Fatalf("parallel not called") } } func TestCParallelPanic(t *testing.T) { c := qt.New(&testing.B{}) defer func() { r := recover() if r != "cannot execute Parallel with underlying concrete type *testing.B" { t.Fatalf("unexpected panic recover: %v", r) } }() c.Parallel() } func TestCDefer(t *testing.T) { c := qt.New(t) var defers []int testDefer(c, func(c *qt.C) { c.Defer(func() { defers = append(defers, 1) }) c.Defer(func() { defers = append(defers, 2) }) // Calling Done twice should not do anything more. c.Done() }) c.Assert(defers, qt.DeepEquals, []int{2, 1}) } func TestCDeferCalledEvenAfterGoexit(t *testing.T) { // The testing package uses runtime.Goexit on // assertion failure, so check that defers are still // called in that case. c := qt.New(t) defers := 0 testDefer(c, func(c *qt.C) { c.Defer(func() { defers++ }) c.Defer(func() { c.SkipNow() }) }) c.Assert(defers, qt.Equals, 1) } func TestCRunDefer(t *testing.T) { c := qt.New(t) defers := 0 testDefer(c, func(c *qt.C) { c.Run("x", func(c *qt.C) { c.Defer(func() { defers++ }) }) }) c.Assert(defers, qt.Equals, 1) } type customT struct { *testing.T data int } func (t *customT) Run(name string, f func(*customT)) bool { return t.T.Run(name, func(t1 *testing.T) { f(&customT{t1, t.data}) }) } func TestCRunCustomType(t *testing.T) { ct := &customT{t, 99} c := qt.New(ct) called := 0 c.Run("test", func(c *qt.C) { called++ ct1, ok := c.TB.(*customT) if !ok { t.Error("TB isn't expected type") } if ct1.data != ct.data { t.Errorf("data not copied correctly; got %v want %v", ct1.data, ct.data) } if ct1 == ct { t.Errorf("old instance passed, not new") } }) if called != 1 { t.Fatalf("subtest was called %d times, not once", called) } } func checkResult(t *testing.T, ok bool, got, want string) { if want != "" { assertPrefix(t, got, want+"stack:\n") assertBool(t, ok, false) return } if got != "" { t.Fatalf("output:\ngot %q\nwant empty", got) } assertBool(t, ok, true) } // testingT can be passed to qt.New for testing purposes. type testingT struct { testing.TB errorBuf bytes.Buffer fatalBuf bytes.Buffer subTestResult bool subTestName string subTestT *testing.T parallel bool } // Error overrides *testing.T.Error so that messages are collected. func (t *testingT) Error(a ...interface{}) { fmt.Fprint(&t.errorBuf, a...) } // Fatal overrides *testing.T.Fatal so that messages are collected and the // goroutine is not killed. func (t *testingT) Fatal(a ...interface{}) { fmt.Fprint(&t.fatalBuf, a...) } func (t *testingT) Parallel() { t.parallel = true } // Fatal overrides *testing.T.Fatal so that messages are collected and the // goroutine is not killed. func (t *testingT) Run(name string, f func(t *testing.T)) bool { t.subTestName, t.subTestT = name, &testing.T{} f(t.subTestT) return t.subTestResult } // errorString returns the error message. func (t *testingT) errorString() string { return t.errorBuf.String() } // fatalString returns the fatal error message. func (t *testingT) fatalString() string { return t.fatalBuf.String() } // assertPrefix fails if the got value does not have the given prefix. func assertPrefix(t testing.TB, got, prefix string) { if h, ok := t.(helper); ok { h.Helper() } if prefix == "" { t.Fatal("prefix: empty value provided") } if !strings.HasPrefix(got, prefix) { t.Fatalf(`prefix: got %q want %q -------------------- got -------------------- %s -------------------- want ------------------- %s ---------------------------------------------`, got, prefix, got, prefix) } } // assertErrHasPrefix fails if the given error is nil or does not have the // given prefix. func assertErrHasPrefix(t testing.TB, err error, prefix string) { if h, ok := t.(helper); ok { h.Helper() } if err == nil { t.Fatalf("error:\ngot nil\nwant %q", prefix) } assertPrefix(t, err.Error(), prefix) } // assertErrIsNil fails if the given error is not nil. func assertErrIsNil(t testing.TB, err error) { if h, ok := t.(helper); ok { h.Helper() } if err != nil { t.Fatalf("error:\ngot %q\nwant nil", err) } } // assertBool fails if the given boolean values don't match. func assertBool(t testing.TB, got, want bool) { if h, ok := t.(helper); ok { h.Helper() } if got != want { t.Fatalf("bool:\ngot %v\nwant %v", got, want) } } // helper is used to check whether the current Go version supports testing // helpers. type helper interface { Helper() } // testingChecker is a quicktest.Checker used in tests. It receives the // provided argNames, adds notes via the provided addNotes function, and when // the check is run the provided error is returned. type testingChecker struct { argNames []string addNotes func(note func(key string, value interface{})) err error } // Check implements quicktest.Checker by returning the stored error. func (c *testingChecker) Check(got interface{}, args []interface{}, note func(key string, value interface{})) error { if c.addNotes != nil { c.addNotes(note) } return c.err } // Info implements quicktest.Checker by returning the stored args. func (c *testingChecker) ArgNames() []string { return c.argNames } func testDefer(c *qt.C, f func(c *qt.C)) { c.Run("defer", func(c *qt.C) { defer c.Done() f(c) }) } quicktest-1.6.0/race_test.go000066400000000000000000000036231356056600300160420ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "sync" "sync/atomic" "testing" qt "github.com/frankban/quicktest" ) func TestConcurrentMethods(t *testing.T) { // This test is designed to be run with the race // detector enabled. It checks that C methods // are safe to call concurrently. // N holds the number of iterations to run any given // operation concurrently with the others. const N = 100 var x, y int32 c := qt.New(dummyT{t}) testDefer(c, func(c *qt.C) { var wg sync.WaitGroup // start calls f in two goroutines, each // running it N times. // All the goroutines get started before we actually // start them running, so that the race detector // has a better chance of catching issues. gogogo := make(chan struct{}) start := func(f func()) { repeat := func() { defer wg.Done() <-gogogo for i := 0; i < N; i++ { f() } } wg.Add(2) go repeat() go repeat() } start(func() { c.Defer(func() { atomic.AddInt32(&x, 1) }) c.Defer(func() { atomic.AddInt32(&y, 1) }) }) start(func() { c.Done() }) start(func() { c.SetFormat(func(v interface{}) string { return "x" }) }) start(func() { // Do an assert to exercise the formatter. c.Check(true, qt.Equals, false) }) start(func() { c.Run("", func(c *qt.C) {}) }) close(gogogo) wg.Wait() }) // Check that all the defer functions ran OK. if x != N*2 || y != N*2 { t.Fatalf("unexpected x, y counts; got %d, %d; want %d, %d", x, y, N*2, N*2) } } // dummyT wraps a *testing.T value suitable // for TestConcurrentMethods so that calling Error // won't fail the test and that it implements // Run correctly. type dummyT struct { *testing.T } func (dummyT) Error(...interface{}) {} func (t dummyT) Run(name string, f func(t dummyT)) bool { return t.T.Run(name, func(t *testing.T) { f(dummyT{t}) }) } quicktest-1.6.0/report.go000066400000000000000000000121161356056600300154010ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest import ( "bytes" "fmt" "go/ast" "go/parser" "go/printer" "go/token" "io" "reflect" "runtime" "strings" ) // reportParams holds parameters for reporting a test error. type reportParams struct { // argNames holds the names for the arguments passed to the checker. argNames []string // got holds the value that was checked. got interface{} // args holds all other arguments (if any) provided to the checker. args []interface{} // comment optionally holds the comment passed when performing the check. comment Comment // notes holds notes added while doing the check. notes []note // format holds the format function that must be used when outputting // values. format formatFunc } // Unquoted indicates that the string must not be pretty printed in the failure // output. This is useful when a checker calls note and does not want the // provided value to be quoted. type Unquoted string // report generates a failure report for the given error, optionally including // in the output the checker arguments, comment and notes included in the // provided report parameters. func report(err error, p reportParams) string { var buf bytes.Buffer buf.WriteByte('\n') writeError(&buf, err, p) writeStack(&buf) return buf.String() } // writeError writes a pretty formatted output of the given error using the // provided report parameters. func writeError(w io.Writer, err error, p reportParams) { values := make(map[string]string) printPair := func(key string, value interface{}) { fmt.Fprintln(w, key+":") var v string if u, ok := value.(Unquoted); ok { v = string(u) } else { v = p.format(value) } if k := values[v]; k != "" { fmt.Fprint(w, prefixf(prefix, "", k)) return } values[v] = key fmt.Fprint(w, prefixf(prefix, "%s", v)) } // Write the checker error. if err != ErrSilent { printPair("error", Unquoted(err.Error())) } // Write the comment if provided. if comment := p.comment.String(); comment != "" { printPair("comment", Unquoted(comment)) } // Write notes if present. for _, n := range p.notes { printPair(n.key, n.value) } if IsBadCheck(err) || err == ErrSilent { // For errors in the checker invocation or for silent errors, do not // show output from args. return } // Write provided args. for i, arg := range append([]interface{}{p.got}, p.args...) { printPair(p.argNames[i], arg) } } // writeStack writes the traceback information for the current failure into the // provided writer. func writeStack(w io.Writer) { fmt.Fprintln(w, "stack:") pc := make([]uintptr, 8) sg := &stmtGetter{ fset: token.NewFileSet(), files: make(map[string]*ast.File, 8), config: &printer.Config{ Mode: printer.UseSpaces, Tabwidth: 4, }, } runtime.Callers(5, pc) frames := runtime.CallersFrames(pc) thisPackage := reflect.TypeOf(C{}).PkgPath() + "." for { frame, more := frames.Next() if strings.HasPrefix(frame.Function, "testing.") || strings.HasPrefix(frame.Function, thisPackage) { // Do not include stdlib test runner and quicktest checker calls. break } fmt.Fprint(w, prefixf(prefix, "%s:%d", frame.File, frame.Line)) stmt, err := sg.Get(frame.File, frame.Line) if err != nil { fmt.Fprint(w, prefixf(prefix+prefix, "<%s>", err)) } else { fmt.Fprint(w, prefixf(prefix+prefix, "%s", stmt)) } if !more { // There are no more callers. break } } } type stmtGetter struct { fset *token.FileSet files map[string]*ast.File config *printer.Config } // Get returns the lines of code of the statement at the given file and line. func (sg *stmtGetter) Get(file string, line int) (string, error) { f := sg.files[file] if f == nil { var err error f, err = parser.ParseFile(sg.fset, file, nil, parser.ParseComments) if err != nil { return "", fmt.Errorf("cannot parse source file: %s", err) } sg.files[file] = f } var stmt string ast.Inspect(f, func(n ast.Node) bool { if n == nil || stmt != "" { return false } pos := sg.fset.Position(n.Pos()).Line end := sg.fset.Position(n.End()).Line // Go < v1.9 reports the line where the statements ends, not the line // where it begins. if line == pos || line == end { var buf bytes.Buffer // TODO: include possible comment after the statement. sg.config.Fprint(&buf, sg.fset, &printer.CommentedNode{ Node: n, Comments: f.Comments, }) stmt = buf.String() return false } return pos < line && line <= end }) return stmt, nil } // prefixf formats the given string with the given args. It also inserts the // final newline if needed and indentation with the given prefix. func prefixf(prefix, format string, args ...interface{}) string { var buf []byte s := strings.TrimSuffix(fmt.Sprintf(format, args...), "\n") for _, line := range strings.Split(s, "\n") { buf = append(buf, prefix...) buf = append(buf, line...) buf = append(buf, '\n') } return string(buf) } // note holds a key/value annotation. type note struct { key string value interface{} } // prefix is the string used to indent blocks of output. const prefix = " " quicktest-1.6.0/report_test.go000066400000000000000000000065151356056600300164460ustar00rootroot00000000000000// Licensed under the MIT license, see LICENCE file for details. package quicktest_test import ( "runtime" "strings" "testing" qt "github.com/frankban/quicktest" ) // The tests in this file rely on their own source code lines. func TestReportOutput(t *testing.T) { tt := &testingT{} c := qt.New(tt) c.Assert(42, qt.Equals, 47) want := ` error: values are not equal got: int(42) want: int(47) stack: $file:18 c.Assert(42, qt.Equals, 47) ` assertReport(t, tt, want) } func f1(c *qt.C) { f2(c) } func f2(c *qt.C) { c.Assert(42, qt.IsNil) // Real assertion here! } func TestIndirectReportOutput(t *testing.T) { tt := &testingT{} c := qt.New(tt) f1(c) want := ` error: 42 is not nil got: int(42) stack: $file:38 c.Assert(42, qt.IsNil) $file:34 f2(c) $file:44 f1(c) ` assertReport(t, tt, want) } func TestMultilineReportOutput(t *testing.T) { tt := &testingT{} c := qt.New(tt) c.Assert( "this string", // Comment 1. qt.Equals, "another string", qt.Commentf("a comment"), // Comment 2. ) // Comment 3. want := ` error: values are not equal comment: a comment got: "this string" want: "another string" stack: $file:64 c.Assert( "this string", // Comment 1. qt.Equals, "another string", qt.Commentf("a comment"), // Comment 2. ) ` assertReport(t, tt, want) } func TestCmpReportOutput(t *testing.T) { tt := &testingT{} c := qt.New(tt) gotExamples := []*reportExample{{ AnInt: 42, ASlice: []string{}, }, { AnInt: 47, ASlice: []string{"these", "are", "the", "voyages"}, }, { AnInt: 1, }, { AnInt: 2, }, { ASlice: []string{"foo", "bar"}, }} wantExamples := []*reportExample{{ AnInt: 42, }, { AnInt: 47, ASlice: []string{"these", "are", "the", "voyages"}, }, { AnInt: 2, }, { AnInt: 1, }, { ASlice: []string{"foo"}, }, {}} checker := qt.WithVerbosity(qt.DeepEquals, false) c.Assert(gotExamples, checker, wantExamples) want := ` error: values are not deep equal diff (-got +want): []*quicktest_test.reportExample{ &{ AnInt: 42, - ASlice: []string{}, + ASlice: nil, }, &{AnInt: 47, ASlice: []string{"these", "are", "the", "voyages"}}, + &{AnInt: 2}, &{AnInt: 1}, - &{AnInt: 2}, &{ AnInt: 0, ASlice: []string{ "foo", - "bar", }, }, + &{}, } stack: $file:120 c.Assert(gotExamples, checker, wantExamples) ` assertReport(t, tt, want) } func assertReport(t *testing.T, tt *testingT, want string) { got := strings.Replace(tt.fatalString(), "\t", " ", -1) // go-cmp can include non-breaking spaces in its output. got = strings.Replace(got, "\u00a0", " ", -1) // Adjust for file names in different systems. _, file, _, ok := runtime.Caller(0) assertBool(t, ok, true) want = strings.Replace(want, "$file", file, -1) if got != want { t.Fatalf(`failure: %q %q ------------------------------ got ------------------------------ %s------------------------------ want ----------------------------- %s-----------------------------------------------------------------`, got, want, got, want) } } type reportExample struct { AnInt int ASlice []string }