continue with output formatter implementation

Этот коммит содержится в:
gedi 2015-06-15 11:15:06 +03:00
родитель 3ee0746a3c
коммит b69fa26b8b
3 изменённых файлов: 173 добавлений и 51 удалений

Просмотреть файл

@ -1,6 +1,7 @@
package godog package godog
import ( import (
"flag"
"fmt" "fmt"
"os" "os"
"path/filepath" "path/filepath"
@ -9,14 +10,37 @@ import (
"github.com/DATA-DOG/godog/gherkin" "github.com/DATA-DOG/godog/gherkin"
) )
type registeredFormatter struct {
name string
fmt Formatter
}
var formatters []*registeredFormatter
// RegisterFormatter registers a feature suite output
// Formatter for its given name
func RegisterFormatter(name string, f Formatter) {
formatters = append(formatters, &registeredFormatter{
name: name,
fmt: f,
})
}
var cfg config var cfg config
func init() {
// @TODO: colorize flag help output
flag.StringVar(&cfg.featuresPath, "features", "features", "Path to feature files")
flag.StringVar(&cfg.formatterName, "formatter", "pretty", "Formatter name")
}
type config struct { type config struct {
featuresPath string featuresPath string
formatterName string formatterName string
} }
func (c config) validate() error { func (c config) validate() error {
// feature path
inf, err := os.Stat(c.featuresPath) inf, err := os.Stat(c.featuresPath)
if err != nil { if err != nil {
return err return err
@ -24,11 +48,20 @@ func (c config) validate() error {
if !inf.IsDir() { if !inf.IsDir() {
return fmt.Errorf("feature path \"%s\" is not a directory.", c.featuresPath) return fmt.Errorf("feature path \"%s\" is not a directory.", c.featuresPath)
} }
switch c.formatterName {
case "pretty": // formatter
// ok var found bool
default: var names []string
return fmt.Errorf("Unsupported formatter name: %s", c.formatterName) for _, f := range formatters {
if f.name == c.formatterName {
found = true
break
}
names = append(names, f.name)
}
if !found {
return fmt.Errorf(`unregistered formatter name: "%s", use one of: %s`, c.formatterName, strings.Join(names, ", "))
} }
return nil return nil
} }
@ -46,7 +79,7 @@ func (c config) features() (lst []*gherkin.Feature, err error) {
}) })
} }
func (c config) formatter() formatter { func (c config) formatter() Formatter {
return &pretty{} return &pretty{}
} }

Просмотреть файл

@ -2,26 +2,93 @@ package godog
import ( import (
"fmt" "fmt"
"strings"
"github.com/DATA-DOG/godog/gherkin" "github.com/DATA-DOG/godog/gherkin"
) )
type formatter interface { func init() {
node(interface{}) RegisterFormatter("pretty", &pretty{})
} }
type pretty struct{} // Formatter is an interface for feature runner output
type Formatter interface {
Node(interface{})
Failed(*gherkin.Step, error)
Passed(*gherkin.Step)
Skipped(*gherkin.Step)
Pending(*gherkin.Step)
}
func (f *pretty) node(node interface{}) { type pretty struct {
feature *gherkin.Feature
scenario *gherkin.Scenario
doneBackground bool
background *gherkin.Background
}
func (f *pretty) line(tok *gherkin.Token) string {
return cl(fmt.Sprintf("#%s:%d", f.feature.Path, tok.Line), magenta)
}
func (f *pretty) canPrintStep(step *gherkin.Step) bool {
if f.background == nil {
return true
}
var backgroundStep bool
for _, s := range f.background.Steps {
if s == step {
backgroundStep = true
break
}
}
if !backgroundStep {
f.doneBackground = true
return true
}
return !f.doneBackground
}
func (f *pretty) Node(node interface{}) {
switch t := node.(type) { switch t := node.(type) {
case *gherkin.Feature: case *gherkin.Feature:
fmt.Println(bcl("Feature: ", white) + t.Title) f.feature = t
fmt.Println(t.Description + "\n") f.doneBackground = false
f.scenario = nil
f.background = nil
fmt.Println("\n"+bcl("Feature: ", white)+t.Title, f.line(t.Token))
fmt.Println(t.Description)
case *gherkin.Background: case *gherkin.Background:
fmt.Println(bcl("Background:", white)) f.background = t
fmt.Println("\n" + bcl("Background:", white))
case *gherkin.Scenario: case *gherkin.Scenario:
fmt.Println(bcl("Scenario: ", white) + t.Title) fmt.Println("\n"+strings.Repeat(" ", t.Token.Indent)+bcl("Scenario: ", white)+t.Title, f.line(t.Token))
case *gherkin.Step: }
fmt.Println(bcl(t.Token.Text, green)) }
func (f *pretty) Passed(step *gherkin.Step) {
if f.canPrintStep(step) {
fmt.Println(cl(step.Token.Text, green))
}
}
func (f *pretty) Skipped(step *gherkin.Step) {
if f.canPrintStep(step) {
fmt.Println(cl(step.Token.Text, cyan))
}
}
func (f *pretty) Pending(step *gherkin.Step) {
if f.canPrintStep(step) {
fmt.Println(cl(step.Token.Text, yellow))
}
}
func (f *pretty) Failed(step *gherkin.Step, err error) {
if f.canPrintStep(step) {
fmt.Println(cl(step.Token.Text, red))
} }
} }

Просмотреть файл

@ -64,6 +64,8 @@ func (f StepHandlerFunc) HandleStep(args ...Arg) error {
return f(args...) return f(args...)
} }
var errPending = fmt.Errorf("pending step")
// Suite is an interface which allows various contexts // Suite is an interface which allows various contexts
// to register step definitions and event handlers // to register step definitions and event handlers
type Suite interface { type Suite interface {
@ -73,19 +75,13 @@ type Suite interface {
type suite struct { type suite struct {
steps map[*regexp.Regexp]StepHandler steps map[*regexp.Regexp]StepHandler
features []*gherkin.Feature features []*gherkin.Feature
fmt formatter fmt Formatter
} }
// New initializes a suite which supports the Suite // New initializes a suite which supports the Suite
// interface. The instance is passed around to all // interface. The instance is passed around to all
// context initialization functions from *_test.go files // context initialization functions from *_test.go files
func New() *suite { func New() *suite {
// @TODO: colorize flag help output
flag.StringVar(&cfg.featuresPath, "features", "features", "Path to feature files")
flag.StringVar(&cfg.formatterName, "formatter", "pretty", "Formatter name")
if !flag.Parsed() {
flag.Parse()
}
return &suite{ return &suite{
steps: make(map[*regexp.Regexp]StepHandler), steps: make(map[*regexp.Regexp]StepHandler),
} }
@ -108,6 +104,11 @@ func (s *suite) Step(exp *regexp.Regexp, h StepHandler) {
// Run - runs a godog feature suite // Run - runs a godog feature suite
func (s *suite) Run() { func (s *suite) Run() {
var err error var err error
if !flag.Parsed() {
flag.Parse()
}
fatal(cfg.validate())
s.fmt = cfg.formatter() s.fmt = cfg.formatter()
s.features, err = cfg.features() s.features, err = cfg.features()
fatal(err) fatal(err)
@ -120,7 +121,14 @@ func (s *suite) Run() {
} }
} }
func (s *suite) runStep(step *gherkin.Step) { func (s *suite) runStep(step *gherkin.Step) (err error) {
defer func() {
if e := recover(); e != nil {
err = e.(error)
s.fmt.Failed(step, err)
}
}()
var handler StepHandler var handler StepHandler
var args []Arg var args []Arg
for r, h := range s.steps { for r, h := range s.steps {
@ -133,41 +141,55 @@ func (s *suite) runStep(step *gherkin.Step) {
} }
} }
if handler == nil { if handler == nil {
fmt.Println("PENDING") s.fmt.Pending(step)
return return errPending
} }
defer func() {
if e := recover(); e != nil { if err = handler.HandleStep(args...); err != nil {
fmt.Println("PANIC") s.fmt.Failed(step, err)
}
}()
if err := handler.HandleStep(args...); err != nil {
fmt.Println("ERR")
} else { } else {
fmt.Println("OK") s.fmt.Passed(step)
}
return
}
func (s *suite) runSteps(steps []*gherkin.Step) bool {
var failed bool
for _, step := range steps {
if failed {
s.fmt.Skipped(step)
continue
}
if err := s.runStep(step); err != nil {
failed = true
}
}
return failed
}
func (s *suite) skipSteps(steps []*gherkin.Step) {
for _, step := range steps {
s.fmt.Skipped(step)
} }
} }
func (s *suite) runFeature(f *gherkin.Feature) { func (s *suite) runFeature(f *gherkin.Feature) {
s.fmt.node(f) s.fmt.Node(f)
var background bool var failed bool
for _, scenario := range f.Scenarios { for _, scenario := range f.Scenarios {
if f.Background != nil { // background
if !background { // @TODO: do not print more than once
s.fmt.node(f.Background) if f.Background != nil && !failed {
s.fmt.Node(f.Background)
failed = s.runSteps(f.Background.Steps)
} }
for _, step := range f.Background.Steps {
s.runStep(step) // scenario
if !background { s.fmt.Node(scenario)
s.fmt.node(step) if failed {
} s.skipSteps(scenario.Steps)
} } else {
background = true s.runSteps(scenario.Steps)
}
s.fmt.node(scenario)
for _, step := range scenario.Steps {
s.runStep(step)
s.fmt.node(step)
} }
} }
} }