Skip to content

Commit 9baac0f

Browse files
authored
Allow suite-level configuration of steps and hooks (cucumber#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
1 parent a6fef3f commit 9baac0f

File tree

4 files changed

+67
-19
lines changed

4 files changed

+67
-19
lines changed

CHANGELOG.md

+11
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,17 @@ This document is formatted according to the principles of [Keep A CHANGELOG](htt
88

99
---
1010

11+
## [Unreleased]
12+
13+
### Added
14+
15+
- Allow suite-level configuration of steps and hooks ([453](https://github.com/cucumber/godog/pull/453) - [vearutop])
16+
17+
## Changed
18+
19+
- Run scenarios in the same goroutine if concurrency is disabled (([453](https://github.com/cucumber/godog/pull/453) - [vearutop]))
20+
21+
1122
## [v0.12.3]
1223

1324
### Added

run.go

+23-12
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,16 @@ func (r *runner) concurrent(rate int) (failed bool) {
5757
fmt.SetStorage(r.storage)
5858
}
5959

60-
testSuiteContext := TestSuiteContext{}
60+
testSuiteContext := TestSuiteContext{
61+
suite: &suite{
62+
fmt: r.fmt,
63+
randomSeed: r.randomSeed,
64+
strict: r.strict,
65+
storage: r.storage,
66+
defaultContext: r.defaultContext,
67+
testingT: r.testingT,
68+
},
69+
}
6170
if r.testSuiteInitializer != nil {
6271
r.testSuiteInitializer(&testSuiteContext)
6372
}
@@ -93,7 +102,7 @@ func (r *runner) concurrent(rate int) (failed bool) {
93102
r.fmt.Feature(ft.GherkinDocument, ft.Uri, ft.Content)
94103
}
95104

96-
go func(fail *bool, pickle *messages.Pickle) {
105+
runPickle := func(fail *bool, pickle *messages.Pickle) {
97106
defer func() {
98107
<-queue // free a space in queue
99108
}()
@@ -102,17 +111,11 @@ func (r *runner) concurrent(rate int) (failed bool) {
102111
return
103112
}
104113

105-
suite := &suite{
106-
fmt: r.fmt,
107-
randomSeed: r.randomSeed,
108-
strict: r.strict,
109-
storage: r.storage,
110-
defaultContext: r.defaultContext,
111-
testingT: r.testingT,
112-
}
114+
// Copy base suite.
115+
suite := *testSuiteContext.suite
113116

114117
if r.scenarioInitializer != nil {
115-
sc := ScenarioContext{suite: suite}
118+
sc := ScenarioContext{suite: &suite}
116119
r.scenarioInitializer(&sc)
117120
}
118121

@@ -122,7 +125,15 @@ func (r *runner) concurrent(rate int) (failed bool) {
122125
*fail = true
123126
copyLock.Unlock()
124127
}
125-
}(&failed, &pickle)
128+
}
129+
130+
if rate == 1 {
131+
// Running within the same goroutine for concurrency 1
132+
// to preserve original stacks and simplify debugging.
133+
runPickle(&failed, &pickle)
134+
} else {
135+
go runPickle(&failed, &pickle)
136+
}
126137
}
127138
}
128139

run_test.go

+18
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package godog
22

33
import (
44
"bytes"
5+
"context"
56
"fmt"
67
"io"
78
"io/ioutil"
@@ -77,9 +78,22 @@ func Test_FailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
7778
ft := models.Feature{GherkinDocument: gd}
7879
ft.Pickles = gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
7980

81+
var beforeScenarioFired, afterScenarioFired int
82+
8083
r := runner{
8184
fmt: formatters.ProgressFormatterFunc("progress", ioutil.Discard),
8285
features: []*models.Feature{&ft},
86+
testSuiteInitializer: func(ctx *TestSuiteContext) {
87+
ctx.ScenarioContext().Before(func(ctx context.Context, sc *Scenario) (context.Context, error) {
88+
beforeScenarioFired++
89+
return ctx, nil
90+
})
91+
92+
ctx.ScenarioContext().After(func(ctx context.Context, sc *Scenario, err error) (context.Context, error) {
93+
afterScenarioFired++
94+
return ctx, nil
95+
})
96+
},
8397
scenarioInitializer: func(ctx *ScenarioContext) {
8498
ctx.Step(`^one$`, func() error { return nil })
8599
ctx.Step(`^two$`, func() error { return ErrPending })
@@ -94,10 +108,14 @@ func Test_FailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
94108

95109
failed := r.concurrent(1)
96110
require.False(t, failed)
111+
assert.Equal(t, 1, beforeScenarioFired)
112+
assert.Equal(t, 1, afterScenarioFired)
97113

98114
r.strict = true
99115
failed = r.concurrent(1)
100116
require.True(t, failed)
117+
assert.Equal(t, 2, beforeScenarioFired)
118+
assert.Equal(t, 2, afterScenarioFired)
101119
}
102120

103121
func Test_FailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {

test_context.go

+15-7
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,10 @@ import (
66
"reflect"
77
"regexp"
88

9-
"github.com/cucumber/messages-go/v16"
10-
119
"github.com/cucumber/godog/formatters"
1210
"github.com/cucumber/godog/internal/builder"
1311
"github.com/cucumber/godog/internal/models"
12+
"github.com/cucumber/messages-go/v16"
1413
)
1514

1615
// GherkinDocument represents gherkin document.
@@ -66,6 +65,8 @@ type Table = messages.PickleTable
6665
type TestSuiteContext struct {
6766
beforeSuiteHandlers []func()
6867
afterSuiteHandlers []func()
68+
69+
suite *suite
6970
}
7071

7172
// BeforeSuite registers a function or method
@@ -83,6 +84,13 @@ func (ctx *TestSuiteContext) AfterSuite(fn func()) {
8384
ctx.afterSuiteHandlers = append(ctx.afterSuiteHandlers, fn)
8485
}
8586

87+
// ScenarioContext allows registering scenario hooks.
88+
func (ctx *TestSuiteContext) ScenarioContext() *ScenarioContext {
89+
return &ScenarioContext{
90+
suite: ctx.suite,
91+
}
92+
}
93+
8694
// ScenarioContext allows various contexts
8795
// to register steps and event handlers.
8896
//
@@ -103,11 +111,11 @@ type StepContext struct {
103111
suite *suite
104112
}
105113

106-
// Before registers a a function or method
114+
// Before registers a function or method
107115
// to be run before every scenario.
108116
//
109117
// It is a good practice to restore the default state
110-
// before every scenario so it would be isolated from
118+
// before every scenario, so it would be isolated from
111119
// any kind of state.
112120
func (ctx ScenarioContext) Before(h BeforeScenarioHook) {
113121
ctx.suite.beforeScenarioHandlers = append(ctx.suite.beforeScenarioHandlers, h)
@@ -139,7 +147,7 @@ func (ctx StepContext) Before(h BeforeStepHook) {
139147
// BeforeStepHook defines a hook before step.
140148
type BeforeStepHook func(ctx context.Context, st *Step) (context.Context, error)
141149

142-
// After registers an function or method
150+
// After registers a function or method
143151
// to be run after every step.
144152
//
145153
// It may be convenient to return a different kind of error
@@ -171,7 +179,7 @@ func (ctx *ScenarioContext) BeforeScenario(fn func(sc *Scenario)) {
171179
})
172180
}
173181

174-
// AfterScenario registers an function or method
182+
// AfterScenario registers a function or method
175183
// to be run after every scenario.
176184
//
177185
// Deprecated: use After.
@@ -195,7 +203,7 @@ func (ctx *ScenarioContext) BeforeStep(fn func(st *Step)) {
195203
})
196204
}
197205

198-
// AfterStep registers an function or method
206+
// AfterStep registers a function or method
199207
// to be run after every step.
200208
//
201209
// It may be convenient to return a different kind of error

0 commit comments

Comments
 (0)