godog/stepdef.go

189 строки
5 КиБ
Go

package godog
import (
"fmt"
"path/filepath"
"reflect"
"regexp"
"runtime"
"strconv"
"strings"
"github.com/DATA-DOG/godog/gherkin"
)
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
// StepDef is a registered step definition
// contains a StepHandler and regexp which
// is used to match a step. Args which
// were matched by last executed step
//
// This structure is passed to the formatter
// when step is matched and is either failed
// or successful
type StepDef struct {
args []interface{}
hv reflect.Value
Expr *regexp.Regexp
Handler interface{}
}
func (sd *StepDef) funcName() string {
ptr := sd.hv.Pointer()
f := runtime.FuncForPC(ptr)
file, line := f.FileLine(ptr)
dir := filepath.Dir(file)
fn := strings.Replace(f.Name(), dir, "", -1)
var parts []string
for _, gr := range matchFuncDefRef.FindAllStringSubmatch(fn, -1) {
parts = append(parts, strings.Trim(gr[1], "_."))
}
if len(parts) > 0 {
// case when suite is a structure with methods
fn = strings.Join(parts, ".")
} else {
// case when steps are just plain funcs
fn = strings.Trim(fn, "_.")
}
return fmt.Sprintf("%s:%d -> %s", filepath.Base(file), line, fn)
}
// run a step with the matched arguments using
// reflect
func (sd *StepDef) run() error {
typ := sd.hv.Type()
if len(sd.args) < typ.NumIn() {
return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.args))
}
var values []reflect.Value
for i := 0; i < typ.NumIn(); i++ {
param := typ.In(i)
switch param.Kind() {
case reflect.Int:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 0)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int(v)))
case reflect.Int64:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 64)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int64: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int64(v)))
case reflect.Int32:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 32)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int32: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int32(v)))
case reflect.Int16:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 16)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int16: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int16(v)))
case reflect.Int8:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseInt(s, 10, 8)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to int8: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(int8(v)))
case reflect.String:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
values = append(values, reflect.ValueOf(s))
case reflect.Float64:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseFloat(s, 64)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to float64: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(v))
case reflect.Float32:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
v, err := strconv.ParseFloat(s, 32)
if err != nil {
return fmt.Errorf(`cannot convert argument %d: "%s" to float32: %s`, i, s, err)
}
values = append(values, reflect.ValueOf(float32(v)))
case reflect.Ptr:
arg := sd.args[i]
switch param.Elem().String() {
case "gherkin.DocString":
v, ok := arg.(*gherkin.DocString)
if !ok {
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg)
}
values = append(values, reflect.ValueOf(v))
case "gherkin.DataTable":
v, ok := arg.(*gherkin.DataTable)
if !ok {
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg)
}
values = append(values, reflect.ValueOf(v))
default:
return fmt.Errorf("the argument %d type %T is not supported", i, arg)
}
case reflect.Slice:
switch param {
case typeOfBytes:
s, err := sd.shouldBeString(i)
if err != nil {
return err
}
values = append(values, reflect.ValueOf([]byte(s)))
default:
return fmt.Errorf("the slice argument %d type %s is not supported", i, param.Kind())
}
default:
return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind())
}
}
ret := sd.hv.Call(values)[0].Interface()
if nil == ret {
return nil
}
return ret.(error)
}
func (sd *StepDef) shouldBeString(idx int) (string, error) {
arg := sd.args[idx]
s, ok := arg.(string)
if !ok {
return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg)
}
return s, nil
}