refactor to use cocumber gherkin3 parser library
* bdde4c4 fix test suite and migration changes * a3b6e01 refactor pretty formatter * 2c0c7ba fix outline scenario handling * f6b411d add a different language test feature add a different language test feature
Этот коммит содержится в:
родитель
7b805b1ee7
коммит
c6d00dd6d5
26 изменённых файлов: 451 добавлений и 1926 удалений
|
@ -12,6 +12,7 @@ script:
|
|||
|
||||
# pull all external dependencies
|
||||
# remove them at all if possible
|
||||
- go get github.com/cucumber/gherkin-go
|
||||
- go get golang.org/x/tools/imports
|
||||
- go get github.com/shiena/ansicolor
|
||||
|
||||
|
|
1
Makefile
1
Makefile
|
@ -10,5 +10,6 @@ test:
|
|||
|
||||
# updates dependencies
|
||||
deps:
|
||||
go get -u github.com/cucumber/gherkin-go
|
||||
go get -u golang.org/x/tools/imports
|
||||
go get -u github.com/shiena/ansicolor
|
||||
|
|
|
@ -114,7 +114,7 @@ Now when you run the `godog godog.feature` again, you should see:
|
|||
|
||||
### Documentation
|
||||
|
||||
See [godoc][godoc] and [gherkin godoc][godoc_gherkin] for general API details.
|
||||
See [godoc][godoc] for general API details.
|
||||
See **.travis.yml** for supported **go** versions.
|
||||
|
||||
The public API is stable enough, but it may break until **1.0.0** version, see `godog --version`.
|
||||
|
@ -137,7 +137,6 @@ All package dependencies are **MIT** or **BSD** licensed.
|
|||
**Godog** is licensed under the [three clause BSD license][license]
|
||||
|
||||
[godoc]: http://godoc.org/github.com/DATA-DOG/godog "Documentation on godoc"
|
||||
[godoc_gherkin]: http://godoc.org/github.com/DATA-DOG/godog/gherkin "Documentation on godoc for gherkin"
|
||||
[golang]: https://golang.org/ "GO programming language"
|
||||
[behat]: http://docs.behat.org/ "Behavior driven development framework for PHP"
|
||||
[cucumber]: https://cucumber.io/ "Behavior driven development framework for Ruby"
|
||||
|
|
18
arguments.go
18
arguments.go
|
@ -4,7 +4,7 @@ import (
|
|||
"fmt"
|
||||
"strconv"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
// Arg is an argument for StepHandler parsed from
|
||||
|
@ -122,17 +122,17 @@ func (a *Arg) Bytes() []byte {
|
|||
return []byte(s)
|
||||
}
|
||||
|
||||
// PyString converts an argument to *gherkin.PyString node
|
||||
func (a *Arg) PyString() *gherkin.PyString {
|
||||
s, ok := a.value.(*gherkin.PyString)
|
||||
a.must(ok, "*gherkin.PyString")
|
||||
// DocString converts an argument to *gherkin.DocString node
|
||||
func (a *Arg) DocString() *gherkin.DocString {
|
||||
s, ok := a.value.(*gherkin.DocString)
|
||||
a.must(ok, "*gherkin.DocString")
|
||||
return s
|
||||
}
|
||||
|
||||
// Table converts an argument to *gherkin.Table node
|
||||
func (a *Arg) Table() *gherkin.Table {
|
||||
s, ok := a.value.(*gherkin.Table)
|
||||
a.must(ok, "*gherkin.Table")
|
||||
// DataTable converts an argument to *gherkin.DataTable node
|
||||
func (a *Arg) DataTable() *gherkin.DataTable {
|
||||
s, ok := a.value.(*gherkin.DataTable)
|
||||
a.must(ok, "*gherkin.DataTable")
|
||||
return s
|
||||
}
|
||||
|
||||
|
|
|
@ -46,7 +46,7 @@ func (f *lsFeature) iHaveFileOrDirectoryNamed(args ...*godog.Arg) (err error) {
|
|||
}
|
||||
|
||||
func (f *lsFeature) iShouldGetOutput(args ...*godog.Arg) error {
|
||||
expected := args[0].PyString().Lines
|
||||
expected := strings.Split(args[0].DocString().Content, "\n")
|
||||
actual := strings.Split(strings.TrimSpace(f.buf.String()), "\n")
|
||||
if len(expected) != len(actual) {
|
||||
return fmt.Errorf("number of expected output lines %d, does not match actual: %d", len(expected), len(actual))
|
||||
|
|
17
features/lang.feature
Обычный файл
17
features/lang.feature
Обычный файл
|
@ -0,0 +1,17 @@
|
|||
# language: lt
|
||||
@lang
|
||||
Savybė: užkrauti savybes
|
||||
Kad būtų galima paleisti savybių testus
|
||||
Kaip testavimo įrankis
|
||||
Aš turiu galėti užregistruoti savybes
|
||||
|
||||
Scenarijus: savybių užkrovimas iš aplanko
|
||||
Duota savybių aplankas "features"
|
||||
Kai aš išskaitau savybes
|
||||
Tada aš turėčiau turėti 4 savybių failus:
|
||||
"""
|
||||
features/events.feature
|
||||
features/lang.feature
|
||||
features/load.feature
|
||||
features/run.feature
|
||||
"""
|
|
@ -6,9 +6,10 @@ Feature: load features
|
|||
Scenario: load features within path
|
||||
Given a feature path "features"
|
||||
When I parse features
|
||||
Then I should have 3 feature files:
|
||||
Then I should have 4 feature files:
|
||||
"""
|
||||
features/events.feature
|
||||
features/lang.feature
|
||||
features/load.feature
|
||||
features/run.feature
|
||||
"""
|
||||
|
|
|
@ -81,7 +81,7 @@ Feature: run features
|
|||
Then I should have 1 scenario registered
|
||||
"""
|
||||
When I run feature suite
|
||||
Then the suite should have passed # we do not treat undefined scenarios as fails
|
||||
Then the suite should have passed
|
||||
And the following step should be passed:
|
||||
"""
|
||||
a feature path "features/load.feature:6"
|
||||
|
|
22
fmt.go
22
fmt.go
|
@ -3,7 +3,7 @@ package godog
|
|||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
type registeredFormatter struct {
|
||||
|
@ -33,6 +33,7 @@ func RegisterFormatter(name, description string, f Formatter) {
|
|||
// formatters needs to be registered with a
|
||||
// RegisterFormatter function call
|
||||
type Formatter interface {
|
||||
Feature(*gherkin.Feature, string)
|
||||
Node(interface{})
|
||||
Failed(*gherkin.Step, *StepDef, error)
|
||||
Passed(*gherkin.Step, *StepDef)
|
||||
|
@ -44,27 +45,22 @@ type Formatter interface {
|
|||
// failed represents a failed step data structure
|
||||
// with all necessary references
|
||||
type failed struct {
|
||||
feature *feature
|
||||
owner interface{}
|
||||
step *gherkin.Step
|
||||
def *StepDef
|
||||
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)
|
||||
return fmt.Sprintf("%s:%d", f.feature.Path, f.step.Location.Line)
|
||||
}
|
||||
|
||||
// passed represents a successful step data structure
|
||||
// with all necessary references
|
||||
type passed struct {
|
||||
feature *feature
|
||||
owner interface{}
|
||||
step *gherkin.Step
|
||||
def *StepDef
|
||||
}
|
||||
|
@ -72,11 +68,15 @@ type passed struct {
|
|||
// skipped represents a skipped step data structure
|
||||
// with all necessary references
|
||||
type skipped struct {
|
||||
feature *feature
|
||||
owner interface{}
|
||||
step *gherkin.Step
|
||||
}
|
||||
|
||||
// undefined represents a pending step data structure
|
||||
// with all necessary references
|
||||
type undefined struct {
|
||||
feature *feature
|
||||
owner interface{}
|
||||
step *gherkin.Step
|
||||
}
|
||||
|
|
258
fmt_pretty.go
258
fmt_pretty.go
|
@ -9,12 +9,13 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterFormatter("pretty", "Prints every feature with runtime statuses.", &pretty{
|
||||
started: time.Now(),
|
||||
indent: 2,
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -22,18 +23,20 @@ var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>")
|
|||
|
||||
// a built in default pretty formatter
|
||||
type pretty struct {
|
||||
feature *gherkin.Feature
|
||||
scope interface{}
|
||||
indent int
|
||||
commentPos int
|
||||
backgroundSteps int
|
||||
|
||||
// outline
|
||||
outlineExamples int
|
||||
outlineNumSteps int
|
||||
outline *gherkin.ScenarioOutline
|
||||
outlineSteps []interface{}
|
||||
outlineNumExample int
|
||||
outlineNumExamples int
|
||||
|
||||
// summary
|
||||
started time.Time
|
||||
features []*gherkin.Feature
|
||||
features []*feature
|
||||
failed []*failed
|
||||
passed []*passed
|
||||
skipped []*skipped
|
||||
|
@ -41,44 +44,65 @@ type pretty struct {
|
|||
}
|
||||
|
||||
// a line number representation in feature file
|
||||
func (f *pretty) line(tok *gherkin.Token) string {
|
||||
return cl(fmt.Sprintf("# %s:%d", f.feature.Path, tok.Line), black)
|
||||
func (f *pretty) line(loc *gherkin.Location) string {
|
||||
return cl(fmt.Sprintf("# %s:%d", f.features[len(f.features)-1].Path, loc.Line), black)
|
||||
}
|
||||
|
||||
func (f *pretty) length(node interface{}) int {
|
||||
switch t := node.(type) {
|
||||
case *gherkin.Background:
|
||||
return f.indent + len(strings.TrimSpace(t.Keyword)+": "+t.Name)
|
||||
case *gherkin.Step:
|
||||
return f.indent*2 + len(strings.TrimSpace(t.Keyword)+" "+t.Text)
|
||||
case *gherkin.Scenario:
|
||||
return f.indent + len(strings.TrimSpace(t.Keyword)+": "+t.Name)
|
||||
case *gherkin.ScenarioOutline:
|
||||
return f.indent + len(strings.TrimSpace(t.Keyword)+": "+t.Name)
|
||||
}
|
||||
panic(fmt.Sprintf("unexpected node %T to determine length", node))
|
||||
}
|
||||
|
||||
func (f *pretty) Feature(ft *gherkin.Feature, p string) {
|
||||
if len(f.features) != 0 {
|
||||
// not a first feature, add a newline
|
||||
fmt.Println("")
|
||||
}
|
||||
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
||||
fmt.Println(bcl(ft.Keyword+": ", white) + ft.Name)
|
||||
if strings.TrimSpace(ft.Description) != "" {
|
||||
for _, line := range strings.Split(ft.Description, "\n") {
|
||||
fmt.Println(s(f.indent) + strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
if ft.Background != nil {
|
||||
f.commentPos = f.longestStep(ft.Background.Steps, f.length(ft.Background))
|
||||
f.backgroundSteps = len(ft.Background.Steps)
|
||||
fmt.Println("\n" + s(f.indent) + bcl(ft.Background.Keyword+": "+ft.Background.Name, white))
|
||||
}
|
||||
}
|
||||
|
||||
// Node takes a gherkin node for formatting
|
||||
func (f *pretty) Node(node interface{}) {
|
||||
switch t := node.(type) {
|
||||
case *gherkin.Feature:
|
||||
if f.feature != nil {
|
||||
// not a first feature, add a newline
|
||||
fmt.Println("")
|
||||
}
|
||||
f.feature = t
|
||||
f.features = append(f.features, t)
|
||||
// print feature header
|
||||
fmt.Println(bcl(t.Token.Keyword+": ", white) + t.Title)
|
||||
fmt.Println(t.Description)
|
||||
// print background header
|
||||
if t.Background != nil {
|
||||
f.commentPos = longestStep(t.Background.Steps, t.Background.Token.Length())
|
||||
f.backgroundSteps = len(t.Background.Steps)
|
||||
fmt.Println("\n" + s(t.Background.Token.Indent) + bcl(t.Background.Token.Keyword+":", white))
|
||||
}
|
||||
case *gherkin.Examples:
|
||||
f.outlineNumExamples = len(t.TableBody)
|
||||
f.outlineNumExample++
|
||||
case *gherkin.Background:
|
||||
f.scope = t
|
||||
case *gherkin.Scenario:
|
||||
f.commentPos = longestStep(t.Steps, t.Token.Length())
|
||||
if t.Outline != nil {
|
||||
f.outlineSteps = []interface{}{} // reset steps list
|
||||
f.commentPos = longestStep(t.Outline.Steps, t.Token.Length())
|
||||
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(t.Token.Keyword+": ", white) + t.Title
|
||||
text += s(f.commentPos-t.Token.Length()+1) + f.line(t.Token)
|
||||
f.scope = t
|
||||
f.commentPos = f.longestStep(t.Steps, f.length(t))
|
||||
text := s(f.indent) + bcl(t.Keyword+": ", white) + t.Name
|
||||
text += s(f.commentPos-f.length(t)+1) + f.line(t.Location)
|
||||
fmt.Println("\n" + text)
|
||||
case *gherkin.ScenarioOutline:
|
||||
f.scope = t
|
||||
f.outline = t
|
||||
f.commentPos = f.longestStep(t.Steps, f.length(t))
|
||||
text := s(f.indent) + bcl(t.Keyword+": ", white) + t.Name
|
||||
text += s(f.commentPos-f.length(t)+1) + f.line(t.Location)
|
||||
fmt.Println("\n" + text)
|
||||
f.outlineNumExample = -1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,7 +111,10 @@ func (f *pretty) Summary() {
|
|||
// failed steps on background are not scenarios
|
||||
var failedScenarios []*failed
|
||||
for _, fail := range f.failed {
|
||||
if fail.step.Scenario != nil {
|
||||
switch fail.owner.(type) {
|
||||
case *gherkin.Scenario:
|
||||
failedScenarios = append(failedScenarios, fail)
|
||||
case *gherkin.ScenarioOutline:
|
||||
failedScenarios = append(failedScenarios, fail)
|
||||
}
|
||||
}
|
||||
|
@ -113,7 +140,16 @@ func (f *pretty) Summary() {
|
|||
}
|
||||
var total, passed int
|
||||
for _, ft := range f.features {
|
||||
total += len(ft.Scenarios)
|
||||
for _, def := range ft.ScenarioDefinitions {
|
||||
switch t := def.(type) {
|
||||
case *gherkin.Scenario:
|
||||
total++
|
||||
case *gherkin.ScenarioOutline:
|
||||
for _, ex := range t.Examples {
|
||||
total += len(ex.TableBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
passed = total
|
||||
|
||||
|
@ -156,18 +192,17 @@ func (f *pretty) Summary() {
|
|||
fmt.Println(elapsed)
|
||||
}
|
||||
|
||||
func (f *pretty) printOutlineExample(scenario *gherkin.Scenario) {
|
||||
func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
||||
var failed error
|
||||
clr := green
|
||||
tbl := scenario.Outline.Examples
|
||||
firstExample := f.outlineExamples == len(tbl.Rows)-1
|
||||
|
||||
example := outline.Examples[f.outlineNumExample]
|
||||
firstExample := f.outlineNumExamples == len(example.TableBody)
|
||||
printSteps := firstExample && f.outlineNumExample == 0
|
||||
|
||||
// var replace make(map[])
|
||||
for i, act := range f.outlineSteps {
|
||||
var c color
|
||||
var def *StepDef
|
||||
var err error
|
||||
|
||||
_, def, c, err = f.stepDetails(act)
|
||||
_, _, def, c, err := f.stepDetails(act)
|
||||
// determine example row status
|
||||
switch {
|
||||
case err != nil:
|
||||
|
@ -178,10 +213,10 @@ func (f *pretty) printOutlineExample(scenario *gherkin.Scenario) {
|
|||
case c == cyan && clr == green:
|
||||
clr = cyan
|
||||
}
|
||||
if firstExample {
|
||||
if printSteps {
|
||||
// in first example, we need to print steps
|
||||
var text string
|
||||
ostep := scenario.Outline.Steps[i]
|
||||
ostep := outline.Steps[i]
|
||||
if def != nil {
|
||||
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
|
||||
var pos int
|
||||
|
@ -197,45 +232,43 @@ func (f *pretty) printOutlineExample(scenario *gherkin.Scenario) {
|
|||
}
|
||||
// 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)
|
||||
text += s(f.commentPos-f.length(ostep)+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)
|
||||
fmt.Println(s(f.indent*2) + cl(strings.TrimSpace(ostep.Keyword), cyan) + " " + text)
|
||||
}
|
||||
}
|
||||
|
||||
cols := make([]string, len(tbl.Rows[0]))
|
||||
max := longest(tbl)
|
||||
cells := make([]string, len(example.TableHeader.Cells))
|
||||
max := longest(example)
|
||||
// an example table header
|
||||
if firstExample {
|
||||
out := scenario.Outline
|
||||
fmt.Println("")
|
||||
fmt.Println(s(out.Token.Indent) + bcl(out.Token.Keyword+":", white))
|
||||
row := tbl.Rows[0]
|
||||
fmt.Println(s(f.indent*2) + bcl(example.Keyword+": ", white) + example.Name)
|
||||
|
||||
for i, col := range row {
|
||||
cols[i] = cl(col, cyan) + s(max[i]-len(col))
|
||||
for i, cell := range example.TableHeader.Cells {
|
||||
cells[i] = cl(cell.Value, cyan) + s(max[i]-len(cell.Value))
|
||||
}
|
||||
fmt.Println(s(tbl.Token.Indent) + "| " + strings.Join(cols, " | ") + " |")
|
||||
fmt.Println(s(f.indent*3) + "| " + strings.Join(cells, " | ") + " |")
|
||||
}
|
||||
|
||||
// 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))
|
||||
row := example.TableBody[len(example.TableBody)-f.outlineNumExamples]
|
||||
for i, cell := range row.Cells {
|
||||
cells[i] = cl(cell.Value, clr) + s(max[i]-len(cell.Value))
|
||||
}
|
||||
fmt.Println(s(tbl.Token.Indent) + "| " + strings.Join(cols, " | ") + " |")
|
||||
fmt.Println(s(f.indent*3) + "| " + strings.Join(cells, " | ") + " |")
|
||||
|
||||
// if there is an error
|
||||
if failed != nil {
|
||||
fmt.Println(s(tbl.Token.Indent) + bcl(failed, red))
|
||||
fmt.Println(s(f.indent*3) + bcl(failed, red))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
||||
text := s(step.Token.Indent) + cl(step.Token.Keyword, c) + " "
|
||||
text := s(f.indent*2) + cl(strings.TrimSpace(step.Keyword), c) + " "
|
||||
switch {
|
||||
case def != nil:
|
||||
if m := (def.Expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 {
|
||||
|
@ -254,38 +287,44 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
|||
}
|
||||
// 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)
|
||||
text += s(f.commentPos-f.length(step)+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))
|
||||
switch t := step.Argument.(type) {
|
||||
case *gherkin.DataTable:
|
||||
f.printTable(t, c)
|
||||
case *gherkin.DocString:
|
||||
fmt.Println(s(f.indent*3) + cl(t.Delimitter, c)) // @TODO: content type
|
||||
for _, ln := range strings.Split(t.Content, "\n") {
|
||||
fmt.Println(s(f.indent*3) + cl(ln, c))
|
||||
}
|
||||
if step.Table != nil {
|
||||
f.printTable(step.Table, c)
|
||||
fmt.Println(s(f.indent*3) + cl(t.Delimitter, c))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *pretty) stepDetails(stepAction interface{}) (step *gherkin.Step, def *StepDef, c color, err error) {
|
||||
func (f *pretty) stepDetails(stepAction interface{}) (owner interface{}, step *gherkin.Step, def *StepDef, c color, err error) {
|
||||
switch typ := stepAction.(type) {
|
||||
case *passed:
|
||||
step = typ.step
|
||||
def = typ.def
|
||||
owner = typ.owner
|
||||
c = green
|
||||
case *failed:
|
||||
step = typ.step
|
||||
def = typ.def
|
||||
owner = typ.owner
|
||||
err = typ.err
|
||||
c = red
|
||||
case *skipped:
|
||||
step = typ.step
|
||||
owner = typ.owner
|
||||
c = cyan
|
||||
case *undefined:
|
||||
step = typ.step
|
||||
owner = typ.owner
|
||||
c = yellow
|
||||
default:
|
||||
fatal(fmt.Errorf("unexpected step type received: %T", typ))
|
||||
|
@ -294,94 +333,101 @@ func (f *pretty) stepDetails(stepAction interface{}) (step *gherkin.Step, def *S
|
|||
}
|
||||
|
||||
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)
|
||||
owner, step, def, c, err := f.stepDetails(stepAction)
|
||||
|
||||
// do not print background more than once
|
||||
if _, ok := owner.(*gherkin.Background); ok {
|
||||
switch {
|
||||
case step.Background != nil && f.backgroundSteps == 0:
|
||||
case f.backgroundSteps == 0:
|
||||
return
|
||||
case step.Background != nil && f.backgroundSteps > 0:
|
||||
case f.backgroundSteps > 0:
|
||||
f.backgroundSteps--
|
||||
}
|
||||
|
||||
if f.outlineExamples != 0 {
|
||||
f.outlineSteps = append(f.outlineSteps, stepAction)
|
||||
if len(f.outlineSteps) == f.outlineNumSteps {
|
||||
// an outline example steps has went through
|
||||
f.printOutlineExample(step.Scenario)
|
||||
f.outlineExamples--
|
||||
}
|
||||
return // wait till example steps
|
||||
|
||||
if outline, ok := owner.(*gherkin.ScenarioOutline); ok {
|
||||
f.outlineSteps = append(f.outlineSteps, stepAction)
|
||||
if len(f.outlineSteps) == len(outline.Steps) {
|
||||
// an outline example steps has went through
|
||||
f.printOutlineExample(outline)
|
||||
f.outlineSteps = []interface{}{}
|
||||
f.outlineNumExamples--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
f.printStep(step, def, c)
|
||||
if err != nil {
|
||||
fmt.Println(s(step.Token.Indent) + bcl(err, red))
|
||||
fmt.Println(s(f.indent*2) + bcl(err, red))
|
||||
}
|
||||
}
|
||||
|
||||
// print table with aligned table cells
|
||||
func (f *pretty) printTable(t *gherkin.Table, c color) {
|
||||
func (f *pretty) printTable(t *gherkin.DataTable, c color) {
|
||||
var l = longest(t)
|
||||
var cols = make([]string, len(t.Rows[0]))
|
||||
var cols = make([]string, len(t.Rows[0].Cells))
|
||||
for _, row := range t.Rows {
|
||||
for i, col := range row {
|
||||
cols[i] = col + s(l[i]-len(col))
|
||||
for i, cell := range row.Cells {
|
||||
cols[i] = cell.Value + s(l[i]-len(cell.Value))
|
||||
}
|
||||
fmt.Println(s(t.Token.Indent) + cl("| "+strings.Join(cols, " | ")+" |", c))
|
||||
fmt.Println(s(f.indent*3) + 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}
|
||||
s := &passed{owner: f.scope, feature: f.features[len(f.features)-1], 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}
|
||||
s := &skipped{owner: f.scope, feature: f.features[len(f.features)-1], 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}
|
||||
s := &undefined{owner: f.scope, feature: f.features[len(f.features)-1], 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}
|
||||
s := &failed{owner: f.scope, feature: f.features[len(f.features)-1], 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 i, col := range row {
|
||||
if longest[i] < len(col) {
|
||||
longest[i] = len(col)
|
||||
func longest(tbl interface{}) []int {
|
||||
var rows []*gherkin.TableRow
|
||||
switch t := tbl.(type) {
|
||||
case *gherkin.Examples:
|
||||
rows = append(rows, t.TableHeader)
|
||||
rows = append(rows, t.TableBody...)
|
||||
case *gherkin.DataTable:
|
||||
rows = append(rows, t.Rows...)
|
||||
}
|
||||
|
||||
longest := make([]int, len(rows[0].Cells))
|
||||
for _, row := range rows {
|
||||
for i, cell := range row.Cells {
|
||||
if longest[i] < len(cell.Value) {
|
||||
longest[i] = len(cell.Value)
|
||||
}
|
||||
}
|
||||
}
|
||||
return longest
|
||||
}
|
||||
|
||||
func longestStep(steps []*gherkin.Step, base int) int {
|
||||
func (f *pretty) longestStep(steps []*gherkin.Step, base int) int {
|
||||
ret := base
|
||||
for _, step := range steps {
|
||||
length := step.Token.Length()
|
||||
length := f.length(step)
|
||||
if length > ret {
|
||||
ret = length
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -20,7 +20,9 @@ type progress struct {
|
|||
stepsPerRow int
|
||||
started time.Time
|
||||
steps int
|
||||
features []*gherkin.Feature
|
||||
|
||||
features []*feature
|
||||
owner interface{}
|
||||
|
||||
failed []*failed
|
||||
passed []*passed
|
||||
|
@ -28,10 +30,18 @@ type progress struct {
|
|||
undefined []*undefined
|
||||
}
|
||||
|
||||
func (f *progress) Node(node interface{}) {
|
||||
switch t := node.(type) {
|
||||
case *gherkin.Feature:
|
||||
f.features = append(f.features, t)
|
||||
func (f *progress) Feature(ft *gherkin.Feature, p string) {
|
||||
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
||||
}
|
||||
|
||||
func (f *progress) Node(n interface{}) {
|
||||
switch t := n.(type) {
|
||||
case *gherkin.ScenarioOutline:
|
||||
f.owner = t
|
||||
case *gherkin.Scenario:
|
||||
f.owner = t
|
||||
case *gherkin.Background:
|
||||
f.owner = t
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -49,13 +59,22 @@ func (f *progress) Summary() {
|
|||
if len(f.failed) > 0 {
|
||||
fmt.Println("\n--- " + cl("Failed steps:", red) + "\n")
|
||||
for _, fail := range f.failed {
|
||||
fmt.Println(s(4) + cl(fail.step.Token.Keyword+" "+fail.step.Text, red) + cl(" # "+fail.line(), black))
|
||||
fmt.Println(s(4) + cl(fail.step.Keyword+" "+fail.step.Text, red) + cl(" # "+fail.line(), black))
|
||||
fmt.Println(s(6) + cl("Error: ", red) + bcl(fail.err, red) + "\n")
|
||||
}
|
||||
}
|
||||
var total, passed int
|
||||
for _, ft := range f.features {
|
||||
total += len(ft.Scenarios)
|
||||
for _, def := range ft.ScenarioDefinitions {
|
||||
switch t := def.(type) {
|
||||
case *gherkin.Scenario:
|
||||
total++
|
||||
case *gherkin.ScenarioOutline:
|
||||
for _, ex := range t.Examples {
|
||||
total += len(ex.TableBody)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
passed = total
|
||||
|
||||
|
@ -116,25 +135,25 @@ func (f *progress) step(step interface{}) {
|
|||
}
|
||||
|
||||
func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
|
||||
s := &passed{step: step, def: match}
|
||||
s := &passed{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match}
|
||||
f.passed = append(f.passed, s)
|
||||
f.step(s)
|
||||
}
|
||||
|
||||
func (f *progress) Skipped(step *gherkin.Step) {
|
||||
s := &skipped{step: step}
|
||||
s := &skipped{owner: f.owner, feature: f.features[len(f.features)-1], step: step}
|
||||
f.skipped = append(f.skipped, s)
|
||||
f.step(s)
|
||||
}
|
||||
|
||||
func (f *progress) Undefined(step *gherkin.Step) {
|
||||
s := &undefined{step: step}
|
||||
s := &undefined{owner: f.owner, feature: f.features[len(f.features)-1], step: step}
|
||||
f.undefined = append(f.undefined, s)
|
||||
f.step(s)
|
||||
}
|
||||
|
||||
func (f *progress) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||
s := &failed{step: step, def: match, err: err}
|
||||
s := &failed{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match, err: err}
|
||||
f.failed = append(f.failed, s)
|
||||
f.step(s)
|
||||
}
|
||||
|
|
27
fmt_test.go
27
fmt_test.go
|
@ -1,10 +1,11 @@
|
|||
package godog
|
||||
|
||||
import "github.com/DATA-DOG/godog/gherkin"
|
||||
import "github.com/cucumber/gherkin-go"
|
||||
|
||||
type testFormatter struct {
|
||||
features []*gherkin.Feature
|
||||
scenarios []*gherkin.Scenario
|
||||
owner interface{}
|
||||
features []*feature
|
||||
scenarios []interface{}
|
||||
|
||||
failed []*failed
|
||||
passed []*passed
|
||||
|
@ -12,29 +13,37 @@ type testFormatter struct {
|
|||
undefined []*undefined
|
||||
}
|
||||
|
||||
func (f *testFormatter) Feature(ft *gherkin.Feature, p string) {
|
||||
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
||||
}
|
||||
|
||||
func (f *testFormatter) Node(node interface{}) {
|
||||
switch t := node.(type) {
|
||||
case *gherkin.Feature:
|
||||
f.features = append(f.features, t)
|
||||
case *gherkin.Scenario:
|
||||
f.scenarios = append(f.scenarios, t)
|
||||
f.owner = t
|
||||
case *gherkin.ScenarioOutline:
|
||||
f.scenarios = append(f.scenarios, t)
|
||||
f.owner = t
|
||||
case *gherkin.Background:
|
||||
f.owner = t
|
||||
}
|
||||
}
|
||||
|
||||
func (f *testFormatter) Summary() {}
|
||||
|
||||
func (f *testFormatter) Passed(step *gherkin.Step, match *StepDef) {
|
||||
f.passed = append(f.passed, &passed{step: step, def: match})
|
||||
f.passed = append(f.passed, &passed{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match})
|
||||
}
|
||||
|
||||
func (f *testFormatter) Skipped(step *gherkin.Step) {
|
||||
f.skipped = append(f.skipped, &skipped{step: step})
|
||||
f.skipped = append(f.skipped, &skipped{owner: f.owner, feature: f.features[len(f.features)-1], step: step})
|
||||
}
|
||||
|
||||
func (f *testFormatter) Undefined(step *gherkin.Step) {
|
||||
f.undefined = append(f.undefined, &undefined{step: step})
|
||||
f.undefined = append(f.undefined, &undefined{owner: f.owner, feature: f.features[len(f.features)-1], step: step})
|
||||
}
|
||||
|
||||
func (f *testFormatter) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||
f.failed = append(f.failed, &failed{step: step, def: match, err: err})
|
||||
f.failed = append(f.failed, &failed{owner: f.owner, feature: f.features[len(f.features)-1], step: step, def: match, err: err})
|
||||
}
|
||||
|
|
|
@ -1,28 +0,0 @@
|
|||
The three clause BSD license (http://en.wikipedia.org/wiki/BSD_licenses)
|
||||
|
||||
Copyright (c) 2015, DataDog.lt team
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
modification, are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* The name DataDog.lt may not be used to endorse or promote products
|
||||
derived from this software without specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
||||
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
||||
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL MICHAEL BOSTOCK BE LIABLE FOR ANY DIRECT,
|
||||
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
||||
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
|
||||
OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
|
||||
EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
@ -1,50 +0,0 @@
|
|||
[](https://travis-ci.org/DATA-DOG/godog)
|
||||
[](https://godoc.org/github.com/DATA-DOG/godog/gherkin)
|
||||
|
||||
# Gherkin Parser for GO
|
||||
|
||||
Package gherkin is a gherkin language parser based on [specification][gherkin]
|
||||
specification. It parses a feature file into the it's structural representation. It also
|
||||
creates an AST tree of gherkin Tokens read from the file.
|
||||
|
||||
With gherkin language you can describe your application behavior as features in
|
||||
human-readable and machine friendly language.
|
||||
|
||||
``` go
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
feature, err := gherkin.ParseFile("ls.feature")
|
||||
switch {
|
||||
case err == gherkin.ErrEmpty:
|
||||
log.Println("the feature file is empty and does not describe any feature")
|
||||
return
|
||||
case err != nil:
|
||||
log.Fatalln("the feature file is incorrect or could not be read:", err)
|
||||
}
|
||||
log.Println("have parsed a feature:", feature.Title, "with", len(feature.Scenarios), "scenarios")
|
||||
}
|
||||
```
|
||||
|
||||
### Documentation
|
||||
|
||||
See [godoc][godoc].
|
||||
|
||||
The public API is stable enough, but it may break until **1.0.0** version, see `godog --version`.
|
||||
|
||||
Has no external dependencies.
|
||||
|
||||
### License
|
||||
|
||||
Licensed under the [three clause BSD license][license]
|
||||
|
||||
[godoc]: http://godoc.org/github.com/DATA-DOG/godog/gherkin "Documentation on godoc for gherkin"
|
||||
[gherkin]: https://cucumber.io/docs/reference "Gherkin feature file language"
|
||||
[license]: http://en.wikipedia.org/wiki/BSD_licenses "The three clause BSD license"
|
|
@ -1,124 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testFeatureSamples = map[string]string{
|
||||
"feature": `Feature: gherkin parser
|
||||
in order to run features
|
||||
as gherkin lexer
|
||||
I need to be able to parse a feature`,
|
||||
"only_title": `Feature: gherkin`,
|
||||
"empty": ``,
|
||||
"invalid": `some text`,
|
||||
"starts_with_newlines": `
|
||||
|
||||
Feature: gherkin`,
|
||||
}
|
||||
|
||||
func (f *Feature) assertTitle(title string, t *testing.T) {
|
||||
if f.Title != title {
|
||||
t.Fatalf("expected feature title to be '%s', but got '%s'", title, f.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func (f *Feature) assertHasNumScenarios(n int, t *testing.T) {
|
||||
if len(f.Scenarios) != n {
|
||||
t.Fatalf("expected feature to have '%d' scenarios, but got '%d'", n, len(f.Scenarios))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parse_normal_feature(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testFeatureSamples["feature"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
ft, err := p.parseFeature()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if ft.Title != "gherkin parser" {
|
||||
t.Fatalf("the feature title '%s' was not expected", ft.Title)
|
||||
}
|
||||
if len(ft.Description) == 0 {
|
||||
t.Fatalf("expected a feature description to be available")
|
||||
}
|
||||
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
FEATURE,
|
||||
TEXT,
|
||||
TEXT,
|
||||
TEXT,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_feature_without_description(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testFeatureSamples["only_title"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
ft, err := p.parseFeature()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if ft.Title != "gherkin" {
|
||||
t.Fatalf("the feature title '%s' was not expected", ft.Title)
|
||||
}
|
||||
if len(ft.Description) > 0 {
|
||||
t.Fatalf("feature description was not expected")
|
||||
}
|
||||
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
FEATURE,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_empty_feature_file(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testFeatureSamples["empty"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
_, err := p.parseFeature()
|
||||
if err != ErrEmpty {
|
||||
t.Fatalf("expected an empty file error, but got none")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parse_invalid_feature_with_random_text(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testFeatureSamples["invalid"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
_, err := p.parseFeature()
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error but got none")
|
||||
}
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
TEXT,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_feature_with_newlines(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testFeatureSamples["starts_with_newlines"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
ft, err := p.parseFeature()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if ft.Title != "gherkin" {
|
||||
t.Fatalf("the feature title '%s' was not expected", ft.Title)
|
||||
}
|
||||
if len(ft.Description) > 0 {
|
||||
t.Fatalf("feature description was not expected")
|
||||
}
|
||||
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
NEWLINE,
|
||||
NEWLINE,
|
||||
FEATURE,
|
||||
}, t)
|
||||
}
|
|
@ -1,420 +0,0 @@
|
|||
/*
|
||||
Package gherkin is a gherkin language parser based on https://cucumber.io/docs/reference
|
||||
specification. It parses a feature file into the it's structural representation. It also
|
||||
creates an AST tree of gherkin Tokens read from the file.
|
||||
|
||||
With gherkin language you can describe your application behavior as features in
|
||||
human-readable and machine friendly language.
|
||||
|
||||
For example, imagine you’re about to create the famous UNIX ls command.
|
||||
Before you begin, you describe how the feature should work, see the example below..
|
||||
|
||||
Example:
|
||||
Feature: ls
|
||||
In order to see the directory structure
|
||||
As a UNIX user
|
||||
I need to be able to list the current directory's contents
|
||||
|
||||
Scenario:
|
||||
Given I am in a directory "test"
|
||||
And I have a file named "foo"
|
||||
And I have a file named "bar"
|
||||
When I run "ls"
|
||||
Then I should get:
|
||||
"""
|
||||
bar
|
||||
foo
|
||||
"""
|
||||
|
||||
As a developer, your work is done as soon as you’ve made the ls command behave as
|
||||
described in the Scenario.
|
||||
|
||||
To read the feature in the example above..
|
||||
|
||||
Example:
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"os"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
)
|
||||
|
||||
func main() {
|
||||
feature, err := gherkin.Parse("ls.feature")
|
||||
switch {
|
||||
case err == gherkin.ErrEmpty:
|
||||
log.Println("the feature file is empty and does not describe any feature")
|
||||
return
|
||||
case err != nil:
|
||||
log.Println("the feature file is incorrect or could not be read:", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
log.Println("have parsed a feature:", feature.Title, "with", len(feature.Scenarios), "scenarios")
|
||||
}
|
||||
|
||||
Now the feature is available in the structure.
|
||||
*/
|
||||
package gherkin
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"os"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// Tag is gherkin feature or scenario tag.
|
||||
// it may be used to filter scenarios.
|
||||
//
|
||||
// tags may be set for a feature, in that case it will
|
||||
// be merged with all scenario tags. or specifically
|
||||
// to a single scenario
|
||||
type Tag string
|
||||
|
||||
// Tags is an array of tags
|
||||
type Tags []Tag
|
||||
|
||||
// Has checks whether the tag list has a tag
|
||||
func (t Tags) Has(tag Tag) bool {
|
||||
for _, tg := range t {
|
||||
if tg == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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
|
||||
//
|
||||
// if Examples table is not nil, then it
|
||||
// means that this is an outline scenario
|
||||
// with a table of examples to be run for
|
||||
// each and every row
|
||||
//
|
||||
// Scenario may have tags which later may
|
||||
// be used to filter out or run specific
|
||||
// initialization tasks
|
||||
type Scenario struct {
|
||||
*Token
|
||||
Title string
|
||||
Steps []*Step
|
||||
Tags Tags
|
||||
Outline *Outline
|
||||
Feature *Feature
|
||||
}
|
||||
|
||||
// Background steps are run before every scenario
|
||||
type Background struct {
|
||||
*Token
|
||||
Steps []*Step
|
||||
Feature *Feature
|
||||
}
|
||||
|
||||
// Step describes a Scenario or Background step
|
||||
type Step struct {
|
||||
*Token
|
||||
Text string
|
||||
Type string
|
||||
PyString *PyString
|
||||
Table *Table
|
||||
Scenario *Scenario
|
||||
Background *Background
|
||||
}
|
||||
|
||||
// Feature describes the whole feature
|
||||
type Feature struct {
|
||||
*Token
|
||||
Path string
|
||||
Tags Tags
|
||||
Description string
|
||||
Title string
|
||||
Background *Background
|
||||
Scenarios []*Scenario
|
||||
AST []*Token
|
||||
}
|
||||
|
||||
// PyString is a multiline text object used with step definition
|
||||
type PyString struct {
|
||||
*Token
|
||||
Raw string // raw multiline string body
|
||||
Lines []string // trimmed lines
|
||||
Step *Step
|
||||
}
|
||||
|
||||
// String returns raw multiline string
|
||||
func (p *PyString) String() string {
|
||||
return p.Raw
|
||||
}
|
||||
|
||||
// Table is a row group object used with
|
||||
// step definition or outline scenario
|
||||
type Table struct {
|
||||
*Token
|
||||
Step *Step
|
||||
Rows [][]string
|
||||
}
|
||||
|
||||
var allSteps = []TokenType{
|
||||
GIVEN,
|
||||
WHEN,
|
||||
THEN,
|
||||
AND,
|
||||
BUT,
|
||||
}
|
||||
|
||||
// ErrEmpty is returned in case if feature file
|
||||
// is completely empty. May be ignored in some use cases
|
||||
var ErrEmpty = errors.New("the feature file is empty")
|
||||
|
||||
type parser struct {
|
||||
lx *lexer
|
||||
path string
|
||||
ast []*Token
|
||||
peeked *Token
|
||||
}
|
||||
|
||||
// ParseFile parses a feature file on the given
|
||||
// path into the Feature struct
|
||||
// Returns a Feature struct and error if there is any
|
||||
func ParseFile(path string) (*Feature, error) {
|
||||
file, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
return Parse(file, path)
|
||||
}
|
||||
|
||||
// Parse the feature as a given name to the Feature struct
|
||||
// Returns a Feature struct and error if there is any
|
||||
func Parse(in io.Reader, name string) (*Feature, error) {
|
||||
return (&parser{
|
||||
lx: newLexer(in),
|
||||
path: name,
|
||||
}).parseFeature()
|
||||
}
|
||||
|
||||
// reads tokens into AST and skips comments or new lines
|
||||
func (p *parser) next() *Token {
|
||||
if len(p.ast) > 0 && p.ast[len(p.ast)-1].Type == EOF {
|
||||
return p.ast[len(p.ast)-1] // has reached EOF, do not record it more than once
|
||||
}
|
||||
tok := p.peek()
|
||||
p.ast = append(p.ast, tok)
|
||||
p.peeked = nil
|
||||
return tok
|
||||
}
|
||||
|
||||
// peaks into next token, skips comments or new lines
|
||||
func (p *parser) peek() *Token {
|
||||
if p.peeked != nil {
|
||||
return p.peeked
|
||||
}
|
||||
|
||||
for p.peeked = p.lx.read(); p.peeked.OfType(COMMENT, NEWLINE); p.peeked = p.lx.read() {
|
||||
p.ast = append(p.ast, p.peeked) // record comments and newlines
|
||||
}
|
||||
|
||||
return p.peeked
|
||||
}
|
||||
|
||||
func (p *parser) err(s string, l int) error {
|
||||
return fmt.Errorf("%s on %s:%d", s, p.path, l)
|
||||
}
|
||||
|
||||
func (p *parser) parseFeature() (ft *Feature, err error) {
|
||||
|
||||
ft = &Feature{Path: p.path, AST: p.ast}
|
||||
switch p.peek().Type {
|
||||
case EOF:
|
||||
return ft, ErrEmpty
|
||||
case TAGS:
|
||||
ft.Tags = p.parseTags()
|
||||
}
|
||||
|
||||
tok := p.next()
|
||||
if tok.Type != FEATURE {
|
||||
return ft, p.err("expected a file to begin with a feature definition, but got '"+tok.Type.String()+"' instead", tok.Line)
|
||||
}
|
||||
ft.Title = tok.Value
|
||||
ft.Token = tok
|
||||
|
||||
var desc []string
|
||||
for ; p.peek().Type == TEXT; tok = p.next() {
|
||||
desc = append(desc, p.peek().Text)
|
||||
}
|
||||
ft.Description = strings.Join(desc, "\n")
|
||||
|
||||
for tok = p.peek(); tok.Type != EOF; tok = p.peek() {
|
||||
// there may be a background
|
||||
if tok.Type == BACKGROUND {
|
||||
if ft.Background != nil {
|
||||
return ft, p.err("there can only be a single background section, but found another", tok.Line)
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
// there may be tags before scenario
|
||||
var tags Tags
|
||||
tags = append(tags, ft.Tags...)
|
||||
if tok.Type == TAGS {
|
||||
for _, t := range p.parseTags() {
|
||||
if !tags.Has(t) {
|
||||
tags = append(tags, t)
|
||||
}
|
||||
}
|
||||
tok = p.peek()
|
||||
}
|
||||
|
||||
// there must be a scenario or scenario outline otherwise
|
||||
if !tok.OfType(SCENARIO, OUTLINE) {
|
||||
if tok.Type == EOF {
|
||||
return ft, nil // there may not be a scenario defined after background
|
||||
}
|
||||
return ft, p.err("expected a scenario or scenario outline, but got '"+tok.Type.String()+"' instead", tok.Line)
|
||||
}
|
||||
|
||||
scenario, err := p.parseScenario()
|
||||
if err != nil {
|
||||
return ft, err
|
||||
}
|
||||
|
||||
scenario.Tags = tags
|
||||
scenario.Feature = ft
|
||||
ft.Scenarios = append(ft.Scenarios, scenario)
|
||||
}
|
||||
|
||||
return ft, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseScenario() (s *Scenario, err error) {
|
||||
tok := p.next()
|
||||
s = &Scenario{Title: tok.Value, Token: tok}
|
||||
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()
|
||||
if peek.Type != TABLEROW {
|
||||
return s, p.err(strings.Join([]string{
|
||||
"expected a table row,",
|
||||
"but got '" + peek.Type.String() + "' instead, for scenario outline examples",
|
||||
}, " "), examples.Line)
|
||||
}
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
func (p *parser) parseSteps() (steps []*Step, err error) {
|
||||
for tok := p.peek(); tok.OfType(allSteps...); tok = p.peek() {
|
||||
step := &Step{Text: tok.Value, Token: tok}
|
||||
|
||||
p.next() // have read a peeked step
|
||||
if step.Text[len(step.Text)-1] == ':' {
|
||||
tok = p.peek()
|
||||
switch tok.Type {
|
||||
case PYSTRING:
|
||||
if step.PyString, err = p.parsePystring(); err != nil {
|
||||
return steps, err
|
||||
}
|
||||
step.PyString.Step = step
|
||||
case TABLEROW:
|
||||
if step.Table, err = p.parseTable(); err != nil {
|
||||
return steps, err
|
||||
}
|
||||
step.Table.Step = step
|
||||
default:
|
||||
return steps, p.err("pystring or table row was expected, but got: '"+tok.Type.String()+"' instead", tok.Line)
|
||||
}
|
||||
}
|
||||
|
||||
steps = append(steps, step)
|
||||
}
|
||||
|
||||
return steps, nil
|
||||
}
|
||||
|
||||
func (p *parser) parsePystring() (*PyString, error) {
|
||||
var tok *Token
|
||||
started := p.next() // skip the start of pystring
|
||||
var lines, trimmed []string
|
||||
for tok = p.next(); !tok.OfType(EOF, PYSTRING); tok = p.next() {
|
||||
lines = append(lines, tok.Text)
|
||||
trimmed = append(trimmed, strings.TrimSpace(tok.Text))
|
||||
}
|
||||
if tok.Type == EOF {
|
||||
return nil, fmt.Errorf("pystring which was opened on %s:%d was not closed", p.path, started.Line)
|
||||
}
|
||||
return &PyString{
|
||||
Raw: strings.Join(lines, "\n"),
|
||||
Lines: trimmed,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseTable() (*Table, error) {
|
||||
tbl := &Table{Token: p.peek()}
|
||||
for row := p.peek(); row.Type == TABLEROW; 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) {
|
||||
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)
|
||||
p.next() // jump over the peeked token
|
||||
}
|
||||
return tbl, nil
|
||||
}
|
||||
|
||||
func (p *parser) parseTags() (tags Tags) {
|
||||
for _, tag := range strings.Split(p.next().Value, " ") {
|
||||
t := Tag(strings.Trim(tag, "@ "))
|
||||
if len(t) > 0 && !tags.Has(t) {
|
||||
tags = append(tags, t)
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
217
gherkin/lexer.go
217
gherkin/lexer.go
|
@ -1,217 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"bufio"
|
||||
"io"
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
var matchers = map[string]*regexp.Regexp{
|
||||
"feature": regexp.MustCompile("^(\\s*)Feature:\\s*([^#]*)(#.*)?"),
|
||||
"scenario": regexp.MustCompile("^(\\s*)Scenario:\\s*([^#]*)(#.*)?"),
|
||||
"scenario_outline": regexp.MustCompile("^(\\s*)Scenario Outline:\\s*([^#]*)(#.*)?"),
|
||||
"examples": regexp.MustCompile("^(\\s*)Examples:(\\s*#.*)?"),
|
||||
"background": regexp.MustCompile("^(\\s*)Background:(\\s*#.*)?"),
|
||||
"step": regexp.MustCompile("^(\\s*)(Given|When|Then|And|But)\\s+([^#]*)(#.*)?"),
|
||||
"comment": regexp.MustCompile("^(\\s*)#(.+)"),
|
||||
"pystring": regexp.MustCompile("^(\\s*)\\\"\\\"\\\""),
|
||||
"tags": 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",
|
||||
NEWLINE: "New line",
|
||||
TAGS: "Tags",
|
||||
COMMENT: "Comment",
|
||||
PYSTRING: "PyString",
|
||||
TABLEROW: "Table row",
|
||||
TEXT: "Text",
|
||||
// general
|
||||
GIVEN: "Given",
|
||||
WHEN: "When",
|
||||
THEN: "Then",
|
||||
AND: "And",
|
||||
BUT: "But",
|
||||
FEATURE: "Feature",
|
||||
BACKGROUND: "Background",
|
||||
SCENARIO: "Scenario",
|
||||
OUTLINE: "Scenario Outline",
|
||||
EXAMPLES: "Examples",
|
||||
}
|
||||
|
||||
type lexer struct {
|
||||
reader *bufio.Reader
|
||||
lines int
|
||||
}
|
||||
|
||||
func newLexer(r io.Reader) *lexer {
|
||||
return &lexer{
|
||||
reader: bufio.NewReader(r),
|
||||
}
|
||||
}
|
||||
|
||||
func (l *lexer) read() *Token {
|
||||
line, err := l.reader.ReadString(byte('\n'))
|
||||
if err != nil && len(line) == 0 {
|
||||
return &Token{
|
||||
Type: EOF,
|
||||
Line: l.lines + 1,
|
||||
Keyword: keywords[EOF],
|
||||
}
|
||||
}
|
||||
l.lines++
|
||||
line = strings.TrimRightFunc(line, unicode.IsSpace)
|
||||
// newline
|
||||
if len(line) == 0 {
|
||||
return &Token{
|
||||
Type: NEWLINE,
|
||||
Line: l.lines,
|
||||
Keyword: keywords[NEWLINE],
|
||||
}
|
||||
}
|
||||
// comment
|
||||
if m := matchers["comment"].FindStringSubmatch(line); len(m) > 0 {
|
||||
comment := strings.TrimSpace(m[2])
|
||||
return &Token{
|
||||
Type: COMMENT,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: comment,
|
||||
Text: line,
|
||||
Comment: comment,
|
||||
Keyword: keywords[COMMENT],
|
||||
}
|
||||
}
|
||||
// pystring
|
||||
if m := matchers["pystring"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: PYSTRING,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Text: line,
|
||||
Keyword: keywords[PYSTRING],
|
||||
}
|
||||
}
|
||||
// step
|
||||
if m := matchers["step"].FindStringSubmatch(line); len(m) > 0 {
|
||||
tok := &Token{
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: strings.TrimSpace(m[3]),
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[4], " #"),
|
||||
}
|
||||
switch m[2] {
|
||||
case "Given":
|
||||
tok.Type = GIVEN
|
||||
case "When":
|
||||
tok.Type = WHEN
|
||||
case "Then":
|
||||
tok.Type = THEN
|
||||
case "And":
|
||||
tok.Type = AND
|
||||
case "But":
|
||||
tok.Type = BUT
|
||||
}
|
||||
tok.Keyword = keywords[tok.Type]
|
||||
return tok
|
||||
}
|
||||
// scenario
|
||||
if m := matchers["scenario"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: SCENARIO,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: strings.TrimSpace(m[2]),
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[3], " #"),
|
||||
Keyword: keywords[SCENARIO],
|
||||
}
|
||||
}
|
||||
// background
|
||||
if m := matchers["background"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: BACKGROUND,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[2], " #"),
|
||||
Keyword: keywords[BACKGROUND],
|
||||
}
|
||||
}
|
||||
// feature
|
||||
if m := matchers["feature"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: FEATURE,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: strings.TrimSpace(m[2]),
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[3], " #"),
|
||||
Keyword: keywords[FEATURE],
|
||||
}
|
||||
}
|
||||
// tags
|
||||
if m := matchers["tags"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: TAGS,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: strings.TrimSpace(m[2]),
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[3], " #"),
|
||||
Keyword: keywords[TAGS],
|
||||
}
|
||||
}
|
||||
// table row
|
||||
if m := matchers["table_row"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: TABLEROW,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: strings.TrimSpace(m[2]),
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[3], " #"),
|
||||
Keyword: keywords[TABLEROW],
|
||||
}
|
||||
}
|
||||
// scenario outline
|
||||
if m := matchers["scenario_outline"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: OUTLINE,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Value: strings.TrimSpace(m[2]),
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[3], " #"),
|
||||
Keyword: keywords[OUTLINE],
|
||||
}
|
||||
}
|
||||
// examples
|
||||
if m := matchers["examples"].FindStringSubmatch(line); len(m) > 0 {
|
||||
return &Token{
|
||||
Type: EXAMPLES,
|
||||
Indent: len(m[1]),
|
||||
Line: l.lines,
|
||||
Text: line,
|
||||
Comment: strings.Trim(m[2], " #"),
|
||||
Keyword: keywords[EXAMPLES],
|
||||
}
|
||||
}
|
||||
// text
|
||||
text := strings.TrimLeftFunc(line, unicode.IsSpace)
|
||||
return &Token{
|
||||
Type: TEXT,
|
||||
Line: l.lines,
|
||||
Value: text,
|
||||
Indent: len(line) - len(text),
|
||||
Text: line,
|
||||
Keyword: keywords[TEXT],
|
||||
}
|
||||
}
|
|
@ -1,221 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testLexerSamples = map[string]string{
|
||||
"feature": `Feature: gherkin lexer
|
||||
in order to run features
|
||||
as gherkin lexer
|
||||
I need to be able to parse a feature`,
|
||||
|
||||
"background": `Background:`,
|
||||
|
||||
"scenario": "Scenario: tokenize feature file",
|
||||
|
||||
"step_given": `Given a feature file`,
|
||||
|
||||
"step_when": `When I try to read it`,
|
||||
|
||||
"comment": `# an important comment`,
|
||||
|
||||
"step_then": `Then it should give me tokens`,
|
||||
|
||||
"step_given_table": `Given there are users:
|
||||
| name | lastname | num |
|
||||
| Jack | Sparrow | 4 |
|
||||
| John | Doe | 79 |`,
|
||||
|
||||
"scenario_outline_with_examples": `Scenario Outline: ls supports kinds of options
|
||||
Given I am in a directory "test"
|
||||
And I have a file named "foo"
|
||||
And I have a file named "bar"
|
||||
When I run "ls" with options "<options>"
|
||||
Then I should see "<result>"
|
||||
|
||||
Examples:
|
||||
| options | result |
|
||||
| -t | bar foo |
|
||||
| -tr | foo bar |`,
|
||||
}
|
||||
|
||||
func Test_feature_read(t *testing.T) {
|
||||
l := newLexer(strings.NewReader(testLexerSamples["feature"]))
|
||||
tok := l.read()
|
||||
if tok.Type != FEATURE {
|
||||
t.Fatalf("Expected a 'feature' type, but got: '%s'", tok.Type)
|
||||
}
|
||||
val := "gherkin lexer"
|
||||
if tok.Value != val {
|
||||
t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value)
|
||||
}
|
||||
if tok.Line != 1 {
|
||||
t.Fatalf("Expected a token line to be '1', but got: '%d'", tok.Line)
|
||||
}
|
||||
if tok.Indent != 0 {
|
||||
t.Fatalf("Expected a token identation to be '0', but got: '%d'", tok.Indent)
|
||||
}
|
||||
|
||||
tok = l.read()
|
||||
if tok.Type != TEXT {
|
||||
t.Fatalf("Expected a 'text' type, but got: '%s'", tok.Type)
|
||||
}
|
||||
val = "in order to run features"
|
||||
if tok.Value != val {
|
||||
t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value)
|
||||
}
|
||||
if tok.Line != 2 {
|
||||
t.Fatalf("Expected a token line to be '2', but got: '%d'", tok.Line)
|
||||
}
|
||||
if tok.Indent != 2 {
|
||||
t.Fatalf("Expected a token identation to be '2', but got: '%d'", tok.Indent)
|
||||
}
|
||||
|
||||
tok = l.read()
|
||||
if tok.Type != TEXT {
|
||||
t.Fatalf("Expected a 'text' type, but got: '%s'", tok.Type)
|
||||
}
|
||||
val = "as gherkin lexer"
|
||||
if tok.Value != val {
|
||||
t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value)
|
||||
}
|
||||
if tok.Line != 3 {
|
||||
t.Fatalf("Expected a token line to be '3', but got: '%d'", tok.Line)
|
||||
}
|
||||
if tok.Indent != 2 {
|
||||
t.Fatalf("Expected a token identation to be '2', but got: '%d'", tok.Indent)
|
||||
}
|
||||
|
||||
tok = l.read()
|
||||
if tok.Type != TEXT {
|
||||
t.Fatalf("Expected a 'text' type, but got: '%s'", tok.Type)
|
||||
}
|
||||
val = "I need to be able to parse a feature"
|
||||
if tok.Value != val {
|
||||
t.Fatalf("Expected a token value to be '%s', but got: '%s'", val, tok.Value)
|
||||
}
|
||||
if tok.Line != 4 {
|
||||
t.Fatalf("Expected a token line to be '4', but got: '%d'", tok.Line)
|
||||
}
|
||||
if tok.Indent != 2 {
|
||||
t.Fatalf("Expected a token identation to be '2', but got: '%d'", tok.Indent)
|
||||
}
|
||||
|
||||
tok = l.read()
|
||||
if tok.Type != EOF {
|
||||
t.Fatalf("Expected an 'eof' type, but got: '%s'", tok.Type)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_minimal_feature(t *testing.T) {
|
||||
file := strings.Join([]string{
|
||||
testLexerSamples["feature"] + "\n",
|
||||
|
||||
indent(2, testLexerSamples["background"]),
|
||||
indent(4, testLexerSamples["step_given"]) + "\n",
|
||||
|
||||
indent(2, testLexerSamples["comment"]),
|
||||
indent(2, testLexerSamples["scenario"]),
|
||||
indent(4, testLexerSamples["step_given"]),
|
||||
indent(4, testLexerSamples["step_when"]),
|
||||
indent(4, testLexerSamples["step_then"]),
|
||||
}, "\n")
|
||||
l := newLexer(strings.NewReader(file))
|
||||
|
||||
var tokens []TokenType
|
||||
for tok := l.read(); tok.Type != EOF; tok = l.read() {
|
||||
tokens = append(tokens, tok.Type)
|
||||
}
|
||||
expected := []TokenType{
|
||||
FEATURE,
|
||||
TEXT,
|
||||
TEXT,
|
||||
TEXT,
|
||||
NEWLINE,
|
||||
|
||||
BACKGROUND,
|
||||
GIVEN,
|
||||
NEWLINE,
|
||||
|
||||
COMMENT,
|
||||
SCENARIO,
|
||||
GIVEN,
|
||||
WHEN,
|
||||
THEN,
|
||||
}
|
||||
for i := 0; i < len(expected); i++ {
|
||||
if expected[i] != tokens[i] {
|
||||
t.Fatalf("expected token '%s' at position: %d, is not the same as actual token: '%s'", expected[i], i, tokens[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_table_row_reading(t *testing.T) {
|
||||
file := strings.Join([]string{
|
||||
indent(2, testLexerSamples["background"]),
|
||||
indent(4, testLexerSamples["step_given_table"]),
|
||||
indent(4, testLexerSamples["step_given"]),
|
||||
}, "\n")
|
||||
l := newLexer(strings.NewReader(file))
|
||||
|
||||
var types []TokenType
|
||||
var values []string
|
||||
var indents []int
|
||||
for tok := l.read(); tok.Type != EOF; tok = l.read() {
|
||||
types = append(types, tok.Type)
|
||||
values = append(values, tok.Value)
|
||||
indents = append(indents, tok.Indent)
|
||||
}
|
||||
expectedTypes := []TokenType{
|
||||
BACKGROUND,
|
||||
GIVEN,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
GIVEN,
|
||||
}
|
||||
expectedIndents := []int{2, 4, 6, 6, 6, 4}
|
||||
for i := 0; i < len(expectedTypes); i++ {
|
||||
if expectedTypes[i] != types[i] {
|
||||
t.Fatalf("expected token type '%s' at position: %d, is not the same as actual: '%s'", expectedTypes[i], i, types[i])
|
||||
}
|
||||
}
|
||||
for i := 0; i < len(expectedIndents); i++ {
|
||||
if expectedIndents[i] != indents[i] {
|
||||
t.Fatalf("expected token indentation '%d' at position: %d, is not the same as actual: '%d'", expectedIndents[i], i, indents[i])
|
||||
}
|
||||
}
|
||||
if values[2] != "name | lastname | num |" {
|
||||
t.Fatalf("table row value '%s' was not expected", values[2])
|
||||
}
|
||||
}
|
||||
|
||||
func Test_lexing_of_scenario_outline(t *testing.T) {
|
||||
l := newLexer(strings.NewReader(testLexerSamples["scenario_outline_with_examples"]))
|
||||
|
||||
var tokens []TokenType
|
||||
for tok := l.read(); tok.Type != EOF; tok = l.read() {
|
||||
tokens = append(tokens, tok.Type)
|
||||
}
|
||||
expected := []TokenType{
|
||||
OUTLINE,
|
||||
GIVEN,
|
||||
AND,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
NEWLINE,
|
||||
|
||||
EXAMPLES,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
}
|
||||
for i := 0; i < len(expected); i++ {
|
||||
if expected[i] != tokens[i] {
|
||||
t.Fatalf("expected token '%s' at position: %d, is not the same as actual token: '%s'", expected[i], i, tokens[i])
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (a *parser) assertMatchesTypes(expected []TokenType, t *testing.T) {
|
||||
key := -1
|
||||
for _, tok := range a.ast {
|
||||
key++
|
||||
if len(expected) <= key {
|
||||
t.Fatalf("there are more tokens in AST then expected, next is '%s'", tok.Type)
|
||||
}
|
||||
if expected[key] != tok.Type {
|
||||
t.Fatalf("expected ast token '%s', but got '%s' at position: %d", expected[key], tok.Type, key)
|
||||
}
|
||||
}
|
||||
if len(expected)-1 != key {
|
||||
t.Fatalf("expected ast length %d, does not match actual: %d", len(expected), key+1)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scenario) assertHasTag(tag string, t *testing.T) {
|
||||
if !s.Tags.Has(Tag(tag)) {
|
||||
t.Fatalf("expected scenario '%s' to have '%s' tag, but it did not", s.Title, tag)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scenario) assertHasNumTags(n int, t *testing.T) {
|
||||
if len(s.Tags) != n {
|
||||
t.Fatalf("expected scenario '%s' to have '%d' tags, but it has '%d'", s.Title, n, len(s.Tags))
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parse_feature_file(t *testing.T) {
|
||||
|
||||
content := strings.Join([]string{
|
||||
// feature
|
||||
"@global-one @cust",
|
||||
testFeatureSamples["feature"] + "\n",
|
||||
// background
|
||||
indent(2, "Background:"),
|
||||
testStepSamples["given_table_hash"] + "\n",
|
||||
// scenario - normal without tags
|
||||
indent(2, "Scenario: user is able to register"),
|
||||
testStepSamples["step_group"] + "\n",
|
||||
// scenario - repeated tag, one extra
|
||||
indent(2, "@user @cust"),
|
||||
indent(2, "Scenario: password is required to login"),
|
||||
testStepSamples["step_group_another"] + "\n",
|
||||
// scenario - no steps yet
|
||||
indent(2, "@todo"), // cust - tag is repeated
|
||||
indent(2, "Scenario: user is able to reset his password") + "\n",
|
||||
// scenario outline
|
||||
testLexerSamples["scenario_outline_with_examples"],
|
||||
}, "\n")
|
||||
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(content)),
|
||||
path: "usual.feature",
|
||||
}
|
||||
ft, err := p.parseFeature()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
ft.assertTitle("gherkin parser", t)
|
||||
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
TAGS,
|
||||
FEATURE,
|
||||
TEXT,
|
||||
TEXT,
|
||||
TEXT,
|
||||
NEWLINE,
|
||||
|
||||
BACKGROUND,
|
||||
GIVEN,
|
||||
TABLEROW,
|
||||
NEWLINE,
|
||||
|
||||
SCENARIO,
|
||||
GIVEN,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
NEWLINE,
|
||||
|
||||
TAGS,
|
||||
SCENARIO,
|
||||
GIVEN,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
NEWLINE,
|
||||
|
||||
TAGS,
|
||||
SCENARIO,
|
||||
NEWLINE,
|
||||
|
||||
OUTLINE,
|
||||
GIVEN,
|
||||
AND,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
NEWLINE,
|
||||
EXAMPLES,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
}, t)
|
||||
|
||||
ft.assertHasNumScenarios(4, t)
|
||||
|
||||
ft.Scenarios[0].assertHasNumTags(2, t)
|
||||
ft.Scenarios[0].assertHasTag("global-one", t)
|
||||
ft.Scenarios[0].assertHasTag("cust", t)
|
||||
|
||||
ft.Scenarios[1].assertHasNumTags(3, t)
|
||||
ft.Scenarios[1].assertHasTag("global-one", t)
|
||||
ft.Scenarios[1].assertHasTag("cust", t)
|
||||
ft.Scenarios[1].assertHasTag("user", t)
|
||||
|
||||
ft.Scenarios[2].assertHasNumTags(3, t)
|
||||
ft.Scenarios[2].assertHasTag("global-one", t)
|
||||
ft.Scenarios[2].assertHasTag("cust", t)
|
||||
ft.Scenarios[2].assertHasTag("todo", t)
|
||||
|
||||
ft.Scenarios[3].assertHasNumTags(2, t)
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func (s *Scenario) assertTitle(title string, t *testing.T) {
|
||||
if s.Title != title {
|
||||
t.Fatalf("expected scenario title to be '%s', but got '%s'", title, s.Title)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Scenario) assertOutlineStep(text string, t *testing.T) *Step {
|
||||
for _, stp := range s.Outline.Steps {
|
||||
if stp.Text == text {
|
||||
return stp
|
||||
}
|
||||
}
|
||||
t.Fatalf("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 {
|
||||
for _, stp := range s.Steps {
|
||||
if stp.Text == text {
|
||||
return stp
|
||||
}
|
||||
}
|
||||
t.Fatalf("expected scenario '%s' to have step: '%s', but it did not", s.Title, text)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *Scenario) assertExampleRow(t *testing.T, num int, cols ...string) {
|
||||
if s.Outline.Examples == nil {
|
||||
t.Fatalf("outline scenario '%s' has no examples", s.Title)
|
||||
}
|
||||
if len(s.Outline.Examples.Rows) <= num {
|
||||
t.Fatalf("outline scenario '%s' table has no row: %d", s.Title, num)
|
||||
}
|
||||
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))
|
||||
}
|
||||
for i, col := range s.Outline.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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parse_scenario_outline(t *testing.T) {
|
||||
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testLexerSamples["scenario_outline_with_examples"])),
|
||||
path: "usual.feature",
|
||||
}
|
||||
s, err := p.parseScenario()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
s.assertTitle("ls supports kinds of options", t)
|
||||
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
OUTLINE,
|
||||
GIVEN,
|
||||
AND,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
NEWLINE,
|
||||
EXAMPLES,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
}, t)
|
||||
|
||||
s.assertOutlineStep(`I am in a directory "test"`, t)
|
||||
s.assertOutlineStep(`I have a file named "foo"`, t)
|
||||
s.assertOutlineStep(`I have a file named "bar"`, t)
|
||||
s.assertOutlineStep(`I run "ls" with options "<options>"`, t)
|
||||
s.assertOutlineStep(`I should see "<result>"`, t)
|
||||
|
||||
s.assertExampleRow(t, 0, "options", "result")
|
||||
s.assertExampleRow(t, 1, "-t", "bar foo")
|
||||
s.assertExampleRow(t, 2, "-tr", "foo bar")
|
||||
}
|
|
@ -1,309 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
)
|
||||
|
||||
var testStepSamples = map[string]string{
|
||||
"given": indent(4, `Given I'm a step`),
|
||||
|
||||
"given_table_hash": `Given there are users:
|
||||
| name | John Doe |`,
|
||||
|
||||
"step_comment": `Given I'm an admin # sets admin permissions`,
|
||||
|
||||
"given_table": `Given there are users:
|
||||
| name | lastname |
|
||||
| John | Doe |
|
||||
| Jane | Doe |`,
|
||||
|
||||
"then_pystring": `Then there should be text:
|
||||
"""
|
||||
Some text
|
||||
And more
|
||||
"""`,
|
||||
|
||||
"when_pystring_empty": `When I do request with body:
|
||||
"""
|
||||
"""`,
|
||||
|
||||
"when_pystring_unclosed": `When I do request with body:
|
||||
"""
|
||||
{"json": "data"}
|
||||
""`,
|
||||
|
||||
"step_group": `Given there are conditions
|
||||
And there are more conditions
|
||||
When I do something
|
||||
Then something should happen`,
|
||||
|
||||
"step_group_another": `Given an admin user "John Doe"
|
||||
And user "John Doe" belongs to user group "editors"
|
||||
When I do something
|
||||
Then I expect the result`,
|
||||
}
|
||||
|
||||
func (s *Step) assertText(text string, t *testing.T) {
|
||||
if s.Text != text {
|
||||
t.Fatalf("expected step text to be '%s', but got '%s'", text, s.Text)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Step) assertPyString(text string, t *testing.T) {
|
||||
if s.PyString == nil {
|
||||
t.Fatalf("step '%s %s' has no pystring", s.Type, s.Text)
|
||||
}
|
||||
if s.PyString.Raw != text {
|
||||
t.Fatalf("expected step pystring body to be '%s', but got '%s'", text, s.PyString.Raw)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *Step) assertComment(comment string, t *testing.T) {
|
||||
if s.Token.Comment != comment {
|
||||
t.Fatalf("expected step '%s' comment to be '%s', but got '%s'", s.Text, comment, s.Token.Comment)
|
||||
}
|
||||
}
|
||||
|
||||
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 {
|
||||
t.Fatalf("step '%s %s' table has no row: %d", s.Type, s.Text, num)
|
||||
}
|
||||
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] {
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func Test_parse_basic_given_step(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["given"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 1 {
|
||||
t.Fatalf("expected one step to be parsed")
|
||||
}
|
||||
|
||||
steps[0].assertText("I'm a step", t)
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
GIVEN,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_step_with_comment(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["step_comment"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 1 {
|
||||
t.Fatalf("expected one step to be parsed")
|
||||
}
|
||||
|
||||
steps[0].assertText("I'm an admin", t)
|
||||
steps[0].assertComment("sets admin permissions", t)
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
GIVEN,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_hash_table_given_step(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["given_table_hash"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 1 {
|
||||
t.Fatalf("expected one step to be parsed")
|
||||
}
|
||||
|
||||
steps[0].assertText("there are users:", t)
|
||||
steps[0].assertTableRow(t, 0, "name", "John Doe")
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
GIVEN,
|
||||
TABLEROW,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_table_given_step(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["given_table"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 1 {
|
||||
t.Fatalf("expected one step to be parsed")
|
||||
}
|
||||
|
||||
steps[0].assertText("there are users:", t)
|
||||
steps[0].assertTableRow(t, 0, "name", "lastname")
|
||||
steps[0].assertTableRow(t, 1, "John", "Doe")
|
||||
steps[0].assertTableRow(t, 2, "Jane", "Doe")
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
GIVEN,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
TABLEROW,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_pystring_step(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["then_pystring"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 1 {
|
||||
t.Fatalf("expected one step to be parsed")
|
||||
}
|
||||
|
||||
steps[0].assertText("there should be text:", t)
|
||||
steps[0].assertPyString(strings.Join([]string{
|
||||
indent(4, "Some text"),
|
||||
indent(4, "And more"),
|
||||
}, "\n"), t)
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
THEN,
|
||||
PYSTRING,
|
||||
TEXT,
|
||||
AND, // we do not care what we parse inside PYSTRING even if its whole behat feature text
|
||||
PYSTRING,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_empty_pystring_step(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["when_pystring_empty"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 1 {
|
||||
t.Fatalf("expected one step to be parsed")
|
||||
}
|
||||
|
||||
steps[0].assertText("I do request with body:", t)
|
||||
steps[0].assertPyString("", t)
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
WHEN,
|
||||
PYSTRING,
|
||||
PYSTRING,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_unclosed_pystring_step(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["when_pystring_unclosed"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
_, err := p.parseSteps()
|
||||
if err == nil {
|
||||
t.Fatalf("expected an error, but got none")
|
||||
}
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
WHEN,
|
||||
PYSTRING,
|
||||
TEXT,
|
||||
TEXT,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_step_group(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["step_group"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 4 {
|
||||
t.Fatalf("expected four steps to be parsed, but got: %d", len(steps))
|
||||
}
|
||||
|
||||
steps[0].assertText("there are conditions", t)
|
||||
steps[1].assertText("there are more conditions", t)
|
||||
steps[2].assertText("I do something", t)
|
||||
steps[3].assertText("something should happen", t)
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
GIVEN,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
||||
|
||||
func Test_parse_another_step_group(t *testing.T) {
|
||||
p := &parser{
|
||||
lx: newLexer(strings.NewReader(testStepSamples["step_group_another"])),
|
||||
path: "some.feature",
|
||||
}
|
||||
steps, err := p.parseSteps()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %s", err)
|
||||
}
|
||||
if len(steps) != 4 {
|
||||
t.Fatalf("expected four steps to be parsed, but got: %d", len(steps))
|
||||
}
|
||||
|
||||
steps[0].assertText(`an admin user "John Doe"`, t)
|
||||
steps[1].assertText(`user "John Doe" belongs to user group "editors"`, t)
|
||||
steps[2].assertText("I do something", t)
|
||||
steps[3].assertText("I expect the result", t)
|
||||
|
||||
p.next() // step over to eof
|
||||
p.assertMatchesTypes([]TokenType{
|
||||
GIVEN,
|
||||
AND,
|
||||
WHEN,
|
||||
THEN,
|
||||
EOF,
|
||||
}, t)
|
||||
}
|
|
@ -1,65 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"unicode"
|
||||
)
|
||||
|
||||
// TokenType defines a gherkin token type
|
||||
type TokenType int
|
||||
|
||||
// TokenType constants
|
||||
const (
|
||||
ILLEGAL TokenType = iota
|
||||
COMMENT
|
||||
NEWLINE
|
||||
EOF
|
||||
TEXT
|
||||
TAGS
|
||||
TABLEROW
|
||||
PYSTRING
|
||||
FEATURE
|
||||
BACKGROUND
|
||||
SCENARIO
|
||||
OUTLINE
|
||||
EXAMPLES
|
||||
GIVEN
|
||||
WHEN
|
||||
THEN
|
||||
AND
|
||||
BUT
|
||||
)
|
||||
|
||||
// String gives a string representation of token type
|
||||
func (t TokenType) String() string {
|
||||
return keywords[t]
|
||||
}
|
||||
|
||||
// Token represents a line in gherkin feature file
|
||||
type Token struct {
|
||||
Type TokenType // type of token
|
||||
Line, Indent int // line and indentation number
|
||||
Value string // interpreted value
|
||||
Text string // same text as read
|
||||
Keyword string // @TODO: the translated keyword
|
||||
Comment string // a comment
|
||||
}
|
||||
|
||||
// OfType checks whether token is one of types
|
||||
func (t *Token) OfType(all ...TokenType) bool {
|
||||
for _, typ := range all {
|
||||
if typ == t.Type {
|
||||
return true
|
||||
}
|
||||
}
|
||||
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)
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package gherkin
|
||||
|
||||
import "strings"
|
||||
|
||||
func indent(n int, s string) string {
|
||||
return strings.Repeat(" ", n) + s
|
||||
}
|
223
suite.go
223
suite.go
|
@ -10,9 +10,14 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
type feature struct {
|
||||
*gherkin.Feature
|
||||
Path string `json:"path"`
|
||||
}
|
||||
|
||||
// Regexp is an unified type for regular expression
|
||||
// it can be either a string or a *regexp.Regexp
|
||||
type Regexp interface{}
|
||||
|
@ -63,16 +68,16 @@ type Suite interface {
|
|||
Step(expr Regexp, h StepHandler)
|
||||
// suite events
|
||||
BeforeSuite(f func())
|
||||
BeforeScenario(f func(*gherkin.Scenario))
|
||||
BeforeScenario(f func(interface{}))
|
||||
BeforeStep(f func(*gherkin.Step))
|
||||
AfterStep(f func(*gherkin.Step, error))
|
||||
AfterScenario(f func(*gherkin.Scenario, error))
|
||||
AfterScenario(f func(interface{}, error))
|
||||
AfterSuite(f func())
|
||||
}
|
||||
|
||||
type suite struct {
|
||||
stepHandlers []*StepDef
|
||||
features []*gherkin.Feature
|
||||
features []*feature
|
||||
fmt Formatter
|
||||
|
||||
failed bool
|
||||
|
@ -87,10 +92,10 @@ type suite struct {
|
|||
|
||||
// suite event handlers
|
||||
beforeSuiteHandlers []func()
|
||||
beforeScenarioHandlers []func(*gherkin.Scenario)
|
||||
beforeScenarioHandlers []func(interface{})
|
||||
beforeStepHandlers []func(*gherkin.Step)
|
||||
afterStepHandlers []func(*gherkin.Step, error)
|
||||
afterScenarioHandlers []func(*gherkin.Scenario, error)
|
||||
afterScenarioHandlers []func(interface{}, error)
|
||||
afterSuiteHandlers []func()
|
||||
}
|
||||
|
||||
|
@ -142,12 +147,15 @@ func (s *suite) BeforeSuite(f func()) {
|
|||
}
|
||||
|
||||
// BeforeScenario registers a function or method
|
||||
// to be run before every scenario.
|
||||
// to be run before every scenario or scenario outline.
|
||||
//
|
||||
// The interface argument may be *gherkin.Scenario
|
||||
// or *gherkin.ScenarioOutline
|
||||
//
|
||||
// It is a good practice to restore the default state
|
||||
// before every scenario so it would be isolated from
|
||||
// any kind of state.
|
||||
func (s *suite) BeforeScenario(f func(*gherkin.Scenario)) {
|
||||
func (s *suite) BeforeScenario(f func(interface{})) {
|
||||
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, f)
|
||||
}
|
||||
|
||||
|
@ -171,8 +179,11 @@ func (s *suite) AfterStep(f func(*gherkin.Step, error)) {
|
|||
}
|
||||
|
||||
// AfterScenario registers an function or method
|
||||
// to be run after every scenario
|
||||
func (s *suite) AfterScenario(f func(*gherkin.Scenario, error)) {
|
||||
// to be run after every scenario or scenario outline
|
||||
//
|
||||
// The interface argument may be *gherkin.Scenario
|
||||
// or *gherkin.ScenarioOutline
|
||||
func (s *suite) AfterScenario(f func(interface{}, error)) {
|
||||
s.afterScenarioHandlers = append(s.afterScenarioHandlers, f)
|
||||
}
|
||||
|
||||
|
@ -255,11 +266,8 @@ func (s *suite) matchStep(step *gherkin.Step) *StepDef {
|
|||
for _, a := range m[1:] {
|
||||
args = append(args, &Arg{value: a})
|
||||
}
|
||||
if step.Table != nil {
|
||||
args = append(args, &Arg{value: step.Table})
|
||||
}
|
||||
if step.PyString != nil {
|
||||
args = append(args, &Arg{value: step.PyString})
|
||||
if step.Argument != nil {
|
||||
args = append(args, &Arg{value: step.Argument})
|
||||
}
|
||||
h.Args = args
|
||||
return h
|
||||
|
@ -277,7 +285,10 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
|
|||
|
||||
defer func() {
|
||||
if e := recover(); e != nil {
|
||||
err = e.(error)
|
||||
err, ok := e.(error)
|
||||
if !ok {
|
||||
err = fmt.Errorf(e.(string))
|
||||
}
|
||||
s.fmt.Failed(step, match, err)
|
||||
}
|
||||
}()
|
||||
|
@ -318,34 +329,57 @@ 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 {
|
||||
func (s *suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Background) (err error) {
|
||||
// run before scenario handlers
|
||||
defer func() {
|
||||
// run after scenario handlers
|
||||
}()
|
||||
|
||||
s.fmt.Node(outline)
|
||||
|
||||
for _, example := range outline.Examples {
|
||||
s.fmt.Node(example)
|
||||
|
||||
placeholders := example.TableHeader.Cells
|
||||
groups := example.TableBody
|
||||
|
||||
for _, group := range groups {
|
||||
for _, f := range s.beforeScenarioHandlers {
|
||||
f(outline)
|
||||
}
|
||||
var steps []*gherkin.Step
|
||||
for _, step := range scenario.Outline.Steps {
|
||||
text := step.Text
|
||||
for _, outlineStep := range outline.Steps {
|
||||
text := outlineStep.Text
|
||||
for i, placeholder := range placeholders {
|
||||
text = strings.Replace(text, "<"+placeholder+">", example[i], -1)
|
||||
text = strings.Replace(text, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
|
||||
}
|
||||
// clone a step
|
||||
cloned := &gherkin.Step{
|
||||
Token: step.Token,
|
||||
step := &gherkin.Step{
|
||||
Node: outlineStep.Node,
|
||||
Text: text,
|
||||
Type: step.Type,
|
||||
PyString: step.PyString,
|
||||
Table: step.Table,
|
||||
Background: step.Background,
|
||||
Scenario: scenario,
|
||||
Keyword: outlineStep.Keyword,
|
||||
Argument: outlineStep.Argument,
|
||||
}
|
||||
steps = append(steps, cloned)
|
||||
steps = append(steps, step)
|
||||
}
|
||||
// run background
|
||||
if b != nil {
|
||||
err = s.runSteps(b.Steps)
|
||||
}
|
||||
switch err {
|
||||
case ErrUndefined:
|
||||
s.skipSteps(steps)
|
||||
case nil:
|
||||
err = s.runSteps(steps)
|
||||
default:
|
||||
s.skipSteps(steps)
|
||||
}
|
||||
|
||||
// set steps to scenario
|
||||
scenario.Steps = steps
|
||||
if err = s.runScenario(scenario); err != nil && err != ErrUndefined {
|
||||
s.failed = true
|
||||
if s.stopOnFailure {
|
||||
for _, f := range s.afterScenarioHandlers {
|
||||
f(outline, err)
|
||||
}
|
||||
|
||||
if s.stopOnFailure && err != ErrUndefined {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
@ -353,15 +387,18 @@ func (s *suite) runOutline(scenario *gherkin.Scenario) (err error) {
|
|||
return
|
||||
}
|
||||
|
||||
func (s *suite) runFeature(f *gherkin.Feature) {
|
||||
s.fmt.Node(f)
|
||||
for _, scenario := range f.Scenarios {
|
||||
func (s *suite) runFeature(f *feature) {
|
||||
s.fmt.Feature(f.Feature, f.Path)
|
||||
for _, scenario := range f.ScenarioDefinitions {
|
||||
var err error
|
||||
// handle scenario outline differently
|
||||
if scenario.Outline != nil {
|
||||
err = s.runOutline(scenario)
|
||||
} else {
|
||||
err = s.runScenario(scenario)
|
||||
if f.Background != nil {
|
||||
s.fmt.Node(f.Background)
|
||||
}
|
||||
switch t := scenario.(type) {
|
||||
case *gherkin.ScenarioOutline:
|
||||
err = s.runOutline(t, f.Background)
|
||||
case *gherkin.Scenario:
|
||||
err = s.runScenario(t, f.Background)
|
||||
}
|
||||
if err != nil && err != ErrUndefined {
|
||||
s.failed = true
|
||||
|
@ -372,16 +409,15 @@ func (s *suite) runFeature(f *gherkin.Feature) {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *suite) runScenario(scenario *gherkin.Scenario) (err error) {
|
||||
func (s *suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) {
|
||||
// run before scenario handlers
|
||||
for _, f := range s.beforeScenarioHandlers {
|
||||
f(scenario)
|
||||
}
|
||||
|
||||
// background
|
||||
if scenario.Feature.Background != nil {
|
||||
s.fmt.Node(scenario.Feature.Background)
|
||||
err = s.runSteps(scenario.Feature.Background.Steps)
|
||||
if b != nil {
|
||||
err = s.runSteps(b.Steps)
|
||||
}
|
||||
|
||||
// scenario
|
||||
|
@ -435,25 +471,33 @@ func (s *suite) parseFeatures() (err error) {
|
|||
// parse features
|
||||
err = filepath.Walk(path, func(p string, f os.FileInfo, err error) error {
|
||||
if err == nil && !f.IsDir() && strings.HasSuffix(p, ".feature") {
|
||||
ft, err := gherkin.ParseFile(p)
|
||||
switch {
|
||||
case err == gherkin.ErrEmpty:
|
||||
// its ok, just skip it
|
||||
case err != nil:
|
||||
reader, err := os.Open(p)
|
||||
if err != nil {
|
||||
return err
|
||||
default:
|
||||
s.features = append(s.features, ft)
|
||||
}
|
||||
ft, err := gherkin.ParseFeature(reader)
|
||||
reader.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.features = append(s.features, &feature{Path: p, Feature: ft})
|
||||
// filter scenario by line number
|
||||
if line != -1 {
|
||||
var scenarios []*gherkin.Scenario
|
||||
for _, s := range ft.Scenarios {
|
||||
if s.Token.Line == line {
|
||||
scenarios = append(scenarios, s)
|
||||
var scenarios []interface{}
|
||||
for _, def := range ft.ScenarioDefinitions {
|
||||
var ln int
|
||||
switch t := def.(type) {
|
||||
case *gherkin.Scenario:
|
||||
ln = t.Location.Line
|
||||
case *gherkin.ScenarioOutline:
|
||||
ln = t.Location.Line
|
||||
}
|
||||
if ln == line {
|
||||
scenarios = append(scenarios, def)
|
||||
break
|
||||
}
|
||||
}
|
||||
ft.Scenarios = scenarios
|
||||
ft.ScenarioDefinitions = scenarios
|
||||
}
|
||||
s.applyTagFilter(ft)
|
||||
}
|
||||
|
@ -477,17 +521,62 @@ func (s *suite) applyTagFilter(ft *gherkin.Feature) {
|
|||
return
|
||||
}
|
||||
|
||||
var scenarios []*gherkin.Scenario
|
||||
for _, scenario := range ft.Scenarios {
|
||||
if s.matchesTags(scenario.Tags) {
|
||||
var scenarios []interface{}
|
||||
for _, scenario := range ft.ScenarioDefinitions {
|
||||
if s.matchesTags(allTags(ft, scenario)) {
|
||||
scenarios = append(scenarios, scenario)
|
||||
}
|
||||
}
|
||||
ft.Scenarios = scenarios
|
||||
ft.ScenarioDefinitions = scenarios
|
||||
}
|
||||
|
||||
func allTags(nodes ...interface{}) []string {
|
||||
var tags, tmp []string
|
||||
for _, node := range nodes {
|
||||
var gr []*gherkin.Tag
|
||||
switch t := node.(type) {
|
||||
case *gherkin.Feature:
|
||||
gr = t.Tags
|
||||
case *gherkin.ScenarioOutline:
|
||||
gr = t.Tags
|
||||
case *gherkin.Scenario:
|
||||
gr = t.Tags
|
||||
case *gherkin.Examples:
|
||||
gr = t.Tags
|
||||
}
|
||||
|
||||
for _, gtag := range gr {
|
||||
tag := strings.TrimSpace(gtag.Name)
|
||||
if tag[0] == '@' {
|
||||
tag = tag[1:]
|
||||
}
|
||||
copy(tmp, tags)
|
||||
var found bool
|
||||
for _, tg := range tmp {
|
||||
if tg == tag {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func hasTag(tags []string, tag string) bool {
|
||||
for _, t := range tags {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters
|
||||
func (s *suite) matchesTags(tags gherkin.Tags) (ok bool) {
|
||||
func (s *suite) matchesTags(tags []string) (ok bool) {
|
||||
ok = true
|
||||
for _, andTags := range strings.Split(s.tags, "&&") {
|
||||
var okComma bool
|
||||
|
@ -495,9 +584,9 @@ func (s *suite) matchesTags(tags gherkin.Tags) (ok bool) {
|
|||
tag = strings.Replace(strings.TrimSpace(tag), "@", "", -1)
|
||||
if tag[0] == '~' {
|
||||
tag = tag[1:]
|
||||
okComma = !tags.Has(gherkin.Tag(tag)) || okComma
|
||||
okComma = !hasTag(tags, tag) || okComma
|
||||
} else {
|
||||
okComma = tags.Has(gherkin.Tag(tag)) || okComma
|
||||
okComma = hasTag(tags, tag) || okComma
|
||||
}
|
||||
}
|
||||
ok = (false != okComma && ok && okComma) || false
|
||||
|
|
|
@ -4,13 +4,13 @@ import (
|
|||
"fmt"
|
||||
"strings"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
"github.com/cucumber/gherkin-go"
|
||||
)
|
||||
|
||||
func SuiteContext(s Suite) {
|
||||
c := &suiteContext{}
|
||||
|
||||
s.BeforeScenario(c.HandleBeforeScenario)
|
||||
s.BeforeScenario(c.ResetBeforeEachScenario)
|
||||
|
||||
s.Step(`^a feature path "([^"]*)"$`, c.featurePath)
|
||||
s.Step(`^I parse features$`, c.parseFeatures)
|
||||
|
@ -28,6 +28,11 @@ func SuiteContext(s Suite) {
|
|||
s.Step(`^a failing step`, c.aFailingStep)
|
||||
s.Step(`^this step should fail`, c.aFailingStep)
|
||||
s.Step(`^the following steps? should be (passed|failed|skipped|undefined):`, c.followingStepsShouldHave)
|
||||
|
||||
// lt
|
||||
s.Step(`^savybių aplankas "([^"]*)"$`, c.featurePath)
|
||||
s.Step(`^aš išskaitau savybes$`, c.parseFeatures)
|
||||
s.Step(`^aš turėčiau turėti ([\d]+) savybių failus:$`, c.iShouldHaveNumFeatureFiles)
|
||||
}
|
||||
|
||||
type firedEvent struct {
|
||||
|
@ -41,7 +46,7 @@ type suiteContext struct {
|
|||
fmt *testFormatter
|
||||
}
|
||||
|
||||
func (s *suiteContext) HandleBeforeScenario(*gherkin.Scenario) {
|
||||
func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
|
||||
// reset whole suite with the state
|
||||
s.fmt = &testFormatter{}
|
||||
s.testedSuite = &suite{fmt: s.fmt}
|
||||
|
@ -52,7 +57,7 @@ func (s *suiteContext) HandleBeforeScenario(*gherkin.Scenario) {
|
|||
}
|
||||
|
||||
func (s *suiteContext) followingStepsShouldHave(args ...*Arg) error {
|
||||
var expected = args[1].PyString().Lines
|
||||
var expected = strings.Split(args[1].DocString().Content, "\n")
|
||||
var actual, unmatched []string
|
||||
var matched []int
|
||||
|
||||
|
@ -116,10 +121,10 @@ func (s *suiteContext) iAmListeningToSuiteEvents(args ...*Arg) error {
|
|||
s.testedSuite.AfterSuite(func() {
|
||||
s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}})
|
||||
})
|
||||
s.testedSuite.BeforeScenario(func(scenario *gherkin.Scenario) {
|
||||
s.testedSuite.BeforeScenario(func(scenario interface{}) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
|
||||
})
|
||||
s.testedSuite.AfterScenario(func(scenario *gherkin.Scenario, err error) {
|
||||
s.testedSuite.AfterScenario(func(scenario interface{}, err error) {
|
||||
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
|
||||
})
|
||||
s.testedSuite.BeforeStep(func(step *gherkin.Step) {
|
||||
|
@ -138,9 +143,9 @@ func (s *suiteContext) aFailingStep(...*Arg) error {
|
|||
// parse a given feature file body as a feature
|
||||
func (s *suiteContext) aFeatureFile(args ...*Arg) error {
|
||||
name := args[0].String()
|
||||
body := args[1].PyString().Raw
|
||||
feature, err := gherkin.Parse(strings.NewReader(body), name)
|
||||
s.testedSuite.features = append(s.testedSuite.features, feature)
|
||||
body := args[1].DocString().Content
|
||||
ft, err := gherkin.ParseFeature(strings.NewReader(body))
|
||||
s.testedSuite.features = append(s.testedSuite.features, &feature{Feature: ft, Path: name})
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -167,7 +172,7 @@ func (s *suiteContext) iShouldHaveNumFeatureFiles(args ...*Arg) error {
|
|||
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
|
||||
expected := strings.Split(args[1].DocString().Content, "\n")
|
||||
var actual []string
|
||||
for _, ft := range s.testedSuite.features {
|
||||
actual = append(actual, ft.Path)
|
||||
|
@ -194,7 +199,7 @@ func (s *suiteContext) iRunFeatureSuite(args ...*Arg) error {
|
|||
func (s *suiteContext) numScenariosRegistered(args ...*Arg) (err error) {
|
||||
var num int
|
||||
for _, ft := range s.testedSuite.features {
|
||||
num += len(ft.Scenarios)
|
||||
num += len(ft.ScenarioDefinitions)
|
||||
}
|
||||
if num != args[0].Int() {
|
||||
err = fmt.Errorf("expected %d scenarios to be registered, but got %d", args[0].Int(), num)
|
||||
|
@ -222,12 +227,18 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(args ...*Arg) error
|
|||
continue
|
||||
}
|
||||
|
||||
scenario := event.args[0].(*gherkin.Scenario)
|
||||
if scenario.Title == args[0].String() {
|
||||
var name string
|
||||
switch t := event.args[0].(type) {
|
||||
case *gherkin.Scenario:
|
||||
name = t.Name
|
||||
case *gherkin.ScenarioOutline:
|
||||
name = t.Name
|
||||
}
|
||||
if name == args[0].String() {
|
||||
return nil
|
||||
}
|
||||
|
||||
found = append(found, scenario.Title)
|
||||
found = append(found, name)
|
||||
}
|
||||
|
||||
if len(found) == 0 {
|
||||
|
@ -238,16 +249,16 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(args ...*Arg) error
|
|||
}
|
||||
|
||||
func (s *suiteContext) 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]))
|
||||
tbl := args[0].DataTable()
|
||||
if len(tbl.Rows[0].Cells) != 2 {
|
||||
return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0].Cells))
|
||||
}
|
||||
|
||||
for _, row := range tbl.Rows {
|
||||
args := []*Arg{
|
||||
StepArgument(""), // ignored
|
||||
StepArgument(row[1]),
|
||||
StepArgument(row[0]),
|
||||
StepArgument(row.Cells[1].Value),
|
||||
StepArgument(row.Cells[0].Value),
|
||||
}
|
||||
if err := s.thereWereNumEventsFired(args...); err != nil {
|
||||
return err
|
||||
|
|
|
@ -2,29 +2,19 @@ package godog
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
)
|
||||
|
||||
func assertNotMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
||||
gtags := gherkin.Tags{}
|
||||
for _, tag := range tags {
|
||||
gtags = append(gtags, gherkin.Tag(tag))
|
||||
}
|
||||
s := &suite{tags: filter}
|
||||
if s.matchesTags(gtags) {
|
||||
t.Errorf(`expected tags: %v not to match tag filter "%s", but it did`, gtags, filter)
|
||||
if s.matchesTags(tags) {
|
||||
t.Errorf(`expected tags: %v not to match tag filter "%s", but it did`, tags, filter)
|
||||
}
|
||||
}
|
||||
|
||||
func assertMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
||||
gtags := gherkin.Tags{}
|
||||
for _, tag := range tags {
|
||||
gtags = append(gtags, gherkin.Tag(tag))
|
||||
}
|
||||
s := &suite{tags: filter}
|
||||
if !s.matchesTags(gtags) {
|
||||
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, gtags, filter)
|
||||
if !s.matchesTags(tags) {
|
||||
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, tags, filter)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче