Skip to content

Commit

Permalink
feat(step_definition): Allows to define step function without return.
Browse files Browse the repository at this point in the history
Issue: It is not possible to use function without return when
       matching steps, resulting in a lot of Nil only error
       returns.

Fix: Allows to provide empty result function by correctly matching
     reflect Calls on step Handler.
     When nothing is returned by the Handler, it will return
     nil as if errors was nil.
  • Loading branch information
tfreville committed Dec 10, 2020
1 parent adfc936 commit 81ac69d
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 18 deletions.
1 change: 1 addition & 0 deletions features/tags.feature
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ Feature: tag filters
Background:
Given passing step
And passing step without return
Scenario Outline: parse a scenario
Given a feature path "<path>"
Expand Down
6 changes: 5 additions & 1 deletion internal/models/stepdef.go
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,11 @@ func (sd *StepDefinition) Run() interface{} {
}
}

return sd.HandlerValue.Call(values)[0].Interface()
res := sd.HandlerValue.Call(values)
if len(res) == 0 {
return nil
}
return res[0].Interface()
}

func (sd *StepDefinition) shouldBeString(idx int) (string, error) {
Expand Down
21 changes: 21 additions & 0 deletions internal/models/stepdef_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,27 @@ import (
"github.com/cucumber/godog/internal/models"
)

func TestShouldSupportEmptyHandlerReturn(t *testing.T) {
fn := func(a int64, b int32, c int16, d int8) {}

def := &models.StepDefinition{
StepDefinition: formatters.StepDefinition{
Handler: fn,
},
HandlerValue: reflect.ValueOf(fn),
}

def.Args = []interface{}{"1", "1", "1", "1"}
if err := def.Run(); err != nil {
t.Fatalf("unexpected error: %v", err)
}

def.Args = []interface{}{"1", "1", "1", strings.Repeat("1", 9)}
if err := def.Run(); err == nil {
t.Fatalf("expected convertion fail for int8, but got none")
}
}

func TestShouldSupportIntTypes(t *testing.T) {
fn := func(a int64, b int32, c int16, d int8) error { return nil }

Expand Down
3 changes: 2 additions & 1 deletion run_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,8 @@ type progressOutput struct {
bottomRows []string
}

func passingStepDef() error { return nil }
func passingStepDef() error { return nil }
func passingStepDefWithoutReturn() {}

func oddEvenStepDef(odd, even int) error { return oddOrEven(odd, even) }

Expand Down
4 changes: 2 additions & 2 deletions suite_context_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ func InitializeScenario(ctx *ScenarioContext) {
return nil
})

ctx.Step(`^(?:a )?passing step without return$`, func() {})
ctx.BeforeStep(tc.inject)
}

Expand Down Expand Up @@ -413,9 +414,8 @@ func (tc *godogFeaturesScenario) aFeatureFile(path string, body *DocString) erro
return err
}

func (tc *godogFeaturesScenario) featurePath(path string) error {
func (tc *godogFeaturesScenario) featurePath(path string) {
tc.paths = append(tc.paths, path)
return nil
}

func (tc *godogFeaturesScenario) parseFeatures() error {
Expand Down
30 changes: 16 additions & 14 deletions test_context.go
Original file line number Diff line number Diff line change
Expand Up @@ -176,8 +176,8 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) {
panic(fmt.Sprintf("expected handler to be func, but got: %T", stepFunc))
}

if typ.NumOut() != 1 {
panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut()))
if typ.NumOut() > 1 {
panic(fmt.Sprintf("expected handler to return either zero or one value, but it has: %d", typ.NumOut()))
}

def := &models.StepDefinition{
Expand All @@ -188,19 +188,21 @@ func (ctx *ScenarioContext) Step(expr, stepFunc interface{}) {
HandlerValue: v,
}

typ = typ.Out(0)
switch typ.Kind() {
case reflect.Interface:
if !typ.Implements(errorInterface) {
panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind()))
if typ.NumOut() == 1 {
typ = typ.Out(0)
switch typ.Kind() {
case reflect.Interface:
if !typ.Implements(errorInterface) {
panic(fmt.Sprintf("expected handler to return an error, but got: %s", typ.Kind()))
}
case reflect.Slice:
if typ.Elem().Kind() != reflect.String {
panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind()))
}
def.Nested = true
default:
panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind()))
}
case reflect.Slice:
if typ.Elem().Kind() != reflect.String {
panic(fmt.Sprintf("expected handler to return []string for multistep, but got: []%s", typ.Kind()))
}
def.Nested = true
default:
panic(fmt.Sprintf("expected handler to return an error or []string, but got: %s", typ.Kind()))
}

ctx.suite.steps = append(ctx.suite.steps, def)
Expand Down

0 comments on commit 81ac69d

Please # to comment.