Allow suite-level configuration of steps and hooks (#453)

* Allow suite-level configuration of steps and hooks

* Fix a few typos

* Update CHANGELOG.md

* Add test

* Run scenario in same goroutine to preserve stack when concurrency is disabled

* Remove redundant check
Этот коммит содержится в:
Viacheslav Poturaev 2022-01-12 23:40:41 +01:00 коммит произвёл GitHub
родитель a6fef3f171
коммит 9baac0fdfa
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 67 добавлений и 19 удалений

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

@ -8,6 +8,17 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
--- ---
## [Unreleased]
### Added
- Allow suite-level configuration of steps and hooks ([453](https://github.com/cucumber/godog/pull/453) - [vearutop])
## Changed
- Run scenarios in the same goroutine if concurrency is disabled (([453](https://github.com/cucumber/godog/pull/453) - [vearutop]))
## [v0.12.3] ## [v0.12.3]
### Added ### Added

35
run.go
Просмотреть файл

@ -57,7 +57,16 @@ func (r *runner) concurrent(rate int) (failed bool) {
fmt.SetStorage(r.storage) fmt.SetStorage(r.storage)
} }
testSuiteContext := TestSuiteContext{} testSuiteContext := TestSuiteContext{
suite: &suite{
fmt: r.fmt,
randomSeed: r.randomSeed,
strict: r.strict,
storage: r.storage,
defaultContext: r.defaultContext,
testingT: r.testingT,
},
}
if r.testSuiteInitializer != nil { if r.testSuiteInitializer != nil {
r.testSuiteInitializer(&testSuiteContext) r.testSuiteInitializer(&testSuiteContext)
} }
@ -93,7 +102,7 @@ func (r *runner) concurrent(rate int) (failed bool) {
r.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.Content) r.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.Content)
} }
go func(fail *bool, pickle *messages.Pickle) { runPickle := func(fail *bool, pickle *messages.Pickle) {
defer func() { defer func() {
<-queue // free a space in queue <-queue // free a space in queue
}() }()
@ -102,17 +111,11 @@ func (r *runner) concurrent(rate int) (failed bool) {
return return
} }
suite := &suite{ // Copy base suite.
fmt: r.fmt, suite := *testSuiteContext.suite
randomSeed: r.randomSeed,
strict: r.strict,
storage: r.storage,
defaultContext: r.defaultContext,
testingT: r.testingT,
}
if r.scenarioInitializer != nil { if r.scenarioInitializer != nil {
sc := ScenarioContext{suite: suite} sc := ScenarioContext{suite: &suite}
r.scenarioInitializer(&sc) r.scenarioInitializer(&sc)
} }
@ -122,7 +125,15 @@ func (r *runner) concurrent(rate int) (failed bool) {
*fail = true *fail = true
copyLock.Unlock() copyLock.Unlock()
} }
}(&failed, &pickle) }
if rate == 1 {
// Running within the same goroutine for concurrency 1
// to preserve original stacks and simplify debugging.
runPickle(&failed, &pickle)
} else {
go runPickle(&failed, &pickle)
}
} }
} }

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

@ -2,6 +2,7 @@ package godog
import ( import (
"bytes" "bytes"
"context"
"fmt" "fmt"
"io" "io"
"io/ioutil" "io/ioutil"
@ -77,9 +78,22 @@ func Test_FailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
ft := models.Feature{GherkinDocument: gd} ft := models.Feature{GherkinDocument: gd}
ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId) ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
var beforeScenarioFired, afterScenarioFired int
r := runner{ r := runner{
fmt: formatters.ProgressFormatterFunc("progress", ioutil.Discard), fmt: formatters.ProgressFormatterFunc("progress", ioutil.Discard),
features: []*models.Feature{&ft}, features: []*models.Feature{&ft},
testSuiteInitializer: func(ctx *TestSuiteContext) {
ctx.ScenarioContext().Before(func(ctx context.Context, sc *Scenario) (context.Context, error) {
beforeScenarioFired++
return ctx, nil
})
ctx.ScenarioContext().After(func(ctx context.Context, sc *Scenario, err error) (context.Context, error) {
afterScenarioFired++
return ctx, nil
})
},
scenarioInitializer: func(ctx *ScenarioContext) { scenarioInitializer: func(ctx *ScenarioContext) {
ctx.Step(`^one$`, func() error { return nil }) ctx.Step(`^one$`, func() error { return nil })
ctx.Step(`^two$`, func() error { return ErrPending }) ctx.Step(`^two$`, func() error { return ErrPending })
@ -94,10 +108,14 @@ func Test_FailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
failed := r.concurrent(1) failed := r.concurrent(1)
require.False(t, failed) require.False(t, failed)
assert.Equal(t, 1, beforeScenarioFired)
assert.Equal(t, 1, afterScenarioFired)
r.strict = true r.strict = true
failed = r.concurrent(1) failed = r.concurrent(1)
require.True(t, failed) require.True(t, failed)
assert.Equal(t, 2, beforeScenarioFired)
assert.Equal(t, 2, afterScenarioFired)
} }
func Test_FailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) { func Test_FailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {

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

@ -6,11 +6,10 @@ import (
"reflect" "reflect"
"regexp" "regexp"
"github.com/cucumber/messages-go/v16"
"github.com/cucumber/godog/formatters" "github.com/cucumber/godog/formatters"
"github.com/cucumber/godog/internal/builder" "github.com/cucumber/godog/internal/builder"
"github.com/cucumber/godog/internal/models" "github.com/cucumber/godog/internal/models"
"github.com/cucumber/messages-go/v16"
) )
// GherkinDocument represents gherkin document. // GherkinDocument represents gherkin document.
@ -66,6 +65,8 @@ type Table = messages.PickleTable
type TestSuiteContext struct { type TestSuiteContext struct {
beforeSuiteHandlers []func() beforeSuiteHandlers []func()
afterSuiteHandlers []func() afterSuiteHandlers []func()
suite *suite
} }
// BeforeSuite registers a function or method // BeforeSuite registers a function or method
@ -83,6 +84,13 @@ func (ctx *TestSuiteContext) AfterSuite(fn func()) {
ctx.afterSuiteHandlers = append(ctx.afterSuiteHandlers, fn) ctx.afterSuiteHandlers = append(ctx.afterSuiteHandlers, fn)
} }
// ScenarioContext allows registering scenario hooks.
func (ctx *TestSuiteContext) ScenarioContext() *ScenarioContext {
return &ScenarioContext{
suite: ctx.suite,
}
}
// ScenarioContext allows various contexts // ScenarioContext allows various contexts
// to register steps and event handlers. // to register steps and event handlers.
// //
@ -103,11 +111,11 @@ type StepContext struct {
suite *suite suite *suite
} }
// Before registers a a function or method // Before registers a function or method
// to be run before every scenario. // to be run before every scenario.
// //
// It is a good practice to restore the default state // It is a good practice to restore the default state
// before every scenario so it would be isolated from // before every scenario, so it would be isolated from
// any kind of state. // any kind of state.
func (ctx ScenarioContext) Before(h BeforeScenarioHook) { func (ctx ScenarioContext) Before(h BeforeScenarioHook) {
ctx.suite.beforeScenarioHandlers = append(ctx.suite.beforeScenarioHandlers, h) ctx.suite.beforeScenarioHandlers = append(ctx.suite.beforeScenarioHandlers, h)
@ -139,7 +147,7 @@ func (ctx StepContext) Before(h BeforeStepHook) {
// BeforeStepHook defines a hook before step. // BeforeStepHook defines a hook before step.
type BeforeStepHook func(ctx context.Context, st *Step) (context.Context, error) type BeforeStepHook func(ctx context.Context, st *Step) (context.Context, error)
// After registers an function or method // After registers a function or method
// to be run after every step. // to be run after every step.
// //
// It may be convenient to return a different kind of error // It may be convenient to return a different kind of error
@ -171,7 +179,7 @@ func (ctx *ScenarioContext) BeforeScenario(fn func(sc *Scenario)) {
}) })
} }
// AfterScenario registers an function or method // AfterScenario registers a function or method
// to be run after every scenario. // to be run after every scenario.
// //
// Deprecated: use After. // Deprecated: use After.
@ -195,7 +203,7 @@ func (ctx *ScenarioContext) BeforeStep(fn func(st *Step)) {
}) })
} }
// AfterStep registers an function or method // AfterStep registers a function or method
// to be run after every step. // to be run after every step.
// //
// It may be convenient to return a different kind of error // It may be convenient to return a different kind of error