Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions internal/models/stepdef.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import (
"github.com/cucumber/godog/formatters"
)

var typeOfBytes = reflect.TypeOf([]byte(nil))
var TypeOfBytes = reflect.TypeOf([]byte(nil))

// matchable errors
var (
Expand All @@ -35,7 +35,7 @@ type StepDefinition struct {
Undefined []string
}

var typeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem()
var TypeOfContext = reflect.TypeOf((*context.Context)(nil)).Elem()

// Run a step with the matched arguments using reflect
// Returns one of ...
Expand All @@ -46,7 +46,7 @@ func (sd *StepDefinition) Run(ctx context.Context) (context.Context, interface{}

typ := sd.HandlerValue.Type()
numIn := typ.NumIn()
hasCtxIn := numIn > 0 && typ.In(0).Implements(typeOfContext)
hasCtxIn := numIn > 0 && typ.In(0).Implements(TypeOfContext)
ctxOffset := 0

if hasCtxIn {
Expand Down Expand Up @@ -221,7 +221,7 @@ func (sd *StepDefinition) Run(ctx context.Context) (context.Context, interface{}
}
case reflect.Slice:
switch param {
case typeOfBytes:
case TypeOfBytes:
s, err := sd.shouldBeString(i)
if err != nil {
return ctx, err
Expand Down
36 changes: 33 additions & 3 deletions test_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -302,9 +302,39 @@ func (ctx ScenarioContext) stepWithKeyword(expr interface{}, stepFunc interface{
panic(fmt.Sprintf("expected handler to be func, but got: %T", stepFunc))
}

// FIXME = Validate the handler function param types here so
// that any errors are discovered early.
// StepDefinition.Run defines the supported types but fails at run time not registration time
numIn := handlerType.NumIn()
hasCtxIn := numIn > 0 && handlerType.In(0).Implements(models.TypeOfContext)
ctxOffset := 0
if hasCtxIn {
ctxOffset = 1
}

for i := ctxOffset; i < numIn; i++ {
param := handlerType.In(i)
switch param.Kind() {
case reflect.Int, reflect.Int64, reflect.Int32, reflect.Int16, reflect.Int8,
reflect.Uint, reflect.Uint64, reflect.Uint32, reflect.Uint16, reflect.Uint8,
reflect.String,
reflect.Float64, reflect.Float32:
// do nothing
case reflect.Ptr:
switch param.Elem().String() {
case "messages.PickleDocString", "messages.PickleTable":
// do nothing
default:
panic(fmt.Sprintf("%s: the data type of parameter %d type *%s is not supported", models.ErrUnsupportedParameterType, i, param.Elem().String()))
}
case reflect.Slice:
switch param {
case models.TypeOfBytes:
// do nothing
default:
panic(fmt.Sprintf("%s: the slice parameter %d type []%s is not supported", models.ErrUnsupportedParameterType, i, param.Elem().Kind()))
}
default:
panic(fmt.Sprintf("%s: the parameter %d type %s is not supported", models.ErrUnsupportedParameterType, i, param.Kind()))
}
}

// Validate the function's return types.
helpPrefix := "expected handler to return one of error or context.Context or godog.Steps or (context.Context, error)"
Expand Down
56 changes: 45 additions & 11 deletions test_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package godog

import (
"context"
messages "github.com/cucumber/messages/go/v21"
"regexp"
"testing"

Expand Down Expand Up @@ -32,6 +33,8 @@ func TestScenarioContext_Step(t *testing.T) {
f: func() { ctx.Step(".*", okStepsResult) }},
{n: "ScenarioContext should accept steps handler with (Context, error) return",
f: func() { ctx.Step(".*", okContextErrorResult) }},
{n: "ScenarioContext should accept steps handler with valid params",
f: func() { ctx.Step(".*", okValidParams) }},
} {
t.Run(c.n, func(t *testing.T) {
assert.NotPanics(t, c.f)
Expand All @@ -56,6 +59,25 @@ func TestScenarioContext_Step(t *testing.T) {
p: "expecting expr to be a *regexp.Regexp or a string or []byte, got type: int",
f: func() { ctx.Step(1251, okVoidResult) }},

{n: "ScenarioContext should panic if step handler params include context.Context, but context.Context is not the first parameter",
p: "func has unsupported parameter type: the parameter 1 type interface is not supported",
f: func() { ctx.Step(".*", nokInvalidParamCtxNotFirst) }},
{n: "ScenarioContext should panic if step handler params include struct",
p: "func has unsupported parameter type: the parameter 0 type struct is not supported",
f: func() { ctx.Step(".*", nokInvalidParamStruct) }},
{n: "ScenarioContext should panic if step handler params include messages.PickleStepArgument struct instead of pointer",
p: "func has unsupported parameter type: the parameter 0 type struct is not supported",
f: func() { ctx.Step(".*", nokInvalidParamPickleStepArgumentStruct) }},
{n: "ScenarioContext should panic if step handler params include messages.PickleDocString struct instead of pointer",
p: "func has unsupported parameter type: the parameter 0 type struct is not supported",
f: func() { ctx.Step(".*", nokInvalidParamPickleDocStringStruct) }},
{n: "ScenarioContext should panic if step handler params include unsupported slice parameter type",
p: "func has unsupported parameter type: the slice parameter 0 type []string is not supported",
f: func() { ctx.Step(".*", nokInvalidParamInvalidSliceType) }},
{n: "ScenarioContext should panic if step handler params include unsupported pointer parameter type",
p: "func has unsupported parameter type: the data type of parameter 0 type *int is not supported",
f: func() { ctx.Step(".*", nokInvalidParamUnsupportedPointer) }},

{n: "ScenarioContext should panic if step return type is []string",
p: "expected handler to return one of error or context.Context or godog.Steps or (context.Context, error), but got: []string",
f: func() { ctx.Step(".*", nokSliceStringResult) }},
Expand All @@ -74,14 +96,26 @@ func TestScenarioContext_Step(t *testing.T) {
})
}
}

func okVoidResult() {}
func okErrorResult() error { return nil }
func okStepsResult() Steps { return nil }
func okContextErrorResult() (context.Context, error) { return nil, nil }
func nokSliceStringResult() []string { return nil }
func nokLimitCase3() (string, int, error) { return "", 0, nil }
func nokLimitCase5() (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 }
func okValidParams(
int, int64, int32, int16, int8,
uint, uint64, uint32, uint16, uint8,
string, float64, float32, []byte,
*messages.PickleDocString, *messages.PickleTable,
) {
}
func okVoidResult() {}
func okErrorResult() error { return nil }
func okStepsResult() Steps { return nil }
func okContextErrorResult() (context.Context, error) { return nil, nil }
func nokInvalidParamCtxNotFirst(int, context.Context) {}
func nokInvalidParamStruct(struct{}) {}
func nokInvalidParamPickleDocStringStruct(messages.PickleDocString) {}
func nokInvalidParamPickleStepArgumentStruct(messages.PickleTable) {}
func nokInvalidParamInvalidSliceType([]string) {}
func nokInvalidParamUnsupportedPointer(*int) {}
func nokSliceStringResult() []string { return nil }
func nokLimitCase3() (string, int, error) { return "", 0, nil }
func nokLimitCase5() (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 }
Loading