@@ -2,6 +2,7 @@ package godog
2
2
3
3
import (
4
4
"context"
5
+ "errors"
5
6
"fmt"
6
7
"reflect"
7
8
"strings"
@@ -27,6 +28,9 @@ var ErrUndefined = fmt.Errorf("step is undefined")
27
28
// step implementation is pending
28
29
var ErrPending = fmt .Errorf ("step implementation is pending" )
29
30
31
+ // ErrSkip should be returned by step definition or a hook if scenario and further steps are to be skipped.
32
+ var ErrSkip = fmt .Errorf ("skipped" )
33
+
30
34
// StepResultStatus describes step result.
31
35
type StepResultStatus = models.StepResultStatus
32
36
@@ -72,34 +76,53 @@ func (s *suite) matchStep(step *messages.PickleStep) *models.StepDefinition {
72
76
return def
73
77
}
74
78
75
- func (s * suite ) runStep (ctx context.Context , pickle * Scenario , step * Step , prevStepErr error , isFirst , isLast bool ) (rctx context.Context , err error ) {
79
+ func (s * suite ) runStep (ctx context.Context , pickle * Scenario , step * Step , scenarioErr error , isFirst , isLast bool ) (rctx context.Context , err error ) {
76
80
var (
77
81
match * models.StepDefinition
78
- sr = models.PickleStepResult { Status : models . Undefined }
82
+ sr = models .NewStepResult ( pickle . Id , step . Id , match )
79
83
)
80
84
81
85
rctx = ctx
86
+ sr .Status = StepUndefined
82
87
83
88
// user multistep definitions may panic
84
89
defer func () {
85
90
if e := recover (); e != nil {
86
- err = & traceError {
87
- msg : fmt .Sprintf ("%v" , e ),
88
- stack : callStack (),
91
+ if err != nil {
92
+ err = & traceError {
93
+ msg : fmt .Sprintf ("%s: %v" , err .Error (), e ),
94
+ stack : callStack (),
95
+ }
96
+ } else {
97
+ err = & traceError {
98
+ msg : fmt .Sprintf ("%v" , e ),
99
+ stack : callStack (),
100
+ }
89
101
}
90
102
}
91
103
92
- earlyReturn := prevStepErr != nil || err == ErrUndefined
93
-
94
- if ! earlyReturn {
95
- sr = models .NewStepResult (pickle .Id , step .Id , match )
104
+ earlyReturn := scenarioErr != nil || err == ErrUndefined
105
+
106
+ switch {
107
+ case errors .Is (err , ErrPending ):
108
+ sr .Status = StepPending
109
+ case errors .Is (err , ErrSkip ) || (err == nil && scenarioErr != nil ):
110
+ sr .Status = StepSkipped
111
+ case errors .Is (err , ErrUndefined ):
112
+ sr .Status = StepUndefined
113
+ case err != nil :
114
+ sr .Status = StepFailed
115
+ case err == nil && scenarioErr == nil :
116
+ sr .Status = StepPassed
96
117
}
97
118
98
119
// Run after step handlers.
99
120
rctx , err = s .runAfterStepHooks (ctx , step , sr .Status , err )
100
121
122
+ shouldFail := s .shouldFail (err )
123
+
101
124
// Trigger after scenario on failing or last step to attach possible hook error to step.
102
- if isLast || ( sr . Status != StepSkipped && sr . Status != StepUndefined && err != nil ) {
125
+ if ! s . shouldFail ( scenarioErr ) && ( isLast || shouldFail ) {
103
126
rctx , err = s .runAfterScenarioHooks (rctx , pickle , err )
104
127
}
105
128
@@ -137,6 +160,7 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
137
160
138
161
match = s .matchStep (step )
139
162
s .storage .MustInsertStepDefintionMatch (step .AstNodeIds [0 ], match )
163
+ sr .Def = match
140
164
s .fmt .Defined (pickle , step , match .GetInternalStepDefinition ())
141
165
142
166
if err != nil {
@@ -162,6 +186,7 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
162
186
Nested : match .Nested ,
163
187
Undefined : undef ,
164
188
}
189
+ sr .Def = match
165
190
}
166
191
167
192
sr = models .NewStepResult (pickle .Id , step .Id , match )
@@ -172,7 +197,7 @@ func (s *suite) runStep(ctx context.Context, pickle *Scenario, step *Step, prevS
172
197
return ctx , ErrUndefined
173
198
}
174
199
175
- if prevStepErr != nil {
200
+ if scenarioErr != nil {
176
201
sr = models .NewStepResult (pickle .Id , step .Id , match )
177
202
sr .Status = models .Skipped
178
203
s .storage .MustInsertPickleStepResult (sr )
@@ -344,18 +369,53 @@ func (s *suite) maybeSubSteps(ctx context.Context, result interface{}) (context.
344
369
345
370
steps , ok := result .(Steps )
346
371
if ! ok {
347
- return ctx , fmt .Errorf ("unexpected error, should have been []string : %T - %+v" , result , result )
372
+ return ctx , fmt .Errorf ("unexpected error, should have been godog.Steps : %T - %+v" , result , result )
348
373
}
349
374
350
375
var err error
351
376
352
377
for _ , text := range steps {
353
378
if def := s .matchStepTextAndType (text , messages .PickleStepType_UNKNOWN ); def == nil {
354
379
return ctx , ErrUndefined
355
- } else if ctx , err = s .maybeSubSteps (def .Run (ctx )); err != nil {
356
- return ctx , fmt .Errorf ("%s: %+v" , text , err )
380
+ } else {
381
+ ctx , err = s .runSubStep (ctx , text , def )
382
+ if err != nil {
383
+ return ctx , err
384
+ }
385
+ }
386
+ }
387
+ return ctx , nil
388
+ }
389
+
390
+ func (s * suite ) runSubStep (ctx context.Context , text string , def * models.StepDefinition ) (_ context.Context , err error ) {
391
+ st := & Step {}
392
+ st .Text = text
393
+ st .Type = messages .PickleStepType_ACTION
394
+
395
+ defer func () {
396
+ status := StepPassed
397
+
398
+ switch {
399
+ case errors .Is (err , ErrUndefined ):
400
+ status = StepUndefined
401
+ case errors .Is (err , ErrPending ):
402
+ status = StepPending
403
+ case err != nil :
404
+ status = StepFailed
357
405
}
406
+
407
+ ctx , err = s .runAfterStepHooks (ctx , st , status , err )
408
+ }()
409
+
410
+ ctx , err = s .runBeforeStepHooks (ctx , st , nil )
411
+ if err != nil {
412
+ return ctx , fmt .Errorf ("%s: %+v" , text , err )
413
+ }
414
+
415
+ if ctx , err = s .maybeSubSteps (def .Run (ctx )); err != nil {
416
+ return ctx , fmt .Errorf ("%s: %+v" , text , err )
358
417
}
418
+
359
419
return ctx , nil
360
420
}
361
421
@@ -405,32 +465,23 @@ func keywordMatches(k formatters.Keyword, stepType messages.PickleStepType) bool
405
465
406
466
func (s * suite ) runSteps (ctx context.Context , pickle * Scenario , steps []* Step ) (context.Context , error ) {
407
467
var (
408
- stepErr , err error
468
+ stepErr , scenarioErr error
409
469
)
410
470
411
471
for i , step := range steps {
412
472
isLast := i == len (steps )- 1
413
473
isFirst := i == 0
414
- ctx , stepErr = s .runStep (ctx , pickle , step , err , isFirst , isLast )
415
- switch stepErr {
416
- case ErrUndefined :
417
- // do not overwrite failed error
418
- if err == ErrUndefined || err == nil {
419
- err = stepErr
420
- }
421
- case ErrPending :
422
- err = stepErr
423
- case nil :
424
- default :
425
- err = stepErr
474
+ ctx , stepErr = s .runStep (ctx , pickle , step , scenarioErr , isFirst , isLast )
475
+ if scenarioErr == nil || s .shouldFail (stepErr ) {
476
+ scenarioErr = stepErr
426
477
}
427
478
}
428
479
429
- return ctx , err
480
+ return ctx , scenarioErr
430
481
}
431
482
432
483
func (s * suite ) shouldFail (err error ) bool {
433
- if err == nil {
484
+ if err == nil || err == ErrSkip {
434
485
return false
435
486
}
436
487
0 commit comments