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

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

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

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

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

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

@ -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])
}

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

@ -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])
}

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

@ -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)
}

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

@ -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))
}