diff --git a/features/events.feature b/features/events.feature index e921aaa..107b072 100644 --- a/features/events.feature +++ b/features/events.feature @@ -51,3 +51,50 @@ Feature: suite events | AfterFeature | 2 | | AfterSuite | 1 | + Scenario: should not trigger events on empty feature + Given a feature "normal.feature" file: + """ + Feature: empty + + Scenario: one + + Scenario: two + """ + When I run feature suite + Then these events had to be fired for a number of times: + | BeforeSuite | 1 | + | BeforeFeature | 0 | + | BeforeScenario | 0 | + | BeforeStep | 0 | + | AfterStep | 0 | + | AfterScenario | 0 | + | AfterFeature | 0 | + | AfterSuite | 1 | + + Scenario: should not trigger events on empty scenarios + Given a feature "normal.feature" file: + """ + Feature: half empty + + Scenario: one + + Scenario: two + Then passing step + + Scenario Outline: three + Then passing step + + Examples: + | a | + | 1 | + """ + When I run feature suite + Then these events had to be fired for a number of times: + | BeforeSuite | 1 | + | BeforeFeature | 1 | + | BeforeScenario | 2 | + | BeforeStep | 2 | + | AfterStep | 2 | + | AfterScenario | 2 | + | AfterFeature | 1 | + | AfterSuite | 1 | diff --git a/fmt_pretty.go b/fmt_pretty.go index 0a1f2f7..cdd18b4 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -89,6 +89,9 @@ func (f *pretty) Node(node interface{}) { f.outline = nil f.steps = len(t.Steps) + f.totalBgSteps f.scenarioKeyword = false + if isEmptyScenario(t) { + f.printUndefinedScenario(t) + } case *gherkin.ScenarioOutline: f.outline = t f.scenario = nil @@ -100,6 +103,21 @@ func (f *pretty) Node(node interface{}) { } } +func (f *pretty) printUndefinedScenario(sc *gherkin.Scenario) { + if f.bgSteps > 0 { + f.commentPos = f.longestStep(f.feature.Background.Steps, f.length(f.feature.Background)) + fmt.Fprintln(f.out, "\n"+s(f.indent)+whiteb(f.feature.Background.Keyword+": "+f.feature.Background.Name)) + + for _, step := range f.feature.Background.Steps { + f.bgSteps-- + f.printStep(step, nil, colors.Cyan) + } + } + text := s(f.indent) + whiteb(f.scenario.Keyword+": ") + sc.Name + text += s(f.commentPos-f.length(f.scenario)+1) + f.line(sc.Location) + fmt.Fprintln(f.out, "\n"+text) +} + // Summary sumarize the feature formatter output func (f *pretty) Summary() { // failed steps on background are not scenarios @@ -284,6 +302,7 @@ func (f *pretty) printStepKind(res *stepResult) { if f.outline != nil { f.outlineSteps = append(f.outlineSteps, res) } + var bgStep bool // if has not printed background yet switch { @@ -292,9 +311,11 @@ func (f *pretty) printStepKind(res *stepResult) { f.commentPos = f.longestStep(f.feature.Background.Steps, f.length(f.feature.Background)) fmt.Fprintln(f.out, "\n"+s(f.indent)+whiteb(f.feature.Background.Keyword+": "+f.feature.Background.Name)) f.bgSteps-- + bgStep = true // subsequent background steps case f.bgSteps > 0: f.bgSteps-- + bgStep = true // first step of scenario, print header and calculate comment position case f.scenario != nil: // print scenario keyword and value if first example @@ -333,7 +354,9 @@ func (f *pretty) printStepKind(res *stepResult) { return } - f.printStep(res.step, res.def, res.typ.clr()) + if !f.isBackgroundStep(res.step) || bgStep { + f.printStep(res.step, res.def, res.typ.clr()) + } if res.err != nil { fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", res.err))) } @@ -342,6 +365,19 @@ func (f *pretty) printStepKind(res *stepResult) { } } +func (f *pretty) isBackgroundStep(step *gherkin.Step) bool { + if f.feature.Background == nil { + return false + } + + for _, bstep := range f.feature.Background.Steps { + if bstep.Location.Line == step.Location.Line { + return true + } + } + return false +} + // print table with aligned table cells func (f *pretty) printTable(t *gherkin.DataTable, c colors.ColorFunc) { var l = longest(t, c) diff --git a/gherkin.go b/gherkin.go index 4280eae..6a4e804 100644 --- a/gherkin.go +++ b/gherkin.go @@ -9,3 +9,28 @@ func examples(ex interface{}) (*gherkin.Examples, bool) { t, ok := ex.(*gherkin.Examples) return t, ok } + +// means there are no scenarios or they do not have steps +func isEmptyFeature(ft *gherkin.Feature) bool { + for _, def := range ft.ScenarioDefinitions { + if !isEmptyScenario(def) { + return false + } + } + return true +} + +// means scenario dooes not have steps +func isEmptyScenario(def interface{}) bool { + switch t := def.(type) { + case *gherkin.Scenario: + if len(t.Steps) > 0 { + return false + } + case *gherkin.ScenarioOutline: + if len(t.Steps) > 0 { + return false + } + } + return true +} diff --git a/suite.go b/suite.go index 7435823..a3c75de 100644 --- a/suite.go +++ b/suite.go @@ -425,8 +425,10 @@ func (s *Suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Backgrou groups := example.TableBody for _, group := range groups { - for _, f := range s.beforeScenarioHandlers { - f(outline) + if !isEmptyScenario(outline) { + for _, f := range s.beforeScenarioHandlers { + f(outline) + } } var steps []*gherkin.Step for _, outlineStep := range outline.Steps { @@ -493,8 +495,10 @@ func (s *Suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Backgrou err := s.runSteps(steps) - for _, f := range s.afterScenarioHandlers { - f(outline, err) + if !isEmptyScenario(outline) { + for _, f := range s.afterScenarioHandlers { + f(outline, err) + } } if s.shouldFail(err) { @@ -521,8 +525,10 @@ func (s *Suite) shouldFail(err error) bool { } func (s *Suite) runFeature(f *feature) { - for _, fn := range s.beforeFeatureHandlers { - fn(f.Feature) + if !isEmptyFeature(f.Feature) { + for _, fn := range s.beforeFeatureHandlers { + fn(f.Feature) + } } s.fmt.Feature(f.Feature, f.Path, f.Content) @@ -541,8 +547,10 @@ func (s *Suite) runFeature(f *feature) { } defer func() { - for _, fn := range s.afterFeatureHandlers { - fn(f.Feature) + if !isEmptyFeature(f.Feature) { + for _, fn := range s.afterFeatureHandlers { + fn(f.Feature) + } } }() @@ -567,6 +575,11 @@ func (s *Suite) runFeature(f *feature) { } func (s *Suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) { + if isEmptyScenario(scenario) { + s.fmt.Node(scenario) + return ErrUndefined + } + // run before scenario handlers for _, f := range s.beforeScenarioHandlers { f(scenario) @@ -704,9 +717,7 @@ func filterFeatures(tags string, collected map[string]*feature) (features []*fea ft.ScenarioDefinitions = scenarios applyTagFilter(tags, ft.Feature) - if len(ft.ScenarioDefinitions) > 0 { - features = append(features, ft) - } + features = append(features, ft) } sort.Sort(sortByOrderGiven(features))