diff --git a/features/formatter/pretty.feature b/features/formatter/pretty.feature index 7b307bea..86d10a89 100644 --- a/features/formatter/pretty.feature +++ b/features/formatter/pretty.feature @@ -449,3 +449,102 @@ Feature: pretty formatter 16 steps (16 passed) 0s """ + + Scenario: Support of Feature Plus Rule + Given a feature "features/simple.feature" file: + """ + Feature: simple feature with a rule + simple feature description + Rule: simple rule + simple rule description + Example: simple scenario + simple scenario description + Given passing step + """ + When I run feature suite with formatter "pretty" + Then the rendered output will be as follows: + """ + Feature: simple feature with a rule + simple feature description + + Example: simple scenario # features/simple.feature:5 + Given passing step # suite_context.go:0 -> SuiteContext.func2 + + 1 scenarios (1 passed) + 1 steps (1 passed) + 0s + """ + + Scenario: Support of Feature Plus Rule with Background + Given a feature "features/simple.feature" file: + """ + Feature: simple feature with a rule with Background + simple feature description + Rule: simple rule + simple rule description + Background: + Given passing step + Example: simple scenario + simple scenario description + Given passing step + """ + When I run feature suite with formatter "pretty" + Then the rendered output will be as follows: + """ + Feature: simple feature with a rule with Background + simple feature description + + Background: + Given passing step # suite_context.go:0 -> SuiteContext.func2 + + Example: simple scenario # features/simple.feature:7 + Given passing step # suite_context.go:0 -> SuiteContext.func2 + + 1 scenarios (1 passed) + 2 steps (2 passed) + 0s + """ + + Scenario: Support of Feature Plus Rule with Scenario Outline + Given a feature "features/simple.feature" file: + """ + Feature: simple feature with a rule with Scenario Outline + simple feature description + Rule: simple rule + simple rule description + Scenario Outline: simple scenario + simple scenario description + + Given step + + Examples: simple examples + | status | + | passing | + | failing | + """ + When I run feature suite with formatter "pretty" + Then the rendered output will be as follows: + """ + Feature: simple feature with a rule with Scenario Outline + simple feature description + + Scenario Outline: simple scenario # features/simple.feature:5 + Given step # suite_context.go:0 -> SuiteContext.func2 + + Examples: simple examples + | status | + | passing | + | failing | + intentional failure + + --- Failed steps: + + Scenario Outline: simple scenario # features/simple.feature:5 + Given failing step # features/simple.feature:8 + Error: intentional failure + + + 2 scenarios (1 passed, 1 failed) + 2 steps (1 passed, 1 failed) + 0s + """ diff --git a/internal/formatters/fmt_pretty.go b/internal/formatters/fmt_pretty.go index 0860dda9..070627a3 100644 --- a/internal/formatters/fmt_pretty.go +++ b/internal/formatters/fmt_pretty.go @@ -12,6 +12,7 @@ import ( "github.com/cucumber/godog/colors" "github.com/cucumber/godog/formatters" + "github.com/cucumber/godog/internal/models" ) func init() { @@ -350,15 +351,38 @@ func (f *Pretty) printTableHeader(row *messages.TableRow, max []int) { f.printTableRow(row, max, cyan) } +func isFirstScenarioInRule(rule *messages.Rule, scenario *messages.Scenario) bool { + if rule == nil || scenario == nil { + return false + } + var firstScenario *messages.Scenario + for _, c := range rule.Children { + if c.Scenario != nil { + firstScenario = c.Scenario + break + } + } + return firstScenario != nil && firstScenario.Id == scenario.Id +} + +func isFirstPickleAndNoRule(feature *models.Feature, pickle *messages.Pickle, rule *messages.Rule) bool { + if rule != nil { + return false + } + return feature.Pickles[0].Id == pickle.Id +} + func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleStep) { feature := f.Storage.MustGetFeature(pickle.Uri) astBackground := feature.FindBackground(pickle.AstNodeIds[0]) astScenario := feature.FindScenario(pickle.AstNodeIds[0]) + astRule := feature.FindRule(pickle.AstNodeIds[0]) astStep := feature.FindStep(pickleStep.AstNodeIds[0]) var astBackgroundStep bool var firstExecutedBackgroundStep bool var backgroundSteps int + if astBackground != nil { backgroundSteps = len(astBackground.Steps) @@ -371,7 +395,7 @@ func (f *Pretty) printStep(pickle *messages.Pickle, pickleStep *messages.PickleS } } - firstPickle := feature.Pickles[0].Id == pickle.Id + firstPickle := isFirstPickleAndNoRule(feature, pickle, astRule) || isFirstScenarioInRule(astRule, astScenario) if astBackgroundStep && !firstPickle { return diff --git a/internal/formatters/formatter-tests/features/rules_with_examples_with_backgrounds.feature b/internal/formatters/formatter-tests/features/rules_with_examples_with_backgrounds.feature new file mode 100644 index 00000000..06d44a9c --- /dev/null +++ b/internal/formatters/formatter-tests/features/rules_with_examples_with_backgrounds.feature @@ -0,0 +1,30 @@ +Feature: rules with examples with backgrounds + + Rule: first rule + + Background: for first rule + Given passing step + And passing step + + Example: rule 1 example 1 + When passing step + Then passing step + + Example: rule 1 example 2 + When passing step + Then passing step + + + Rule: second rule + + Background: for second rule + Given passing step + And passing step + + Example: rule 1 example 1 + When passing step + Then passing step + + Example: rule 2 example 2 + When passing step + Then passing step diff --git a/internal/formatters/formatter-tests/pretty/rules_with_examples_with_backgrounds b/internal/formatters/formatter-tests/pretty/rules_with_examples_with_backgrounds new file mode 100644 index 00000000..b3d36c3b --- /dev/null +++ b/internal/formatters/formatter-tests/pretty/rules_with_examples_with_backgrounds @@ -0,0 +1,29 @@ +Feature: rules with examples with backgrounds + + Background: for first rule + Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + And passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + + Example: rule 1 example 1 # formatter-tests/features/rules_with_examples_with_backgrounds.feature:9 + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + + Example: rule 1 example 2 # formatter-tests/features/rules_with_examples_with_backgrounds.feature:13 + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + + Background: for second rule + Given passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + And passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + + Example: rule 1 example 1 # formatter-tests/features/rules_with_examples_with_backgrounds.feature:24 + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + + Example: rule 2 example 2 # formatter-tests/features/rules_with_examples_with_backgrounds.feature:28 + When passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + Then passing step # fmt_output_test.go:101 -> github.com/cucumber/godog/internal/formatters_test.passingStepDef + +4 scenarios (4 passed) +16 steps (16 passed) +0s diff --git a/internal/models/feature.go b/internal/models/feature.go index d38a224a..4edd65da 100644 --- a/internal/models/feature.go +++ b/internal/models/feature.go @@ -13,12 +13,35 @@ type Feature struct { Content []byte } -// FindScenario ... +// FindRule returns the rule to which the given scenario belongs +func (f Feature) FindRule(astScenarioID string) *messages.Rule { + for _, child := range f.GherkinDocument.Feature.Children { + if ru := child.Rule; ru != nil { + if rc := child.Rule; rc != nil { + for _, rcc := range rc.Children { + if sc := rcc.Scenario; sc != nil && sc.Id == astScenarioID { + return ru + } + } + } + } + } + return nil +} + +// FindScenario returns the scenario in the feature or in a rule in the feature func (f Feature) FindScenario(astScenarioID string) *messages.Scenario { for _, child := range f.GherkinDocument.Feature.Children { if sc := child.Scenario; sc != nil && sc.Id == astScenarioID { return sc } + if rc := child.Rule; rc != nil { + for _, rcc := range rc.Children { + if sc := rcc.Scenario; sc != nil && sc.Id == astScenarioID { + return sc + } + } + } } return nil @@ -36,6 +59,18 @@ func (f Feature) FindBackground(astScenarioID string) *messages.Background { if sc := child.Scenario; sc != nil && sc.Id == astScenarioID { return bg } + + if ru := child.Rule; ru != nil { + for _, rc := range ru.Children { + if tmp := rc.Background; tmp != nil { + bg = tmp + } + + if sc := rc.Scenario; sc != nil && sc.Id == astScenarioID { + return bg + } + } + } } return nil @@ -53,6 +88,19 @@ func (f Feature) FindExample(exampleAstID string) (*messages.Examples, *messages } } } + if ru := child.Rule; ru != nil { + for _, rc := range ru.Children { + if sc := rc.Scenario; sc != nil { + for _, example := range sc.Examples { + for _, row := range example.TableBody { + if row.Id == exampleAstID { + return example, row + } + } + } + } + } + } } return nil, nil @@ -61,6 +109,27 @@ func (f Feature) FindExample(exampleAstID string) (*messages.Examples, *messages // FindStep ... func (f Feature) FindStep(astStepID string) *messages.Step { for _, child := range f.GherkinDocument.Feature.Children { + + if ru := child.Rule; ru != nil { + for _, ch := range ru.Children { + if sc := ch.Scenario; sc != nil { + for _, step := range sc.Steps { + if step.Id == astStepID { + return step + } + } + } + + if bg := ch.Background; bg != nil { + for _, step := range bg.Steps { + if step.Id == astStepID { + return step + } + } + } + } + } + if sc := child.Scenario; sc != nil { for _, step := range sc.Steps { if step.Id == astStepID { diff --git a/internal/models/feature_test.go b/internal/models/feature_test.go index 89fac1e1..100ad2b1 100644 --- a/internal/models/feature_test.go +++ b/internal/models/feature_test.go @@ -3,6 +3,7 @@ package models_test import ( "testing" + "github.com/cucumber/godog/internal/models" "github.com/cucumber/godog/internal/testutils" "github.com/stretchr/testify/assert" ) @@ -32,29 +33,76 @@ func Test_Find(t *testing.T) { assert.NotNilf(t, step, "expected step to not be nil") } }) + + t.Run("rule", func(t *testing.T) { + sc := ft.FindRule(ft.Pickles[0].AstNodeIds[0]) + assert.Nilf(t, sc, "expected rule to be nil") + }) } -func Test_NotFind(t *testing.T) { - ft := testutils.BuildTestFeature(t) +func Test_FindInRule(t *testing.T) { + + ft := testutils.BuildTestFeatureWithRules(t) + + t.Run("rule", func(t *testing.T) { + sc := ft.FindRule(ft.Pickles[0].AstNodeIds[0]) + assert.NotNilf(t, sc, "expected rule to not be nil") + }) t.Run("scenario", func(t *testing.T) { - sc := ft.FindScenario("-") - assert.Nilf(t, sc, "expected scenario to be nil") + sc := ft.FindScenario(ft.Pickles[0].AstNodeIds[0]) + assert.NotNilf(t, sc, "expected scenario to not be nil") }) t.Run("background", func(t *testing.T) { - bg := ft.FindBackground("-") - assert.Nilf(t, bg, "expected background to be nil") + bg := ft.FindBackground(ft.Pickles[0].AstNodeIds[0]) + assert.NotNilf(t, bg, "expected background to not be nil") }) t.Run("example", func(t *testing.T) { - example, row := ft.FindExample("-") - assert.Nilf(t, example, "expected example to be nil") - assert.Nilf(t, row, "expected table row to be nil") + example, row := ft.FindExample(ft.Pickles[1].AstNodeIds[1]) + assert.NotNilf(t, example, "expected example to not be nil") + assert.NotNilf(t, row, "expected table row to not be nil") }) t.Run("step", func(t *testing.T) { - step := ft.FindStep("-") - assert.Nilf(t, step, "expected step to be nil") + for _, ps := range ft.Pickles[0].Steps { + step := ft.FindStep(ps.AstNodeIds[0]) + assert.NotNilf(t, step, "expected step to not be nil") + } }) } + +func Test_NotFind(t *testing.T) { + testCases := []struct { + Feature models.Feature + }{ + {testutils.BuildTestFeature(t)}, + {testutils.BuildTestFeatureWithRules(t)}, + } + + for _, tc := range testCases { + + ft := tc.Feature + t.Run("scenario", func(t *testing.T) { + sc := ft.FindScenario("-") + assert.Nilf(t, sc, "expected scenario to be nil") + }) + + t.Run("background", func(t *testing.T) { + bg := ft.FindBackground("-") + assert.Nilf(t, bg, "expected background to be nil") + }) + + t.Run("example", func(t *testing.T) { + example, row := ft.FindExample("-") + assert.Nilf(t, example, "expected example to be nil") + assert.Nilf(t, row, "expected table row to be nil") + }) + + t.Run("step", func(t *testing.T) { + step := ft.FindStep("-") + assert.Nilf(t, step, "expected step to be nil") + }) + } +} diff --git a/internal/testutils/utils.go b/internal/testutils/utils.go index fa70ebfd..4c1c8afe 100644 --- a/internal/testutils/utils.go +++ b/internal/testutils/utils.go @@ -58,3 +58,53 @@ Scenario Outline: Eat out of Examples: | begin | dec | remain | | 12 | 5 | 7 |` + +// BuildTestFeature creates a feature with rules for testing purpose. +// +// The created feature includes: +// - a background +// - one normal scenario with three steps +// - one outline scenario with one example and three steps +func BuildTestFeatureWithRules(t *testing.T) models.Feature { + newIDFunc := (&messages.Incrementing{}).NewId + + gherkinDocument, err := gherkin.ParseGherkinDocument(strings.NewReader(featureWithRuleContent), newIDFunc) + require.NoError(t, err) + + path := t.Name() + gherkinDocument.Uri = path + pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc) + + ft := models.Feature{GherkinDocument: gherkinDocument, Pickles: pickles, Content: []byte(featureWithRuleContent)} + require.Len(t, ft.Pickles, 2) + + require.Len(t, ft.Pickles[0].AstNodeIds, 1) + require.Len(t, ft.Pickles[0].Steps, 3) + + require.Len(t, ft.Pickles[1].AstNodeIds, 2) + require.Len(t, ft.Pickles[1].Steps, 3) + + return ft +} + +const featureWithRuleContent = `Feature: eat godogs +In order to be happy +As a hungry gopher +I need to be able to eat godogs + +Rule: eating godogs + +Background: + Given there are godogs + +Scenario: Eat 5 out of 12 + When I eat 5 + Then there should be 7 remaining + +Scenario Outline: Eat out of + When I eat + Then there should be remaining + + Examples: + | begin | dec | remain | + | 12 | 5 | 7 |` diff --git a/run_test.go b/run_test.go index bbe4843b..e36d1fca 100644 --- a/run_test.go +++ b/run_test.go @@ -432,11 +432,11 @@ func Test_AllFeaturesRun(t *testing.T) { ...................................................................... 140 ...................................................................... 210 ...................................................................... 280 -........................................ 320 +................................................. 329 -83 scenarios (83 passed) -320 steps (320 passed) +86 scenarios (86 passed) +329 steps (329 passed) 0s ` @@ -459,11 +459,11 @@ func Test_AllFeaturesRunAsSubtests(t *testing.T) { ...................................................................... 140 ...................................................................... 210 ...................................................................... 280 -........................................ 320 +................................................. 329 -83 scenarios (83 passed) -320 steps (320 passed) +86 scenarios (86 passed) +329 steps (329 passed) 0s `