add suite events and possibility to listen to them

Этот коммит содержится в:
gedi 2015-06-18 14:33:34 +03:00
родитель 0f6ec04318
коммит 00fab00e22
5 изменённых файлов: 289 добавлений и 62 удалений

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

@ -13,6 +13,13 @@ type Arg struct {
value interface{} value interface{}
} }
// StepArgument func creates a step argument.
// used in cases when calling another step from
// within a StepHandlerFunc
func StepArgument(value interface{}) *Arg {
return &Arg{value: value}
}
// Float64 converts an argument to float64 // Float64 converts an argument to float64
// or panics if unable to convert it // or panics if unable to convert it
func (a *Arg) Float64() float64 { func (a *Arg) Float64() float64 {

103
events.go Обычный файл
Просмотреть файл

@ -0,0 +1,103 @@
package godog
import "github.com/DATA-DOG/godog/gherkin"
// BeforeSuiteHandler can be registered
// in Suite to be executed once before
// running a feature suite
type BeforeSuiteHandler interface {
HandleBeforeSuite()
}
// BeforeSuiteHandlerFunc is a function implementing
// BeforeSuiteHandler interface
type BeforeSuiteHandlerFunc func()
// HandleBeforeSuite is called once before suite
func (f BeforeSuiteHandlerFunc) HandleBeforeSuite() {
f()
}
// BeforeScenarioHandler can be registered
// in Suite to be executed before every scenario
// which will be run
type BeforeScenarioHandler interface {
HandleBeforeScenario(scenario *gherkin.Scenario)
}
// BeforeScenarioHandlerFunc is a function implementing
// BeforeScenarioHandler interface
type BeforeScenarioHandlerFunc func(scenario *gherkin.Scenario)
// HandleBeforeScenario is called with a *gherkin.Scenario argument
// for before every scenario which is run by suite
func (f BeforeScenarioHandlerFunc) HandleBeforeScenario(scenario *gherkin.Scenario) {
f(scenario)
}
// BeforeStepHandler can be registered
// in Suite to be executed before every step
// which will be run
type BeforeStepHandler interface {
HandleBeforeStep(step *gherkin.Step)
}
// BeforeStepHandlerFunc is a function implementing
// BeforeStepHandler interface
type BeforeStepHandlerFunc func(step *gherkin.Step)
// HandleBeforeStep is called with a *gherkin.Step argument
// for before every step which is run by suite
func (f BeforeStepHandlerFunc) HandleBeforeStep(step *gherkin.Step) {
f(step)
}
// AfterStepHandler can be registered
// in Suite to be executed after every step
// which will be run
type AfterStepHandler interface {
HandleAfterStep(step *gherkin.Step, status Status)
}
// AfterStepHandlerFunc is a function implementing
// AfterStepHandler interface
type AfterStepHandlerFunc func(step *gherkin.Step, status Status)
// HandleAfterStep is called with a *gherkin.Step argument
// for after every step which is run by suite
func (f AfterStepHandlerFunc) HandleAfterStep(step *gherkin.Step, status Status) {
f(step, status)
}
// AfterScenarioHandler can be registered
// in Suite to be executed after every scenario
// which will be run
type AfterScenarioHandler interface {
HandleAfterScenario(scenario *gherkin.Scenario, status Status)
}
// AfterScenarioHandlerFunc is a function implementing
// AfterScenarioHandler interface
type AfterScenarioHandlerFunc func(scenario *gherkin.Scenario, status Status)
// HandleAfterScenario is called with a *gherkin.Scenario argument
// for after every scenario which is run by suite
func (f AfterScenarioHandlerFunc) HandleAfterScenario(scenario *gherkin.Scenario, status Status) {
f(scenario, status)
}
// AfterSuiteHandler can be registered
// in Suite to be executed once after
// running a feature suite
type AfterSuiteHandler interface {
HandleAfterSuite()
}
// AfterSuiteHandlerFunc is a function implementing
// AfterSuiteHandler interface
type AfterSuiteHandlerFunc func()
// HandleAfterSuite is called once after suite
func (f AfterSuiteHandlerFunc) HandleAfterSuite() {
f()
}

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

@ -4,10 +4,9 @@ Feature: suite hooks
I need to provide a way to hook into these events I need to provide a way to hook into these events
Background: Background:
Given I have a before scenario hook Given I'm listening to suite events
And a feature path "features/load_features.feature:6"
And I parse features
Scenario: triggers before scenario hook Scenario: triggers before scenario hook
When I run features Given a feature path "features/load_features.feature:6"
Then I should have a scenario "load features within path" recorded in the hook When I run feature suite
Then there was event triggered before scenario "load features within path"

146
suite.go
Просмотреть файл

@ -11,22 +11,27 @@ import (
"github.com/DATA-DOG/godog/gherkin" "github.com/DATA-DOG/godog/gherkin"
) )
type stepsStatus int // Status represents a step status
type Status int
const ( const (
stepsStatusPassed stepsStatus = iota invalid Status = iota
stepsStatusFailed Passed
stepsStatusUndefined Failed
Undefined
) )
type BeforeScenarioHandler interface { // String represents status as string
BeforeScenario(scenario *gherkin.Scenario) func (s Status) String() string {
} switch s {
case Passed:
type BeforeScenarioHandlerFunc func(scenario *gherkin.Scenario) return "passed"
case Failed:
func (f BeforeScenarioHandlerFunc) BeforeScenario(scenario *gherkin.Scenario) { return "failed"
f(scenario) case Undefined:
return "undefined"
}
return "invalid"
} }
// Objects implementing the StepHandler interface can be // Objects implementing the StepHandler interface can be
@ -66,16 +71,29 @@ type stepMatchHandler struct {
// to register step definitions and event handlers // to register step definitions and event handlers
type Suite interface { type Suite interface {
Step(expr *regexp.Regexp, h StepHandler) Step(expr *regexp.Regexp, h StepHandler)
// suite events
BeforeSuite(h BeforeSuiteHandler)
BeforeScenario(h BeforeScenarioHandler) BeforeScenario(h BeforeScenarioHandler)
BeforeStep(h BeforeStepHandler)
AfterStep(h AfterStepHandler)
AfterScenario(h AfterScenarioHandler)
AfterSuite(h AfterSuiteHandler)
} }
type suite struct { type suite struct {
beforeScenarioHandlers []BeforeScenarioHandler stepHandlers []*stepMatchHandler
stepHandlers []*stepMatchHandler features []*gherkin.Feature
features []*gherkin.Feature fmt Formatter
fmt Formatter
failed bool failed bool
// suite event handlers
beforeSuiteHandlers []BeforeSuiteHandler
beforeScenarioHandlers []BeforeScenarioHandler
beforeStepHandlers []BeforeStepHandler
afterStepHandlers []AfterStepHandler
afterScenarioHandlers []AfterScenarioHandler
afterSuiteHandlers []AfterSuiteHandler
} }
// New initializes a suite which supports the Suite // New initializes a suite which supports the Suite
@ -102,10 +120,42 @@ func (s *suite) Step(expr *regexp.Regexp, h StepHandler) {
}) })
} }
// BeforeSuite registers a BeforeSuiteHandler
// to be run once before suite runner
func (s *suite) BeforeSuite(h BeforeSuiteHandler) {
s.beforeSuiteHandlers = append(s.beforeSuiteHandlers, h)
}
// BeforeScenario registers a BeforeScenarioHandler
// to be run before every scenario
func (s *suite) BeforeScenario(h BeforeScenarioHandler) { func (s *suite) BeforeScenario(h BeforeScenarioHandler) {
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, h) s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, h)
} }
// BeforeStep registers a BeforeStepHandler
// to be run before every scenario
func (s *suite) BeforeStep(h BeforeStepHandler) {
s.beforeStepHandlers = append(s.beforeStepHandlers, h)
}
// AfterStep registers an AfterStepHandler
// to be run after every scenario
func (s *suite) AfterStep(h AfterStepHandler) {
s.afterStepHandlers = append(s.afterStepHandlers, h)
}
// AfterScenario registers an AfterScenarioHandler
// to be run after every scenario
func (s *suite) AfterScenario(h AfterScenarioHandler) {
s.afterScenarioHandlers = append(s.afterScenarioHandlers, h)
}
// AfterSuite registers a AfterSuiteHandler
// to be run once after suite runner
func (s *suite) AfterSuite(h AfterSuiteHandler) {
s.afterSuiteHandlers = append(s.afterSuiteHandlers, h)
}
// 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
@ -129,6 +179,19 @@ func (s *suite) Run() {
s.features, err = cfg.features() s.features, err = cfg.features()
fatal(err) fatal(err)
s.run()
if s.failed {
os.Exit(1)
}
}
func (s *suite) run() {
// run before suite handlers
for _, h := range s.beforeSuiteHandlers {
h.HandleBeforeSuite()
}
// run features
for _, f := range s.features { for _, f := range s.features {
s.runFeature(f) s.runFeature(f)
if s.failed && cfg.stopOnFailure { if s.failed && cfg.stopOnFailure {
@ -136,10 +199,11 @@ func (s *suite) Run() {
break break
} }
} }
s.fmt.Summary() // run after suite handlers
if s.failed { for _, h := range s.afterSuiteHandlers {
os.Exit(1) h.HandleAfterSuite()
} }
s.fmt.Summary()
} }
func (s *suite) runStep(step *gherkin.Step) (err error) { func (s *suite) runStep(step *gherkin.Step) (err error) {
@ -180,18 +244,31 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
return return
} }
func (s *suite) runSteps(steps []*gherkin.Step) (st stepsStatus) { func (s *suite) runSteps(steps []*gherkin.Step) (st Status) {
for _, step := range steps { for _, step := range steps {
if st != stepsStatusPassed { if st == Failed || st == Undefined {
s.fmt.Skipped(step) s.fmt.Skipped(step)
continue continue
} }
// run before step handlers
for _, h := range s.beforeStepHandlers {
h.HandleBeforeStep(step)
}
err := s.runStep(step) err := s.runStep(step)
switch { switch err {
case err == errPending: case errPending:
st = stepsStatusUndefined st = Undefined
case err != nil: case nil:
st = stepsStatusFailed st = Passed
default:
st = Failed
}
// run after step handlers
for _, h := range s.afterStepHandlers {
h.HandleAfterStep(step, st)
} }
} }
return return
@ -206,11 +283,11 @@ func (s *suite) skipSteps(steps []*gherkin.Step) {
func (s *suite) runFeature(f *gherkin.Feature) { func (s *suite) runFeature(f *gherkin.Feature) {
s.fmt.Node(f) s.fmt.Node(f)
for _, scenario := range f.Scenarios { for _, scenario := range f.Scenarios {
var status stepsStatus var status Status
// run before scenario handlers // run before scenario handlers
for _, h := range s.beforeScenarioHandlers { for _, h := range s.beforeScenarioHandlers {
h.BeforeScenario(scenario) h.HandleBeforeScenario(scenario)
} }
// background // background
@ -222,15 +299,20 @@ func (s *suite) runFeature(f *gherkin.Feature) {
// scenario // scenario
s.fmt.Node(scenario) s.fmt.Node(scenario)
switch { switch {
case status == stepsStatusFailed: case status == Failed:
s.skipSteps(scenario.Steps) s.skipSteps(scenario.Steps)
case status == stepsStatusUndefined: case status == Undefined:
s.skipSteps(scenario.Steps) s.skipSteps(scenario.Steps)
default: case status == invalid:
status = s.runSteps(scenario.Steps) status = s.runSteps(scenario.Steps)
} }
if status == stepsStatusFailed { // run after scenario handlers
for _, h := range s.afterScenarioHandlers {
h.HandleAfterScenario(scenario, status)
}
if status == Failed {
s.failed = true s.failed = true
if cfg.stopOnFailure { if cfg.stopOnFailure {
return return

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

@ -3,28 +3,35 @@ package godog
import ( import (
"fmt" "fmt"
"regexp" "regexp"
"strings"
"github.com/DATA-DOG/godog/gherkin" "github.com/DATA-DOG/godog/gherkin"
) )
type suiteFeature struct { type firedEvent struct {
suite name string
// for hook tests args []interface{}
befScenarioHook *gherkin.Scenario
} }
func (s *suiteFeature) BeforeScenario(scenario *gherkin.Scenario) { type suiteFeature struct {
suite
events []*firedEvent
}
func (s *suiteFeature) HandleBeforeScenario(scenario *gherkin.Scenario) {
// reset feature paths // reset feature paths
cfg.paths = []string{} cfg.paths = []string{}
// reset hook test references // reset event stack
s.befScenarioHook = nil s.events = []*firedEvent{}
// reset formatter, which collects all details // reset formatter, which collects all details
s.fmt = &testFormatter{} s.fmt = &testFormatter{}
} }
func (s *suiteFeature) iHaveBeforeScenarioHook(args ...*Arg) error { func (s *suiteFeature) iAmListeningToSuiteEvents(args ...*Arg) error {
s.suite.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) { s.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) {
s.befScenarioHook = scenario s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{
scenario,
}})
})) }))
return nil return nil
} }
@ -59,10 +66,11 @@ func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) error {
return nil return nil
} }
func (s *suiteFeature) iRunFeatures(args ...*Arg) error { func (s *suiteFeature) iRunFeatureSuite(args ...*Arg) error {
for _, f := range s.features { if err := s.parseFeatures(); err != nil {
s.runFeature(f) return err
} }
s.run()
return nil return nil
} }
@ -77,14 +85,39 @@ func (s *suiteFeature) numScenariosRegistered(args ...*Arg) (err error) {
return return
} }
func (s *suiteFeature) iShouldHaveScenarioRecordedInHook(args ...*Arg) (err error) { func (s *suiteFeature) thereWereNumEventsFired(args ...*Arg) error {
if s.befScenarioHook == nil { var num int
return fmt.Errorf("there was no scenario executed in before hook") for _, event := range s.events {
if event.name == args[2].String() {
num++
}
} }
if s.befScenarioHook.Title != args[0].String() { if num != args[1].Int() {
err = fmt.Errorf(`expected "%s" scenario to be run in hook, but got "%s"`, args[0].String(), s.befScenarioHook.Title) return fmt.Errorf("expected %d %s events to be fired, but got %d", args[1].Int(), args[2].String(), num)
} }
return return nil
}
func (s *suiteFeature) thereWasEventTriggeredBeforeScenario(args ...*Arg) error {
var found []string
for _, event := range s.events {
if event.name != "BeforeScenario" {
continue
}
scenario := event.args[0].(*gherkin.Scenario)
if scenario.Title == args[0].String() {
return nil
}
found = append(found, scenario.Title)
}
if len(found) == 0 {
return fmt.Errorf("before scenario event was never triggered or listened")
}
return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, args[0].String(), `"`+strings.Join(found, `", "`)+`"`)
} }
func SuiteContext(g Suite) { func SuiteContext(g Suite) {
@ -107,12 +140,15 @@ func SuiteContext(g Suite) {
regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`), regexp.MustCompile(`^I should have ([\d]+) scenarios? registered$`),
StepHandlerFunc(s.numScenariosRegistered)) StepHandlerFunc(s.numScenariosRegistered))
g.Step( g.Step(
regexp.MustCompile(`^I have a before scenario hook$`), regexp.MustCompile(`^I'm listening to suite events$`),
StepHandlerFunc(s.iHaveBeforeScenarioHook)) StepHandlerFunc(s.iAmListeningToSuiteEvents))
g.Step( g.Step(
regexp.MustCompile(`^I run features$`), regexp.MustCompile(`^I run feature suite$`),
StepHandlerFunc(s.iRunFeatures)) StepHandlerFunc(s.iRunFeatureSuite))
g.Step( g.Step(
regexp.MustCompile(`^I should have a scenario "([^"]*)" recorded in the hook$`), regexp.MustCompile(`^there (was|were) ([\d]+) "([^"]*)" events? fired$`),
StepHandlerFunc(s.iShouldHaveScenarioRecordedInHook)) StepHandlerFunc(s.thereWereNumEventsFired))
g.Step(
regexp.MustCompile(`^there was event triggered before scenario "([^"]*)"$`),
StepHandlerFunc(s.thereWasEventTriggeredBeforeScenario))
} }