github.com/Secbyte/godog@v0.7.14-0.20200116175429-d8f0aeeb70cf/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/DATA-DOG/godog/gherkin" 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 // StepDef 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 StepDef 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 *StepDef) 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 *StepDef) 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 "gherkin.DocString": 172 v, ok := arg.(*gherkin.DocString) 173 if !ok { 174 return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg) 175 } 176 values = append(values, reflect.ValueOf(v)) 177 case "gherkin.DataTable": 178 v, ok := arg.(*gherkin.DataTable) 179 if !ok { 180 return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg) 181 } 182 values = append(values, reflect.ValueOf(v)) 183 default: 184 return fmt.Errorf("the argument %d type %T is not supported", i, arg) 185 } 186 case reflect.Slice: 187 switch param { 188 case typeOfBytes: 189 s, err := sd.shouldBeString(i) 190 if err != nil { 191 return err 192 } 193 values = append(values, reflect.ValueOf([]byte(s))) 194 default: 195 return fmt.Errorf("the slice argument %d type %s is not supported", i, param.Kind()) 196 } 197 default: 198 return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind()) 199 } 200 } 201 return sd.hv.Call(values)[0].Interface() 202 } 203 204 func (sd *StepDef) shouldBeString(idx int) (string, error) { 205 arg := sd.args[idx] 206 s, ok := arg.(string) 207 if !ok { 208 return "", fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to string`, idx, arg, arg) 209 } 210 return s, nil 211 }