From 46fcac2e01e2163071b3547e64ce8dfe8226bcea Mon Sep 17 00:00:00 2001 From: corentin koerckel Date: Wed, 11 Sep 2024 13:21:44 +0000 Subject: [PATCH] feat: Add support for step data tables --- features/datatable.feature | 12 +++++ gobdd.go | 108 ++++++++++++++++++++++++++++--------- gobdd_test.go | 21 ++++++++ 3 files changed, 115 insertions(+), 26 deletions(-) create mode 100644 features/datatable.feature diff --git a/features/datatable.feature b/features/datatable.feature new file mode 100644 index 0000000..24e25f7 --- /dev/null +++ b/features/datatable.feature @@ -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 + """ \ No newline at end of file diff --git a/gobdd.go b/gobdd.go index aab3987..2ab5787 100644 --- a/gobdd.go +++ b/gobdd.go @@ -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:] + var params []interface{} + for _, m := range matches { + 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 { + params = append(params, *step.DataTable) } t.Run(fmt.Sprintf("%s %s", strings.TrimSpace(step.Keyword), step.Text), func(t *testing.T) { @@ -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) @@ -611,38 +619,86 @@ 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))) - } - - if inType.Kind() == reflect.Int { - s := paramType.Interface().([]uint8) - p, _ := strconv.Atoi(string(s)) - paramType = reflect.ValueOf(p) - } - - if inType.Kind() == reflect.Float32 { - s := paramType.Interface().([]uint8) - p, _ := strconv.ParseFloat(string(s), 32) - paramType = reflect.ValueOf(float32(p)) +func paramType(param interface{}, inType reflect.Type) (reflect.Value, error) { + switch inType.Kind() { + case reflect.String: + s, err := shouldBeString(param) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(s), nil + case reflect.Int: + s, err := shouldBeString(param) + if err != nil { + return reflect.Value{}, err + } + p, err := strconv.Atoi(s) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(p), nil + case reflect.Float32: + s, err := shouldBeString(param) + if err != nil { + return reflect.Value{}, err + } + p, err := strconv.ParseFloat(s, 32) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(float32(p)), nil + case reflect.Float64: + s, err := shouldBeString(param) + if err != nil { + return reflect.Value{}, err + } + p, err := strconv.ParseFloat(s, 64) + if err != nil { + return reflect.Value{}, err + } + return reflect.ValueOf(p), nil + 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()) + } + if v, ok := param.([]byte); ok { + return reflect.ValueOf(v), nil + } + return reflect.Value{}, fmt.Errorf("cannot convert %v of type %T to []byte", param, param) + 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()) + } + if v, ok := param.(msgs.DataTable); ok { + return reflect.ValueOf(v), nil + } + return reflect.Value{}, fmt.Errorf("cannot convert %v of type %T to messages.DataTable", param, param) + default: + return reflect.Value{}, fmt.Errorf("the type %s is not supported", inType.Kind()) } +} - if inType.Kind() == reflect.Float64 { - s := paramType.Interface().([]uint8) - p, _ := strconv.ParseFloat(string(s), 32) - paramType = reflect.ValueOf(p) +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) } - - return paramType } func (s *Suite) findStepDef(text string) (stepDef, error) { diff --git a/gobdd_test.go b/gobdd_test.go index 46c1366..a6c5d74 100644 --- a/gobdd_test.go +++ b/gobdd_test.go @@ -4,6 +4,7 @@ import ( "errors" "fmt" "regexp" + "strings" "testing" msgs "github.com/cucumber/messages/go/v24" @@ -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")) @@ -299,6 +308,18 @@ 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 { + 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 {