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{}
}
// 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
// or panics if unable to convert it
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
Background:
Given I have a before scenario hook
And a feature path "features/load_features.feature:6"
And I parse features
Given I'm listening to suite events
Scenario: triggers before scenario hook
When I run features
Then I should have a scenario "load features within path" recorded in the hook
Given a feature path "features/load_features.feature:6"
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"
)
type stepsStatus int
// Status represents a step status
type Status int
const (
stepsStatusPassed stepsStatus = iota
stepsStatusFailed
stepsStatusUndefined
invalid Status = iota
Passed
Failed
Undefined
)
type BeforeScenarioHandler interface {
BeforeScenario(scenario *gherkin.Scenario)
}
type BeforeScenarioHandlerFunc func(scenario *gherkin.Scenario)
func (f BeforeScenarioHandlerFunc) BeforeScenario(scenario *gherkin.Scenario) {
f(scenario)
// String represents status as string
func (s Status) String() string {
switch s {
case Passed:
return "passed"
case Failed:
return "failed"
case Undefined:
return "undefined"
}
return "invalid"
}
// Objects implementing the StepHandler interface can be
@ -66,16 +71,29 @@ type stepMatchHandler struct {
// to register step definitions and event handlers
type Suite interface {
Step(expr *regexp.Regexp, h StepHandler)
// suite events
BeforeSuite(h BeforeSuiteHandler)
BeforeScenario(h BeforeScenarioHandler)
BeforeStep(h BeforeStepHandler)
AfterStep(h AfterStepHandler)
AfterScenario(h AfterScenarioHandler)
AfterSuite(h AfterSuiteHandler)
}
type suite struct {
beforeScenarioHandlers []BeforeScenarioHandler
stepHandlers []*stepMatchHandler
features []*gherkin.Feature
fmt Formatter
stepHandlers []*stepMatchHandler
features []*gherkin.Feature
fmt Formatter
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
@ -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) {
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
func (s *suite) Run() {
var err error
@ -129,6 +179,19 @@ func (s *suite) Run() {
s.features, err = cfg.features()
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 {
s.runFeature(f)
if s.failed && cfg.stopOnFailure {
@ -136,10 +199,11 @@ func (s *suite) Run() {
break
}
}
s.fmt.Summary()
if s.failed {
os.Exit(1)
// run after suite handlers
for _, h := range s.afterSuiteHandlers {
h.HandleAfterSuite()
}
s.fmt.Summary()
}
func (s *suite) runStep(step *gherkin.Step) (err error) {
@ -180,18 +244,31 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
return
}
func (s *suite) runSteps(steps []*gherkin.Step) (st stepsStatus) {
func (s *suite) runSteps(steps []*gherkin.Step) (st Status) {
for _, step := range steps {
if st != stepsStatusPassed {
if st == Failed || st == Undefined {
s.fmt.Skipped(step)
continue
}
// run before step handlers
for _, h := range s.beforeStepHandlers {
h.HandleBeforeStep(step)
}
err := s.runStep(step)
switch {
case err == errPending:
st = stepsStatusUndefined
case err != nil:
st = stepsStatusFailed
switch err {
case errPending:
st = Undefined
case nil:
st = Passed
default:
st = Failed
}
// run after step handlers
for _, h := range s.afterStepHandlers {
h.HandleAfterStep(step, st)
}
}
return
@ -206,11 +283,11 @@ func (s *suite) skipSteps(steps []*gherkin.Step) {
func (s *suite) runFeature(f *gherkin.Feature) {
s.fmt.Node(f)
for _, scenario := range f.Scenarios {
var status stepsStatus
var status Status
// run before scenario handlers
for _, h := range s.beforeScenarioHandlers {
h.BeforeScenario(scenario)
h.HandleBeforeScenario(scenario)
}
// background
@ -222,15 +299,20 @@ func (s *suite) runFeature(f *gherkin.Feature) {
// scenario
s.fmt.Node(scenario)
switch {
case status == stepsStatusFailed:
case status == Failed:
s.skipSteps(scenario.Steps)
case status == stepsStatusUndefined:
case status == Undefined:
s.skipSteps(scenario.Steps)
default:
case status == invalid:
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
if cfg.stopOnFailure {
return

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

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