From b69fa26b8bf4c71569e8fa2f43307da0e2891a29 Mon Sep 17 00:00:00 2001 From: gedi Date: Mon, 15 Jun 2015 11:15:06 +0300 Subject: [PATCH] continue with output formatter implementation --- config.go | 45 +++++++++++++++++++++---- formatter.go | 87 +++++++++++++++++++++++++++++++++++++++++++------ suite.go | 92 ++++++++++++++++++++++++++++++++-------------------- 3 files changed, 173 insertions(+), 51 deletions(-) diff --git a/config.go b/config.go index feb57bd..e6e16d9 100644 --- a/config.go +++ b/config.go @@ -1,6 +1,7 @@ package godog import ( + "flag" "fmt" "os" "path/filepath" @@ -9,14 +10,37 @@ import ( "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, ®isteredFormatter{ + name: name, + fmt: f, + }) +} + 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 { featuresPath string formatterName string } func (c config) validate() error { + // feature path inf, err := os.Stat(c.featuresPath) if err != nil { return err @@ -24,11 +48,20 @@ func (c config) validate() error { if !inf.IsDir() { return fmt.Errorf("feature path \"%s\" is not a directory.", c.featuresPath) } - switch c.formatterName { - case "pretty": - // ok - default: - return fmt.Errorf("Unsupported formatter name: %s", c.formatterName) + + // formatter + var found bool + var names []string + 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 } @@ -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{} } diff --git a/formatter.go b/formatter.go index 12525e2..7d046eb 100644 --- a/formatter.go +++ b/formatter.go @@ -2,26 +2,93 @@ package godog import ( "fmt" + "strings" "github.com/DATA-DOG/godog/gherkin" ) -type formatter interface { - node(interface{}) +func init() { + 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) { case *gherkin.Feature: - fmt.Println(bcl("Feature: ", white) + t.Title) - fmt.Println(t.Description + "\n") + f.feature = t + 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: - fmt.Println(bcl("Background:", white)) + f.background = t + fmt.Println("\n" + bcl("Background:", white)) case *gherkin.Scenario: - fmt.Println(bcl("Scenario: ", white) + t.Title) - case *gherkin.Step: - fmt.Println(bcl(t.Token.Text, green)) + fmt.Println("\n"+strings.Repeat(" ", t.Token.Indent)+bcl("Scenario: ", white)+t.Title, f.line(t.Token)) + } +} + +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)) } } diff --git a/suite.go b/suite.go index 008c5fd..7a804be 100644 --- a/suite.go +++ b/suite.go @@ -64,6 +64,8 @@ func (f StepHandlerFunc) HandleStep(args ...Arg) error { return f(args...) } +var errPending = fmt.Errorf("pending step") + // Suite is an interface which allows various contexts // to register step definitions and event handlers type Suite interface { @@ -73,19 +75,13 @@ type Suite interface { type suite struct { steps map[*regexp.Regexp]StepHandler features []*gherkin.Feature - fmt formatter + fmt Formatter } // New initializes a suite which supports the Suite // interface. The instance is passed around to all // context initialization functions from *_test.go files 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{ 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 func (s *suite) Run() { var err error + if !flag.Parsed() { + flag.Parse() + } + fatal(cfg.validate()) + s.fmt = cfg.formatter() s.features, err = cfg.features() 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 args []Arg for r, h := range s.steps { @@ -133,41 +141,55 @@ func (s *suite) runStep(step *gherkin.Step) { } } if handler == nil { - fmt.Println("PENDING") - return + s.fmt.Pending(step) + return errPending } - defer func() { - if e := recover(); e != nil { - fmt.Println("PANIC") - } - }() - if err := handler.HandleStep(args...); err != nil { - fmt.Println("ERR") + + if err = handler.HandleStep(args...); err != nil { + s.fmt.Failed(step, err) } 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) { - s.fmt.node(f) - var background bool + s.fmt.Node(f) + var failed bool for _, scenario := range f.Scenarios { - if f.Background != nil { - if !background { - s.fmt.node(f.Background) - } - for _, step := range f.Background.Steps { - s.runStep(step) - if !background { - s.fmt.node(step) - } - } - background = true + // background + // @TODO: do not print more than once + if f.Background != nil && !failed { + s.fmt.Node(f.Background) + failed = s.runSteps(f.Background.Steps) } - s.fmt.node(scenario) - for _, step := range scenario.Steps { - s.runStep(step) - s.fmt.node(step) + + // scenario + s.fmt.Node(scenario) + if failed { + s.skipSteps(scenario.Steps) + } else { + s.runSteps(scenario.Steps) } } }