From 49130c8b08887b1b92550772abe0699a6643ef3c Mon Sep 17 00:00:00 2001 From: gedi Date: Thu, 18 Jun 2015 17:26:28 +0300 Subject: [PATCH] test table node and events fired --- features/events.feature | 34 ++++++++++++++++ features/hooks.feature | 12 ------ features/load_features.feature | 6 +-- formatter_pretty.go | 62 +++++++++++++++++------------ gherkin/gherkin.go | 8 ++-- gherkin/scenario_test.go | 6 +-- gherkin/steps_test.go | 6 +-- suite.go | 5 ++- suite_test.go | 71 +++++++++++++++++++++++++--------- 9 files changed, 140 insertions(+), 70 deletions(-) create mode 100644 features/events.feature delete mode 100644 features/hooks.feature diff --git a/features/events.feature b/features/events.feature new file mode 100644 index 0000000..3e39959 --- /dev/null +++ b/features/events.feature @@ -0,0 +1,34 @@ +Feature: suite events + In order to run tasks before and after important events + As a test suite + I need to provide a way to hook into these events + + Background: + Given I'm listening to suite events + + Scenario: triggers before scenario event + Given a feature path "features/load_features.feature:6" + When I run feature suite + Then there was event triggered before scenario "load features within path" + + Scenario: triggers appropriate events for a single scenario + Given a feature path "features/load_features.feature:6" + When I run feature suite + Then these events had to be fired for a number of times: + | BeforeSuite | 1 | + | BeforeScenario | 1 | + | BeforeStep | 3 | + | AfterStep | 3 | + | AfterScenario | 1 | + | AfterSuite | 1 | + + Scenario: triggers appropriate events whole feature + Given a feature path "features/load_features.feature" + When I run feature suite + Then these events had to be fired for a number of times: + | BeforeSuite | 1 | + | BeforeScenario | 4 | + | BeforeStep | 13 | + | AfterStep | 13 | + | AfterScenario | 4 | + | AfterSuite | 1 | diff --git a/features/hooks.feature b/features/hooks.feature deleted file mode 100644 index 581f0ec..0000000 --- a/features/hooks.feature +++ /dev/null @@ -1,12 +0,0 @@ -Feature: suite hooks - In order to run tasks before and after important events - As a test suite - I need to provide a way to hook into these events - - Background: - Given I'm listening to suite events - - Scenario: triggers before scenario hook - Given a feature path "features/load_features.feature:6" - When I run feature suite - Then there was event triggered before scenario "load features within path" diff --git a/features/load_features.feature b/features/load_features.feature index 99ebfb6..cbb5138 100644 --- a/features/load_features.feature +++ b/features/load_features.feature @@ -8,7 +8,7 @@ Feature: load features When I parse features Then I should have 2 feature files: """ - features/hooks.feature + features/events.feature features/load_features.feature """ @@ -27,10 +27,10 @@ Feature: load features Scenario: load a number of feature files Given a feature path "features/load_features.feature" - And a feature path "features/hooks.feature" + And a feature path "features/events.feature" When I parse features Then I should have 2 feature files: """ features/load_features.feature - features/hooks.feature + features/events.feature """ diff --git a/formatter_pretty.go b/formatter_pretty.go index f413a86..c22c9ac 100644 --- a/formatter_pretty.go +++ b/formatter_pretty.go @@ -23,6 +23,7 @@ type pretty struct { commentPos int doneBackground bool background *gherkin.Background + scenario *gherkin.Scenario // summary started time.Time @@ -38,20 +39,6 @@ func (f *pretty) line(tok *gherkin.Token) string { return cl(fmt.Sprintf("# %s:%d", f.feature.Path, tok.Line), black) } -// checks whether it should not print a background step once again -func (f *pretty) canPrintStep(step *gherkin.Step) bool { - if f.background == nil { - return true - } - - if step.Background == nil { - f.doneBackground = true - return true - } - - return !f.doneBackground -} - // Node takes a gherkin node for formatting func (f *pretty) Node(node interface{}) { switch t := node.(type) { @@ -61,25 +48,27 @@ func (f *pretty) Node(node interface{}) { fmt.Println("") } f.feature = t - f.doneBackground = false + f.scenario = nil f.background = nil f.features = append(f.features, t) fmt.Println(bcl("Feature: ", white) + t.Title) fmt.Println(t.Description) case *gherkin.Background: - // determine comment position based on step length - f.commentPos = len(t.Token.Text) - for _, step := range t.Steps { - if len(step.Token.Text) > f.commentPos { - f.commentPos = len(step.Token.Text) - } - } - // do not repeat background - if !f.doneBackground { + // do not repeat background for the same feature + if f.background == nil && f.scenario == nil { f.background = t + // determine comment position based on step length + f.commentPos = len(t.Token.Text) + for _, step := range t.Steps { + if len(step.Token.Text) > f.commentPos { + f.commentPos = len(step.Token.Text) + } + } + // print background node fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white)) } case *gherkin.Scenario: + f.scenario = t // determine comment position based on step length f.commentPos = len(t.Token.Text) for _, step := range t.Steps { @@ -180,7 +169,8 @@ func (f *pretty) printStep(stepAction interface{}) { fatal(fmt.Errorf("unexpected step type received: %T", typ)) } - if !f.canPrintStep(step) { + // do not print background more than once + if f.scenario == nil && step.Background != f.background { return } @@ -225,11 +215,33 @@ func (f *pretty) printStep(stepAction interface{}) { fmt.Println(cl(step.PyString.Raw, c)) fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c)) } + if step.Table != nil { + f.printTable(step.Table, c) + } if err != nil { fmt.Println(s(step.Token.Indent) + bcl(err, red)) } } +// print table with aligned table cells +func (f *pretty) printTable(t *gherkin.Table, c color) { + var longest = make([]int, len(t.Rows[0])) + var cols = make([]string, len(t.Rows[0])) + for _, row := range t.Rows { + for i, col := range row { + if longest[i] < len(col) { + longest[i] = len(col) + } + } + } + for _, row := range t.Rows { + for i, col := range row { + cols[i] = col + s(longest[i]-len(col)) + } + fmt.Println(s(t.Token.Indent) + cl("| "+strings.Join(cols, " | ")+" |", c)) + } +} + // Passed is called to represent a passed step func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) { s := &passed{step: step, handler: match} diff --git a/gherkin/gherkin.go b/gherkin/gherkin.go index 3bb6156..cc3e860 100644 --- a/gherkin/gherkin.go +++ b/gherkin/gherkin.go @@ -155,7 +155,7 @@ type Table struct { *Token OutlineScenario *Scenario Step *Step - rows [][]string + Rows [][]string } var allSteps = []TokenType{ @@ -369,17 +369,17 @@ func (p *parser) parsePystring() (*PyString, error) { } func (p *parser) parseTable() (*Table, error) { - tbl := &Table{} + tbl := &Table{Token: p.peek()} for row := p.peek(); row.Type == TABLE_ROW; row = p.peek() { var cols []string for _, r := range strings.Split(strings.Trim(row.Value, "|"), "|") { cols = append(cols, strings.TrimFunc(r, unicode.IsSpace)) } // ensure the same colum number for each row - if len(tbl.rows) > 0 && len(tbl.rows[0]) != len(cols) { + if len(tbl.Rows) > 0 && len(tbl.Rows[0]) != len(cols) { return tbl, p.err("table row has not the same number of columns compared to previous row", row.Line) } - tbl.rows = append(tbl.rows, cols) + tbl.Rows = append(tbl.Rows, cols) p.next() // jump over the peeked token } return tbl, nil diff --git a/gherkin/scenario_test.go b/gherkin/scenario_test.go index cd8cd20..68a7d4d 100644 --- a/gherkin/scenario_test.go +++ b/gherkin/scenario_test.go @@ -25,13 +25,13 @@ func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) { if s.Examples == nil { t.Fatalf("outline scenario '%s' has no examples", s.Title) } - if len(s.Examples.rows) <= num { + if len(s.Examples.Rows) <= num { t.Fatalf("outline scenario '%s' table has no row: %d", s.Title, num) } - if len(s.Examples.rows[num]) != len(cols) { + if len(s.Examples.Rows[num]) != len(cols) { t.Fatalf("outline scenario '%s' table row length, does not match expected: %d", s.Title, len(cols)) } - for i, col := range s.Examples.rows[num] { + for i, col := range s.Examples.Rows[num] { if col != cols[i] { t.Fatalf("outline scenario '%s' table row %d, column %d - value '%s', does not match expected: %s", s.Title, num, i, col, cols[i]) } diff --git a/gherkin/steps_test.go b/gherkin/steps_test.go index 4870bdc..24c8618 100644 --- a/gherkin/steps_test.go +++ b/gherkin/steps_test.go @@ -69,13 +69,13 @@ func (s *Step) assertTableRow(t *testing.T, num int, cols ...string) { if s.Table == nil { t.Fatalf("step '%s %s' has no table", s.Type, s.Text) } - if len(s.Table.rows) <= num { + if len(s.Table.Rows) <= num { t.Fatalf("step '%s %s' table has no row: %d", s.Type, s.Text, num) } - if len(s.Table.rows[num]) != len(cols) { + if len(s.Table.Rows[num]) != len(cols) { t.Fatalf("step '%s %s' table row length, does not match expected: %d", s.Type, s.Text, len(cols)) } - for i, col := range s.Table.rows[num] { + for i, col := range s.Table.Rows[num] { if col != cols[i] { t.Fatalf("step '%s %s' table row %d, column %d - value '%s', does not match expected: %s", s.Type, s.Text, num, i, col, cols[i]) } diff --git a/suite.go b/suite.go index ea2a973..b34f116 100644 --- a/suite.go +++ b/suite.go @@ -11,9 +11,10 @@ import ( "github.com/DATA-DOG/godog/gherkin" ) -// Status represents a step status +// Status represents a step or scenario status type Status int +// step or scenario status constants const ( invalid Status = iota Passed @@ -303,7 +304,7 @@ func (s *suite) runFeature(f *gherkin.Feature) { s.skipSteps(scenario.Steps) case status == Undefined: s.skipSteps(scenario.Steps) - case status == invalid: + case status == Passed || status == invalid: status = s.runSteps(scenario.Steps) } diff --git a/suite_test.go b/suite_test.go index b33bf7c..aa6a9ad 100644 --- a/suite_test.go +++ b/suite_test.go @@ -14,24 +14,39 @@ type firedEvent struct { } type suiteFeature struct { - suite - events []*firedEvent + testedSuite *suite + events []*firedEvent } func (s *suiteFeature) HandleBeforeScenario(scenario *gherkin.Scenario) { + // reset whole suite with the state + s.testedSuite = &suite{fmt: &testFormatter{}} + // our tested suite will have the same context registered + SuiteContext(s.testedSuite) // reset feature paths cfg.paths = []string{} - // reset event stack + // reset all fired events s.events = []*firedEvent{} - // reset formatter, which collects all details - s.fmt = &testFormatter{} } func (s *suiteFeature) iAmListeningToSuiteEvents(args ...*Arg) error { - s.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) { - s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{ - scenario, - }}) + s.testedSuite.BeforeSuite(BeforeSuiteHandlerFunc(func() { + s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}}) + })) + s.testedSuite.AfterSuite(AfterSuiteHandlerFunc(func() { + s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}}) + })) + s.testedSuite.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) { + s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}}) + })) + s.testedSuite.AfterScenario(AfterScenarioHandlerFunc(func(scenario *gherkin.Scenario, status Status) { + s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, status}}) + })) + s.testedSuite.BeforeStep(BeforeStepHandlerFunc(func(step *gherkin.Step) { + s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}}) + })) + s.testedSuite.AfterStep(AfterStepHandlerFunc(func(step *gherkin.Step, status Status) { + s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, status}}) })) return nil } @@ -42,17 +57,17 @@ func (s *suiteFeature) featurePath(args ...*Arg) error { } func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) { - s.features, err = cfg.features() + s.testedSuite.features, err = cfg.features() return } func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) error { - if len(s.features) != args[0].Int() { - return fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.features)) + if len(s.testedSuite.features) != args[0].Int() { + return fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.testedSuite.features)) } expected := args[1].PyString().Lines var actual []string - for _, ft := range s.features { + for _, ft := range s.testedSuite.features { actual = append(actual, ft.Path) } if len(expected) != len(actual) { @@ -70,13 +85,13 @@ func (s *suiteFeature) iRunFeatureSuite(args ...*Arg) error { if err := s.parseFeatures(); err != nil { return err } - s.run() + s.testedSuite.run() return nil } func (s *suiteFeature) numScenariosRegistered(args ...*Arg) (err error) { var num int - for _, ft := range s.features { + for _, ft := range s.testedSuite.features { num += len(ft.Scenarios) } if num != args[0].Int() { @@ -120,11 +135,28 @@ func (s *suiteFeature) thereWasEventTriggeredBeforeScenario(args ...*Arg) error return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, args[0].String(), `"`+strings.Join(found, `", "`)+`"`) } -func SuiteContext(g Suite) { - s := &suiteFeature{ - suite: suite{}, +func (s *suiteFeature) theseEventsHadToBeFiredForNumberOfTimes(args ...*Arg) error { + tbl := args[0].Table() + if len(tbl.Rows[0]) != 2 { + return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0])) } + for _, row := range tbl.Rows { + args := []*Arg{ + StepArgument(""), // ignored + StepArgument(row[1]), + StepArgument(row[0]), + } + if err := s.thereWereNumEventsFired(args...); err != nil { + return err + } + } + return nil +} + +func SuiteContext(g Suite) { + s := &suiteFeature{} + g.BeforeScenario(s) g.Step( @@ -151,4 +183,7 @@ func SuiteContext(g Suite) { g.Step( regexp.MustCompile(`^there was event triggered before scenario "([^"]*)"$`), StepHandlerFunc(s.thereWasEventTriggeredBeforeScenario)) + g.Step( + regexp.MustCompile(`^these events had to be fired for a number of times:$`), + StepHandlerFunc(s.theseEventsHadToBeFiredForNumberOfTimes)) }