test table node and events fired

Этот коммит содержится в:
gedi 2015-06-18 17:26:28 +03:00
родитель 00fab00e22
коммит 49130c8b08
9 изменённых файлов: 140 добавлений и 70 удалений

34
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 |

Просмотреть файл

@ -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"

Просмотреть файл

@ -8,7 +8,7 @@ Feature: load features
When I parse features When I parse features
Then I should have 2 feature files: Then I should have 2 feature files:
""" """
features/hooks.feature features/events.feature
features/load_features.feature features/load_features.feature
""" """
@ -27,10 +27,10 @@ Feature: load features
Scenario: load a number of feature files Scenario: load a number of feature files
Given a feature path "features/load_features.feature" 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 When I parse features
Then I should have 2 feature files: Then I should have 2 feature files:
""" """
features/load_features.feature features/load_features.feature
features/hooks.feature features/events.feature
""" """

Просмотреть файл

@ -23,6 +23,7 @@ type pretty struct {
commentPos int commentPos int
doneBackground bool doneBackground bool
background *gherkin.Background background *gherkin.Background
scenario *gherkin.Scenario
// summary // summary
started time.Time 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) 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 // Node takes a gherkin node for formatting
func (f *pretty) Node(node interface{}) { func (f *pretty) Node(node interface{}) {
switch t := node.(type) { switch t := node.(type) {
@ -61,25 +48,27 @@ func (f *pretty) Node(node interface{}) {
fmt.Println("") fmt.Println("")
} }
f.feature = t f.feature = t
f.doneBackground = false f.scenario = nil
f.background = nil f.background = nil
f.features = append(f.features, t) f.features = append(f.features, t)
fmt.Println(bcl("Feature: ", white) + t.Title) fmt.Println(bcl("Feature: ", white) + t.Title)
fmt.Println(t.Description) fmt.Println(t.Description)
case *gherkin.Background: case *gherkin.Background:
// determine comment position based on step length // do not repeat background for the same feature
f.commentPos = len(t.Token.Text) if f.background == nil && f.scenario == nil {
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 {
f.background = t 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)) fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white))
} }
case *gherkin.Scenario: case *gherkin.Scenario:
f.scenario = t
// determine comment position based on step length // determine comment position based on step length
f.commentPos = len(t.Token.Text) f.commentPos = len(t.Token.Text)
for _, step := range t.Steps { for _, step := range t.Steps {
@ -180,7 +169,8 @@ func (f *pretty) printStep(stepAction interface{}) {
fatal(fmt.Errorf("unexpected step type received: %T", typ)) 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 return
} }
@ -225,11 +215,33 @@ func (f *pretty) printStep(stepAction interface{}) {
fmt.Println(cl(step.PyString.Raw, c)) fmt.Println(cl(step.PyString.Raw, c))
fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c)) fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c))
} }
if step.Table != nil {
f.printTable(step.Table, c)
}
if err != nil { if err != nil {
fmt.Println(s(step.Token.Indent) + bcl(err, red)) 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 // Passed is called to represent a passed step
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) { func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
s := &passed{step: step, handler: match} s := &passed{step: step, handler: match}

Просмотреть файл

@ -155,7 +155,7 @@ type Table struct {
*Token *Token
OutlineScenario *Scenario OutlineScenario *Scenario
Step *Step Step *Step
rows [][]string Rows [][]string
} }
var allSteps = []TokenType{ var allSteps = []TokenType{
@ -369,17 +369,17 @@ func (p *parser) parsePystring() (*PyString, error) {
} }
func (p *parser) parseTable() (*Table, 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() { for row := p.peek(); row.Type == TABLE_ROW; row = p.peek() {
var cols []string var cols []string
for _, r := range strings.Split(strings.Trim(row.Value, "|"), "|") { for _, r := range strings.Split(strings.Trim(row.Value, "|"), "|") {
cols = append(cols, strings.TrimFunc(r, unicode.IsSpace)) cols = append(cols, strings.TrimFunc(r, unicode.IsSpace))
} }
// ensure the same colum number for each row // 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) 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 p.next() // jump over the peeked token
} }
return tbl, nil return tbl, nil

Просмотреть файл

@ -25,13 +25,13 @@ func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) {
if s.Examples == nil { if s.Examples == nil {
t.Fatalf("outline scenario '%s' has no examples", s.Title) 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) 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)) 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] { 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]) t.Fatalf("outline scenario '%s' table row %d, column %d - value '%s', does not match expected: %s", s.Title, num, i, col, cols[i])
} }

Просмотреть файл

@ -69,13 +69,13 @@ func (s *Step) assertTableRow(t *testing.T, num int, cols ...string) {
if s.Table == nil { if s.Table == nil {
t.Fatalf("step '%s %s' has no table", s.Type, s.Text) 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) 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)) 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] { 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]) 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])
} }

Просмотреть файл

@ -11,9 +11,10 @@ import (
"github.com/DATA-DOG/godog/gherkin" "github.com/DATA-DOG/godog/gherkin"
) )
// Status represents a step status // Status represents a step or scenario status
type Status int type Status int
// step or scenario status constants
const ( const (
invalid Status = iota invalid Status = iota
Passed Passed
@ -303,7 +304,7 @@ func (s *suite) runFeature(f *gherkin.Feature) {
s.skipSteps(scenario.Steps) s.skipSteps(scenario.Steps)
case status == Undefined: case status == Undefined:
s.skipSteps(scenario.Steps) s.skipSteps(scenario.Steps)
case status == invalid: case status == Passed || status == invalid:
status = s.runSteps(scenario.Steps) status = s.runSteps(scenario.Steps)
} }

Просмотреть файл

@ -14,24 +14,39 @@ type firedEvent struct {
} }
type suiteFeature struct { type suiteFeature struct {
suite testedSuite *suite
events []*firedEvent events []*firedEvent
} }
func (s *suiteFeature) HandleBeforeScenario(scenario *gherkin.Scenario) { 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 // reset feature paths
cfg.paths = []string{} cfg.paths = []string{}
// reset event stack // reset all fired events
s.events = []*firedEvent{} s.events = []*firedEvent{}
// reset formatter, which collects all details
s.fmt = &testFormatter{}
} }
func (s *suiteFeature) iAmListeningToSuiteEvents(args ...*Arg) error { func (s *suiteFeature) iAmListeningToSuiteEvents(args ...*Arg) error {
s.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) { s.testedSuite.BeforeSuite(BeforeSuiteHandlerFunc(func() {
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{ s.events = append(s.events, &firedEvent{"BeforeSuite", []interface{}{}})
scenario, }))
}}) 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 return nil
} }
@ -42,17 +57,17 @@ func (s *suiteFeature) featurePath(args ...*Arg) error {
} }
func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) { func (s *suiteFeature) parseFeatures(args ...*Arg) (err error) {
s.features, err = cfg.features() s.testedSuite.features, err = cfg.features()
return return
} }
func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) error { func (s *suiteFeature) iShouldHaveNumFeatureFiles(args ...*Arg) error {
if len(s.features) != args[0].Int() { 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.features)) return fmt.Errorf("expected %d features to be parsed, but have %d", args[0].Int(), len(s.testedSuite.features))
} }
expected := args[1].PyString().Lines expected := args[1].PyString().Lines
var actual []string var actual []string
for _, ft := range s.features { for _, ft := range s.testedSuite.features {
actual = append(actual, ft.Path) actual = append(actual, ft.Path)
} }
if len(expected) != len(actual) { if len(expected) != len(actual) {
@ -70,13 +85,13 @@ func (s *suiteFeature) iRunFeatureSuite(args ...*Arg) error {
if err := s.parseFeatures(); err != nil { if err := s.parseFeatures(); err != nil {
return err return err
} }
s.run() s.testedSuite.run()
return nil return nil
} }
func (s *suiteFeature) numScenariosRegistered(args ...*Arg) (err error) { func (s *suiteFeature) numScenariosRegistered(args ...*Arg) (err error) {
var num int var num int
for _, ft := range s.features { for _, ft := range s.testedSuite.features {
num += len(ft.Scenarios) num += len(ft.Scenarios)
} }
if num != args[0].Int() { 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, `", "`)+`"`) return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, args[0].String(), `"`+strings.Join(found, `", "`)+`"`)
} }
func SuiteContext(g Suite) { func (s *suiteFeature) theseEventsHadToBeFiredForNumberOfTimes(args ...*Arg) error {
s := &suiteFeature{ tbl := args[0].Table()
suite: suite{}, 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.BeforeScenario(s)
g.Step( g.Step(
@ -151,4 +183,7 @@ func SuiteContext(g Suite) {
g.Step( g.Step(
regexp.MustCompile(`^there was event triggered before scenario "([^"]*)"$`), regexp.MustCompile(`^there was event triggered before scenario "([^"]*)"$`),
StepHandlerFunc(s.thereWasEventTriggeredBeforeScenario)) StepHandlerFunc(s.thereWasEventTriggeredBeforeScenario))
g.Step(
regexp.MustCompile(`^these events had to be fired for a number of times:$`),
StepHandlerFunc(s.theseEventsHadToBeFiredForNumberOfTimes))
} }