diff --git a/features/run.feature b/features/run.feature index 2abf5d0..a4e7173 100644 --- a/features/run.feature +++ b/features/run.feature @@ -94,3 +94,72 @@ Feature: run features """ I should have 1 scenario registered """ + + Scenario: should match undefined steps in a row + Given a feature "undefined.feature" file: + """ + Feature: undefined feature + + Scenario: parse a scenario + Given undefined step + When undefined action + Then I should have 1 scenario registered + """ + When I run feature suite + Then the suite should have passed + And the following steps should be undefined: + """ + undefined step + undefined action + """ + And the following step should be skipped: + """ + I should have 1 scenario registered + """ + + Scenario: should skip steps on pending + Given a feature "pending.feature" file: + """ + Feature: pending feature + + Scenario: parse a scenario + Given undefined step + When pending step + Then I should have 1 scenario registered + """ + When I run feature suite + Then the suite should have passed + And the following step should be undefined: + """ + undefined step + """ + And the following step should be skipped: + """ + pending step + I should have 1 scenario registered + """ + + Scenario: should handle pending step + Given a feature "pending.feature" file: + """ + Feature: pending feature + + Scenario: parse a scenario + Given a feature path "features/load.feature:6" + When pending step + Then I should have 1 scenario registered + """ + When I run feature suite + Then the suite should have passed + And the following step should be passed: + """ + a feature path "features/load.feature:6" + """ + And the following step should be pending: + """ + pending step + """ + And the following step should be skipped: + """ + I should have 1 scenario registered + """ diff --git a/fmt.go b/fmt.go index f964a36..e38887b 100644 --- a/fmt.go +++ b/fmt.go @@ -39,6 +39,7 @@ type Formatter interface { Passed(*gherkin.Step, *StepDef) Skipped(*gherkin.Step) Undefined(*gherkin.Step) + Pending(*gherkin.Step, *StepDef) Summary() } @@ -73,10 +74,19 @@ type skipped struct { step *gherkin.Step } -// undefined represents a pending step data structure +// undefined represents an undefined step data structure // with all necessary references type undefined struct { feature *feature owner interface{} step *gherkin.Step } + +// pending represents a pending step data structure +// with all necessary references +type pending struct { + feature *feature + owner interface{} + step *gherkin.Step + def *StepDef +} diff --git a/fmt_pretty.go b/fmt_pretty.go index e5ac60f..b47540b 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -27,7 +27,6 @@ type pretty struct { backgroundSteps int // outline - outline *gherkin.ScenarioOutline outlineSteps []interface{} outlineNumExample int outlineNumExamples int @@ -39,6 +38,7 @@ type pretty struct { passed []*passed skipped []*skipped undefined []*undefined + pending []*pending } // a line number representation in feature file @@ -95,7 +95,6 @@ func (f *pretty) Node(node interface{}) { fmt.Println("\n" + text) case *gherkin.ScenarioOutline: f.scope = t - f.outline = t f.commentPos = f.longestStep(t.Steps, f.length(t)) text := s(f.indent) + bcl(t.Keyword+": ", white) + t.Name text += s(f.commentPos-f.length(t)+1) + f.line(t.Location) @@ -136,7 +135,7 @@ func (f *pretty) Summary() { fmt.Println(" " + cl(fail, red)) } } - var total, passed int + var total, passed, undefined int for _, ft := range f.features { for _, def := range ft.ScenarioDefinitions { switch t := def.(type) { @@ -150,12 +149,22 @@ func (f *pretty) Summary() { } } passed = total + var owner interface{} + for _, undef := range f.undefined { + if owner != undef.owner { + undefined++ + owner = undef.owner + } + } var steps, parts, scenarios []string - nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending) if len(f.passed) > 0 { steps = append(steps, cl(fmt.Sprintf("%d passed", len(f.passed)), green)) } + if len(f.pending) > 0 { + steps = append(steps, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow)) + } if len(f.failed) > 0 { passed -= len(f.failed) parts = append(parts, cl(fmt.Sprintf("%d failed", len(f.failed)), red)) @@ -165,9 +174,9 @@ func (f *pretty) Summary() { steps = append(steps, cl(fmt.Sprintf("%d skipped", len(f.skipped)), cyan)) } if len(f.undefined) > 0 { - passed -= len(f.undefined) - parts = append(parts, cl(fmt.Sprintf("%d undefined", len(f.undefined)), yellow)) - steps = append(steps, parts[len(parts)-1]) + passed -= undefined + parts = append(parts, cl(fmt.Sprintf("%d undefined", undefined), yellow)) + steps = append(steps, cl(fmt.Sprintf("%d undefined", len(f.undefined)), yellow)) } if passed > 0 { scenarios = append(scenarios, cl(fmt.Sprintf("%d passed", passed), green)) @@ -324,6 +333,11 @@ func (f *pretty) stepDetails(stepAction interface{}) (owner interface{}, step *g step = typ.step owner = typ.owner c = yellow + case *pending: + step = typ.step + def = typ.def + owner = typ.owner + c = yellow default: fatal(fmt.Errorf("unexpected step type received: %T", typ)) } @@ -358,6 +372,9 @@ func (f *pretty) printStepKind(stepAction interface{}) { if err != nil { fmt.Println(s(f.indent*2) + bcl(err, red)) } + if _, ok := stepAction.(*pending); ok { + fmt.Println(s(f.indent*3) + cl("TODO: write pending definition", yellow)) + } } // print table with aligned table cells @@ -400,6 +417,12 @@ func (f *pretty) Failed(step *gherkin.Step, match *StepDef, err error) { f.failed = append(f.failed, s) } +func (f *pretty) Pending(step *gherkin.Step, match *StepDef) { + s := &pending{owner: f.scope, feature: f.features[len(f.features)-1], step: step, def: match} + f.printStepKind(s) + f.pending = append(f.pending, s) +} + // longest gives a list of longest columns of all rows in Table func longest(tbl interface{}) []int { var rows []*gherkin.TableRow diff --git a/fmt_progress.go b/fmt_progress.go index cf748e3..72a4ddc 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -28,6 +28,7 @@ type progress struct { passed []*passed skipped []*skipped undefined []*undefined + pending []*pending } func (f *progress) Feature(ft *gherkin.Feature, p string) { @@ -63,7 +64,7 @@ func (f *progress) Summary() { fmt.Println(s(6) + cl("Error: ", red) + bcl(fail.err, red) + "\n") } } - var total, passed int + var total, passed, undefined int for _, ft := range f.features { for _, def := range ft.ScenarioDefinitions { switch t := def.(type) { @@ -77,12 +78,22 @@ func (f *progress) Summary() { } } passed = total + var owner interface{} + for _, undef := range f.undefined { + if owner != undef.owner { + undefined++ + owner = undef.owner + } + } var steps, parts, scenarios []string - nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending) if len(f.passed) > 0 { steps = append(steps, cl(fmt.Sprintf("%d passed", len(f.passed)), green)) } + if len(f.pending) > 0 { + steps = append(steps, cl(fmt.Sprintf("%d pending", len(f.pending)), yellow)) + } if len(f.failed) > 0 { passed -= len(f.failed) parts = append(parts, cl(fmt.Sprintf("%d failed", len(f.failed)), red)) @@ -92,9 +103,9 @@ func (f *progress) Summary() { steps = append(steps, cl(fmt.Sprintf("%d skipped", len(f.skipped)), cyan)) } if len(f.undefined) > 0 { - passed -= len(f.undefined) - parts = append(parts, cl(fmt.Sprintf("%d undefined", len(f.undefined)), yellow)) - steps = append(steps, parts[len(parts)-1]) + passed -= undefined + parts = append(parts, cl(fmt.Sprintf("%d undefined", undefined), yellow)) + steps = append(steps, cl(fmt.Sprintf("%d undefined", len(f.undefined)), yellow)) } if passed > 0 { scenarios = append(scenarios, cl(fmt.Sprintf("%d passed", passed), green)) @@ -127,6 +138,8 @@ func (f *progress) step(step interface{}) { fmt.Print(cl("F", red)) case *undefined: fmt.Print(cl("U", yellow)) + case *pending: + fmt.Print(cl("P", yellow)) } f.steps++ if math.Mod(float64(f.steps), float64(f.stepsPerRow)) == 0 { @@ -157,3 +170,9 @@ func (f *progress) Failed(step *gherkin.Step, match *StepDef, err error) { f.failed = append(f.failed, s) f.step(s) } + +func (f *progress) Pending(step *gherkin.Step, match *StepDef) { + s := &pending{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match} + f.pending = append(f.pending, s) + f.step(s) +} diff --git a/fmt_test.go b/fmt_test.go index 84921e7..d46d605 100644 --- a/fmt_test.go +++ b/fmt_test.go @@ -11,6 +11,7 @@ type testFormatter struct { passed []*passed skipped []*skipped undefined []*undefined + pending []*pending } func (f *testFormatter) Feature(ft *gherkin.Feature, p string) { @@ -47,3 +48,8 @@ func (f *testFormatter) Undefined(step *gherkin.Step) { func (f *testFormatter) Failed(step *gherkin.Step, match *StepDef, err error) { f.failed = append(f.failed, &failed{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match, err: err}) } + +func (f *testFormatter) Pending(step *gherkin.Step, match *StepDef) { + s := &pending{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match} + f.pending = append(f.pending, s) +} diff --git a/suite.go b/suite.go index 062070a..0af6ddd 100644 --- a/suite.go +++ b/suite.go @@ -24,6 +24,10 @@ type feature struct { // ErrUndefined is returned in case if step definition was not found var ErrUndefined = fmt.Errorf("step is undefined") +// ErrPending should be returned by step definition if +// step implementation is pending +var ErrPending = fmt.Errorf("step implementation is pending") + // Suite is an interface which allows various contexts // to register steps and event handlers. // @@ -282,13 +286,18 @@ func (s *suite) matchStep(step *gherkin.Step) *StepDef { return nil } -func (s *suite) runStep(step *gherkin.Step) (err error) { +func (s *suite) runStep(step *gherkin.Step, prevStepErr error) (err error) { match := s.matchStep(step) if match == nil { s.fmt.Undefined(step) return ErrUndefined } + if prevStepErr != nil { + s.fmt.Skipped(step) + return nil + } + defer func() { if e := recover(); e != nil { err, ok := e.(error) @@ -299,27 +308,35 @@ func (s *suite) runStep(step *gherkin.Step) (err error) { } }() - if err = match.run(); err != nil { - s.fmt.Failed(step, match, err) - } else { + err = match.run() + switch err { + case nil: s.fmt.Passed(step, match) + case ErrPending: + s.fmt.Pending(step, match) + default: + s.fmt.Failed(step, match, err) } return } func (s *suite) runSteps(steps []*gherkin.Step) (err error) { for _, step := range steps { - if err != nil { - s.fmt.Skipped(step) - continue - } - // run before step handlers for _, f := range s.beforeStepHandlers { f(step) } - err = s.runStep(step) + stepErr := s.runStep(step, err) + switch stepErr { + case ErrUndefined: + err = stepErr + case ErrPending: + err = stepErr + case nil: + default: + err = stepErr + } // run after step handlers for _, f := range s.afterStepHandlers { @@ -401,7 +418,7 @@ func (s *suite) runFeature(f *feature) { case *gherkin.Scenario: err = s.runScenario(t, f.Background) } - if err != nil && err != ErrUndefined { + if err != nil && err != ErrUndefined && err != ErrPending { s.failed = true if s.stopOnFailure { return @@ -424,6 +441,8 @@ func (s *suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) ( // scenario s.fmt.Node(scenario) switch err { + case ErrPending: + s.skipSteps(scenario.Steps) case ErrUndefined: s.skipSteps(scenario.Steps) case nil: diff --git a/suite_test.go b/suite_test.go index cc9272b..e5954bb 100644 --- a/suite_test.go +++ b/suite_test.go @@ -28,12 +28,16 @@ func SuiteContext(s Suite) { s.Step(`^a failing step`, c.aFailingStep) s.Step(`^this step should fail`, c.aFailingStep) - s.Step(`^the following steps? should be (passed|failed|skipped|undefined):`, c.followingStepsShouldHave) + s.Step(`^the following steps? should be (passed|failed|skipped|undefined|pending):`, c.followingStepsShouldHave) // lt s.Step(`^savybių aplankas "([^"]*)"$`, c.featurePath) s.Step(`^aš išskaitau savybes$`, c.parseFeatures) s.Step(`^aš turėčiau turėti ([\d]+) savybių failus:$`, c.iShouldHaveNumFeatureFiles) + + s.Step(`^pending step$`, func(...*Arg) error { + return ErrPending + }) } type firedEvent struct { @@ -79,6 +83,10 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.Do for _, st := range s.fmt.undefined { actual = append(actual, st.step.Text) } + case "pending": + for _, st := range s.fmt.pending { + actual = append(actual, st.step.Text) + } default: return fmt.Errorf("unexpected step status wanted: %s", status) }