support scenario outline with example table
* 042235c expose StepDef which is needed to formatters * e9474dc fix outline scenario formatting * f02c6ce fix comment position calculation for outlines * 5d7f128 remove step status type, use standard error
Этот коммит содержится в:
родитель
2e696381c9
коммит
df26aa1c1c
12 изменённых файлов: 481 добавлений и 291 удалений
16
events.go
16
events.go
|
@ -56,34 +56,34 @@ func (f BeforeStepHandlerFunc) HandleBeforeStep(step *gherkin.Step) {
|
||||||
// in Suite to be executed after every step
|
// in Suite to be executed after every step
|
||||||
// which will be run
|
// which will be run
|
||||||
type AfterStepHandler interface {
|
type AfterStepHandler interface {
|
||||||
HandleAfterStep(step *gherkin.Step, status Status)
|
HandleAfterStep(step *gherkin.Step, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterStepHandlerFunc is a function implementing
|
// AfterStepHandlerFunc is a function implementing
|
||||||
// AfterStepHandler interface
|
// AfterStepHandler interface
|
||||||
type AfterStepHandlerFunc func(step *gherkin.Step, status Status)
|
type AfterStepHandlerFunc func(step *gherkin.Step, err error)
|
||||||
|
|
||||||
// HandleAfterStep is called with a *gherkin.Step argument
|
// HandleAfterStep is called with a *gherkin.Step argument
|
||||||
// for after every step which is run by suite
|
// for after every step which is run by suite
|
||||||
func (f AfterStepHandlerFunc) HandleAfterStep(step *gherkin.Step, status Status) {
|
func (f AfterStepHandlerFunc) HandleAfterStep(step *gherkin.Step, err error) {
|
||||||
f(step, status)
|
f(step, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterScenarioHandler can be registered
|
// AfterScenarioHandler can be registered
|
||||||
// in Suite to be executed after every scenario
|
// in Suite to be executed after every scenario
|
||||||
// which will be run
|
// which will be run
|
||||||
type AfterScenarioHandler interface {
|
type AfterScenarioHandler interface {
|
||||||
HandleAfterScenario(scenario *gherkin.Scenario, status Status)
|
HandleAfterScenario(scenario *gherkin.Scenario, err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterScenarioHandlerFunc is a function implementing
|
// AfterScenarioHandlerFunc is a function implementing
|
||||||
// AfterScenarioHandler interface
|
// AfterScenarioHandler interface
|
||||||
type AfterScenarioHandlerFunc func(scenario *gherkin.Scenario, status Status)
|
type AfterScenarioHandlerFunc func(scenario *gherkin.Scenario, err error)
|
||||||
|
|
||||||
// HandleAfterScenario is called with a *gherkin.Scenario argument
|
// HandleAfterScenario is called with a *gherkin.Scenario argument
|
||||||
// for after every scenario which is run by suite
|
// for after every scenario which is run by suite
|
||||||
func (f AfterScenarioHandlerFunc) HandleAfterScenario(scenario *gherkin.Scenario, status Status) {
|
func (f AfterScenarioHandlerFunc) HandleAfterScenario(scenario *gherkin.Scenario, err error) {
|
||||||
f(scenario, status)
|
f(scenario, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterSuiteHandler can be registered
|
// AfterSuiteHandler can be registered
|
||||||
|
|
|
@ -27,8 +27,8 @@ Feature: suite events
|
||||||
When I run feature suite
|
When I run feature suite
|
||||||
Then these events had to be fired for a number of times:
|
Then these events had to be fired for a number of times:
|
||||||
| BeforeSuite | 1 |
|
| BeforeSuite | 1 |
|
||||||
| BeforeScenario | 4 |
|
| BeforeScenario | 6 |
|
||||||
| BeforeStep | 13 |
|
| BeforeStep | 19 |
|
||||||
| AfterStep | 13 |
|
| AfterStep | 19 |
|
||||||
| AfterScenario | 4 |
|
| AfterScenario | 6 |
|
||||||
| AfterSuite | 1 |
|
| AfterSuite | 1 |
|
||||||
|
|
|
@ -21,10 +21,16 @@ Feature: load features
|
||||||
features/load.feature
|
features/load.feature
|
||||||
"""
|
"""
|
||||||
|
|
||||||
Scenario: load a feature file with a specified scenario
|
Scenario Outline: loaded feature should have a number of scenarios
|
||||||
Given a feature path "features/load.feature:6"
|
Given a feature path "<feature>"
|
||||||
When I parse features
|
When I parse features
|
||||||
Then I should have 1 scenario registered
|
Then I should have <number> scenario registered
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
| feature | number |
|
||||||
|
| features/load.feature:3 | 0 |
|
||||||
|
| features/load.feature:6 | 1 |
|
||||||
|
| features/load.feature | 4 |
|
||||||
|
|
||||||
Scenario: load a number of feature files
|
Scenario: load a number of feature files
|
||||||
Given a feature path "features/load.feature"
|
Given a feature path "features/load.feature"
|
||||||
|
|
14
fmt.go
14
fmt.go
|
@ -10,8 +10,8 @@ import (
|
||||||
// output summary presentation
|
// output summary presentation
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Node(interface{})
|
Node(interface{})
|
||||||
Failed(*gherkin.Step, *stepMatchHandler, error)
|
Failed(*gherkin.Step, *StepDef, error)
|
||||||
Passed(*gherkin.Step, *stepMatchHandler)
|
Passed(*gherkin.Step, *StepDef)
|
||||||
Skipped(*gherkin.Step)
|
Skipped(*gherkin.Step)
|
||||||
Undefined(*gherkin.Step)
|
Undefined(*gherkin.Step)
|
||||||
Summary()
|
Summary()
|
||||||
|
@ -20,9 +20,9 @@ type Formatter interface {
|
||||||
// failed represents a failed step data structure
|
// failed represents a failed step data structure
|
||||||
// with all necessary references
|
// with all necessary references
|
||||||
type failed struct {
|
type failed struct {
|
||||||
step *gherkin.Step
|
step *gherkin.Step
|
||||||
handler *stepMatchHandler
|
def *StepDef
|
||||||
err error
|
err error
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f failed) line() string {
|
func (f failed) line() string {
|
||||||
|
@ -41,8 +41,8 @@ func (f failed) line() string {
|
||||||
// passed represents a successful step data structure
|
// passed represents a successful step data structure
|
||||||
// with all necessary references
|
// with all necessary references
|
||||||
type passed struct {
|
type passed struct {
|
||||||
step *gherkin.Step
|
step *gherkin.Step
|
||||||
handler *stepMatchHandler
|
def *StepDef
|
||||||
}
|
}
|
||||||
|
|
||||||
// skipped represents a skipped step data structure
|
// skipped represents a skipped step data structure
|
||||||
|
|
320
fmt_pretty.go
320
fmt_pretty.go
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"math"
|
"math"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
@ -17,6 +18,8 @@ func init() {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var outlinePlaceholderRegexp *regexp.Regexp = regexp.MustCompile("<[^>]+>")
|
||||||
|
|
||||||
// a built in default pretty formatter
|
// a built in default pretty formatter
|
||||||
type pretty struct {
|
type pretty struct {
|
||||||
feature *gherkin.Feature
|
feature *gherkin.Feature
|
||||||
|
@ -25,6 +28,11 @@ type pretty struct {
|
||||||
background *gherkin.Background
|
background *gherkin.Background
|
||||||
scenario *gherkin.Scenario
|
scenario *gherkin.Scenario
|
||||||
|
|
||||||
|
// outline
|
||||||
|
outlineExamples int
|
||||||
|
outlineNumSteps int
|
||||||
|
outlineSteps []interface{}
|
||||||
|
|
||||||
// summary
|
// summary
|
||||||
started time.Time
|
started time.Time
|
||||||
features []*gherkin.Feature
|
features []*gherkin.Feature
|
||||||
|
@ -51,33 +59,31 @@ func (f *pretty) Node(node interface{}) {
|
||||||
f.scenario = nil
|
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(t.Token.Keyword+": ", white) + t.Title)
|
||||||
fmt.Println(t.Description)
|
fmt.Println(t.Description)
|
||||||
case *gherkin.Background:
|
case *gherkin.Background:
|
||||||
// do not repeat background for the same feature
|
// do not repeat background for the same feature
|
||||||
if f.background == nil && f.scenario == nil {
|
if f.background == nil && f.scenario == nil {
|
||||||
f.background = t
|
f.background = t
|
||||||
// determine comment position based on step length
|
f.commentPos = longestStep(t.Steps, t.Token.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
|
// print background node
|
||||||
fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white))
|
fmt.Println("\n" + s(t.Token.Indent) + bcl(t.Token.Keyword+":", white))
|
||||||
}
|
}
|
||||||
case *gherkin.Scenario:
|
case *gherkin.Scenario:
|
||||||
f.scenario = t
|
f.scenario = t
|
||||||
// determine comment position based on step length
|
f.commentPos = longestStep(t.Steps, t.Token.Length())
|
||||||
f.commentPos = len(t.Token.Text)
|
if t.Outline != nil {
|
||||||
for _, step := range t.Steps {
|
f.outlineSteps = []interface{}{} // reset steps list
|
||||||
if len(step.Token.Text) > f.commentPos {
|
f.commentPos = longestStep(t.Outline.Steps, t.Token.Length())
|
||||||
f.commentPos = len(step.Token.Text)
|
if f.outlineExamples == 0 {
|
||||||
|
f.outlineNumSteps = len(t.Outline.Steps)
|
||||||
|
f.outlineExamples = len(t.Outline.Examples.Rows) - 1
|
||||||
|
} else {
|
||||||
|
return // already printed an outline
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
text := s(t.Token.Indent) + bcl("Scenario: ", white) + t.Title
|
text := s(t.Token.Indent) + bcl(t.Token.Keyword+": ", white) + t.Title
|
||||||
text += s(f.commentPos-len(t.Token.Text)+1) + f.line(t.Token)
|
text += s(f.commentPos-t.Token.Length()+1) + f.line(t.Token)
|
||||||
fmt.Println("\n" + text)
|
fmt.Println("\n" + text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -93,8 +99,22 @@ func (f *pretty) Summary() {
|
||||||
}
|
}
|
||||||
if len(failedScenarios) > 0 {
|
if len(failedScenarios) > 0 {
|
||||||
fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n")
|
fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n")
|
||||||
|
var unique []string
|
||||||
for _, fail := range failedScenarios {
|
for _, fail := range failedScenarios {
|
||||||
fmt.Println(" " + cl(fail.line(), red))
|
var found bool
|
||||||
|
for _, in := range unique {
|
||||||
|
if in == fail.line() {
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
unique = append(unique, fail.line())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, fail := range unique {
|
||||||
|
fmt.Println(" " + cl(fail, red))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
var total, passed int
|
var total, passed int
|
||||||
|
@ -142,21 +162,129 @@ func (f *pretty) Summary() {
|
||||||
fmt.Println(elapsed)
|
fmt.Println(elapsed)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) printStep(stepAction interface{}) {
|
func (f *pretty) printOutlineExample() {
|
||||||
var c color
|
var failed error
|
||||||
var step *gherkin.Step
|
clr := green
|
||||||
var h *stepMatchHandler
|
tbl := f.scenario.Outline.Examples
|
||||||
var err error
|
firstExample := f.outlineExamples == len(tbl.Rows)-1
|
||||||
var suffix, prefix string
|
|
||||||
|
|
||||||
|
for i, act := range f.outlineSteps {
|
||||||
|
var c color
|
||||||
|
var def *StepDef
|
||||||
|
var err error
|
||||||
|
|
||||||
|
_, def, c, err = f.stepDetails(act)
|
||||||
|
// determine example row status
|
||||||
|
switch {
|
||||||
|
case err != nil:
|
||||||
|
failed = err
|
||||||
|
clr = red
|
||||||
|
case c == yellow:
|
||||||
|
clr = yellow
|
||||||
|
case c == cyan && clr == green:
|
||||||
|
clr = cyan
|
||||||
|
}
|
||||||
|
if firstExample {
|
||||||
|
// in first example, we need to print steps
|
||||||
|
var text string
|
||||||
|
ostep := f.scenario.Outline.Steps[i]
|
||||||
|
if def != nil {
|
||||||
|
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
|
||||||
|
var pos int
|
||||||
|
for i := 0; i < len(m); i++ {
|
||||||
|
pair := m[i]
|
||||||
|
text += cl(ostep.Text[pos:pair[0]], cyan)
|
||||||
|
text += bcl(ostep.Text[pair[0]:pair[1]], cyan)
|
||||||
|
pos = pair[1]
|
||||||
|
}
|
||||||
|
text += cl(ostep.Text[pos:len(ostep.Text)], cyan)
|
||||||
|
} else {
|
||||||
|
text = cl(ostep.Text, cyan)
|
||||||
|
}
|
||||||
|
// use reflect to get step handler function name
|
||||||
|
name := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name()
|
||||||
|
text += s(f.commentPos-ostep.Token.Length()+1) + cl(fmt.Sprintf("# %s", name), black)
|
||||||
|
} else {
|
||||||
|
text = cl(ostep.Text, cyan)
|
||||||
|
}
|
||||||
|
// print the step outline
|
||||||
|
fmt.Println(s(ostep.Token.Indent) + cl(ostep.Token.Keyword, cyan) + " " + text)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cols := make([]string, len(tbl.Rows[0]))
|
||||||
|
max := longest(tbl)
|
||||||
|
// an example table header
|
||||||
|
if firstExample {
|
||||||
|
out := f.scenario.Outline
|
||||||
|
fmt.Println("")
|
||||||
|
fmt.Println(s(out.Token.Indent) + bcl(out.Token.Keyword+":", white))
|
||||||
|
row := tbl.Rows[0]
|
||||||
|
|
||||||
|
for i, col := range row {
|
||||||
|
cols[i] = cl(col, cyan) + s(max[i]-len(col))
|
||||||
|
}
|
||||||
|
fmt.Println(s(tbl.Token.Indent) + "| " + strings.Join(cols, " | ") + " |")
|
||||||
|
}
|
||||||
|
|
||||||
|
// an example table row
|
||||||
|
row := tbl.Rows[len(tbl.Rows)-f.outlineExamples]
|
||||||
|
for i, col := range row {
|
||||||
|
cols[i] = cl(col, clr) + s(max[i]-len(col))
|
||||||
|
}
|
||||||
|
fmt.Println(s(tbl.Token.Indent) + "| " + strings.Join(cols, " | ") + " |")
|
||||||
|
|
||||||
|
// if there is an error
|
||||||
|
if failed != nil {
|
||||||
|
fmt.Println(s(tbl.Token.Indent) + bcl(failed, red))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
||||||
|
text := s(step.Token.Indent) + cl(step.Token.Keyword, c) + " "
|
||||||
|
switch {
|
||||||
|
case def != nil:
|
||||||
|
if m := (def.Expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 {
|
||||||
|
var pos, i int
|
||||||
|
for pos, i = 0, 0; i < len(m); i++ {
|
||||||
|
if math.Mod(float64(i), 2) == 0 {
|
||||||
|
text += cl(step.Text[pos:m[i]], c)
|
||||||
|
} else {
|
||||||
|
text += bcl(step.Text[pos:m[i]], c)
|
||||||
|
}
|
||||||
|
pos = m[i]
|
||||||
|
}
|
||||||
|
text += cl(step.Text[pos:len(step.Text)], c)
|
||||||
|
} else {
|
||||||
|
text += cl(step.Text, c)
|
||||||
|
}
|
||||||
|
// use reflect to get step handler function name
|
||||||
|
name := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name()
|
||||||
|
text += s(f.commentPos-step.Token.Length()+1) + cl(fmt.Sprintf("# %s", name), black)
|
||||||
|
default:
|
||||||
|
text += cl(step.Text, c)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println(text)
|
||||||
|
if step.PyString != nil {
|
||||||
|
fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c))
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) stepDetails(stepAction interface{}) (step *gherkin.Step, def *StepDef, c color, err error) {
|
||||||
switch typ := stepAction.(type) {
|
switch typ := stepAction.(type) {
|
||||||
case *passed:
|
case *passed:
|
||||||
step = typ.step
|
step = typ.step
|
||||||
h = typ.handler
|
def = typ.def
|
||||||
c = green
|
c = green
|
||||||
case *failed:
|
case *failed:
|
||||||
step = typ.step
|
step = typ.step
|
||||||
h = typ.handler
|
def = typ.def
|
||||||
err = typ.err
|
err = typ.err
|
||||||
c = red
|
c = red
|
||||||
case *skipped:
|
case *skipped:
|
||||||
|
@ -168,56 +296,33 @@ func (f *pretty) printStep(stepAction interface{}) {
|
||||||
default:
|
default:
|
||||||
fatal(fmt.Errorf("unexpected step type received: %T", typ))
|
fatal(fmt.Errorf("unexpected step type received: %T", typ))
|
||||||
}
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) printStepKind(stepAction interface{}) {
|
||||||
|
var c color
|
||||||
|
var step *gherkin.Step
|
||||||
|
var def *StepDef
|
||||||
|
var err error
|
||||||
|
|
||||||
|
step, def, c, err = f.stepDetails(stepAction)
|
||||||
|
|
||||||
// do not print background more than once
|
// do not print background more than once
|
||||||
if f.scenario == nil && step.Background != f.background {
|
if f.scenario == nil && step.Background != f.background {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if h != nil {
|
if f.outlineExamples != 0 {
|
||||||
if m := (h.expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 {
|
f.outlineSteps = append(f.outlineSteps, stepAction)
|
||||||
var pos, i int
|
if len(f.outlineSteps) == f.outlineNumSteps {
|
||||||
for pos, i = 0, 0; i < len(m); i++ {
|
// an outline example steps has went through
|
||||||
if math.Mod(float64(i), 2) == 0 {
|
f.printOutlineExample()
|
||||||
suffix += cl(step.Text[pos:m[i]], c)
|
f.outlineExamples -= 1
|
||||||
} else {
|
|
||||||
suffix += bcl(step.Text[pos:m[i]], c)
|
|
||||||
}
|
|
||||||
pos = m[i]
|
|
||||||
}
|
|
||||||
suffix += cl(step.Text[pos:len(step.Text)], c)
|
|
||||||
} else {
|
|
||||||
suffix = cl(step.Text, c)
|
|
||||||
}
|
}
|
||||||
// use reflect to get step handler function name
|
return // wait till example steps
|
||||||
name := runtime.FuncForPC(reflect.ValueOf(h.handler).Pointer()).Name()
|
|
||||||
suffix += s(f.commentPos-len(step.Token.Text)+1) + cl(fmt.Sprintf("# %s", name), black)
|
|
||||||
} else {
|
|
||||||
suffix = cl(step.Text, c)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
prefix = s(step.Token.Indent)
|
f.printStep(step, def, c)
|
||||||
switch step.Token.Type {
|
|
||||||
case gherkin.GIVEN:
|
|
||||||
prefix += cl("Given", c)
|
|
||||||
case gherkin.WHEN:
|
|
||||||
prefix += cl("When", c)
|
|
||||||
case gherkin.THEN:
|
|
||||||
prefix += cl("Then", c)
|
|
||||||
case gherkin.AND:
|
|
||||||
prefix += cl("And", c)
|
|
||||||
case gherkin.BUT:
|
|
||||||
prefix += cl("But", c)
|
|
||||||
}
|
|
||||||
fmt.Println(prefix, suffix)
|
|
||||||
if step.PyString != nil {
|
|
||||||
fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c))
|
|
||||||
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 {
|
if err != nil {
|
||||||
fmt.Println(s(step.Token.Indent) + bcl(err, red))
|
fmt.Println(s(step.Token.Indent) + bcl(err, red))
|
||||||
}
|
}
|
||||||
|
@ -225,8 +330,47 @@ func (f *pretty) printStep(stepAction interface{}) {
|
||||||
|
|
||||||
// print table with aligned table cells
|
// print table with aligned table cells
|
||||||
func (f *pretty) printTable(t *gherkin.Table, c color) {
|
func (f *pretty) printTable(t *gherkin.Table, c color) {
|
||||||
var longest = make([]int, len(t.Rows[0]))
|
var l = longest(t)
|
||||||
var cols = make([]string, len(t.Rows[0]))
|
var cols = make([]string, len(t.Rows[0]))
|
||||||
|
for _, row := range t.Rows {
|
||||||
|
for i, col := range row {
|
||||||
|
cols[i] = col + s(l[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 *StepDef) {
|
||||||
|
s := &passed{step: step, def: match}
|
||||||
|
f.printStepKind(s)
|
||||||
|
f.passed = append(f.passed, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Skipped is called to represent a passed step
|
||||||
|
func (f *pretty) Skipped(step *gherkin.Step) {
|
||||||
|
s := &skipped{step: step}
|
||||||
|
f.printStepKind(s)
|
||||||
|
f.skipped = append(f.skipped, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Undefined is called to represent a pending step
|
||||||
|
func (f *pretty) Undefined(step *gherkin.Step) {
|
||||||
|
s := &undefined{step: step}
|
||||||
|
f.printStepKind(s)
|
||||||
|
f.undefined = append(f.undefined, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Failed is called to represent a failed step
|
||||||
|
func (f *pretty) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||||
|
s := &failed{step: step, def: match, err: err}
|
||||||
|
f.printStepKind(s)
|
||||||
|
f.failed = append(f.failed, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
// longest gives a list of longest columns of all rows in Table
|
||||||
|
func longest(t *gherkin.Table) []int {
|
||||||
|
var longest = make([]int, len(t.Rows[0]))
|
||||||
for _, row := range t.Rows {
|
for _, row := range t.Rows {
|
||||||
for i, col := range row {
|
for i, col := range row {
|
||||||
if longest[i] < len(col) {
|
if longest[i] < len(col) {
|
||||||
|
@ -234,38 +378,16 @@ func (f *pretty) printTable(t *gherkin.Table, c color) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, row := range t.Rows {
|
return longest
|
||||||
for i, col := range row {
|
}
|
||||||
cols[i] = col + s(longest[i]-len(col))
|
|
||||||
|
func longestStep(steps []*gherkin.Step, base int) int {
|
||||||
|
ret := base
|
||||||
|
for _, step := range steps {
|
||||||
|
length := step.Token.Length()
|
||||||
|
if length > base {
|
||||||
|
ret = length
|
||||||
}
|
}
|
||||||
fmt.Println(s(t.Token.Indent) + cl("| "+strings.Join(cols, " | ")+" |", c))
|
|
||||||
}
|
}
|
||||||
}
|
return ret
|
||||||
|
|
||||||
// Passed is called to represent a passed step
|
|
||||||
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
|
|
||||||
s := &passed{step: step, handler: match}
|
|
||||||
f.printStep(s)
|
|
||||||
f.passed = append(f.passed, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skipped is called to represent a passed step
|
|
||||||
func (f *pretty) Skipped(step *gherkin.Step) {
|
|
||||||
s := &skipped{step: step}
|
|
||||||
f.printStep(s)
|
|
||||||
f.skipped = append(f.skipped, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Undefined is called to represent a pending step
|
|
||||||
func (f *pretty) Undefined(step *gherkin.Step) {
|
|
||||||
s := &undefined{step: step}
|
|
||||||
f.printStep(s)
|
|
||||||
f.undefined = append(f.undefined, s)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Failed is called to represent a failed step
|
|
||||||
func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) {
|
|
||||||
s := &failed{step: step, handler: match, err: err}
|
|
||||||
f.printStep(s)
|
|
||||||
f.failed = append(f.failed, s)
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,8 +23,8 @@ func (f *testFormatter) Node(node interface{}) {
|
||||||
|
|
||||||
func (f *testFormatter) Summary() {}
|
func (f *testFormatter) Summary() {}
|
||||||
|
|
||||||
func (f *testFormatter) Passed(step *gherkin.Step, match *stepMatchHandler) {
|
func (f *testFormatter) Passed(step *gherkin.Step, match *StepDef) {
|
||||||
f.passed = append(f.passed, &passed{step: step, handler: match})
|
f.passed = append(f.passed, &passed{step: step, def: match})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testFormatter) Skipped(step *gherkin.Step) {
|
func (f *testFormatter) Skipped(step *gherkin.Step) {
|
||||||
|
@ -35,6 +35,6 @@ func (f *testFormatter) Undefined(step *gherkin.Step) {
|
||||||
f.undefined = append(f.undefined, &undefined{step: step})
|
f.undefined = append(f.undefined, &undefined{step: step})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testFormatter) Failed(step *gherkin.Step, match *stepMatchHandler, err error) {
|
func (f *testFormatter) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||||
f.failed = append(f.failed, &failed{step: step, handler: match, err: err})
|
f.failed = append(f.failed, &failed{step: step, def: match, err: err})
|
||||||
}
|
}
|
||||||
|
|
|
@ -88,6 +88,17 @@ func (t Tags) Has(tag Tag) bool {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Outline is a scenario outline with an
|
||||||
|
// example table. Steps are listed with
|
||||||
|
// placeholders which are replaced with
|
||||||
|
// each example table row
|
||||||
|
type Outline struct {
|
||||||
|
*Token
|
||||||
|
Scenario *Scenario
|
||||||
|
Steps []*Step
|
||||||
|
Examples *Table
|
||||||
|
}
|
||||||
|
|
||||||
// Scenario describes the scenario details
|
// Scenario describes the scenario details
|
||||||
//
|
//
|
||||||
// if Examples table is not nil, then it
|
// if Examples table is not nil, then it
|
||||||
|
@ -100,11 +111,11 @@ func (t Tags) Has(tag Tag) bool {
|
||||||
// initialization tasks
|
// initialization tasks
|
||||||
type Scenario struct {
|
type Scenario struct {
|
||||||
*Token
|
*Token
|
||||||
Title string
|
Title string
|
||||||
Steps []*Step
|
Steps []*Step
|
||||||
Tags Tags
|
Tags Tags
|
||||||
Examples *Table
|
Outline *Outline
|
||||||
Feature *Feature
|
Feature *Feature
|
||||||
}
|
}
|
||||||
|
|
||||||
// Background steps are run before every scenario
|
// Background steps are run before every scenario
|
||||||
|
@ -154,9 +165,8 @@ func (p *PyString) String() string {
|
||||||
// step definition or outline scenario
|
// step definition or outline scenario
|
||||||
type Table struct {
|
type Table struct {
|
||||||
*Token
|
*Token
|
||||||
OutlineScenario *Scenario
|
Step *Step
|
||||||
Step *Step
|
Rows [][]string
|
||||||
Rows [][]string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var allSteps = []TokenType{
|
var allSteps = []TokenType{
|
||||||
|
@ -320,10 +330,18 @@ func (p *parser) parseScenario() (s *Scenario, err error) {
|
||||||
"but got '" + peek.Type.String() + "' instead, for scenario outline examples",
|
"but got '" + peek.Type.String() + "' instead, for scenario outline examples",
|
||||||
}, " "), examples.Line)
|
}, " "), examples.Line)
|
||||||
}
|
}
|
||||||
if s.Examples, err = p.parseTable(); err != nil {
|
s.Outline = &Outline{
|
||||||
|
Token: examples,
|
||||||
|
Scenario: s,
|
||||||
|
Steps: s.Steps,
|
||||||
|
}
|
||||||
|
s.Steps = []*Step{} // move steps to outline
|
||||||
|
if s.Outline.Examples, err = p.parseTable(); err != nil {
|
||||||
return s, err
|
return s, err
|
||||||
}
|
}
|
||||||
s.Examples.OutlineScenario = s
|
if len(s.Outline.Examples.Rows) < 2 {
|
||||||
|
return s, p.err("expected an example table to have at least two rows: header and at least one example", examples.Line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,6 +21,30 @@ var matchers = map[string]*regexp.Regexp{
|
||||||
"table_row": regexp.MustCompile("^(\\s*)\\|([^#]*)(#.*)?"),
|
"table_row": regexp.MustCompile("^(\\s*)\\|([^#]*)(#.*)?"),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// for now only english language is supported
|
||||||
|
var keywords = map[TokenType]string{
|
||||||
|
// special
|
||||||
|
ILLEGAL: "Illegal",
|
||||||
|
EOF: "End of file",
|
||||||
|
NEW_LINE: "New line",
|
||||||
|
TAGS: "Tags",
|
||||||
|
COMMENT: "Comment",
|
||||||
|
PYSTRING: "PyString",
|
||||||
|
TABLE_ROW: "Table row",
|
||||||
|
TEXT: "Text",
|
||||||
|
// general
|
||||||
|
GIVEN: "Given",
|
||||||
|
WHEN: "When",
|
||||||
|
THEN: "Then",
|
||||||
|
AND: "And",
|
||||||
|
BUT: "But",
|
||||||
|
FEATURE: "Feature",
|
||||||
|
BACKGROUND: "Background",
|
||||||
|
SCENARIO: "Scenario",
|
||||||
|
SCENARIO_OUTLINE: "Scenario Outline",
|
||||||
|
EXAMPLES: "Examples",
|
||||||
|
}
|
||||||
|
|
||||||
type lexer struct {
|
type lexer struct {
|
||||||
reader *bufio.Reader
|
reader *bufio.Reader
|
||||||
lines int
|
lines int
|
||||||
|
@ -36,8 +60,9 @@ func (l *lexer) read() *Token {
|
||||||
line, err := l.reader.ReadString(byte('\n'))
|
line, err := l.reader.ReadString(byte('\n'))
|
||||||
if err != nil && len(line) == 0 {
|
if err != nil && len(line) == 0 {
|
||||||
return &Token{
|
return &Token{
|
||||||
Type: EOF,
|
Type: EOF,
|
||||||
Line: l.lines,
|
Line: l.lines + 1,
|
||||||
|
Keyword: keywords[EOF],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
l.lines++
|
l.lines++
|
||||||
|
@ -45,8 +70,9 @@ func (l *lexer) read() *Token {
|
||||||
// newline
|
// newline
|
||||||
if len(line) == 0 {
|
if len(line) == 0 {
|
||||||
return &Token{
|
return &Token{
|
||||||
Type: NEW_LINE,
|
Type: NEW_LINE,
|
||||||
Line: l.lines,
|
Line: l.lines,
|
||||||
|
Keyword: keywords[NEW_LINE],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// comment
|
// comment
|
||||||
|
@ -59,15 +85,17 @@ func (l *lexer) read() *Token {
|
||||||
Value: comment,
|
Value: comment,
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: comment,
|
Comment: comment,
|
||||||
|
Keyword: keywords[COMMENT],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// pystring
|
// pystring
|
||||||
if m := matchers["pystring"].FindStringSubmatch(line); len(m) > 0 {
|
if m := matchers["pystring"].FindStringSubmatch(line); len(m) > 0 {
|
||||||
return &Token{
|
return &Token{
|
||||||
Type: PYSTRING,
|
Type: PYSTRING,
|
||||||
Indent: len(m[1]),
|
Indent: len(m[1]),
|
||||||
Line: l.lines,
|
Line: l.lines,
|
||||||
Text: line,
|
Text: line,
|
||||||
|
Keyword: keywords[PYSTRING],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// step
|
// step
|
||||||
|
@ -91,6 +119,7 @@ func (l *lexer) read() *Token {
|
||||||
case "But":
|
case "But":
|
||||||
tok.Type = BUT
|
tok.Type = BUT
|
||||||
}
|
}
|
||||||
|
tok.Keyword = keywords[tok.Type]
|
||||||
return tok
|
return tok
|
||||||
}
|
}
|
||||||
// scenario
|
// scenario
|
||||||
|
@ -102,6 +131,7 @@ func (l *lexer) read() *Token {
|
||||||
Value: strings.TrimSpace(m[2]),
|
Value: strings.TrimSpace(m[2]),
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[3], " #"),
|
Comment: strings.Trim(m[3], " #"),
|
||||||
|
Keyword: keywords[SCENARIO],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// background
|
// background
|
||||||
|
@ -112,6 +142,7 @@ func (l *lexer) read() *Token {
|
||||||
Line: l.lines,
|
Line: l.lines,
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[2], " #"),
|
Comment: strings.Trim(m[2], " #"),
|
||||||
|
Keyword: keywords[BACKGROUND],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// feature
|
// feature
|
||||||
|
@ -123,6 +154,7 @@ func (l *lexer) read() *Token {
|
||||||
Value: strings.TrimSpace(m[2]),
|
Value: strings.TrimSpace(m[2]),
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[3], " #"),
|
Comment: strings.Trim(m[3], " #"),
|
||||||
|
Keyword: keywords[FEATURE],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// tags
|
// tags
|
||||||
|
@ -134,6 +166,7 @@ func (l *lexer) read() *Token {
|
||||||
Value: strings.TrimSpace(m[2]),
|
Value: strings.TrimSpace(m[2]),
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[3], " #"),
|
Comment: strings.Trim(m[3], " #"),
|
||||||
|
Keyword: keywords[TAGS],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// table row
|
// table row
|
||||||
|
@ -145,6 +178,7 @@ func (l *lexer) read() *Token {
|
||||||
Value: strings.TrimSpace(m[2]),
|
Value: strings.TrimSpace(m[2]),
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[3], " #"),
|
Comment: strings.Trim(m[3], " #"),
|
||||||
|
Keyword: keywords[TABLE_ROW],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// scenario outline
|
// scenario outline
|
||||||
|
@ -156,6 +190,7 @@ func (l *lexer) read() *Token {
|
||||||
Value: strings.TrimSpace(m[2]),
|
Value: strings.TrimSpace(m[2]),
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[3], " #"),
|
Comment: strings.Trim(m[3], " #"),
|
||||||
|
Keyword: keywords[SCENARIO_OUTLINE],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// examples
|
// examples
|
||||||
|
@ -166,15 +201,17 @@ func (l *lexer) read() *Token {
|
||||||
Line: l.lines,
|
Line: l.lines,
|
||||||
Text: line,
|
Text: line,
|
||||||
Comment: strings.Trim(m[2], " #"),
|
Comment: strings.Trim(m[2], " #"),
|
||||||
|
Keyword: keywords[EXAMPLES],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// text
|
// text
|
||||||
text := strings.TrimLeftFunc(line, unicode.IsSpace)
|
text := strings.TrimLeftFunc(line, unicode.IsSpace)
|
||||||
return &Token{
|
return &Token{
|
||||||
Type: TEXT,
|
Type: TEXT,
|
||||||
Line: l.lines,
|
Line: l.lines,
|
||||||
Value: text,
|
Value: text,
|
||||||
Indent: len(line) - len(text),
|
Indent: len(line) - len(text),
|
||||||
Text: line,
|
Text: line,
|
||||||
|
Keyword: keywords[TEXT],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,6 +11,16 @@ func (s *Scenario) assertTitle(title string, t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *Scenario) assertOutlineStep(text string, t *testing.T) *Step {
|
||||||
|
for _, stp := range s.Outline.Steps {
|
||||||
|
if stp.Text == text {
|
||||||
|
return stp
|
||||||
|
}
|
||||||
|
}
|
||||||
|
t.Fatal("expected scenario '%s' to have step: '%s', but it did not", s.Title, text)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (s *Scenario) assertStep(text string, t *testing.T) *Step {
|
func (s *Scenario) assertStep(text string, t *testing.T) *Step {
|
||||||
for _, stp := range s.Steps {
|
for _, stp := range s.Steps {
|
||||||
if stp.Text == text {
|
if stp.Text == text {
|
||||||
|
@ -22,16 +32,16 @@ func (s *Scenario) assertStep(text string, t *testing.T) *Step {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) {
|
func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) {
|
||||||
if s.Examples == nil {
|
if s.Outline.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.Outline.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.Outline.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.Outline.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])
|
||||||
}
|
}
|
||||||
|
@ -64,11 +74,11 @@ func Test_parse_scenario_outline(t *testing.T) {
|
||||||
TABLE_ROW,
|
TABLE_ROW,
|
||||||
}, t)
|
}, t)
|
||||||
|
|
||||||
s.assertStep(`I am in a directory "test"`, t)
|
s.assertOutlineStep(`I am in a directory "test"`, t)
|
||||||
s.assertStep(`I have a file named "foo"`, t)
|
s.assertOutlineStep(`I have a file named "foo"`, t)
|
||||||
s.assertStep(`I have a file named "bar"`, t)
|
s.assertOutlineStep(`I have a file named "bar"`, t)
|
||||||
s.assertStep(`I run "ls" with options "<options>"`, t)
|
s.assertOutlineStep(`I run "ls" with options "<options>"`, t)
|
||||||
s.assertStep(`I should see "<result>"`, t)
|
s.assertOutlineStep(`I should see "<result>"`, t)
|
||||||
|
|
||||||
s.assertExampleRow(t, 0, "options", "result")
|
s.assertExampleRow(t, 0, "options", "result")
|
||||||
s.assertExampleRow(t, 1, "-t", "bar foo")
|
s.assertExampleRow(t, 1, "-t", "bar foo")
|
||||||
|
|
|
@ -1,29 +1,26 @@
|
||||||
package gherkin
|
package gherkin
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"unicode"
|
||||||
|
)
|
||||||
|
|
||||||
type TokenType int
|
type TokenType int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
ILLEGAL TokenType = iota
|
ILLEGAL TokenType = iota
|
||||||
|
|
||||||
specials
|
|
||||||
COMMENT
|
COMMENT
|
||||||
NEW_LINE
|
NEW_LINE
|
||||||
EOF
|
EOF
|
||||||
|
|
||||||
elements
|
|
||||||
TEXT
|
TEXT
|
||||||
TAGS
|
TAGS
|
||||||
TABLE_ROW
|
TABLE_ROW
|
||||||
PYSTRING
|
PYSTRING
|
||||||
|
|
||||||
keywords
|
|
||||||
FEATURE
|
FEATURE
|
||||||
BACKGROUND
|
BACKGROUND
|
||||||
SCENARIO
|
SCENARIO
|
||||||
SCENARIO_OUTLINE
|
SCENARIO_OUTLINE
|
||||||
EXAMPLES
|
EXAMPLES
|
||||||
|
|
||||||
steps
|
|
||||||
GIVEN
|
GIVEN
|
||||||
WHEN
|
WHEN
|
||||||
THEN
|
THEN
|
||||||
|
@ -31,54 +28,22 @@ const (
|
||||||
BUT
|
BUT
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// String gives a string representation of token type
|
||||||
func (t TokenType) String() string {
|
func (t TokenType) String() string {
|
||||||
switch t {
|
return keywords[t]
|
||||||
case COMMENT:
|
|
||||||
return "comment"
|
|
||||||
case NEW_LINE:
|
|
||||||
return "new line"
|
|
||||||
case EOF:
|
|
||||||
return "end of file"
|
|
||||||
case TEXT:
|
|
||||||
return "text"
|
|
||||||
case TAGS:
|
|
||||||
return "tags"
|
|
||||||
case TABLE_ROW:
|
|
||||||
return "table row"
|
|
||||||
case PYSTRING:
|
|
||||||
return "pystring"
|
|
||||||
case FEATURE:
|
|
||||||
return "feature"
|
|
||||||
case BACKGROUND:
|
|
||||||
return "background"
|
|
||||||
case SCENARIO:
|
|
||||||
return "scenario"
|
|
||||||
case SCENARIO_OUTLINE:
|
|
||||||
return "scenario outline"
|
|
||||||
case EXAMPLES:
|
|
||||||
return "examples"
|
|
||||||
case GIVEN:
|
|
||||||
return "given step"
|
|
||||||
case WHEN:
|
|
||||||
return "when step"
|
|
||||||
case THEN:
|
|
||||||
return "then step"
|
|
||||||
case AND:
|
|
||||||
return "and step"
|
|
||||||
case BUT:
|
|
||||||
return "but step"
|
|
||||||
}
|
|
||||||
return "illegal"
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Token represents a line in gherkin feature file
|
||||||
type Token struct {
|
type Token struct {
|
||||||
Type TokenType // type of token
|
Type TokenType // type of token
|
||||||
Line, Indent int // line and indentation number
|
Line, Indent int // line and indentation number
|
||||||
Value string // interpreted value
|
Value string // interpreted value
|
||||||
Text string // same text as read
|
Text string // same text as read
|
||||||
|
Keyword string // @TODO: the translated keyword
|
||||||
Comment string // a comment
|
Comment string // a comment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// OfType checks whether token is one of types
|
||||||
func (t *Token) OfType(all ...TokenType) bool {
|
func (t *Token) OfType(all ...TokenType) bool {
|
||||||
for _, typ := range all {
|
for _, typ := range all {
|
||||||
if typ == t.Type {
|
if typ == t.Type {
|
||||||
|
@ -87,3 +52,12 @@ func (t *Token) OfType(all ...TokenType) bool {
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Length gives a token text length with indentation
|
||||||
|
// and keyword, but without comment
|
||||||
|
func (t *Token) Length() int {
|
||||||
|
if pos := strings.Index(t.Text, "#"); pos != -1 {
|
||||||
|
return len(strings.TrimRightFunc(t.Text[:pos], unicode.IsSpace))
|
||||||
|
}
|
||||||
|
return len(t.Text)
|
||||||
|
}
|
||||||
|
|
193
suite.go
193
suite.go
|
@ -7,6 +7,7 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"runtime"
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/DATA-DOG/godog/gherkin"
|
"github.com/DATA-DOG/godog/gherkin"
|
||||||
)
|
)
|
||||||
|
@ -20,30 +21,6 @@ type Regexp interface{}
|
||||||
// or a step handler
|
// or a step handler
|
||||||
type Handler interface{}
|
type Handler interface{}
|
||||||
|
|
||||||
// Status represents a step or scenario status
|
|
||||||
type Status int
|
|
||||||
|
|
||||||
// step or scenario status constants
|
|
||||||
const (
|
|
||||||
Invalid Status = iota
|
|
||||||
Passed
|
|
||||||
Failed
|
|
||||||
Undefined
|
|
||||||
)
|
|
||||||
|
|
||||||
// String represents status as string
|
|
||||||
func (s Status) String() string {
|
|
||||||
switch s {
|
|
||||||
case Passed:
|
|
||||||
return "passed"
|
|
||||||
case Failed:
|
|
||||||
return "failed"
|
|
||||||
case Undefined:
|
|
||||||
return "undefined"
|
|
||||||
}
|
|
||||||
return "invalid"
|
|
||||||
}
|
|
||||||
|
|
||||||
// Objects implementing the StepHandler interface can be
|
// Objects implementing the StepHandler interface can be
|
||||||
// registered as step definitions in godog
|
// registered as step definitions in godog
|
||||||
//
|
//
|
||||||
|
@ -70,11 +47,17 @@ func (f StepHandlerFunc) HandleStep(args ...*Arg) error {
|
||||||
return f(args...)
|
return f(args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
var errPending = fmt.Errorf("pending step")
|
// ErrUndefined is returned in case if step definition was not found
|
||||||
|
var ErrUndefined = fmt.Errorf("step is undefined")
|
||||||
|
|
||||||
type stepMatchHandler struct {
|
// StepDef is a registered step definition
|
||||||
handler StepHandler
|
// contains a StepHandler, a regexp which
|
||||||
expr *regexp.Regexp
|
// is used to match a step and Args which
|
||||||
|
// were matched by last step
|
||||||
|
type StepDef struct {
|
||||||
|
Args []*Arg
|
||||||
|
Handler StepHandler
|
||||||
|
Expr *regexp.Regexp
|
||||||
}
|
}
|
||||||
|
|
||||||
// Suite is an interface which allows various contexts
|
// Suite is an interface which allows various contexts
|
||||||
|
@ -91,7 +74,7 @@ type Suite interface {
|
||||||
}
|
}
|
||||||
|
|
||||||
type suite struct {
|
type suite struct {
|
||||||
stepHandlers []*stepMatchHandler
|
stepHandlers []*StepDef
|
||||||
features []*gherkin.Feature
|
features []*gherkin.Feature
|
||||||
fmt Formatter
|
fmt Formatter
|
||||||
|
|
||||||
|
@ -151,9 +134,9 @@ func (s *suite) Step(expr Regexp, h Handler) {
|
||||||
default:
|
default:
|
||||||
panic(fmt.Sprintf("expecting handler to satisfy StepHandler interface, got type: %T", h))
|
panic(fmt.Sprintf("expecting handler to satisfy StepHandler interface, got type: %T", h))
|
||||||
}
|
}
|
||||||
s.stepHandlers = append(s.stepHandlers, &stepMatchHandler{
|
s.stepHandlers = append(s.stepHandlers, &StepDef{
|
||||||
handler: handler,
|
Handler: handler,
|
||||||
expr: regex,
|
Expr: regex,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -243,12 +226,10 @@ func (s *suite) run() {
|
||||||
s.fmt.Summary()
|
s.fmt.Summary()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suite) runStep(step *gherkin.Step) (err error) {
|
func (s *suite) matchStep(step *gherkin.Step) *StepDef {
|
||||||
var match *stepMatchHandler
|
|
||||||
var args []*Arg
|
|
||||||
for _, h := range s.stepHandlers {
|
for _, h := range s.stepHandlers {
|
||||||
if m := h.expr.FindStringSubmatch(step.Text); len(m) > 0 {
|
if m := h.Expr.FindStringSubmatch(step.Text); len(m) > 0 {
|
||||||
match = h
|
var args []*Arg
|
||||||
for _, a := range m[1:] {
|
for _, a := range m[1:] {
|
||||||
args = append(args, &Arg{value: a})
|
args = append(args, &Arg{value: a})
|
||||||
}
|
}
|
||||||
|
@ -258,12 +239,18 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
|
||||||
if step.PyString != nil {
|
if step.PyString != nil {
|
||||||
args = append(args, &Arg{value: step.PyString})
|
args = append(args, &Arg{value: step.PyString})
|
||||||
}
|
}
|
||||||
break
|
h.Args = args
|
||||||
|
return h
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *suite) runStep(step *gherkin.Step) (err error) {
|
||||||
|
match := s.matchStep(step)
|
||||||
if match == nil {
|
if match == nil {
|
||||||
s.fmt.Undefined(step)
|
s.fmt.Undefined(step)
|
||||||
return errPending
|
return ErrUndefined
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -273,7 +260,7 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
if err = match.handler.HandleStep(args...); err != nil {
|
if err = match.Handler.HandleStep(match.Args...); err != nil {
|
||||||
s.fmt.Failed(step, match, err)
|
s.fmt.Failed(step, match, err)
|
||||||
} else {
|
} else {
|
||||||
s.fmt.Passed(step, match)
|
s.fmt.Passed(step, match)
|
||||||
|
@ -281,9 +268,9 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suite) runSteps(steps []*gherkin.Step) (st Status) {
|
func (s *suite) runSteps(steps []*gherkin.Step) (err error) {
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
if st == Failed || st == Undefined {
|
if err != nil {
|
||||||
s.fmt.Skipped(step)
|
s.fmt.Skipped(step)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
@ -293,19 +280,11 @@ func (s *suite) runSteps(steps []*gherkin.Step) (st Status) {
|
||||||
h.HandleBeforeStep(step)
|
h.HandleBeforeStep(step)
|
||||||
}
|
}
|
||||||
|
|
||||||
err := s.runStep(step)
|
err = s.runStep(step)
|
||||||
switch err {
|
|
||||||
case errPending:
|
|
||||||
st = Undefined
|
|
||||||
case nil:
|
|
||||||
st = Passed
|
|
||||||
default:
|
|
||||||
st = Failed
|
|
||||||
}
|
|
||||||
|
|
||||||
// run after step handlers
|
// run after step handlers
|
||||||
for _, h := range s.afterStepHandlers {
|
for _, h := range s.afterStepHandlers {
|
||||||
h.HandleAfterStep(step, st)
|
h.HandleAfterStep(step, err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
|
@ -317,39 +296,52 @@ func (s *suite) skipSteps(steps []*gherkin.Step) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *suite) runOutline(scenario *gherkin.Scenario) (err error) {
|
||||||
|
placeholders := scenario.Outline.Examples.Rows[0]
|
||||||
|
examples := scenario.Outline.Examples.Rows[1:]
|
||||||
|
for _, example := range examples {
|
||||||
|
var steps []*gherkin.Step
|
||||||
|
for _, step := range scenario.Outline.Steps {
|
||||||
|
text := step.Text
|
||||||
|
for i, placeholder := range placeholders {
|
||||||
|
text = strings.Replace(text, "<"+placeholder+">", example[i], -1)
|
||||||
|
}
|
||||||
|
// clone a step
|
||||||
|
cloned := &gherkin.Step{
|
||||||
|
Token: step.Token,
|
||||||
|
Text: text,
|
||||||
|
Type: step.Type,
|
||||||
|
PyString: step.PyString,
|
||||||
|
Table: step.Table,
|
||||||
|
Background: step.Background,
|
||||||
|
Scenario: scenario,
|
||||||
|
}
|
||||||
|
steps = append(steps, cloned)
|
||||||
|
}
|
||||||
|
|
||||||
|
// set steps to scenario
|
||||||
|
scenario.Steps = steps
|
||||||
|
if err = s.runScenario(scenario); err != nil && err != ErrUndefined {
|
||||||
|
s.failed = true
|
||||||
|
if cfg.stopOnFailure {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (s *suite) runFeature(f *gherkin.Feature) {
|
func (s *suite) runFeature(f *gherkin.Feature) {
|
||||||
s.fmt.Node(f)
|
s.fmt.Node(f)
|
||||||
for _, scenario := range f.Scenarios {
|
for _, scenario := range f.Scenarios {
|
||||||
var status Status
|
var err error
|
||||||
|
// handle scenario outline differently
|
||||||
// run before scenario handlers
|
if scenario.Outline != nil {
|
||||||
for _, h := range s.beforeScenarioHandlers {
|
err = s.runOutline(scenario)
|
||||||
h.HandleBeforeScenario(scenario)
|
} else {
|
||||||
|
err = s.runScenario(scenario)
|
||||||
}
|
}
|
||||||
|
if err != nil && err != ErrUndefined {
|
||||||
// background
|
|
||||||
if f.Background != nil {
|
|
||||||
s.fmt.Node(f.Background)
|
|
||||||
status = s.runSteps(f.Background.Steps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// scenario
|
|
||||||
s.fmt.Node(scenario)
|
|
||||||
switch {
|
|
||||||
case status == Failed:
|
|
||||||
s.skipSteps(scenario.Steps)
|
|
||||||
case status == Undefined:
|
|
||||||
s.skipSteps(scenario.Steps)
|
|
||||||
case status == Passed || status == Invalid:
|
|
||||||
status = s.runSteps(scenario.Steps)
|
|
||||||
}
|
|
||||||
|
|
||||||
// run after scenario handlers
|
|
||||||
for _, h := range s.afterScenarioHandlers {
|
|
||||||
h.HandleAfterScenario(scenario, status)
|
|
||||||
}
|
|
||||||
|
|
||||||
if status == Failed {
|
|
||||||
s.failed = true
|
s.failed = true
|
||||||
if cfg.stopOnFailure {
|
if cfg.stopOnFailure {
|
||||||
return
|
return
|
||||||
|
@ -358,16 +350,47 @@ func (s *suite) runFeature(f *gherkin.Feature) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *suite) runScenario(scenario *gherkin.Scenario) (err error) {
|
||||||
|
// run before scenario handlers
|
||||||
|
for _, h := range s.beforeScenarioHandlers {
|
||||||
|
h.HandleBeforeScenario(scenario)
|
||||||
|
}
|
||||||
|
|
||||||
|
// background
|
||||||
|
if scenario.Feature.Background != nil {
|
||||||
|
s.fmt.Node(scenario.Feature.Background)
|
||||||
|
err = s.runSteps(scenario.Feature.Background.Steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// scenario
|
||||||
|
s.fmt.Node(scenario)
|
||||||
|
switch err {
|
||||||
|
case ErrUndefined:
|
||||||
|
s.skipSteps(scenario.Steps)
|
||||||
|
case nil:
|
||||||
|
err = s.runSteps(scenario.Steps)
|
||||||
|
default:
|
||||||
|
s.skipSteps(scenario.Steps)
|
||||||
|
}
|
||||||
|
|
||||||
|
// run after scenario handlers
|
||||||
|
for _, h := range s.afterScenarioHandlers {
|
||||||
|
h.HandleAfterScenario(scenario, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (st *suite) printStepDefinitions() {
|
func (st *suite) printStepDefinitions() {
|
||||||
var longest int
|
var longest int
|
||||||
for _, def := range st.stepHandlers {
|
for _, def := range st.stepHandlers {
|
||||||
if longest < len(def.expr.String()) {
|
if longest < len(def.Expr.String()) {
|
||||||
longest = len(def.expr.String())
|
longest = len(def.Expr.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
for _, def := range st.stepHandlers {
|
for _, def := range st.stepHandlers {
|
||||||
location := runtime.FuncForPC(reflect.ValueOf(def.handler).Pointer()).Name()
|
location := runtime.FuncForPC(reflect.ValueOf(def.Handler).Pointer()).Name()
|
||||||
fmt.Println(cl(def.expr.String(), yellow)+s(longest-len(def.expr.String())), cl("# "+location, black))
|
fmt.Println(cl(def.Expr.String(), yellow)+s(longest-len(def.Expr.String())), cl("# "+location, black))
|
||||||
}
|
}
|
||||||
if len(st.stepHandlers) == 0 {
|
if len(st.stepHandlers) == 0 {
|
||||||
fmt.Println("there were no contexts registered, could not find any step definition..")
|
fmt.Println("there were no contexts registered, could not find any step definition..")
|
||||||
|
|
|
@ -121,14 +121,14 @@ func (s *suiteContext) iAmListeningToSuiteEvents(args ...*Arg) error {
|
||||||
s.testedSuite.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) {
|
s.testedSuite.BeforeScenario(BeforeScenarioHandlerFunc(func(scenario *gherkin.Scenario) {
|
||||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
|
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
|
||||||
}))
|
}))
|
||||||
s.testedSuite.AfterScenario(AfterScenarioHandlerFunc(func(scenario *gherkin.Scenario, status Status) {
|
s.testedSuite.AfterScenario(AfterScenarioHandlerFunc(func(scenario *gherkin.Scenario, err error) {
|
||||||
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, status}})
|
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
|
||||||
}))
|
}))
|
||||||
s.testedSuite.BeforeStep(BeforeStepHandlerFunc(func(step *gherkin.Step) {
|
s.testedSuite.BeforeStep(BeforeStepHandlerFunc(func(step *gherkin.Step) {
|
||||||
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
|
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
|
||||||
}))
|
}))
|
||||||
s.testedSuite.AfterStep(AfterStepHandlerFunc(func(step *gherkin.Step, status Status) {
|
s.testedSuite.AfterStep(AfterStepHandlerFunc(func(step *gherkin.Step, err error) {
|
||||||
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, status}})
|
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, err}})
|
||||||
}))
|
}))
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче