From fca39e4e8960f36f0930852ab20faa14ca18bff7 Mon Sep 17 00:00:00 2001 From: tfreville Date: Thu, 10 Dec 2020 15:59:10 +0100 Subject: [PATCH] test(convey): Add goconvey for test to ease test writting. Add tests for panic in test_context initialization. Made as independ commit so it can be rollbacked. --- go.mod | 1 + go.sum | 4 +++ internal/models/stepdef.go | 40 ++++++++++++++--------- test_context.go | 5 ++- test_context_test.go | 67 ++++++++++++++++++++++++++++++++++++++ 5 files changed, 101 insertions(+), 16 deletions(-) create mode 100644 test_context_test.go diff --git a/go.mod b/go.mod index 646ab05..791608a 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/cucumber/messages-go/v16 v16.0.1 github.com/hashicorp/go-memdb v1.3.0 github.com/hashicorp/go-uuid v1.0.2 // indirect + github.com/smartystreets/goconvey v1.6.4 github.com/spf13/cobra v1.1.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.7.0 diff --git a/go.sum b/go.sum index 6c851b3..beb9ba4 100644 --- a/go.sum +++ b/go.sum @@ -71,6 +71,7 @@ github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OI github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1 h1:EGx4pi6eqNxGaHF6qqu48+N2wcFQ5qg5FXgOdqsJ5d8= github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY= github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -109,6 +110,7 @@ github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANyt github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo= github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7VTCxuUUipMqKk8s4w= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= @@ -161,7 +163,9 @@ github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529/go.mod h1:DxrIzT+xaE7yg65j358z/aeFdxmN0P9QXhEzd20vsDc= github.com/shurcooL/sanitized_anchor_name v1.0.0/go.mod h1:1NzhyTcUVG4SuEtjjoZeVRXNmyL/1OwPU0+IJeTBvfc= github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPxbbu5VWo= +github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= +github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM= github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA= diff --git a/internal/models/stepdef.go b/internal/models/stepdef.go index d2a2419..98b0a00 100644 --- a/internal/models/stepdef.go +++ b/internal/models/stepdef.go @@ -1,6 +1,7 @@ package models import ( + "errors" "fmt" "reflect" "strconv" @@ -12,6 +13,13 @@ import ( var typeOfBytes = reflect.TypeOf([]byte(nil)) +// matchable errors +var ( + ErrUnmatchedStepArgumentNumber = errors.New("func received more arguments than expected") + ErrCannotConvert = errors.New("cannot convert argument") + ErrUnsupportedArgumentType = errors.New("unsupported argument type") +) + // StepDefinition ... type StepDefinition struct { formatters.StepDefinition @@ -28,8 +36,9 @@ type StepDefinition struct { func (sd *StepDefinition) Run() interface{} { typ := sd.HandlerValue.Type() if len(sd.Args) < typ.NumIn() { - return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.Args)) + return fmt.Errorf("%w: expected %d arguments, matched %d from step", ErrUnmatchedStepArgumentNumber, typ.NumIn(), len(sd.Args)) } + var values []reflect.Value for i := 0; i < typ.NumIn(); i++ { param := typ.In(i) @@ -41,7 +50,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseInt(s, 10, 0) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to int: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to int: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(int(v))) case reflect.Int64: @@ -51,7 +60,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseInt(s, 10, 64) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to int64: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to int64: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(int64(v))) case reflect.Int32: @@ -61,7 +70,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseInt(s, 10, 32) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to int32: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to int32: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(int32(v))) case reflect.Int16: @@ -71,7 +80,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseInt(s, 10, 16) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to int16: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to int16: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(int16(v))) case reflect.Int8: @@ -81,7 +90,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseInt(s, 10, 8) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to int8: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to int8: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(int8(v))) case reflect.String: @@ -97,7 +106,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseFloat(s, 64) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to float64: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to float64: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(v)) case reflect.Float32: @@ -107,7 +116,7 @@ func (sd *StepDefinition) Run() interface{} { } v, err := strconv.ParseFloat(s, 32) if err != nil { - return fmt.Errorf(`cannot convert argument %d: "%s" to float32: %s`, i, s, err) + return fmt.Errorf(`%w %d: "%s" to float32: %s`, ErrCannotConvert, i, s, err) } values = append(values, reflect.ValueOf(float32(v))) case reflect.Ptr: @@ -124,7 +133,7 @@ func (sd *StepDefinition) Run() interface{} { break } - return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleDocString`, i, arg, arg) + return fmt.Errorf(`%w %d: "%v" of type "%T" to *messages.PickleDocString`, ErrCannotConvert, i, arg, arg) case "messages.PickleTable": if v, ok := arg.(*messages.PickleStepArgument); ok { values = append(values, reflect.ValueOf(v.DataTable)) @@ -136,9 +145,9 @@ func (sd *StepDefinition) Run() interface{} { break } - return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleTable`, i, arg, arg) + return fmt.Errorf(`%w %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleTable`, ErrCannotConvert, i, arg, arg) default: - return fmt.Errorf("the argument %d type %T is not supported %s", i, arg, param.Elem().String()) + return fmt.Errorf("%w: the argument %d type %T is not supported %s", ErrUnsupportedArgumentType, i, arg, param.Elem().String()) } case reflect.Slice: switch param { @@ -149,10 +158,10 @@ func (sd *StepDefinition) Run() interface{} { } values = append(values, reflect.ValueOf([]byte(s))) default: - return fmt.Errorf("the slice argument %d type %s is not supported", i, param.Kind()) + return fmt.Errorf("%w: the slice argument %d type %s is not supported", ErrUnsupportedArgumentType, i, param.Kind()) } default: - return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind()) + return fmt.Errorf("%w: the argument %d type %s is not supported", ErrUnsupportedArgumentType, i, param.Kind()) } } @@ -160,6 +169,7 @@ func (sd *StepDefinition) Run() interface{} { if len(res) == 0 { return nil } + return res[0].Interface() } @@ -170,13 +180,13 @@ func (sd *StepDefinition) shouldBeString(idx int) (string, error) { return arg, nil case *messages.PickleStepArgument: if arg.DocString == nil { - return "", fmt.Errorf(`cannot convert DocString is not set`) + return "", fmt.Errorf(`%w %d: "%v" of type "%T": DocString is not set`, ErrCannotConvert, idx, arg, arg) } return arg.DocString.Content, nil case *messages.PickleDocString: return arg.Content, nil default: - return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg) + return "", fmt.Errorf(`%w %d: "%v" of type "%T" to string`, ErrCannotConvert, idx, arg, arg) } } diff --git a/test_context.go b/test_context.go index 7ad539f..5aef077 100644 --- a/test_context.go +++ b/test_context.go @@ -12,6 +12,9 @@ import ( "github.com/cucumber/godog/internal/models" ) +// matchable errors +var () + // Scenario represents the executed scenario type Scenario = messages.Pickle @@ -197,7 +200,7 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) { } case reflect.Slice: if typ.Elem().Kind() != reflect.String { - panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind())) + panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Elem().Kind())) } def.Nested = true default: diff --git a/test_context_test.go b/test_context_test.go new file mode 100644 index 0000000..2f10a3b --- /dev/null +++ b/test_context_test.go @@ -0,0 +1,67 @@ +package godog + +import ( + "fmt" + "regexp" + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func TestScenarioContext_Step(t *testing.T) { + Convey("When adding steps to ScenarioContext ", t, func() { + ctx := ScenarioContext{suite: &suite{}} + + Convey("It should accept steps defined with regexp.Regexp", func() { + re := regexp.MustCompile(`(?:it is a test)?.{10}x*`) + So(func() { ctx.Step(re, okEmptyResult) }, ShouldNotPanic) + }) + + Convey("It should accept steps defined with bytes slice", func() { + So(func() { ctx.Step([]byte("(?:it is a test)?.{10}x*"), okEmptyResult) }, ShouldNotPanic) + }) + + Convey("It should accept steps handler with empty return", func() { + So(func() { ctx.Step(".*", okEmptyResult) }, ShouldNotPanic) + }) + + Convey("It should accept steps handler with error return", func() { + So(func() { ctx.Step(".*", okErrorResult) }, ShouldNotPanic) + }) + + Convey("It should accept steps handler with string slice return", func() { + So(func() { ctx.Step(".*", okSliceResult) }, ShouldNotPanic) + }) + + Convey("It should panic if step expression is neither a string, regex or byte slice", func() { + So(func() { ctx.Step(1251, okSliceResult) }, ShouldPanicWith, fmt.Sprintf("expecting expr to be a *regexp.Regexp or a string, got type: %T", 12)) + }) + Convey("It should panic if step handler", func() { + Convey("is not a function", func() { + So(func() { ctx.Step(".*", 124) }, ShouldPanicWith, fmt.Sprintf("expected handler to be func, but got: %T", 12)) + }) + + Convey("has more than 1 return value", func() { + So(func() { ctx.Step(".*", nokLimitCase) }, ShouldPanicWith, fmt.Sprintf("expected handler to return either zero or one value, but it has: 2")) + So(func() { ctx.Step(".*", nokMore) }, ShouldPanicWith, fmt.Sprintf("expected handler to return either zero or one value, but it has: 5")) + }) + + Convey("return type is not an error or string slice or void", func() { + So(func() { ctx.Step(".*", nokInvalidReturnInterfaceType) }, ShouldPanicWith, "expected handler to return an error, but got: interface") + So(func() { ctx.Step(".*", nokInvalidReturnSliceType) }, ShouldPanicWith, "expected handler to return []string for multistep, but got: []int") + So(func() { ctx.Step(".*", nokInvalidReturnOtherType) }, ShouldPanicWith, "expected handler to return an error or []string, but got: chan") + }) + }) + + }) + +} + +func okEmptyResult() {} +func okErrorResult() error { return nil } +func okSliceResult() []string { return nil } +func nokLimitCase() (int, error) { return 0, nil } +func nokMore() (int, int, int, int, error) { return 0, 0, 0, 0, nil } +func nokInvalidReturnInterfaceType() interface{} { return 0 } +func nokInvalidReturnSliceType() []int { return nil } +func nokInvalidReturnOtherType() chan int { return nil }