diff --git a/.travis.yml b/.travis.yml index a9066b7..1d2eb1a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -11,7 +11,6 @@ script: - go get github.com/golang/lint/golint # pull all external dependencies - # remove them at all if possible - go get github.com/cucumber/gherkin-go - go get golang.org/x/tools/imports - go get github.com/shiena/ansicolor @@ -20,10 +19,10 @@ script: - go test -v ./... - go test -race ./... - # test me with myself + # run features - go run cmd/godog/main.go -f progress # code correctness - - sh -c 'if [ ! -z "$(go fmt ./...)" ]; then exit 1; fi' - - golint ./... + - sh -c 'RES="$(go fmt ./...)"; if [ ! -z "$RES" ]; then echo $RES; exit 1; fi' + - sh -c 'RES="$(golint ./...)"; if [ ! -z "$RES" ]; then echo $RES; exit 1; fi' - go vet ./... diff --git a/README.md b/README.md index c168be8..2f272bc 100644 --- a/README.md +++ b/README.md @@ -121,7 +121,7 @@ The public API is stable enough, but it may break until **1.0.0** version, see ` ### FAQ **Q:** Where can I configure common options globally? -**A:** You can't. Alias your common or project based commands: `alias mygodog="godog --format=progress"` +**A:** You can't. Alias your common or project based commands: `alias mygodog="godog --format=progress --tags=@wip"` ### Contributions diff --git a/fmt_pretty.go b/fmt_pretty.go index aeeadc3..e5ac60f 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -3,9 +3,7 @@ package godog import ( "fmt" "math" - "reflect" "regexp" - "runtime" "strings" "time" @@ -230,9 +228,7 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) { } else { text = cl(ostep.Text, cyan) } - // use reflect to get step handler function name - name := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name() - text += s(f.commentPos-f.length(ostep)+1) + cl(fmt.Sprintf("# %s", name), black) + text += s(f.commentPos-f.length(ostep)+1) + cl(fmt.Sprintf("# %s", def.funcName()), black) } else { text = cl(ostep.Text, cyan) } @@ -285,9 +281,7 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) { } else { text += cl(step.Text, c) } - // use reflect to get step handler function name - name := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name() - text += s(f.commentPos-f.length(step)+1) + cl(fmt.Sprintf("# %s", name), black) + text += s(f.commentPos-f.length(step)+1) + cl(fmt.Sprintf("# %s", def.funcName()), black) default: text += cl(step.Text, c) } @@ -297,7 +291,11 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) { case *gherkin.DataTable: f.printTable(t, c) case *gherkin.DocString: - fmt.Println(s(f.indent*3) + cl(t.Delimitter, c)) // @TODO: content type + var ct string + if len(t.ContentType) > 0 { + ct = " " + cl(t.ContentType, c) + } + fmt.Println(s(f.indent*3) + cl(t.Delimitter, c) + ct) for _, ln := range strings.Split(t.Content, "\n") { fmt.Println(s(f.indent*3) + cl(ln, c)) } diff --git a/stepdef.go b/stepdef.go new file mode 100644 index 0000000..202195d --- /dev/null +++ b/stepdef.go @@ -0,0 +1,166 @@ +package godog + +import ( + "fmt" + "reflect" + "regexp" + "runtime" + "strconv" + + "github.com/cucumber/gherkin-go" +) + +// 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 { + return runtime.FuncForPC(sd.hv.Pointer()).Name() +} + +// 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 +} diff --git a/suite.go b/suite.go index d7126b6..062070a 100644 --- a/suite.go +++ b/suite.go @@ -14,6 +14,7 @@ import ( ) var errorInterface = reflect.TypeOf((*error)(nil)).Elem() +var typeOfBytes = reflect.TypeOf([]byte(nil)) type feature struct { *gherkin.Feature @@ -23,144 +24,6 @@ type feature struct { // ErrUndefined is returned in case if step definition was not found var ErrUndefined = fmt.Errorf("step is undefined") -// 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) 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) - } - 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 -} - // Suite is an interface which allows various contexts // to register steps and event handlers. // @@ -207,9 +70,9 @@ type Suite interface { } type suite struct { - stepHandlers []*StepDef - features []*feature - fmt Formatter + steps []*StepDef + features []*feature + fmt Formatter failed bool @@ -273,7 +136,7 @@ func (s *suite) Step(expr interface{}, stepFunc interface{}) { if typ.Out(0).Kind() != reflect.Interface || !typ.Out(0).Implements(errorInterface) { panic(fmt.Sprintf("expected handler to return an error interface, but we have: %s", typ.Out(0).Kind())) } - s.stepHandlers = append(s.stepHandlers, &StepDef{ + s.steps = append(s.steps, &StepDef{ Handler: stepFunc, Expr: regex, hv: v, @@ -403,7 +266,7 @@ func (s *suite) run() { } func (s *suite) matchStep(step *gherkin.Step) *StepDef { - for _, h := range s.stepHandlers { + for _, h := range s.steps { if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 { var args []interface{} for _, m := range m[1:] { @@ -473,11 +336,6 @@ func (s *suite) skipSteps(steps []*gherkin.Step) { } func (s *suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Background) (err error) { - // run before scenario handlers - defer func() { - // run after scenario handlers - }() - s.fmt.Node(outline) for _, example := range outline.Examples { @@ -584,17 +442,17 @@ func (s *suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) ( func (s *suite) printStepDefinitions() { var longest int - for _, def := range s.stepHandlers { + for _, def := range s.steps { if longest < len(def.Expr.String()) { longest = len(def.Expr.String()) } } - for _, def := range s.stepHandlers { - location := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name() + for _, def := range s.steps { + location := runtime.FuncForPC(def.hv.Pointer()).Name() spaces := strings.Repeat(" ", longest-len(def.Expr.String())) fmt.Println(cl(def.Expr.String(), yellow)+spaces, cl("# "+location, black)) } - if len(s.stepHandlers) == 0 { + if len(s.steps) == 0 { fmt.Println("there were no contexts registered, could not find any step definition..") } }