From 2d3d04e0e6fa89634c1720cd20b89bdc84bba915 Mon Sep 17 00:00:00 2001 From: gedi Date: Tue, 16 Jun 2015 17:22:58 +0300 Subject: [PATCH] more specific types and references for gherkin nodes --- arguments.go | 35 ++++++++++++++++++ cmd/godog/main.go | 2 + formatter.go | 92 ++++++++++++++++++---------------------------- gherkin/gherkin.go | 54 +++++++++++++++++++++------ suite.go | 30 --------------- 5 files changed, 114 insertions(+), 99 deletions(-) create mode 100644 arguments.go diff --git a/arguments.go b/arguments.go new file mode 100644 index 0000000..3f5d70d --- /dev/null +++ b/arguments.go @@ -0,0 +1,35 @@ +package godog + +import ( + "fmt" + "strconv" +) + +// Arg is an argument for StepHandler parsed from +// the regexp submatch to handle the step +type Arg string + +// Float converts an argument to float64 +// or panics if unable to convert it +func (a Arg) Float() float64 { + v, err := strconv.ParseFloat(string(a), 64) + if err == nil { + return v + } + panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, a, err)) +} + +// Int converts an argument to int64 +// or panics if unable to convert it +func (a Arg) Int() int64 { + v, err := strconv.ParseInt(string(a), 10, 0) + if err == nil { + return v + } + panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, a, err)) +} + +// String converts an argument to string +func (a Arg) String() string { + return string(a) +} diff --git a/cmd/godog/main.go b/cmd/godog/main.go index 2e9e958..fc076d7 100644 --- a/cmd/godog/main.go +++ b/cmd/godog/main.go @@ -37,6 +37,8 @@ func main() { // @TODO: support for windows cmd := exec.Command("sh", "-c", c) cmd.Stdout = stdout + // @TODO: do not read stderr on production version + cmd.Stderr = stdout err = cmd.Run() switch err.(type) { diff --git a/formatter.go b/formatter.go index 4253f79..025c66a 100644 --- a/formatter.go +++ b/formatter.go @@ -31,7 +31,6 @@ type Formatter interface { // general pretty formatter structure type pretty struct { feature *gherkin.Feature - scenario *gherkin.Scenario commentPos int doneBackground bool background *gherkin.Background @@ -48,34 +47,39 @@ type pretty struct { // failed represents a failed step data structure // with all necessary references type failed struct { - feature *gherkin.Feature - scenario *gherkin.Scenario - step *gherkin.Step - err error + step *gherkin.Step + err error +} + +func (f failed) line() string { + var tok *gherkin.Token + var ft *gherkin.Feature + if f.step.Scenario != nil { + tok = f.step.Scenario.Token + ft = f.step.Scenario.Feature + } else { + tok = f.step.Background.Token + ft = f.step.Background.Feature + } + return fmt.Sprintf("%s:%d", ft.Path, tok.Line) } // passed represents a successful step data structure // with all necessary references type passed struct { - feature *gherkin.Feature - scenario *gherkin.Scenario - step *gherkin.Step + step *gherkin.Step } // skipped represents a skipped step data structure // with all necessary references type skipped struct { - feature *gherkin.Feature - scenario *gherkin.Scenario - step *gherkin.Step + step *gherkin.Step } // undefined represents a pending step data structure // with all necessary references type undefined struct { - feature *gherkin.Feature - scenario *gherkin.Scenario - step *gherkin.Step + step *gherkin.Step } // a line number representation in feature file @@ -85,24 +89,10 @@ func (f *pretty) line(tok *gherkin.Token) string { // 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 f.background != nil { + return step.Background != nil } - - var backgroundStep bool - for _, s := range f.background.Steps { - if s == step { - backgroundStep = true - break - } - } - - if !backgroundStep { - f.doneBackground = true - return true - } - - return !f.doneBackground + return true } // Node takes a gherkin node for formatting @@ -111,7 +101,6 @@ func (f *pretty) Node(node interface{}) { case *gherkin.Feature: 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 + "\n") @@ -120,7 +109,6 @@ func (f *pretty) Node(node interface{}) { f.background = t fmt.Println(bcl("Background:", white) + "\n") case *gherkin.Scenario: - f.scenario = t f.commentPos = len(t.Token.Text) for _, step := range t.Steps { if len(step.Token.Text) > f.commentPos { @@ -135,10 +123,17 @@ func (f *pretty) Node(node interface{}) { // Summary sumarize the feature formatter output func (f *pretty) Summary() { - if len(f.failed) > 0 { + // failed steps on background are not scenarios + var failedScenarios []*failed + for _, fail := range f.failed { + if fail.step.Scenario != nil { + failedScenarios = append(failedScenarios, fail) + } + } + if len(failedScenarios) > 0 { fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n") - for _, fail := range f.failed { - fmt.Println(" " + cl(fmt.Sprintf("%s:%d", fail.feature.Path, fail.scenario.Token.Line), red)) + for _, fail := range failedScenarios { + fmt.Println(" " + cl(fail.line(), red)) } } var total, passed int @@ -230,11 +225,7 @@ func (f *pretty) printMatchedStep(step *gherkin.Step, match *stepMatchHandler, c // Passed is called to represent a passed step func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) { f.printMatchedStep(step, match, green) - f.passed = append(f.passed, &passed{ - feature: f.feature, - scenario: f.scenario, - step: step, - }) + f.passed = append(f.passed, &passed{step}) } // Skipped is called to represent a passed step @@ -242,11 +233,7 @@ func (f *pretty) Skipped(step *gherkin.Step) { if f.canPrintStep(step) { fmt.Println(cl(step.Token.Text, cyan)) } - f.skipped = append(f.skipped, &skipped{ - feature: f.feature, - scenario: f.scenario, - step: step, - }) + f.skipped = append(f.skipped, &skipped{step}) } // Undefined is called to represent a pending step @@ -254,21 +241,12 @@ func (f *pretty) Undefined(step *gherkin.Step) { if f.canPrintStep(step) { fmt.Println(cl(step.Token.Text, yellow)) } - f.undefined = append(f.undefined, &undefined{ - feature: f.feature, - scenario: f.scenario, - step: step, - }) + f.undefined = append(f.undefined, &undefined{step}) } // Failed is called to represent a failed step func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) { f.printMatchedStep(step, match, red) fmt.Println(strings.Repeat(" ", step.Token.Indent) + bcl(err, red)) - f.failed = append(f.failed, &failed{ - feature: f.feature, - scenario: f.scenario, - step: step, - err: err, - }) + f.failed = append(f.failed, &failed{step, err}) } diff --git a/gherkin/gherkin.go b/gherkin/gherkin.go index ade38c9..307b35c 100644 --- a/gherkin/gherkin.go +++ b/gherkin/gherkin.go @@ -102,22 +102,26 @@ type Scenario struct { Title string Steps []*Step Tags Tags - Examples *Table + Examples *ExampleTable + Feature *Feature } // Background steps are run before every scenario type Background struct { *Token - Steps []*Step + Steps []*Step + Feature *Feature } // Step describes a Scenario or Background step type Step struct { *Token - Text string - Type string - PyString *PyString - Table *Table + Text string + Type string + PyString *PyString + Table *StepTable + Scenario *Scenario + Background *Background } // Feature describes the whole feature @@ -136,14 +140,28 @@ type Feature struct { type PyString struct { *Token Body string + Step *Step } // Table is a row group object used with step definition -type Table struct { +type table struct { *Token rows [][]string } +// ExampleTable is a row group object for +// scenario outline examples +type ExampleTable struct { + *table + OutlineScenario *Scenario +} + +// StepTable is a row group object for steps +type StepTable struct { + *table + Step *Step +} + var allSteps = []TokenType{ GIVEN, WHEN, @@ -238,11 +256,14 @@ func (p *parser) parseFeature() (ft *Feature, err error) { return ft, p.err("there can only be a single background section, but found another", tok.Line) } - ft.Background = &Background{Token: tok} + ft.Background = &Background{Token: tok, Feature: ft} p.next() // jump to background steps if ft.Background.Steps, err = p.parseSteps(); err != nil { return ft, err } + for _, step := range ft.Background.Steps { + step.Background = ft.Background + } tok = p.peek() // peek to scenario or tags } @@ -269,6 +290,7 @@ func (p *parser) parseFeature() (ft *Feature, err error) { } scenario.Tags = tags + scenario.Feature = ft ft.Scenarios = append(ft.Scenarios, scenario) } @@ -281,6 +303,9 @@ func (p *parser) parseScenario() (s *Scenario, err error) { if s.Steps, err = p.parseSteps(); err != nil { return s, err } + for _, step := range s.Steps { + step.Scenario = s + } if examples := p.peek(); examples.Type == EXAMPLES { p.next() // jump over the peeked token peek := p.peek() @@ -290,9 +315,11 @@ func (p *parser) parseScenario() (s *Scenario, err error) { "but got '" + peek.Type.String() + "' instead, for scenario outline examples", }, " "), examples.Line) } - if s.Examples, err = p.parseTable(); err != nil { + tbl, err := p.parseTable() + if err != nil { return s, err } + s.Examples = &ExampleTable{OutlineScenario: s, table: tbl} } return s, nil } @@ -309,10 +336,13 @@ func (p *parser) parseSteps() (steps []*Step, err error) { if step.PyString, err = p.parsePystring(); err != nil { return steps, err } + step.PyString.Step = step case TABLE_ROW: - if step.Table, err = p.parseTable(); err != nil { + tbl, err := p.parseTable() + if err != nil { return steps, err } + step.Table = &StepTable{Step: step, table: tbl} default: return steps, p.err("pystring or table row was expected, but got: '"+tok.Type.String()+"' instead", tok.Line) } @@ -339,8 +369,8 @@ func (p *parser) parsePystring() (*PyString, error) { }, nil } -func (p *parser) parseTable() (*Table, error) { - tbl := &Table{} +func (p *parser) parseTable() (*table, error) { + tbl := &table{} for row := p.peek(); row.Type == TABLE_ROW; row = p.peek() { var cols []string for _, r := range strings.Split(strings.Trim(row.Value, "|"), "|") { diff --git a/suite.go b/suite.go index 5d721f5..c00d8d5 100644 --- a/suite.go +++ b/suite.go @@ -4,40 +4,10 @@ import ( "flag" "fmt" "regexp" - "strconv" "github.com/DATA-DOG/godog/gherkin" ) -// Arg is an argument for StepHandler parsed from -// the regexp submatch to handle the step -type Arg string - -// Float converts an argument to float64 -// or panics if unable to convert it -func (a Arg) Float() float64 { - v, err := strconv.ParseFloat(string(a), 64) - if err == nil { - return v - } - panic(fmt.Sprintf(`cannot convert "%s" to float64: %s`, a, err)) -} - -// Int converts an argument to int64 -// or panics if unable to convert it -func (a Arg) Int() int64 { - v, err := strconv.ParseInt(string(a), 10, 0) - if err == nil { - return v - } - panic(fmt.Sprintf(`cannot convert "%s" to int64: %s`, a, err)) -} - -// String converts an argument to string -func (a Arg) String() string { - return string(a) -} - // Objects implementing the StepHandler interface can be // registered as step definitions in godog //