Skip to content

Commit

Permalink
feat: Add support for step data tables (#154)
Browse files Browse the repository at this point in the history
  • Loading branch information
ckoerckel authored Sep 25, 2024
1 parent b8e6b8c commit 3e8861a
Show file tree
Hide file tree
Showing 3 changed files with 124 additions and 21 deletions.
12 changes: 12 additions & 0 deletions features/datatable.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
Feature: dataTable feature
Scenario: compare text with dataTable
When I concat all the columns and row together using " - " to separate the columns
|r1c1|r1c2|r1c3|
|r2c1|r2c2|r2c3|
|r3c1|r3c2|r3c3|
Then the result should equal argument:
"""
r1c1 - r1c2 - r1c3
r2c1 - r2c2 - r2c3
r3c1 - r3c2 - r3c3
"""
110 changes: 89 additions & 21 deletions gobdd.go
Original file line number Diff line number Diff line change
Expand Up @@ -572,9 +572,17 @@ func (s *Suite) runStep(ctx Context, t *testing.T, step *msgs.Step) {
t.Fatalf("cannot find step definition for step: %s%s", step.Keyword, step.Text)
}

params := def.expr.FindSubmatch([]byte(step.Text))[1:]
matches := def.expr.FindSubmatch([]byte(step.Text))[1:]
params := make([]interface{}, 0, len(matches)) // defining the slices capacity instead of the length to use append
for _, m := range matches {

Check failure on line 577 in gobdd.go

View workflow job for this annotation

GitHub Actions / Lint

only one cuddle assignment allowed before range statement (wsl)
params = append(params, m)
}

if step.DocString != nil {
params = append(params, []byte(step.DocString.Content))
params = append(params, step.DocString.Content)
}
if step.DataTable != nil {

Check failure on line 584 in gobdd.go

View workflow job for this annotation

GitHub Actions / Lint

if statements should only be cuddled with assignments (wsl)
params = append(params, *step.DataTable)
}

t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(step.Keyword), step.Text), func(t *testing.T) {
Expand All @@ -589,7 +597,7 @@ func (s *Suite) runStep(ctx Context, t *testing.T, step *msgs.Step) {
})
}

func (def *stepDef) run(ctx Context, t TestingT, params [][]byte) { // nolint:interfacer
func (def *stepDef) run(ctx Context, t TestingT, params []interface{}) { // nolint:interfacer
defer func() {
if r := recover(); r != nil {
t.Errorf("%+v", r)
Expand All @@ -611,38 +619,98 @@ func (def *stepDef) run(ctx Context, t TestingT, params [][]byte) { // nolint:in
}

inType := d.Type().In(i + 2)
paramType := paramType(v, inType)

paramType, err := paramType(v, inType)
if err != nil {
t.Fatal(err)
}

in = append(in, paramType)
}

d.Call(in)
}

func paramType(param []byte, inType reflect.Type) reflect.Value {
paramType := reflect.ValueOf(param)
if inType.Kind() == reflect.String {
paramType = reflect.ValueOf(string(paramType.Interface().([]uint8)))
func paramType(param interface{}, inType reflect.Type) (reflect.Value, error) {
switch inType.Kind() { // nolint:exhaustive - the linter does not recognize 'default:' to satisfy exhaustiveness

Check failure on line 635 in gobdd.go

View workflow job for this annotation

GitHub Actions / Lint

missing cases in switch of type reflect.Kind: reflect.Invalid, reflect.Bool, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr, reflect.Complex64, reflect.Complex128, reflect.Array, reflect.Chan, reflect.Func, reflect.Interface, reflect.Map, reflect.Pointer|reflect.Ptr, reflect.UnsafePointer (exhaustive)
case reflect.String:
s, err := shouldBeString(param)
return reflect.ValueOf(s), err
case reflect.Int:
v, err := shouldBeInt(param)
return reflect.ValueOf(v), err
case reflect.Float32:
v, err := shouldBeFloat(param, 32)
return reflect.ValueOf(float32(v)), err
case reflect.Float64:
v, err := shouldBeFloat(param, 64)
return reflect.ValueOf(v), err
case reflect.Slice:
// only []byte is supported
if inType != reflect.TypeOf([]byte(nil)) {
return reflect.Value{}, fmt.Errorf("the slice argument type %s is not supported", inType.Kind())
}

v, err := shouldBeByteSlice(param)

return reflect.ValueOf(v), err
case reflect.Struct:
// the only struct supported is the one introduced by cucumber
if inType != reflect.TypeOf(msgs.DataTable{}) {
return reflect.Value{}, fmt.Errorf("the struct argument type %s is not supported", inType.Kind())
}

v, err := shouldBeDataTable(param)

return reflect.ValueOf(v), err
default:
return reflect.Value{}, fmt.Errorf("the type %s is not supported", inType.Kind())
}
}

if inType.Kind() == reflect.Int {
s := paramType.Interface().([]uint8)
p, _ := strconv.Atoi(string(s))
paramType = reflect.ValueOf(p)
func shouldBeDataTable(input interface{}) (msgs.DataTable, error) {
if v, ok := input.(msgs.DataTable); ok {
return v, nil
}

if inType.Kind() == reflect.Float32 {
s := paramType.Interface().([]uint8)
p, _ := strconv.ParseFloat(string(s), 32)
paramType = reflect.ValueOf(float32(p))
return msgs.DataTable{}, fmt.Errorf("cannot convert %v of type %T to messages.DataTable", input, input)
}

func shouldBeByteSlice(input interface{}) ([]byte, error) {
if v, ok := input.([]byte); ok {
return v, nil
}

if inType.Kind() == reflect.Float64 {
s := paramType.Interface().([]uint8)
p, _ := strconv.ParseFloat(string(s), 32)
paramType = reflect.ValueOf(p)
return nil, fmt.Errorf("cannot convert %v of type %T to []byte", input, input)
}

func shouldBeInt(input interface{}) (int, error) {
s, err := shouldBeString(input)
if err != nil {
return 0, err
}

return paramType
return strconv.Atoi(s)
}

func shouldBeFloat(input interface{}, bitSize int) (float64, error) {
s, err := shouldBeString(input)
if err != nil {
return 0, err
}

return strconv.ParseFloat(s, bitSize)
}

func shouldBeString(input interface{}) (string, error) {
switch v := input.(type) {
case string:
return v, nil
case []byte:
return string(v), nil
default:
return "", fmt.Errorf("cannot convert %v of type %T to string", input, input)
}
}

func (s *Suite) findStepDef(text string) (stepDef, error) {
Expand Down
23 changes: 23 additions & 0 deletions gobdd_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"errors"
"fmt"
"regexp"
"strings"
"testing"

msgs "github.com/cucumber/messages/go/v24"
Expand Down Expand Up @@ -84,6 +85,14 @@ func TestArguments(t *testing.T) {
suite.Run()
}

func TestDatatable(t *testing.T) {
suite := NewSuite(t, WithFeaturesPath("features/datatable.feature"))
suite.AddStep(`I concat all the columns and row together using {text} to separate the columns`, concatTable)
suite.AddStep(`the result should equal argument:`, checkt)

suite.Run()
}

func TestScenarioOutlineExecutesAllTests(t *testing.T) {
c := 0
suite := NewSuite(t, WithFeaturesPath("features/outline.feature"))
Expand Down Expand Up @@ -299,6 +308,20 @@ func concat(_ StepTest, ctx Context, var1, var2 string) {
ctx.Set("stringRes", var1+var2)
}

func concatTable(_ StepTest, ctx Context, separator string, table msgs.DataTable) {
rows := make([]string, 0, len(table.Rows))
for _, row := range table.Rows {

Check failure on line 313 in gobdd_test.go

View workflow job for this annotation

GitHub Actions / Lint

ranges should only be cuddled with assignments used in the iteration (wsl)
values := make([]string, 0, len(row.Cells))
for _, cell := range row.Cells {
values = append(values, cell.Value)
}

rows = append(rows, strings.Join(values, separator))
}

ctx.Set("stringRes", strings.Join(rows, "\n"))
}

func checkt(t StepTest, ctx Context, text string) {
received, err := ctx.GetString("stringRes")
if err != nil {
Expand Down

0 comments on commit 3e8861a

Please sign in to comment.