github.com/lonnblad/godog@v0.7.14-0.20200306004719-1b0cb3259847/stepdef.go (about)

     1  package godog
     2  
     3  import (
     4  	"fmt"
     5  	"os"
     6  	"path/filepath"
     7  	"reflect"
     8  	"regexp"
     9  	"runtime"
    10  	"strconv"
    11  	"strings"
    12  
    13  	"github.com/cucumber/messages-go/v9"
    14  )
    15  
    16  var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
    17  
    18  // Steps allows to nest steps
    19  // instead of returning an error in step func
    20  // it is possible to return combined steps:
    21  //
    22  //   func multistep(name string) godog.Steps {
    23  //     return godog.Steps{
    24  //       fmt.Sprintf(`an user named "%s"`, name),
    25  //       fmt.Sprintf(`user "%s" is authenticated`, name),
    26  //     }
    27  //   }
    28  //
    29  // These steps will be matched and executed in
    30  // sequential order. The first one which fails
    31  // will result in main step failure.
    32  type Steps []string
    33  
    34  // StepDefinition is a registered step definition
    35  // contains a StepHandler and regexp which
    36  // is used to match a step. Args which
    37  // were matched by last executed step
    38  //
    39  // This structure is passed to the formatter
    40  // when step is matched and is either failed
    41  // or successful
    42  type StepDefinition struct {
    43  	args    []interface{}
    44  	hv      reflect.Value
    45  	Expr    *regexp.Regexp
    46  	Handler interface{}
    47  
    48  	// multistep related
    49  	nested    bool
    50  	undefined []string
    51  }
    52  
    53  func (sd *StepDefinition) definitionID() string {
    54  	ptr := sd.hv.Pointer()
    55  	f := runtime.FuncForPC(ptr)
    56  	file, line := f.FileLine(ptr)
    57  	dir := filepath.Dir(file)
    58  
    59  	fn := strings.Replace(f.Name(), dir, "", -1)
    60  	var parts []string
    61  	for _, gr := range matchFuncDefRef.FindAllStringSubmatch(fn, -1) {
    62  		parts = append(parts, strings.Trim(gr[1], "_."))
    63  	}
    64  	if len(parts) > 0 {
    65  		// case when suite is a structure with methods
    66  		fn = strings.Join(parts, ".")
    67  	} else {
    68  		// case when steps are just plain funcs
    69  		fn = strings.Trim(fn, "_.")
    70  	}
    71  
    72  	if pkg := os.Getenv("GODOG_TESTED_PACKAGE"); len(pkg) > 0 {
    73  		fn = strings.Replace(fn, pkg, "", 1)
    74  		fn = strings.TrimLeft(fn, ".")
    75  		fn = strings.Replace(fn, "..", ".", -1)
    76  	}
    77  
    78  	return fmt.Sprintf("%s:%d -> %s", filepath.Base(file), line, fn)
    79  }
    80  
    81  // run a step with the matched arguments using
    82  // reflect
    83  func (sd *StepDefinition) run() interface{} {
    84  	typ := sd.hv.Type()
    85  	if len(sd.args) < typ.NumIn() {
    86  		return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.args))
    87  	}
    88  	var values []reflect.Value
    89  	for i := 0; i < typ.NumIn(); i++ {
    90  		param := typ.In(i)
    91  		switch param.Kind() {
    92  		case reflect.Int:
    93  			s, err := sd.shouldBeString(i)
    94  			if err != nil {
    95  				return err
    96  			}
    97  			v, err := strconv.ParseInt(s, 10, 0)
    98  			if err != nil {
    99  				return fmt.Errorf(`cannot convert argument %d: "%s" to int: %s`, i, s, err)
   100  			}
   101  			values = append(values, reflect.ValueOf(int(v)))
   102  		case reflect.Int64:
   103  			s, err := sd.shouldBeString(i)
   104  			if err != nil {
   105  				return err
   106  			}
   107  			v, err := strconv.ParseInt(s, 10, 64)
   108  			if err != nil {
   109  				return fmt.Errorf(`cannot convert argument %d: "%s" to int64: %s`, i, s, err)
   110  			}
   111  			values = append(values, reflect.ValueOf(int64(v)))
   112  		case reflect.Int32:
   113  			s, err := sd.shouldBeString(i)
   114  			if err != nil {
   115  				return err
   116  			}
   117  			v, err := strconv.ParseInt(s, 10, 32)
   118  			if err != nil {
   119  				return fmt.Errorf(`cannot convert argument %d: "%s" to int32: %s`, i, s, err)
   120  			}
   121  			values = append(values, reflect.ValueOf(int32(v)))
   122  		case reflect.Int16:
   123  			s, err := sd.shouldBeString(i)
   124  			if err != nil {
   125  				return err
   126  			}
   127  			v, err := strconv.ParseInt(s, 10, 16)
   128  			if err != nil {
   129  				return fmt.Errorf(`cannot convert argument %d: "%s" to int16: %s`, i, s, err)
   130  			}
   131  			values = append(values, reflect.ValueOf(int16(v)))
   132  		case reflect.Int8:
   133  			s, err := sd.shouldBeString(i)
   134  			if err != nil {
   135  				return err
   136  			}
   137  			v, err := strconv.ParseInt(s, 10, 8)
   138  			if err != nil {
   139  				return fmt.Errorf(`cannot convert argument %d: "%s" to int8: %s`, i, s, err)
   140  			}
   141  			values = append(values, reflect.ValueOf(int8(v)))
   142  		case reflect.String:
   143  			s, err := sd.shouldBeString(i)
   144  			if err != nil {
   145  				return err
   146  			}
   147  			values = append(values, reflect.ValueOf(s))
   148  		case reflect.Float64:
   149  			s, err := sd.shouldBeString(i)
   150  			if err != nil {
   151  				return err
   152  			}
   153  			v, err := strconv.ParseFloat(s, 64)
   154  			if err != nil {
   155  				return fmt.Errorf(`cannot convert argument %d: "%s" to float64: %s`, i, s, err)
   156  			}
   157  			values = append(values, reflect.ValueOf(v))
   158  		case reflect.Float32:
   159  			s, err := sd.shouldBeString(i)
   160  			if err != nil {
   161  				return err
   162  			}
   163  			v, err := strconv.ParseFloat(s, 32)
   164  			if err != nil {
   165  				return fmt.Errorf(`cannot convert argument %d: "%s" to float32: %s`, i, s, err)
   166  			}
   167  			values = append(values, reflect.ValueOf(float32(v)))
   168  		case reflect.Ptr:
   169  			arg := sd.args[i]
   170  			switch param.Elem().String() {
   171  			case "messages.PickleStepArgument_PickleDocString":
   172  				if v, ok := arg.(*messages.PickleStepArgument); ok {
   173  					values = append(values, reflect.ValueOf(v.GetDocString()))
   174  					break
   175  				}
   176  
   177  				if v, ok := arg.(*messages.PickleStepArgument_PickleDocString); ok {
   178  					values = append(values, reflect.ValueOf(v))
   179  					break
   180  				}
   181  
   182  				return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleDocString`, i, arg, arg)
   183  			case "messages.PickleStepArgument_PickleTable":
   184  				if v, ok := arg.(*messages.PickleStepArgument); ok {
   185  					values = append(values, reflect.ValueOf(v.GetDataTable()))
   186  					break
   187  				}
   188  
   189  				if v, ok := arg.(*messages.PickleStepArgument_PickleTable); ok {
   190  					values = append(values, reflect.ValueOf(v))
   191  					break
   192  				}
   193  
   194  				return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleTable`, i, arg, arg)
   195  			default:
   196  				return fmt.Errorf("the argument %d type %T is not supported %s", i, arg, param.Elem().String())
   197  			}
   198  		case reflect.Slice:
   199  			switch param {
   200  			case typeOfBytes:
   201  				s, err := sd.shouldBeString(i)
   202  				if err != nil {
   203  					return err
   204  				}
   205  				values = append(values, reflect.ValueOf([]byte(s)))
   206  			default:
   207  				return fmt.Errorf("the slice argument %d type %s is not supported", i, param.Kind())
   208  			}
   209  		default:
   210  			return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind())
   211  		}
   212  	}
   213  
   214  	return sd.hv.Call(values)[0].Interface()
   215  }
   216  
   217  func (sd *StepDefinition) shouldBeString(idx int) (string, error) {
   218  	arg := sd.args[idx]
   219  	s, ok := arg.(string)
   220  	if !ok {
   221  		return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg)
   222  	}
   223  	return s, nil
   224  }