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
Этот коммит содержится в:
родитель
a6fef3f171
коммит
9baac0fdfa
4 изменённых файлов: 67 добавлений и 19 удалений
11
CHANGELOG.md
11
CHANGELOG.md
|
@ -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
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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
18
run_test.go
18
run_test.go
|
@ -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
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче