Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add support for step data tables #154

Merged
merged 1 commit into from
Sep 25, 2024
Merged
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
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 @@
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 (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 @@
}

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 @@
"errors"
"fmt"
"regexp"
"strings"
"testing"

msgs "github.com/cucumber/messages/go/v24"
Expand Down Expand Up @@ -84,6 +85,14 @@
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 @@
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
Loading