Migrated to github.com/cucumber/gherkin-go - v9.2.0
Signed-off-by: Fredrik Lönnblad <fredrik+github@lonnblad.com>
Этот коммит содержится в:
родитель
9a9954aaef
коммит
c2c123f90a
41 изменённых файлов: 1220 добавлений и 13300 удалений
|
@ -23,7 +23,6 @@ commands:
|
||||||
description: "Run go vet"
|
description: "Run go vet"
|
||||||
steps:
|
steps:
|
||||||
- run: go vet github.com/cucumber/godog
|
- run: go vet github.com/cucumber/godog
|
||||||
- run: go vet github.com/cucumber/godog/gherkin
|
|
||||||
- run: go vet github.com/cucumber/godog/colors
|
- run: go vet github.com/cucumber/godog/colors
|
||||||
fmt:
|
fmt:
|
||||||
description: "Run go fmt"
|
description: "Run go fmt"
|
||||||
|
|
|
@ -56,8 +56,8 @@ need to store state within steps (a response), we should introduce a structure w
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
"github.com/cucumber/godog"
|
"github.com/cucumber/godog"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiFeature struct {
|
type apiFeature struct {
|
||||||
|
@ -71,7 +71,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) error {
|
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) error {
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -98,8 +98,8 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
|
||||||
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
"github.com/cucumber/godog"
|
"github.com/cucumber/godog"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiFeature struct {
|
type apiFeature struct {
|
||||||
|
@ -142,7 +142,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err error) {
|
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) error {
|
||||||
var expected, actual []byte
|
var expected, actual []byte
|
||||||
var data interface{}
|
var data interface{}
|
||||||
if err = json.Unmarshal([]byte(body.Content), &data); err != nil {
|
if err = json.Unmarshal([]byte(body.Content), &data); err != nil {
|
||||||
|
|
|
@ -8,14 +8,14 @@ import (
|
||||||
"reflect"
|
"reflect"
|
||||||
|
|
||||||
"github.com/cucumber/godog"
|
"github.com/cucumber/godog"
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
type apiFeature struct {
|
type apiFeature struct {
|
||||||
resp *httptest.ResponseRecorder
|
resp *httptest.ResponseRecorder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiFeature) resetResponse(interface{}) {
|
func (a *apiFeature) resetResponse(*messages.Pickle) {
|
||||||
a.resp = httptest.NewRecorder()
|
a.resp = httptest.NewRecorder()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,7 +51,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err error) {
|
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) (err error) {
|
||||||
var expected, actual interface{}
|
var expected, actual interface{}
|
||||||
|
|
||||||
// re-encode expected response
|
// re-encode expected response
|
||||||
|
|
|
@ -11,7 +11,7 @@ import (
|
||||||
|
|
||||||
txdb "github.com/DATA-DOG/go-txdb"
|
txdb "github.com/DATA-DOG/go-txdb"
|
||||||
"github.com/cucumber/godog"
|
"github.com/cucumber/godog"
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -71,7 +71,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err error) {
|
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) (err error) {
|
||||||
var expected, actual interface{}
|
var expected, actual interface{}
|
||||||
|
|
||||||
// re-encode expected response
|
// re-encode expected response
|
||||||
|
@ -91,7 +91,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (a *apiFeature) thereAreUsers(users *gherkin.DataTable) error {
|
func (a *apiFeature) thereAreUsers(users *messages.PickleStepArgument_PickleTable) error {
|
||||||
var fields []string
|
var fields []string
|
||||||
var marks []string
|
var marks []string
|
||||||
head := users.Rows[0].Cells
|
head := users.Rows[0].Cells
|
||||||
|
|
|
@ -9,6 +9,7 @@ import (
|
||||||
|
|
||||||
"github.com/cucumber/godog"
|
"github.com/cucumber/godog"
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
|
messages "github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var opt = godog.Options{Output: colors.Colored(os.Stdout)}
|
var opt = godog.Options{Output: colors.Colored(os.Stdout)}
|
||||||
|
@ -56,7 +57,7 @@ func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(`^I eat (\d+)$`, iEat)
|
s.Step(`^I eat (\d+)$`, iEat)
|
||||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||||
|
|
||||||
s.BeforeScenario(func(interface{}) {
|
s.BeforeScenario(func(*messages.Pickle) {
|
||||||
Godogs = 0 // clean the state before every scenario
|
Godogs = 0 // clean the state before every scenario
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -181,7 +181,7 @@ func (cw *tagColorWriter) Write(p []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cw.state == outsideCsiCode {
|
if cw.state == outsideCsiCode {
|
||||||
nw, err = cw.w.Write(p[first:len(p)])
|
nw, err = cw.w.Write(p[first:])
|
||||||
r += nw
|
r += nw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -409,7 +409,7 @@ func (cw *ansiColorWriter) Write(p []byte) (int, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if cw.mode != discardNonColorEscSeq || cw.state == outsideCsiCode {
|
if cw.mode != discardNonColorEscSeq || cw.state == outsideCsiCode {
|
||||||
nw, err = cw.w.Write(p[first:len(p)])
|
nw, err = cw.w.Write(p[first:])
|
||||||
r += nw
|
r += nw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -257,7 +257,7 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 7,
|
"line": 7,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:64"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -269,7 +269,7 @@ Feature: cucumber json formatter
|
||||||
"name": "a failing step",
|
"name": "a failing step",
|
||||||
"line": 8,
|
"line": 8,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:47"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
|
@ -323,9 +323,9 @@ Feature: cucumber json formatter
|
||||||
{
|
{
|
||||||
"keyword": "Given ",
|
"keyword": "Given ",
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 11,
|
"line": 7,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:64"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -345,9 +345,9 @@ Feature: cucumber json formatter
|
||||||
{
|
{
|
||||||
"keyword": "Given ",
|
"keyword": "Given ",
|
||||||
"name": "failing step",
|
"name": "failing step",
|
||||||
"line": 12,
|
"line": 7,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:47"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "failed",
|
"status": "failed",
|
||||||
|
@ -448,7 +448,7 @@ Feature: cucumber json formatter
|
||||||
"line": 8
|
"line": 8
|
||||||
},
|
},
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:64"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -501,7 +501,7 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 7,
|
"line": 7,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:64"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "passed",
|
"status": "passed",
|
||||||
|
@ -535,7 +535,7 @@ Feature: cucumber json formatter
|
||||||
"name": "passing step",
|
"name": "passing step",
|
||||||
"line": 10,
|
"line": 10,
|
||||||
"match": {
|
"match": {
|
||||||
"location": "suite_context.go:64"
|
"location": "suite_context.go:0"
|
||||||
},
|
},
|
||||||
"result": {
|
"result": {
|
||||||
"status": "skipped"
|
"status": "skipped"
|
||||||
|
|
|
@ -40,7 +40,7 @@ Feature: load features
|
||||||
| feature | number |
|
| feature | number |
|
||||||
| features/load.feature:3 | 0 |
|
| features/load.feature:3 | 0 |
|
||||||
| features/load.feature:6 | 1 |
|
| features/load.feature:6 | 1 |
|
||||||
| features/load.feature | 4 |
|
| features/load.feature | 6 |
|
||||||
|
|
||||||
Scenario: load a number of feature files
|
Scenario: load a number of feature files
|
||||||
Given a feature path "features/load.feature"
|
Given a feature path "features/load.feature"
|
||||||
|
|
|
@ -48,7 +48,7 @@ Feature: undefined step snippets
|
||||||
When I run feature suite
|
When I run feature suite
|
||||||
Then the undefined step snippets should be:
|
Then the undefined step snippets should be:
|
||||||
"""
|
"""
|
||||||
func iSendRequestToWith(arg1, arg2 string, arg3 *gherkin.DataTable) error {
|
func iSendRequestToWith(arg1, arg2 string, arg3 *messages.PickleStepArgument_PickleTable) error {
|
||||||
return godog.ErrPending
|
return godog.ErrPending
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Различия файлов не показаны, т.к. их слишком много
Показать различия
457
fmt.go
457
fmt.go
|
@ -15,7 +15,8 @@ import (
|
||||||
"unicode"
|
"unicode"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
// some snippet formatting regexps
|
// some snippet formatting regexps
|
||||||
|
@ -43,7 +44,7 @@ var undefinedSnippetsTpl = template.Must(template.New("snippets").Funcs(snippetH
|
||||||
type undefinedSnippet struct {
|
type undefinedSnippet struct {
|
||||||
Method string
|
Method string
|
||||||
Expr string
|
Expr string
|
||||||
argument interface{} // gherkin step argument
|
argument *messages.PickleStepArgument
|
||||||
}
|
}
|
||||||
|
|
||||||
type registeredFormatter struct {
|
type registeredFormatter struct {
|
||||||
|
@ -97,14 +98,14 @@ func AvailableFormatters() map[string]string {
|
||||||
// formatters needs to be registered with a
|
// formatters needs to be registered with a
|
||||||
// godog.Format function call
|
// godog.Format function call
|
||||||
type Formatter interface {
|
type Formatter interface {
|
||||||
Feature(*gherkin.Feature, string, []byte)
|
Feature(*messages.GherkinDocument, string, []byte)
|
||||||
Node(interface{})
|
Pickle(*messages.Pickle)
|
||||||
Defined(*gherkin.Step, *StepDef)
|
Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
|
||||||
Failed(*gherkin.Step, *StepDef, error)
|
Failed(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition, error)
|
||||||
Passed(*gherkin.Step, *StepDef)
|
Passed(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
|
||||||
Skipped(*gherkin.Step, *StepDef)
|
Skipped(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
|
||||||
Undefined(*gherkin.Step, *StepDef)
|
Undefined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
|
||||||
Pending(*gherkin.Step, *StepDef)
|
Pending(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
|
||||||
Summary()
|
Summary()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -120,17 +121,17 @@ type ConcurrentFormatter interface {
|
||||||
// suite name and io.Writer to record output
|
// suite name and io.Writer to record output
|
||||||
type FormatterFunc func(string, io.Writer) Formatter
|
type FormatterFunc func(string, io.Writer) Formatter
|
||||||
|
|
||||||
type stepType int
|
type stepResultStatus int
|
||||||
|
|
||||||
const (
|
const (
|
||||||
passed stepType = iota
|
passed stepResultStatus = iota
|
||||||
failed
|
failed
|
||||||
skipped
|
skipped
|
||||||
undefined
|
undefined
|
||||||
pending
|
pending
|
||||||
)
|
)
|
||||||
|
|
||||||
func (st stepType) clr() colors.ColorFunc {
|
func (st stepResultStatus) clr() colors.ColorFunc {
|
||||||
switch st {
|
switch st {
|
||||||
case passed:
|
case passed:
|
||||||
return green
|
return green
|
||||||
|
@ -143,7 +144,7 @@ func (st stepType) clr() colors.ColorFunc {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (st stepType) String() string {
|
func (st stepResultStatus) String() string {
|
||||||
switch st {
|
switch st {
|
||||||
case passed:
|
case passed:
|
||||||
return "passed"
|
return "passed"
|
||||||
|
@ -161,65 +162,17 @@ func (st stepType) String() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
type stepResult struct {
|
type stepResult struct {
|
||||||
typ stepType
|
status stepResultStatus
|
||||||
feature *feature
|
time time.Time
|
||||||
owner interface{}
|
err error
|
||||||
step *gherkin.Step
|
|
||||||
time time.Time
|
owner *messages.Pickle
|
||||||
def *StepDef
|
step *messages.Pickle_PickleStep
|
||||||
err error
|
def *StepDefinition
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f stepResult) line() string {
|
func newStepResult(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) *stepResult {
|
||||||
return fmt.Sprintf("%s:%d", f.feature.Path, f.step.Location.Line)
|
return &stepResult{time: timeNowFunc(), owner: pickle, step: step, def: match}
|
||||||
}
|
|
||||||
|
|
||||||
func (f stepResult) scenarioDesc() string {
|
|
||||||
if sc, ok := f.owner.(*gherkin.Scenario); ok {
|
|
||||||
return fmt.Sprintf("%s: %s", sc.Keyword, sc.Name)
|
|
||||||
}
|
|
||||||
|
|
||||||
if row, ok := f.owner.(*gherkin.TableRow); ok {
|
|
||||||
for _, def := range f.feature.Feature.ScenarioDefinitions {
|
|
||||||
out, ok := def.(*gherkin.ScenarioOutline)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ex := range out.Examples {
|
|
||||||
for _, rw := range ex.TableBody {
|
|
||||||
if rw.Location.Line == row.Location.Line {
|
|
||||||
return fmt.Sprintf("%s: %s", out.Keyword, out.Name)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.line() // was not expecting different owner
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f stepResult) scenarioLine() string {
|
|
||||||
if sc, ok := f.owner.(*gherkin.Scenario); ok {
|
|
||||||
return fmt.Sprintf("%s:%d", f.feature.Path, sc.Location.Line)
|
|
||||||
}
|
|
||||||
|
|
||||||
if row, ok := f.owner.(*gherkin.TableRow); ok {
|
|
||||||
for _, def := range f.feature.Feature.ScenarioDefinitions {
|
|
||||||
out, ok := def.(*gherkin.ScenarioOutline)
|
|
||||||
if !ok {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, ex := range out.Examples {
|
|
||||||
for _, rw := range ex.TableBody {
|
|
||||||
if rw.Location.Line == row.Location.Line {
|
|
||||||
return fmt.Sprintf("%s:%d", f.feature.Path, out.Location.Line)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return f.line() // was not expecting different owner
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func newBaseFmt(suite string, out io.Writer) *basefmt {
|
func newBaseFmt(suite string, out io.Writer) *basefmt {
|
||||||
|
@ -239,217 +192,205 @@ type basefmt struct {
|
||||||
owner interface{}
|
owner interface{}
|
||||||
indent int
|
indent int
|
||||||
|
|
||||||
started time.Time
|
started time.Time
|
||||||
features []*feature
|
features []*feature
|
||||||
failed []*stepResult
|
|
||||||
passed []*stepResult
|
|
||||||
skipped []*stepResult
|
|
||||||
undefined []*stepResult
|
|
||||||
pending []*stepResult
|
|
||||||
|
|
||||||
lock *sync.Mutex
|
lock *sync.Mutex
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Node(n interface{}) {
|
func (f *basefmt) lastFeature() *feature {
|
||||||
f.lock.Lock()
|
return f.features[len(f.features)-1]
|
||||||
defer f.lock.Unlock()
|
}
|
||||||
|
|
||||||
switch t := n.(type) {
|
func (f *basefmt) lastStepResult() *stepResult {
|
||||||
case *gherkin.Scenario:
|
return f.lastFeature().lastStepResult()
|
||||||
f.owner = t
|
}
|
||||||
feature := f.features[len(f.features)-1]
|
|
||||||
feature.Scenarios = append(feature.Scenarios, &scenario{Name: t.Name, time: timeNowFunc()})
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
feature := f.features[len(f.features)-1]
|
|
||||||
feature.Scenarios = append(feature.Scenarios, &scenario{OutlineName: t.Name})
|
|
||||||
case *gherkin.TableRow:
|
|
||||||
f.owner = t
|
|
||||||
|
|
||||||
feature := f.features[len(f.features)-1]
|
func (f *basefmt) findScenario(scenarioAstID string) *messages.GherkinDocument_Feature_Scenario {
|
||||||
lastExample := feature.Scenarios[len(feature.Scenarios)-1]
|
for _, ft := range f.features {
|
||||||
|
if sc := ft.findScenario(scenarioAstID); sc != nil {
|
||||||
newExample := scenario{OutlineName: lastExample.OutlineName, ExampleNo: lastExample.ExampleNo + 1, time: timeNowFunc()}
|
return sc
|
||||||
newExample.Name = fmt.Sprintf("%s #%d", newExample.OutlineName, newExample.ExampleNo)
|
|
||||||
|
|
||||||
const firstExample = 1
|
|
||||||
if newExample.ExampleNo == firstExample {
|
|
||||||
feature.Scenarios[len(feature.Scenarios)-1] = &newExample
|
|
||||||
} else {
|
|
||||||
feature.Scenarios = append(feature.Scenarios, &newExample)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Defined(*gherkin.Step, *StepDef) {
|
func (f *basefmt) findBackground(scenarioAstID string) *messages.GherkinDocument_Feature_Background {
|
||||||
f.lock.Lock()
|
for _, ft := range f.features {
|
||||||
defer f.lock.Unlock()
|
if bg := ft.findBackground(scenarioAstID); bg != nil {
|
||||||
}
|
return bg
|
||||||
|
}
|
||||||
func (f *basefmt) Feature(ft *gherkin.Feature, p string, c []byte) {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
|
|
||||||
f.features = append(f.features, &feature{Path: p, Feature: ft, time: timeNowFunc()})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *basefmt) Passed(step *gherkin.Step, match *StepDef) {
|
|
||||||
f.lock.Lock()
|
|
||||||
defer f.lock.Unlock()
|
|
||||||
|
|
||||||
s := &stepResult{
|
|
||||||
owner: f.owner,
|
|
||||||
feature: f.features[len(f.features)-1],
|
|
||||||
step: step,
|
|
||||||
def: match,
|
|
||||||
typ: passed,
|
|
||||||
time: timeNowFunc(),
|
|
||||||
}
|
}
|
||||||
f.passed = append(f.passed, s)
|
|
||||||
|
|
||||||
f.features[len(f.features)-1].appendStepResult(s)
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Skipped(step *gherkin.Step, match *StepDef) {
|
func (f *basefmt) findExample(exampleAstID string) (*messages.GherkinDocument_Feature_Scenario_Examples, *messages.GherkinDocument_Feature_TableRow) {
|
||||||
|
for _, ft := range f.features {
|
||||||
|
if es, rs := ft.findExample(exampleAstID); es != nil && rs != nil {
|
||||||
|
return es, rs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) findStep(stepAstID string) *messages.GherkinDocument_Feature_Step {
|
||||||
|
for _, ft := range f.features {
|
||||||
|
if st := ft.findStep(stepAstID); st != nil {
|
||||||
|
return st
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) Pickle(p *messages.Pickle) {
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
s := &stepResult{
|
feature := f.features[len(f.features)-1]
|
||||||
owner: f.owner,
|
feature.pickleResults = append(feature.pickleResults, &pickleResult{Name: p.Name, time: timeNowFunc()})
|
||||||
feature: f.features[len(f.features)-1],
|
|
||||||
step: step,
|
|
||||||
def: match,
|
|
||||||
typ: skipped,
|
|
||||||
time: timeNowFunc(),
|
|
||||||
}
|
|
||||||
f.skipped = append(f.skipped, s)
|
|
||||||
|
|
||||||
f.features[len(f.features)-1].appendStepResult(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Undefined(step *gherkin.Step, match *StepDef) {
|
func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {}
|
||||||
|
|
||||||
|
func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) {
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
s := &stepResult{
|
f.features = append(f.features, &feature{Path: p, GherkinDocument: ft, time: timeNowFunc()})
|
||||||
owner: f.owner,
|
|
||||||
feature: f.features[len(f.features)-1],
|
|
||||||
step: step,
|
|
||||||
def: match,
|
|
||||||
typ: undefined,
|
|
||||||
time: timeNowFunc(),
|
|
||||||
}
|
|
||||||
f.undefined = append(f.undefined, s)
|
|
||||||
|
|
||||||
f.features[len(f.features)-1].appendStepResult(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Failed(step *gherkin.Step, match *StepDef, err error) {
|
func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
s := &stepResult{
|
s := newStepResult(pickle, step, match)
|
||||||
owner: f.owner,
|
s.status = passed
|
||||||
feature: f.features[len(f.features)-1],
|
f.lastFeature().appendStepResult(s)
|
||||||
step: step,
|
|
||||||
def: match,
|
|
||||||
err: err,
|
|
||||||
typ: failed,
|
|
||||||
time: timeNowFunc(),
|
|
||||||
}
|
|
||||||
f.failed = append(f.failed, s)
|
|
||||||
|
|
||||||
f.features[len(f.features)-1].appendStepResult(s)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Pending(step *gherkin.Step, match *StepDef) {
|
func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
s := &stepResult{
|
s := newStepResult(pickle, step, match)
|
||||||
owner: f.owner,
|
s.status = skipped
|
||||||
feature: f.features[len(f.features)-1],
|
f.lastFeature().appendStepResult(s)
|
||||||
step: step,
|
}
|
||||||
def: match,
|
|
||||||
typ: pending,
|
|
||||||
time: timeNowFunc(),
|
|
||||||
}
|
|
||||||
f.pending = append(f.pending, s)
|
|
||||||
|
|
||||||
f.features[len(f.features)-1].appendStepResult(s)
|
func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
s := newStepResult(pickle, step, match)
|
||||||
|
s.status = undefined
|
||||||
|
f.lastFeature().appendStepResult(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
s := newStepResult(pickle, step, match)
|
||||||
|
s.status = failed
|
||||||
|
s.err = err
|
||||||
|
f.lastFeature().appendStepResult(s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
|
f.lock.Lock()
|
||||||
|
defer f.lock.Unlock()
|
||||||
|
|
||||||
|
s := newStepResult(pickle, step, match)
|
||||||
|
s.status = pending
|
||||||
|
f.lastFeature().appendStepResult(s)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) Summary() {
|
func (f *basefmt) Summary() {
|
||||||
var total, passed, undefined int
|
var totalSc, passedSc, undefinedSc int
|
||||||
for _, ft := range f.features {
|
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int
|
||||||
for _, def := range ft.ScenarioDefinitions {
|
|
||||||
switch t := def.(type) {
|
for _, feat := range f.features {
|
||||||
case *gherkin.Scenario:
|
for _, pr := range feat.pickleResults {
|
||||||
total++
|
var prStatus stepResultStatus
|
||||||
if len(t.Steps) == 0 {
|
totalSc++
|
||||||
undefined++
|
|
||||||
}
|
if len(pr.stepResults) == 0 {
|
||||||
case *gherkin.ScenarioOutline:
|
prStatus = undefined
|
||||||
for _, ex := range t.Examples {
|
}
|
||||||
total += len(ex.TableBody)
|
|
||||||
if len(t.Steps) == 0 {
|
for _, sr := range pr.stepResults {
|
||||||
undefined += len(ex.TableBody)
|
totalSt++
|
||||||
}
|
|
||||||
|
switch sr.status {
|
||||||
|
case passed:
|
||||||
|
prStatus = passed
|
||||||
|
passedSt++
|
||||||
|
case failed:
|
||||||
|
prStatus = failed
|
||||||
|
failedSt++
|
||||||
|
case skipped:
|
||||||
|
skippedSt++
|
||||||
|
case undefined:
|
||||||
|
prStatus = undefined
|
||||||
|
undefinedSt++
|
||||||
|
case pending:
|
||||||
|
prStatus = pending
|
||||||
|
pendingSt++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
if prStatus == passed {
|
||||||
passed = total - undefined
|
passedSc++
|
||||||
var owner interface{}
|
} else if prStatus == undefined {
|
||||||
for _, undef := range f.undefined {
|
undefinedSc++
|
||||||
if owner != undef.owner {
|
}
|
||||||
undefined++
|
|
||||||
owner = undef.owner
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var steps, parts, scenarios []string
|
var steps, parts, scenarios []string
|
||||||
nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending)
|
if passedSt > 0 {
|
||||||
if len(f.passed) > 0 {
|
steps = append(steps, green(fmt.Sprintf("%d passed", passedSt)))
|
||||||
steps = append(steps, green(fmt.Sprintf("%d passed", len(f.passed))))
|
|
||||||
}
|
}
|
||||||
if len(f.failed) > 0 {
|
if failedSt > 0 {
|
||||||
passed -= len(f.failed)
|
parts = append(parts, red(fmt.Sprintf("%d failed", failedSt)))
|
||||||
parts = append(parts, red(fmt.Sprintf("%d failed", len(f.failed))))
|
steps = append(steps, red(fmt.Sprintf("%d failed", failedSt)))
|
||||||
steps = append(steps, parts[len(parts)-1])
|
|
||||||
}
|
}
|
||||||
if len(f.pending) > 0 {
|
if pendingSt > 0 {
|
||||||
passed -= len(f.pending)
|
parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt)))
|
||||||
parts = append(parts, yellow(fmt.Sprintf("%d pending", len(f.pending))))
|
steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt)))
|
||||||
steps = append(steps, yellow(fmt.Sprintf("%d pending", len(f.pending))))
|
|
||||||
}
|
}
|
||||||
if len(f.undefined) > 0 {
|
if undefinedSt > 0 {
|
||||||
passed -= undefined
|
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
|
||||||
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefined)))
|
steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt)))
|
||||||
steps = append(steps, yellow(fmt.Sprintf("%d undefined", len(f.undefined))))
|
} else if undefinedSc > 0 {
|
||||||
} else if undefined > 0 {
|
|
||||||
// there may be some scenarios without steps
|
// there may be some scenarios without steps
|
||||||
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefined)))
|
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
|
||||||
}
|
}
|
||||||
if len(f.skipped) > 0 {
|
if skippedSt > 0 {
|
||||||
steps = append(steps, cyan(fmt.Sprintf("%d skipped", len(f.skipped))))
|
steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt)))
|
||||||
}
|
}
|
||||||
if passed > 0 {
|
if passedSc > 0 {
|
||||||
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passed)))
|
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc)))
|
||||||
}
|
}
|
||||||
scenarios = append(scenarios, parts...)
|
scenarios = append(scenarios, parts...)
|
||||||
elapsed := timeNowFunc().Sub(f.started)
|
elapsed := timeNowFunc().Sub(f.started)
|
||||||
|
|
||||||
fmt.Fprintln(f.out, "")
|
fmt.Fprintln(f.out, "")
|
||||||
if total == 0 {
|
|
||||||
|
if totalSc == 0 {
|
||||||
fmt.Fprintln(f.out, "No scenarios")
|
fmt.Fprintln(f.out, "No scenarios")
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", total, strings.Join(scenarios, ", ")))
|
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", totalSc, strings.Join(scenarios, ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
if nsteps == 0 {
|
if totalSt == 0 {
|
||||||
fmt.Fprintln(f.out, "No steps")
|
fmt.Fprintln(f.out, "No steps")
|
||||||
} else {
|
} else {
|
||||||
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", totalSt, strings.Join(steps, ", ")))
|
||||||
}
|
}
|
||||||
|
|
||||||
elapsedString := elapsed.String()
|
elapsedString := elapsed.String()
|
||||||
|
@ -484,21 +425,6 @@ func (f *basefmt) Copy(cf ConcurrentFormatter) {
|
||||||
for _, v := range source.features {
|
for _, v := range source.features {
|
||||||
f.features = append(f.features, v)
|
f.features = append(f.features, v)
|
||||||
}
|
}
|
||||||
for _, v := range source.failed {
|
|
||||||
f.failed = append(f.failed, v)
|
|
||||||
}
|
|
||||||
for _, v := range source.passed {
|
|
||||||
f.passed = append(f.passed, v)
|
|
||||||
}
|
|
||||||
for _, v := range source.skipped {
|
|
||||||
f.skipped = append(f.skipped, v)
|
|
||||||
}
|
|
||||||
for _, v := range source.undefined {
|
|
||||||
f.undefined = append(f.undefined, v)
|
|
||||||
}
|
|
||||||
for _, v := range source.pending {
|
|
||||||
f.pending = append(f.pending, v)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -529,12 +455,13 @@ func (s *undefinedSnippet) Args() (ret string) {
|
||||||
args = append(args, reflect.String.String())
|
args = append(args, reflect.String.String())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.argument != nil {
|
if s.argument != nil {
|
||||||
switch s.argument.(type) {
|
if s.argument.GetDocString() != nil {
|
||||||
case *gherkin.DocString:
|
args = append(args, "*messages.PickleStepArgument_PickleDocString")
|
||||||
args = append(args, "*gherkin.DocString")
|
}
|
||||||
case *gherkin.DataTable:
|
if s.argument.GetDataTable() != nil {
|
||||||
args = append(args, "*gherkin.DataTable")
|
args = append(args, "*messages.PickleStepArgument_PickleTable")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -550,15 +477,30 @@ func (s *undefinedSnippet) Args() (ret string) {
|
||||||
return strings.TrimSpace(strings.TrimRight(ret, ", ") + " " + last)
|
return strings.TrimSpace(strings.TrimRight(ret, ", ") + " " + last)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f *basefmt) findStepResults(status stepResultStatus) (res []*stepResult) {
|
||||||
|
for _, feat := range f.features {
|
||||||
|
for _, pr := range feat.pickleResults {
|
||||||
|
for _, sr := range pr.stepResults {
|
||||||
|
if sr.status == status {
|
||||||
|
res = append(res, sr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
func (f *basefmt) snippets() string {
|
func (f *basefmt) snippets() string {
|
||||||
if len(f.undefined) == 0 {
|
undefinedStepResults := f.findStepResults(undefined)
|
||||||
|
if len(undefinedStepResults) == 0 {
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
var index int
|
var index int
|
||||||
var snips []*undefinedSnippet
|
var snips []*undefinedSnippet
|
||||||
// build snippets
|
// build snippets
|
||||||
for _, u := range f.undefined {
|
for _, u := range undefinedStepResults {
|
||||||
steps := []string{u.step.Text}
|
steps := []string{u.step.Text}
|
||||||
arg := u.step.Argument
|
arg := u.step.Argument
|
||||||
if u.def != nil {
|
if u.def != nil {
|
||||||
|
@ -587,7 +529,7 @@ func (f *basefmt) snippets() string {
|
||||||
name = strings.Join(words, "")
|
name = strings.Join(words, "")
|
||||||
if len(name) == 0 {
|
if len(name) == 0 {
|
||||||
index++
|
index++
|
||||||
name = fmt.Sprintf("stepDefinition%d", index)
|
name = fmt.Sprintf("StepDefinitioninition%d", index)
|
||||||
}
|
}
|
||||||
|
|
||||||
var found bool
|
var found bool
|
||||||
|
@ -611,25 +553,6 @@ func (f *basefmt) snippets() string {
|
||||||
return strings.Replace(buf.String(), " \n", "\n", -1)
|
return strings.Replace(buf.String(), " \n", "\n", -1)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *basefmt) isLastStep(s *gherkin.Step) bool {
|
func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool {
|
||||||
ft := f.features[len(f.features)-1]
|
return pickle.Steps[len(pickle.Steps)-1].Id == step.Id
|
||||||
|
|
||||||
for _, def := range ft.ScenarioDefinitions {
|
|
||||||
if outline, ok := def.(*gherkin.ScenarioOutline); ok {
|
|
||||||
for n, step := range outline.Steps {
|
|
||||||
if step.Location.Line == s.Location.Line {
|
|
||||||
return n == len(outline.Steps)-1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if scenario, ok := def.(*gherkin.Scenario); ok {
|
|
||||||
for n, step := range scenario.Steps {
|
|
||||||
if step.Location.Line == s.Location.Line {
|
|
||||||
return n == len(scenario.Steps)-1
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
237
fmt_cucumber.go
237
fmt_cucumber.go
|
@ -15,11 +15,10 @@ import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -107,7 +106,7 @@ type cukefmt struct {
|
||||||
// it restricts this formatter to run only in synchronous single
|
// it restricts this formatter to run only in synchronous single
|
||||||
// threaded execution. Unless running a copy of formatter for each feature
|
// threaded execution. Unless running a copy of formatter for each feature
|
||||||
path string
|
path string
|
||||||
stat stepType // last step status, before skipped
|
status stepResultStatus // last step status, before skipped
|
||||||
ID string // current test id.
|
ID string // current test id.
|
||||||
results []cukeFeatureJSON // structure that represent cuke results
|
results []cukeFeatureJSON // structure that represent cuke results
|
||||||
curStep *cukeStep // track the current step
|
curStep *cukeStep // track the current step
|
||||||
|
@ -122,115 +121,80 @@ type cukefmt struct {
|
||||||
// of the example name inorder to build id fields.
|
// of the example name inorder to build id fields.
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Node(n interface{}) {
|
func (f *cukefmt) Pickle(pickle *messages.Pickle) {
|
||||||
f.basefmt.Node(n)
|
f.basefmt.Pickle(pickle)
|
||||||
|
|
||||||
switch t := n.(type) {
|
scenario := f.findScenario(pickle.AstNodeIds[0])
|
||||||
|
|
||||||
// When the example definition is seen we just need track the id and
|
f.curFeature.Elements = append(f.curFeature.Elements, cukeElement{})
|
||||||
// append the name associated with the example as part of the id.
|
f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements)-1]
|
||||||
case *gherkin.Examples:
|
|
||||||
|
|
||||||
f.curExampleName = makeID(t.Name)
|
f.curElement.Name = pickle.Name
|
||||||
f.curRow = 2 // there can be more than one example set per outline so reset row count.
|
f.curElement.Line = int(scenario.Location.Line)
|
||||||
// cucumber counts the header row as an example when creating the id.
|
f.curElement.Description = scenario.Description
|
||||||
|
f.curElement.Keyword = scenario.Keyword
|
||||||
// store any example level tags in a temp location.
|
f.curElement.ID = f.curFeature.ID + ";" + makeID(pickle.Name)
|
||||||
f.curExampleTags = make([]cukeTag, len(t.Tags))
|
f.curElement.Type = "scenario"
|
||||||
for idx, element := range t.Tags {
|
f.curElement.Tags = make([]cukeTag, len(scenario.Tags)+len(f.curFeature.Tags))
|
||||||
f.curExampleTags[idx].Line = element.Location.Line
|
|
||||||
f.curExampleTags[idx].Name = element.Name
|
|
||||||
}
|
|
||||||
|
|
||||||
// The outline node creates a placeholder and the actual element is added as each TableRow is processed.
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
|
|
||||||
f.curOutline = cukeElement{}
|
|
||||||
f.curOutline.Name = t.Name
|
|
||||||
f.curOutline.Line = t.Location.Line
|
|
||||||
f.curOutline.Description = t.Description
|
|
||||||
f.curOutline.Keyword = t.Keyword
|
|
||||||
f.curOutline.ID = f.curFeature.ID + ";" + makeID(t.Name)
|
|
||||||
f.curOutline.Type = "scenario"
|
|
||||||
f.curOutline.Tags = make([]cukeTag, len(t.Tags)+len(f.curFeature.Tags))
|
|
||||||
|
|
||||||
|
if len(f.curElement.Tags) > 0 {
|
||||||
// apply feature level tags
|
// apply feature level tags
|
||||||
if len(f.curOutline.Tags) > 0 {
|
copy(f.curElement.Tags, f.curFeature.Tags)
|
||||||
copy(f.curOutline.Tags, f.curFeature.Tags)
|
|
||||||
|
|
||||||
// apply outline level tags.
|
// apply scenario level tags.
|
||||||
for idx, element := range t.Tags {
|
for idx, element := range scenario.Tags {
|
||||||
f.curOutline.Tags[idx+len(f.curFeature.Tags)].Line = element.Location.Line
|
f.curElement.Tags[idx+len(f.curFeature.Tags)].Line = int(element.Location.Line)
|
||||||
f.curOutline.Tags[idx+len(f.curFeature.Tags)].Name = element.Name
|
f.curElement.Tags[idx+len(f.curFeature.Tags)].Name = element.Name
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
examples := scenario.GetExamples()
|
||||||
|
if len(examples) > 0 {
|
||||||
|
rowID := pickle.AstNodeIds[1]
|
||||||
|
|
||||||
|
for _, example := range examples {
|
||||||
|
// apply example level tags.
|
||||||
|
for _, element := range example.Tags {
|
||||||
|
tag := cukeTag{Line: int(element.Location.Line), Name: element.Name}
|
||||||
|
f.curElement.Tags = append(f.curElement.Tags, tag)
|
||||||
|
}
|
||||||
|
|
||||||
|
for idx, row := range example.TableBody {
|
||||||
|
if rowID == row.Id {
|
||||||
|
f.curElement.ID += fmt.Sprintf(";%s;%d", makeID(example.Name), idx+2)
|
||||||
|
f.curElement.Line = int(row.Location.Line)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// This scenario adds the element to the output immediately.
|
|
||||||
case *gherkin.Scenario:
|
|
||||||
f.curFeature.Elements = append(f.curFeature.Elements, cukeElement{})
|
|
||||||
f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements)-1]
|
|
||||||
|
|
||||||
f.curElement.Name = t.Name
|
|
||||||
f.curElement.Line = t.Location.Line
|
|
||||||
f.curElement.Description = t.Description
|
|
||||||
f.curElement.Keyword = t.Keyword
|
|
||||||
f.curElement.ID = f.curFeature.ID + ";" + makeID(t.Name)
|
|
||||||
f.curElement.Type = "scenario"
|
|
||||||
f.curElement.Tags = make([]cukeTag, len(t.Tags)+len(f.curFeature.Tags))
|
|
||||||
|
|
||||||
if len(f.curElement.Tags) > 0 {
|
|
||||||
// apply feature level tags
|
|
||||||
copy(f.curElement.Tags, f.curFeature.Tags)
|
|
||||||
|
|
||||||
// apply scenario level tags.
|
|
||||||
for idx, element := range t.Tags {
|
|
||||||
f.curElement.Tags[idx+len(f.curFeature.Tags)].Line = element.Location.Line
|
|
||||||
f.curElement.Tags[idx+len(f.curFeature.Tags)].Name = element.Name
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// This is an outline scenario and the element is added to the output as
|
|
||||||
// the TableRows are encountered.
|
|
||||||
case *gherkin.TableRow:
|
|
||||||
tmpElem := f.curOutline
|
|
||||||
tmpElem.Line = t.Location.Line
|
|
||||||
tmpElem.ID = tmpElem.ID + ";" + f.curExampleName + ";" + strconv.Itoa(f.curRow)
|
|
||||||
f.curRow++
|
|
||||||
f.curFeature.Elements = append(f.curFeature.Elements, tmpElem)
|
|
||||||
f.curElement = &f.curFeature.Elements[len(f.curFeature.Elements)-1]
|
|
||||||
|
|
||||||
// copy in example level tags.
|
|
||||||
f.curElement.Tags = append(f.curElement.Tags, f.curExampleTags...)
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Feature(ft *gherkin.Feature, p string, c []byte) {
|
func (f *cukefmt) Feature(gd *messages.GherkinDocument, p string, c []byte) {
|
||||||
|
f.basefmt.Feature(gd, p, c)
|
||||||
|
|
||||||
f.basefmt.Feature(ft, p, c)
|
|
||||||
f.path = p
|
f.path = p
|
||||||
f.ID = makeID(ft.Name)
|
f.ID = makeID(gd.Feature.Name)
|
||||||
f.results = append(f.results, cukeFeatureJSON{})
|
f.results = append(f.results, cukeFeatureJSON{})
|
||||||
|
|
||||||
f.curFeature = &f.results[len(f.results)-1]
|
f.curFeature = &f.results[len(f.results)-1]
|
||||||
f.curFeature.URI = p
|
f.curFeature.URI = p
|
||||||
f.curFeature.Name = ft.Name
|
f.curFeature.Name = gd.Feature.Name
|
||||||
f.curFeature.Keyword = ft.Keyword
|
f.curFeature.Keyword = gd.Feature.Keyword
|
||||||
f.curFeature.Line = ft.Location.Line
|
f.curFeature.Line = int(gd.Feature.Location.Line)
|
||||||
f.curFeature.Description = ft.Description
|
f.curFeature.Description = gd.Feature.Description
|
||||||
f.curFeature.ID = f.ID
|
f.curFeature.ID = f.ID
|
||||||
f.curFeature.Tags = make([]cukeTag, len(ft.Tags))
|
f.curFeature.Tags = make([]cukeTag, len(gd.Feature.Tags))
|
||||||
|
|
||||||
for idx, element := range ft.Tags {
|
for idx, element := range gd.Feature.Tags {
|
||||||
f.curFeature.Tags[idx].Line = element.Location.Line
|
f.curFeature.Tags[idx].Line = int(element.Location.Line)
|
||||||
f.curFeature.Tags[idx].Name = element.Name
|
f.curFeature.Tags[idx].Name = element.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
f.curFeature.Comments = make([]cukeComment, len(ft.Comments))
|
f.curFeature.Comments = make([]cukeComment, len(gd.Comments))
|
||||||
for idx, comment := range ft.Comments {
|
for idx, comment := range gd.Comments {
|
||||||
f.curFeature.Comments[idx].Value = strings.TrimSpace(comment.Text)
|
f.curFeature.Comments[idx].Value = strings.TrimSpace(comment.Text)
|
||||||
f.curFeature.Comments[idx].Line = comment.Location.Line
|
f.curFeature.Comments[idx].Line = int(comment.Location.Line)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -244,49 +208,37 @@ func (f *cukefmt) Summary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) step(res *stepResult) {
|
func (f *cukefmt) step(res *stepResult) {
|
||||||
|
d := int(timeNowFunc().Sub(f.startTime).Nanoseconds())
|
||||||
// determine if test case has finished
|
f.curStep.Result.Duration = &d
|
||||||
switch t := f.owner.(type) {
|
f.curStep.Result.Status = res.status.String()
|
||||||
case *gherkin.TableRow:
|
if res.err != nil {
|
||||||
d := int(timeNowFunc().Sub(f.startTime).Nanoseconds())
|
f.curStep.Result.Error = res.err.Error()
|
||||||
f.curStep.Result.Duration = &d
|
|
||||||
f.curStep.Line = t.Location.Line
|
|
||||||
f.curStep.Result.Status = res.typ.String()
|
|
||||||
if res.err != nil {
|
|
||||||
f.curStep.Result.Error = res.err.Error()
|
|
||||||
}
|
|
||||||
case *gherkin.Scenario:
|
|
||||||
d := int(timeNowFunc().Sub(f.startTime).Nanoseconds())
|
|
||||||
f.curStep.Result.Duration = &d
|
|
||||||
f.curStep.Result.Status = res.typ.String()
|
|
||||||
if res.err != nil {
|
|
||||||
f.curStep.Result.Error = res.err.Error()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) {
|
func (f *cukefmt) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep, def *StepDefinition) {
|
||||||
|
|
||||||
f.startTime = timeNowFunc() // start timing the step
|
f.startTime = timeNowFunc() // start timing the step
|
||||||
f.curElement.Steps = append(f.curElement.Steps, cukeStep{})
|
f.curElement.Steps = append(f.curElement.Steps, cukeStep{})
|
||||||
f.curStep = &f.curElement.Steps[len(f.curElement.Steps)-1]
|
f.curStep = &f.curElement.Steps[len(f.curElement.Steps)-1]
|
||||||
|
|
||||||
f.curStep.Name = step.Text
|
step := f.findStep(pickleStep.AstNodeIds[0])
|
||||||
f.curStep.Line = step.Location.Line
|
|
||||||
|
f.curStep.Name = pickleStep.Text
|
||||||
|
f.curStep.Line = int(step.Location.Line)
|
||||||
f.curStep.Keyword = step.Keyword
|
f.curStep.Keyword = step.Keyword
|
||||||
|
|
||||||
if _, ok := step.Argument.(*gherkin.DocString); ok {
|
arg := pickleStep.Argument
|
||||||
|
|
||||||
|
if arg.GetDocString() != nil && step.GetDocString() != nil {
|
||||||
f.curStep.Docstring = &cukeDocstring{}
|
f.curStep.Docstring = &cukeDocstring{}
|
||||||
f.curStep.Docstring.ContentType = strings.TrimSpace(step.Argument.(*gherkin.DocString).ContentType)
|
f.curStep.Docstring.ContentType = strings.TrimSpace(arg.GetDocString().MediaType)
|
||||||
f.curStep.Docstring.Line = step.Argument.(*gherkin.DocString).Location.Line
|
f.curStep.Docstring.Line = int(step.GetDocString().Location.Line)
|
||||||
f.curStep.Docstring.Value = step.Argument.(*gherkin.DocString).Content
|
f.curStep.Docstring.Value = arg.GetDocString().Content
|
||||||
}
|
}
|
||||||
|
|
||||||
if _, ok := step.Argument.(*gherkin.DataTable); ok {
|
if arg.GetDataTable() != nil {
|
||||||
dataTable := step.Argument.(*gherkin.DataTable)
|
f.curStep.DataTable = make([]*cukeDataTableRow, len(arg.GetDataTable().Rows))
|
||||||
|
for i, row := range arg.GetDataTable().Rows {
|
||||||
f.curStep.DataTable = make([]*cukeDataTableRow, len(dataTable.Rows))
|
|
||||||
for i, row := range dataTable.Rows {
|
|
||||||
cells := make([]string, len(row.Cells))
|
cells := make([]string, len(row.Cells))
|
||||||
for j, cell := range row.Cells {
|
for j, cell := range row.Cells {
|
||||||
cells[j] = cell.Value
|
cells[j] = cell.Value
|
||||||
|
@ -300,42 +252,47 @@ func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Passed(step *gherkin.Step, match *StepDef) {
|
func (f *cukefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Passed(step, match)
|
f.basefmt.Passed(pickle, step, match)
|
||||||
f.stat = passed
|
|
||||||
f.step(f.passed[len(f.passed)-1])
|
f.status = passed
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Skipped(step *gherkin.Step, match *StepDef) {
|
func (f *cukefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Skipped(step, match)
|
f.basefmt.Skipped(pickle, step, match)
|
||||||
f.step(f.skipped[len(f.skipped)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
|
|
||||||
// no duration reported for skipped.
|
// no duration reported for skipped.
|
||||||
f.curStep.Result.Duration = nil
|
f.curStep.Result.Duration = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Undefined(step *gherkin.Step, match *StepDef) {
|
func (f *cukefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Undefined(step, match)
|
f.basefmt.Undefined(pickle, step, match)
|
||||||
f.stat = undefined
|
|
||||||
f.step(f.undefined[len(f.undefined)-1])
|
f.status = undefined
|
||||||
|
f.step(f.lastStepResult())
|
||||||
|
|
||||||
// the location for undefined is the feature file location not the step file.
|
// the location for undefined is the feature file location not the step file.
|
||||||
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, step.Location.Line)
|
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, f.findStep(step.AstNodeIds[0]).Location.Line)
|
||||||
f.curStep.Result.Duration = nil
|
f.curStep.Result.Duration = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Failed(step *gherkin.Step, match *StepDef, err error) {
|
func (f *cukefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||||
f.basefmt.Failed(step, match, err)
|
f.basefmt.Failed(pickle, step, match, err)
|
||||||
f.stat = failed
|
|
||||||
f.step(f.failed[len(f.failed)-1])
|
f.status = failed
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *cukefmt) Pending(step *gherkin.Step, match *StepDef) {
|
func (f *cukefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.stat = pending
|
f.basefmt.Pending(pickle, step, match)
|
||||||
f.basefmt.Pending(step, match)
|
|
||||||
f.step(f.pending[len(f.pending)-1])
|
f.status = pending
|
||||||
|
f.step(f.lastStepResult())
|
||||||
|
|
||||||
// the location for pending is the feature file location not the step file.
|
// the location for pending is the feature file location not the step file.
|
||||||
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, step.Location.Line)
|
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, f.findStep(step.AstNodeIds[0]).Location.Line)
|
||||||
f.curStep.Result.Duration = nil
|
f.curStep.Result.Duration = nil
|
||||||
}
|
}
|
||||||
|
|
110
fmt_events.go
110
fmt_events.go
|
@ -5,7 +5,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
const nanoSec = 1000000
|
const nanoSec = 1000000
|
||||||
|
@ -41,8 +41,8 @@ type events struct {
|
||||||
// it restricts this formatter to run only in synchronous single
|
// it restricts this formatter to run only in synchronous single
|
||||||
// threaded execution. Unless running a copy of formatter for each feature
|
// threaded execution. Unless running a copy of formatter for each feature
|
||||||
path string
|
path string
|
||||||
stat stepType // last step status, before skipped
|
status stepResultStatus // last step status, before skipped
|
||||||
outlineSteps int // number of current outline scenario steps
|
outlineSteps int // number of current outline scenario steps
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) event(ev interface{}) {
|
func (f *events) event(ev interface{}) {
|
||||||
|
@ -53,25 +53,12 @@ func (f *events) event(ev interface{}) {
|
||||||
fmt.Fprintln(f.out, string(data))
|
fmt.Fprintln(f.out, string(data))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Node(n interface{}) {
|
func (f *events) Pickle(pickle *messages.Pickle) {
|
||||||
f.basefmt.Node(n)
|
f.basefmt.Pickle(pickle)
|
||||||
|
|
||||||
var id string
|
scenario := f.findScenario(pickle.AstNodeIds[0])
|
||||||
var undefined bool
|
id := fmt.Sprintf("%s:%d", f.path, scenario.Location.Line)
|
||||||
switch t := n.(type) {
|
undefined := len(pickle.Steps) == 0
|
||||||
case *gherkin.Scenario:
|
|
||||||
id = fmt.Sprintf("%s:%d", f.path, t.Location.Line)
|
|
||||||
undefined = len(t.Steps) == 0
|
|
||||||
case *gherkin.TableRow:
|
|
||||||
id = fmt.Sprintf("%s:%d", f.path, t.Location.Line)
|
|
||||||
undefined = f.outlineSteps == 0
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
f.outlineSteps = len(t.Steps)
|
|
||||||
}
|
|
||||||
|
|
||||||
if len(id) == 0 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
f.event(&struct {
|
f.event(&struct {
|
||||||
Event string `json:"event"`
|
Event string `json:"event"`
|
||||||
|
@ -100,7 +87,7 @@ func (f *events) Node(n interface{}) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
|
func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) {
|
||||||
f.basefmt.Feature(ft, p, c)
|
f.basefmt.Feature(ft, p, c)
|
||||||
f.path = p
|
f.path = p
|
||||||
f.event(&struct {
|
f.event(&struct {
|
||||||
|
@ -109,7 +96,7 @@ func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||||
Source string `json:"source"`
|
Source string `json:"source"`
|
||||||
}{
|
}{
|
||||||
"TestSource",
|
"TestSource",
|
||||||
fmt.Sprintf("%s:%d", p, ft.Location.Line),
|
fmt.Sprintf("%s:%d", p, ft.Feature.Location.Line),
|
||||||
string(c),
|
string(c),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -117,10 +104,10 @@ func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||||
func (f *events) Summary() {
|
func (f *events) Summary() {
|
||||||
// @TODO: determine status
|
// @TODO: determine status
|
||||||
status := passed
|
status := passed
|
||||||
if len(f.failed) > 0 {
|
if len(f.findStepResults(failed)) > 0 {
|
||||||
status = failed
|
status = failed
|
||||||
} else if len(f.passed) == 0 {
|
} else if len(f.findStepResults(passed)) == 0 {
|
||||||
if len(f.undefined) > len(f.pending) {
|
if len(f.findStepResults(undefined)) > len(f.findStepResults(pending)) {
|
||||||
status = undefined
|
status = undefined
|
||||||
} else {
|
} else {
|
||||||
status = pending
|
status = pending
|
||||||
|
@ -148,6 +135,8 @@ func (f *events) Summary() {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) step(res *stepResult) {
|
func (f *events) step(res *stepResult) {
|
||||||
|
step := f.findStep(res.step.AstNodeIds[0])
|
||||||
|
|
||||||
var errMsg string
|
var errMsg string
|
||||||
if res.err != nil {
|
if res.err != nil {
|
||||||
errMsg = res.err.Error()
|
errMsg = res.err.Error()
|
||||||
|
@ -160,23 +149,15 @@ func (f *events) step(res *stepResult) {
|
||||||
Summary string `json:"summary,omitempty"`
|
Summary string `json:"summary,omitempty"`
|
||||||
}{
|
}{
|
||||||
"TestStepFinished",
|
"TestStepFinished",
|
||||||
fmt.Sprintf("%s:%d", f.path, res.step.Location.Line),
|
fmt.Sprintf("%s:%d", f.path, step.Location.Line),
|
||||||
timeNowFunc().UnixNano() / nanoSec,
|
timeNowFunc().UnixNano() / nanoSec,
|
||||||
res.typ.String(),
|
res.status.String(),
|
||||||
errMsg,
|
errMsg,
|
||||||
})
|
})
|
||||||
|
|
||||||
// determine if test case has finished
|
scenario := f.findScenario(res.owner.AstNodeIds[0])
|
||||||
var finished bool
|
line := scenario.Location.Line
|
||||||
var line int
|
finished := isLastStep(res.owner, res.step)
|
||||||
switch t := f.owner.(type) {
|
|
||||||
case *gherkin.TableRow:
|
|
||||||
line = t.Location.Line
|
|
||||||
finished = f.isLastStep(res.step)
|
|
||||||
case *gherkin.Scenario:
|
|
||||||
line = t.Location.Line
|
|
||||||
finished = f.isLastStep(res.step)
|
|
||||||
}
|
|
||||||
|
|
||||||
if finished {
|
if finished {
|
||||||
f.event(&struct {
|
f.event(&struct {
|
||||||
|
@ -188,14 +169,16 @@ func (f *events) step(res *stepResult) {
|
||||||
"TestCaseFinished",
|
"TestCaseFinished",
|
||||||
fmt.Sprintf("%s:%d", f.path, line),
|
fmt.Sprintf("%s:%d", f.path, line),
|
||||||
timeNowFunc().UnixNano() / nanoSec,
|
timeNowFunc().UnixNano() / nanoSec,
|
||||||
f.stat.String(),
|
f.status.String(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Defined(step *gherkin.Step, def *StepDef) {
|
func (f *events) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep, def *StepDefinition) {
|
||||||
|
step := f.findStep(pickleStep.AstNodeIds[0])
|
||||||
|
|
||||||
if def != nil {
|
if def != nil {
|
||||||
m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]
|
m := def.Expr.FindStringSubmatchIndex(pickleStep.Text)[2:]
|
||||||
var args [][2]int
|
var args [][2]int
|
||||||
for i := 0; i < len(m)/2; i++ {
|
for i := 0; i < len(m)/2; i++ {
|
||||||
pair := m[i : i*2+2]
|
pair := m[i : i*2+2]
|
||||||
|
@ -233,31 +216,36 @@ func (f *events) Defined(step *gherkin.Step, def *StepDef) {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Passed(step *gherkin.Step, match *StepDef) {
|
func (f *events) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Passed(step, match)
|
f.basefmt.Passed(pickle, step, match)
|
||||||
f.stat = passed
|
|
||||||
f.step(f.passed[len(f.passed)-1])
|
f.status = passed
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Skipped(step *gherkin.Step, match *StepDef) {
|
func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Skipped(step, match)
|
f.basefmt.Skipped(pickle, step, match)
|
||||||
f.step(f.skipped[len(f.skipped)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Undefined(step *gherkin.Step, match *StepDef) {
|
func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Undefined(step, match)
|
f.basefmt.Undefined(pickle, step, match)
|
||||||
f.stat = undefined
|
|
||||||
f.step(f.undefined[len(f.undefined)-1])
|
f.status = undefined
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Failed(step *gherkin.Step, match *StepDef, err error) {
|
func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||||
f.basefmt.Failed(step, match, err)
|
f.basefmt.Failed(pickle, step, match, err)
|
||||||
f.stat = failed
|
|
||||||
f.step(f.failed[len(f.failed)-1])
|
f.status = failed
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *events) Pending(step *gherkin.Step, match *StepDef) {
|
func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.stat = pending
|
f.basefmt.Pending(pickle, step, match)
|
||||||
f.basefmt.Pending(step, match)
|
|
||||||
f.step(f.pending[len(f.pending)-1])
|
f.status = pending
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
34
fmt_junit.go
34
fmt_junit.go
|
@ -64,45 +64,55 @@ func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*f
|
||||||
|
|
||||||
for idx, feat := range features {
|
for idx, feat := range features {
|
||||||
ts := junitTestSuite{
|
ts := junitTestSuite{
|
||||||
Name: feat.Name,
|
Name: feat.GherkinDocument.Feature.Name,
|
||||||
Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()),
|
Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()),
|
||||||
TestCases: make([]*junitTestCase, len(feat.Scenarios)),
|
TestCases: make([]*junitTestCase, len(feat.pickleResults)),
|
||||||
}
|
}
|
||||||
|
|
||||||
for idx, scenario := range feat.Scenarios {
|
var testcaseNames = make(map[string]int)
|
||||||
tc := junitTestCase{
|
for _, pickleResult := range feat.pickleResults {
|
||||||
Name: scenario.Name,
|
testcaseNames[pickleResult.Name] = testcaseNames[pickleResult.Name] + 1
|
||||||
Time: junitTimeDuration(scenario.startedAt(), scenario.finishedAt()),
|
}
|
||||||
|
|
||||||
|
var outlineNo = make(map[string]int)
|
||||||
|
for idx, pickleResult := range feat.pickleResults {
|
||||||
|
tc := junitTestCase{}
|
||||||
|
tc.Time = junitTimeDuration(pickleResult.startedAt(), pickleResult.finishedAt())
|
||||||
|
|
||||||
|
tc.Name = pickleResult.Name
|
||||||
|
if testcaseNames[tc.Name] > 1 {
|
||||||
|
outlineNo[tc.Name] = outlineNo[tc.Name] + 1
|
||||||
|
tc.Name += fmt.Sprintf(" #%d", outlineNo[tc.Name])
|
||||||
}
|
}
|
||||||
|
|
||||||
ts.Tests++
|
ts.Tests++
|
||||||
suite.Tests++
|
suite.Tests++
|
||||||
|
|
||||||
for _, step := range scenario.Steps {
|
for _, stepResult := range pickleResult.stepResults {
|
||||||
switch step.typ {
|
switch stepResult.status {
|
||||||
case passed:
|
case passed:
|
||||||
tc.Status = passed.String()
|
tc.Status = passed.String()
|
||||||
case failed:
|
case failed:
|
||||||
tc.Status = failed.String()
|
tc.Status = failed.String()
|
||||||
tc.Failure = &junitFailure{
|
tc.Failure = &junitFailure{
|
||||||
Message: fmt.Sprintf("%s %s: %s", step.step.Type, step.step.Text, step.err),
|
Message: fmt.Sprintf("Step %s: %s", stepResult.step.Text, stepResult.err),
|
||||||
}
|
}
|
||||||
case skipped:
|
case skipped:
|
||||||
tc.Error = append(tc.Error, &junitError{
|
tc.Error = append(tc.Error, &junitError{
|
||||||
Type: "skipped",
|
Type: "skipped",
|
||||||
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
|
Message: fmt.Sprintf("Step %s", stepResult.step.Text),
|
||||||
})
|
})
|
||||||
case undefined:
|
case undefined:
|
||||||
tc.Status = undefined.String()
|
tc.Status = undefined.String()
|
||||||
tc.Error = append(tc.Error, &junitError{
|
tc.Error = append(tc.Error, &junitError{
|
||||||
Type: "undefined",
|
Type: "undefined",
|
||||||
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
|
Message: fmt.Sprintf("Step %s", stepResult.step.Text),
|
||||||
})
|
})
|
||||||
case pending:
|
case pending:
|
||||||
tc.Status = pending.String()
|
tc.Status = pending.String()
|
||||||
tc.Error = append(tc.Error, &junitError{
|
tc.Error = append(tc.Error, &junitError{
|
||||||
Type: "pending",
|
Type: "pending",
|
||||||
Message: fmt.Sprintf("%s %s: TODO: write pending definition", step.step.Type, step.step.Text),
|
Message: fmt.Sprintf("Step %s: TODO: write pending definition", stepResult.step.Text),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,8 +8,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var sampleGherkinFeature = `
|
var sampleGherkinFeature = `
|
||||||
|
@ -49,19 +53,22 @@ Feature: junit formatter
|
||||||
`
|
`
|
||||||
|
|
||||||
func TestJUnitFormatterOutput(t *testing.T) {
|
func TestJUnitFormatterOutput(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature))
|
const path = "any.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(sampleGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
s := &Suite{
|
s := &Suite{
|
||||||
fmt: junitFunc("junit", w),
|
fmt: junitFunc("junit", w),
|
||||||
features: []*feature{&feature{
|
features: []*feature{{
|
||||||
Path: "any.feature",
|
GherkinDocument: gd,
|
||||||
Feature: feat,
|
pickles: pickles,
|
||||||
Content: []byte(sampleGherkinFeature),
|
Path: path,
|
||||||
|
Content: []byte(sampleGherkinFeature),
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,19 +158,18 @@ func TestJUnitFormatterOutput(t *testing.T) {
|
||||||
},
|
},
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
|
||||||
s.run()
|
s.run()
|
||||||
s.fmt.Summary()
|
s.fmt.Summary()
|
||||||
|
|
||||||
var exp bytes.Buffer
|
var exp bytes.Buffer
|
||||||
if _, err = io.WriteString(&exp, xml.Header); err != nil {
|
_, err = io.WriteString(&exp, xml.Header)
|
||||||
t.Fatalf("unexpected error: %v", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
enc := xml.NewEncoder(&exp)
|
enc := xml.NewEncoder(&exp)
|
||||||
enc.Indent("", " ")
|
enc.Indent("", " ")
|
||||||
if err = enc.Encode(expected); err != nil {
|
err = enc.Encode(expected)
|
||||||
t.Fatalf("unexpected error: %v", err)
|
require.NoError(t, err)
|
||||||
}
|
|
||||||
if buf.String() != exp.String() {
|
assert.Equal(t, exp.String(), buf.String())
|
||||||
t.Fatalf("expected output does not match: %s", buf.String())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
636
fmt_pretty.go
636
fmt_pretty.go
|
@ -3,13 +3,13 @@ package godog
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math"
|
|
||||||
"regexp"
|
"regexp"
|
||||||
"strings"
|
"strings"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -25,79 +25,59 @@ var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>")
|
||||||
// a built in default pretty formatter
|
// a built in default pretty formatter
|
||||||
type pretty struct {
|
type pretty struct {
|
||||||
*basefmt
|
*basefmt
|
||||||
|
|
||||||
// currently processed
|
|
||||||
feature *gherkin.Feature
|
|
||||||
scenario *gherkin.Scenario
|
|
||||||
outline *gherkin.ScenarioOutline
|
|
||||||
|
|
||||||
// state
|
|
||||||
bgSteps int
|
|
||||||
totalBgSteps int
|
|
||||||
steps int
|
|
||||||
commentPos int
|
|
||||||
|
|
||||||
// whether scenario or scenario outline keyword was printed
|
|
||||||
scenarioKeyword bool
|
|
||||||
|
|
||||||
// outline
|
|
||||||
outlineSteps []*stepResult
|
|
||||||
outlineNumExample int
|
|
||||||
outlineNumExamples int
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) Feature(ft *gherkin.Feature, p string, c []byte) {
|
func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) {
|
||||||
if len(f.features) != 0 {
|
f.basefmt.Feature(gd, p, c)
|
||||||
// not a first feature, add a newline
|
f.printFeature(gd.Feature)
|
||||||
fmt.Fprintln(f.out, "")
|
}
|
||||||
|
|
||||||
|
// Pickle takes a gherkin node for formatting
|
||||||
|
func (f *pretty) Pickle(pickle *messages.Pickle) {
|
||||||
|
f.basefmt.Pickle(pickle)
|
||||||
|
|
||||||
|
if len(pickle.Steps) == 0 {
|
||||||
|
f.printUndefinedPickle(pickle)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
}
|
||||||
fmt.Fprintln(f.out, keywordAndName(ft.Keyword, ft.Name))
|
|
||||||
if strings.TrimSpace(ft.Description) != "" {
|
func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
for _, line := range strings.Split(ft.Description, "\n") {
|
f.basefmt.Passed(pickle, step, match)
|
||||||
|
f.printStep(f.lastStepResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
|
f.basefmt.Skipped(pickle, step, match)
|
||||||
|
f.printStep(f.lastStepResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
|
f.basefmt.Undefined(pickle, step, match)
|
||||||
|
f.printStep(f.lastStepResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||||
|
f.basefmt.Failed(pickle, step, match, err)
|
||||||
|
f.printStep(f.lastStepResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
|
f.basefmt.Pending(pickle, step, match)
|
||||||
|
f.printStep(f.lastStepResult())
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) printFeature(feature *messages.GherkinDocument_Feature) {
|
||||||
|
if len(f.features) != 0 {
|
||||||
|
fmt.Fprintln(f.out, "") // not a first feature, add a newline
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(f.out, keywordAndName(feature.Keyword, feature.Name))
|
||||||
|
if strings.TrimSpace(feature.Description) != "" {
|
||||||
|
for _, line := range strings.Split(feature.Description, "\n") {
|
||||||
fmt.Fprintln(f.out, s(f.indent)+strings.TrimSpace(line))
|
fmt.Fprintln(f.out, s(f.indent)+strings.TrimSpace(line))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.feature = ft
|
|
||||||
f.scenario = nil
|
|
||||||
f.outline = nil
|
|
||||||
f.bgSteps = 0
|
|
||||||
f.totalBgSteps = 0
|
|
||||||
if ft.Background != nil {
|
|
||||||
f.bgSteps = len(ft.Background.Steps)
|
|
||||||
f.totalBgSteps = len(ft.Background.Steps)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Node takes a gherkin node for formatting
|
|
||||||
func (f *pretty) Node(node interface{}) {
|
|
||||||
f.basefmt.Node(node)
|
|
||||||
|
|
||||||
switch t := node.(type) {
|
|
||||||
case *gherkin.Examples:
|
|
||||||
f.outlineNumExamples = len(t.TableBody)
|
|
||||||
f.outlineNumExample++
|
|
||||||
case *gherkin.Scenario:
|
|
||||||
f.scenario = t
|
|
||||||
f.outline = nil
|
|
||||||
f.steps = len(t.Steps) + f.totalBgSteps
|
|
||||||
f.scenarioKeyword = false
|
|
||||||
if isEmptyScenario(t) {
|
|
||||||
f.printUndefinedScenario(t)
|
|
||||||
}
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
f.outline = t
|
|
||||||
f.scenario = nil
|
|
||||||
f.outlineNumExample = -1
|
|
||||||
f.scenarioKeyword = false
|
|
||||||
if isEmptyScenario(t) {
|
|
||||||
f.printUndefinedScenario(t)
|
|
||||||
}
|
|
||||||
case *gherkin.TableRow:
|
|
||||||
f.steps = len(f.outline.Steps) + f.totalBgSteps
|
|
||||||
f.outlineSteps = []*stepResult{}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func keywordAndName(keyword, name string) string {
|
func keywordAndName(keyword, name string) string {
|
||||||
|
@ -108,342 +88,302 @@ func keywordAndName(keyword, name string) string {
|
||||||
return title
|
return title
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) printUndefinedScenario(sc interface{}) {
|
func (f *pretty) scenarioLengths(scenarioAstID string) (scenarioHeaderLength int, maxLength int) {
|
||||||
if f.bgSteps > 0 {
|
astScenario := f.findScenario(scenarioAstID)
|
||||||
bg := f.feature.Background
|
astBackground := f.findBackground(scenarioAstID)
|
||||||
f.commentPos = f.longestStep(bg.Steps, f.length(bg))
|
|
||||||
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(bg.Keyword, bg.Name))
|
|
||||||
|
|
||||||
for _, step := range bg.Steps {
|
scenarioHeaderLength = f.lengthPickle(astScenario.Keyword, astScenario.Name)
|
||||||
f.bgSteps--
|
maxLength = f.longestStep(astScenario.Steps, scenarioHeaderLength)
|
||||||
f.printStep(step, nil, colors.Cyan)
|
|
||||||
|
if astBackground != nil {
|
||||||
|
maxLength = f.longestStep(astBackground.Steps, maxLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
return scenarioHeaderLength, maxLength
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) printScenarioHeader(astScenario *messages.GherkinDocument_Feature_Scenario, spaceFilling int) {
|
||||||
|
text := s(f.indent) + keywordAndName(astScenario.Keyword, astScenario.Name)
|
||||||
|
text += s(spaceFilling) + f.line(astScenario.Location)
|
||||||
|
fmt.Fprintln(f.out, "\n"+text)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) {
|
||||||
|
astScenario := f.findScenario(pickle.AstNodeIds[0])
|
||||||
|
astBackground := f.findBackground(pickle.AstNodeIds[0])
|
||||||
|
|
||||||
|
scenarioHeaderLength, maxLength := f.scenarioLengths(pickle.AstNodeIds[0])
|
||||||
|
|
||||||
|
if astBackground != nil {
|
||||||
|
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name))
|
||||||
|
for _, step := range astBackground.Steps {
|
||||||
|
text := s(f.indent) + cyan(strings.TrimSpace(step.Keyword)) + " " + cyan(step.Text)
|
||||||
|
fmt.Fprintln(f.out, text)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
switch t := sc.(type) {
|
// do not print scenario headers and examples multiple times
|
||||||
case *gherkin.Scenario:
|
if len(astScenario.Examples) > 0 {
|
||||||
f.commentPos = f.longestStep(t.Steps, f.length(sc))
|
exampleTable, exampleRow := f.findExample(pickle.AstNodeIds[1])
|
||||||
text := s(f.indent) + keywordAndName(t.Keyword, t.Name)
|
firstExampleRow := exampleTable.TableBody[0].Id == exampleRow.Id
|
||||||
text += s(f.commentPos-f.length(t)+1) + f.line(t.Location)
|
firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line
|
||||||
fmt.Fprintln(f.out, "\n"+text)
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
f.commentPos = f.longestStep(t.Steps, f.length(sc))
|
|
||||||
text := s(f.indent) + keywordAndName(t.Keyword, t.Name)
|
|
||||||
text += s(f.commentPos-f.length(t)+1) + f.line(t.Location)
|
|
||||||
fmt.Fprintln(f.out, "\n"+text)
|
|
||||||
|
|
||||||
for _, example := range t.Examples {
|
if !(firstExamplesTable && firstExampleRow) {
|
||||||
max := longest(example, cyan)
|
return
|
||||||
f.printExampleHeader(example, max)
|
}
|
||||||
for _, row := range example.TableBody {
|
}
|
||||||
f.printExampleRow(row, max, cyan)
|
|
||||||
}
|
f.printScenarioHeader(astScenario, maxLength-scenarioHeaderLength)
|
||||||
|
|
||||||
|
for _, examples := range astScenario.Examples {
|
||||||
|
max := longestExampleRow(examples, cyan, cyan)
|
||||||
|
|
||||||
|
fmt.Fprintln(f.out, "")
|
||||||
|
fmt.Fprintln(f.out, s(f.indent*2)+keywordAndName(examples.Keyword, examples.Name))
|
||||||
|
|
||||||
|
f.printTableHeader(examples.TableHeader, max)
|
||||||
|
|
||||||
|
for _, row := range examples.TableBody {
|
||||||
|
f.printTableRow(row, max, cyan)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Summary sumarize the feature formatter output
|
// Summary sumarize the feature formatter output
|
||||||
func (f *pretty) Summary() {
|
func (f *pretty) Summary() {
|
||||||
if len(f.failed) > 0 {
|
failedStepResults := f.findStepResults(failed)
|
||||||
|
if len(failedStepResults) > 0 {
|
||||||
fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n")
|
fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n")
|
||||||
for _, fail := range f.failed {
|
for _, fail := range failedStepResults {
|
||||||
fmt.Fprintln(f.out, s(2)+red(fail.scenarioDesc())+blackb(" # "+fail.scenarioLine()))
|
astScenario := f.findScenario(fail.owner.AstNodeIds[0])
|
||||||
fmt.Fprintln(f.out, s(4)+red(strings.TrimSpace(fail.step.Keyword)+" "+fail.step.Text)+blackb(" # "+fail.line()))
|
scenarioDesc := fmt.Sprintf("%s: %s", astScenario.Keyword, fail.owner.Name)
|
||||||
|
|
||||||
|
astStep := f.findStep(fail.step.AstNodeIds[0])
|
||||||
|
stepDesc := strings.TrimSpace(astStep.Keyword) + " " + fail.step.Text
|
||||||
|
|
||||||
|
fmt.Fprintln(f.out, s(2)+red(scenarioDesc)+f.line(astScenario.Location))
|
||||||
|
fmt.Fprintln(f.out, s(4)+red(stepDesc)+f.line(astStep.Location))
|
||||||
fmt.Fprintln(f.out, s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n")
|
fmt.Fprintln(f.out, s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
f.basefmt.Summary()
|
f.basefmt.Summary()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps int) {
|
||||||
var msg string
|
var errorMsg string
|
||||||
var clr colors.ColorFunc
|
var clr = green
|
||||||
|
|
||||||
ex := outline.Examples[f.outlineNumExample]
|
astScenario := f.findScenario(pickle.AstNodeIds[0])
|
||||||
example, hasExamples := examples(ex)
|
scenarioHeaderLength, maxLength := f.scenarioLengths(pickle.AstNodeIds[0])
|
||||||
if !hasExamples {
|
|
||||||
|
exampleTable, exampleRow := f.findExample(pickle.AstNodeIds[1])
|
||||||
|
printExampleHeader := exampleTable.TableBody[0].Id == exampleRow.Id
|
||||||
|
firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line
|
||||||
|
|
||||||
|
firstExecutedScenarioStep := len(f.lastFeature().lastPickleResult().stepResults) == backgroundSteps+1
|
||||||
|
if firstExamplesTable && printExampleHeader && firstExecutedScenarioStep {
|
||||||
|
f.printScenarioHeader(astScenario, maxLength-scenarioHeaderLength)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(exampleTable.TableBody) == 0 {
|
||||||
// do not print empty examples
|
// do not print empty examples
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
firstExample := f.outlineNumExamples == len(example.TableBody)
|
lastStep := len(f.lastFeature().lastPickleResult().stepResults) == len(pickle.Steps)
|
||||||
printSteps := firstExample && f.outlineNumExample == 0
|
if !lastStep {
|
||||||
|
// do not print examples unless all steps has finished
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
for i, res := range f.outlineSteps {
|
for _, result := range f.lastFeature().lastPickleResult().stepResults {
|
||||||
// determine example row status
|
// determine example row status
|
||||||
switch {
|
switch {
|
||||||
case res.typ == failed:
|
case result.status == failed:
|
||||||
msg = res.err.Error()
|
errorMsg = result.err.Error()
|
||||||
clr = res.typ.clr()
|
clr = result.status.clr()
|
||||||
case res.typ == undefined || res.typ == pending:
|
case result.status == undefined || result.status == pending:
|
||||||
clr = res.typ.clr()
|
clr = result.status.clr()
|
||||||
case res.typ == skipped && clr == nil:
|
case result.status == skipped && clr == nil:
|
||||||
clr = cyan
|
clr = cyan
|
||||||
}
|
}
|
||||||
if printSteps && i >= f.totalBgSteps {
|
|
||||||
|
if firstExamplesTable && printExampleHeader {
|
||||||
// in first example, we need to print steps
|
// in first example, we need to print steps
|
||||||
var text string
|
var text string
|
||||||
ostep := outline.Steps[i-f.totalBgSteps]
|
|
||||||
if res.def != nil {
|
astStep := f.findStep(result.step.AstNodeIds[0])
|
||||||
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
|
|
||||||
|
if result.def != nil {
|
||||||
|
if m := outlinePlaceholderRegexp.FindAllStringIndex(astStep.Text, -1); len(m) > 0 {
|
||||||
var pos int
|
var pos int
|
||||||
for i := 0; i < len(m); i++ {
|
for i := 0; i < len(m); i++ {
|
||||||
pair := m[i]
|
pair := m[i]
|
||||||
text += cyan(ostep.Text[pos:pair[0]])
|
text += cyan(astStep.Text[pos:pair[0]])
|
||||||
text += cyanb(ostep.Text[pair[0]:pair[1]])
|
text += cyanb(astStep.Text[pair[0]:pair[1]])
|
||||||
pos = pair[1]
|
pos = pair[1]
|
||||||
}
|
}
|
||||||
text += cyan(ostep.Text[pos:len(ostep.Text)])
|
text += cyan(astStep.Text[pos:len(astStep.Text)])
|
||||||
} else {
|
} else {
|
||||||
text = cyan(ostep.Text)
|
text = cyan(astStep.Text)
|
||||||
}
|
}
|
||||||
text += s(f.commentPos-f.length(ostep)+1) + blackb(fmt.Sprintf("# %s", res.def.definitionID()))
|
|
||||||
|
_, maxLength := f.scenarioLengths(result.owner.AstNodeIds[0])
|
||||||
|
stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text)
|
||||||
|
|
||||||
|
text += s(maxLength - stepLength)
|
||||||
|
text += " " + blackb("# "+result.def.definitionID())
|
||||||
} else {
|
} else {
|
||||||
text = cyan(ostep.Text)
|
text = cyan(astStep.Text)
|
||||||
}
|
}
|
||||||
// print the step outline
|
// print the step outline
|
||||||
fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(ostep.Keyword))+" "+text)
|
fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(astStep.Keyword))+" "+text)
|
||||||
|
|
||||||
// print step argument
|
if table := result.step.Argument.GetDataTable(); table != nil {
|
||||||
// @TODO: need to make example header cells bold
|
f.printTable(table, cyan)
|
||||||
switch t := ostep.Argument.(type) {
|
}
|
||||||
case *gherkin.DataTable:
|
|
||||||
f.printTable(t, cyan)
|
if docString := astStep.GetDocString(); docString != nil {
|
||||||
case *gherkin.DocString:
|
f.printDocString(docString)
|
||||||
var ct string
|
|
||||||
if len(t.ContentType) > 0 {
|
|
||||||
ct = " " + cyan(t.ContentType)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+cyan(t.Delimitter)+ct)
|
|
||||||
for _, ln := range strings.Split(t.Content, "\n") {
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+cyan(ln))
|
|
||||||
}
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+cyan(t.Delimitter))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if clr == nil {
|
max := longestExampleRow(exampleTable, clr, cyan)
|
||||||
clr = green
|
|
||||||
}
|
|
||||||
|
|
||||||
max := longest(example, clr, cyan)
|
|
||||||
// an example table header
|
// an example table header
|
||||||
if firstExample {
|
if printExampleHeader {
|
||||||
f.printExampleHeader(example, max)
|
fmt.Fprintln(f.out, "")
|
||||||
|
fmt.Fprintln(f.out, s(f.indent*2)+keywordAndName(exampleTable.Keyword, exampleTable.Name))
|
||||||
|
|
||||||
|
f.printTableHeader(exampleTable.TableHeader, max)
|
||||||
}
|
}
|
||||||
|
|
||||||
// an example table row
|
f.printTableRow(exampleRow, max, clr)
|
||||||
row := example.TableBody[len(example.TableBody)-f.outlineNumExamples]
|
|
||||||
f.printExampleRow(row, max, clr)
|
|
||||||
|
|
||||||
// if there is an error
|
if errorMsg != "" {
|
||||||
if msg != "" {
|
fmt.Fprintln(f.out, s(f.indent*4)+redb(errorMsg))
|
||||||
fmt.Fprintln(f.out, s(f.indent*4)+redb(msg))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) printExampleRow(row *gherkin.TableRow, max []int, clr colors.ColorFunc) {
|
func (f *pretty) printTableRow(row *messages.GherkinDocument_Feature_TableRow, max []int, clr colors.ColorFunc) {
|
||||||
cells := make([]string, len(row.Cells))
|
cells := make([]string, len(row.Cells))
|
||||||
|
|
||||||
for i, cell := range row.Cells {
|
for i, cell := range row.Cells {
|
||||||
val := clr(cell.Value)
|
val := clr(cell.Value)
|
||||||
ln := utf8.RuneCountInString(val)
|
ln := utf8.RuneCountInString(val)
|
||||||
cells[i] = val + s(max[i]-ln)
|
cells[i] = val + s(max[i]-ln)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |")
|
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) printExampleHeader(example *gherkin.Examples, max []int) {
|
func (f *pretty) printTableHeader(row *messages.GherkinDocument_Feature_TableRow, max []int) {
|
||||||
cells := make([]string, len(example.TableHeader.Cells))
|
f.printTableRow(row, max, cyan)
|
||||||
// an example table header
|
|
||||||
fmt.Fprintln(f.out, "")
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*2)+keywordAndName(example.Keyword, example.Name))
|
|
||||||
|
|
||||||
for i, cell := range example.TableHeader.Cells {
|
|
||||||
val := cyan(cell.Value)
|
|
||||||
ln := utf8.RuneCountInString(val)
|
|
||||||
cells[i] = val + s(max[i]-ln)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c colors.ColorFunc) {
|
func (f *pretty) printStep(result *stepResult) {
|
||||||
text := s(f.indent*2) + c(strings.TrimSpace(step.Keyword)) + " "
|
astBackground := f.findBackground(result.owner.AstNodeIds[0])
|
||||||
switch {
|
astScenario := f.findScenario(result.owner.AstNodeIds[0])
|
||||||
case def != nil:
|
astStep := f.findStep(result.step.AstNodeIds[0])
|
||||||
if m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]; len(m) > 0 {
|
|
||||||
var pos, i int
|
var backgroundSteps int
|
||||||
for pos, i = 0, 0; i < len(m); i++ {
|
if astBackground != nil {
|
||||||
if m[i] == -1 {
|
backgroundSteps = len(astBackground.Steps)
|
||||||
continue // no index for this match
|
|
||||||
}
|
|
||||||
if math.Mod(float64(i), 2) == 0 {
|
|
||||||
text += c(step.Text[pos:m[i]])
|
|
||||||
} else {
|
|
||||||
text += colors.Bold(c)(step.Text[pos:m[i]])
|
|
||||||
}
|
|
||||||
pos = m[i]
|
|
||||||
}
|
|
||||||
text += c(step.Text[pos:len(step.Text)])
|
|
||||||
} else {
|
|
||||||
text += c(step.Text)
|
|
||||||
}
|
|
||||||
text += s(f.commentPos-f.length(step)+1) + blackb(fmt.Sprintf("# %s", def.definitionID()))
|
|
||||||
default:
|
|
||||||
text += c(step.Text)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(f.out, text)
|
astBackgroundStep := backgroundSteps > 0 && backgroundSteps >= len(f.lastFeature().lastPickleResult().stepResults)
|
||||||
switch t := step.Argument.(type) {
|
|
||||||
case *gherkin.DataTable:
|
|
||||||
f.printTable(t, c)
|
|
||||||
case *gherkin.DocString:
|
|
||||||
var ct string
|
|
||||||
if len(t.ContentType) > 0 {
|
|
||||||
ct = " " + c(t.ContentType)
|
|
||||||
}
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+c(t.Delimitter)+ct)
|
|
||||||
for _, ln := range strings.Split(t.Content, "\n") {
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+c(ln))
|
|
||||||
}
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+c(t.Delimitter))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *pretty) printStepKind(res *stepResult) {
|
if astBackgroundStep {
|
||||||
f.steps--
|
if len(f.lastFeature().pickleResults) > 1 {
|
||||||
if f.outline != nil {
|
return
|
||||||
f.outlineSteps = append(f.outlineSteps, res)
|
}
|
||||||
}
|
|
||||||
var bgStep bool
|
|
||||||
bg := f.feature.Background
|
|
||||||
|
|
||||||
// if has not printed background yet
|
firstExecutedBackgroundStep := astBackground != nil && len(f.lastFeature().lastPickleResult().stepResults) == 1
|
||||||
switch {
|
if firstExecutedBackgroundStep {
|
||||||
// first background step
|
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name))
|
||||||
case f.bgSteps > 0 && f.bgSteps == len(bg.Steps):
|
|
||||||
f.commentPos = f.longestStep(bg.Steps, f.length(bg))
|
|
||||||
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(bg.Keyword, bg.Name))
|
|
||||||
f.bgSteps--
|
|
||||||
bgStep = true
|
|
||||||
// subsequent background steps
|
|
||||||
case f.bgSteps > 0:
|
|
||||||
f.bgSteps--
|
|
||||||
bgStep = true
|
|
||||||
// first step of scenario, print header and calculate comment position
|
|
||||||
case f.scenario != nil:
|
|
||||||
// print scenario keyword and value if first example
|
|
||||||
if !f.scenarioKeyword {
|
|
||||||
f.commentPos = f.longestStep(f.scenario.Steps, f.length(f.scenario))
|
|
||||||
if bg != nil {
|
|
||||||
if bgLen := f.longestStep(bg.Steps, f.length(bg)); bgLen > f.commentPos {
|
|
||||||
f.commentPos = bgLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text := s(f.indent) + keywordAndName(f.scenario.Keyword, f.scenario.Name)
|
|
||||||
text += s(f.commentPos-f.length(f.scenario)+1) + f.line(f.scenario.Location)
|
|
||||||
fmt.Fprintln(f.out, "\n"+text)
|
|
||||||
f.scenarioKeyword = true
|
|
||||||
}
|
|
||||||
// first step of outline scenario, print header and calculate comment position
|
|
||||||
case f.outline != nil:
|
|
||||||
// print scenario keyword and value if first example
|
|
||||||
if !f.scenarioKeyword {
|
|
||||||
f.commentPos = f.longestStep(f.outline.Steps, f.length(f.outline))
|
|
||||||
if bg != nil {
|
|
||||||
if bgLen := f.longestStep(bg.Steps, f.length(bg)); bgLen > f.commentPos {
|
|
||||||
f.commentPos = bgLen
|
|
||||||
}
|
|
||||||
}
|
|
||||||
text := s(f.indent) + keywordAndName(f.outline.Keyword, f.outline.Name)
|
|
||||||
text += s(f.commentPos-f.length(f.outline)+1) + f.line(f.outline.Location)
|
|
||||||
fmt.Fprintln(f.out, "\n"+text)
|
|
||||||
f.scenarioKeyword = true
|
|
||||||
}
|
|
||||||
if len(f.outlineSteps) == len(f.outline.Steps)+f.totalBgSteps {
|
|
||||||
// an outline example steps has went through
|
|
||||||
f.printOutlineExample(f.outline)
|
|
||||||
f.outlineNumExamples--
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !astBackgroundStep && len(astScenario.Examples) > 0 {
|
||||||
|
f.printOutlineExample(result.owner, backgroundSteps)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if !f.isBackgroundStep(res.step) || bgStep {
|
scenarioHeaderLength, maxLength := f.scenarioLengths(result.owner.AstNodeIds[0])
|
||||||
f.printStep(res.step, res.def, res.typ.clr())
|
stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text)
|
||||||
|
|
||||||
|
firstExecutedScenarioStep := len(f.lastFeature().lastPickleResult().stepResults) == backgroundSteps+1
|
||||||
|
if !astBackgroundStep && firstExecutedScenarioStep {
|
||||||
|
f.printScenarioHeader(astScenario, maxLength-scenarioHeaderLength)
|
||||||
}
|
}
|
||||||
if res.err != nil {
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", res.err)))
|
text := s(f.indent) + result.status.clr()(strings.TrimSpace(astStep.Keyword)) + " " + result.status.clr()(astStep.Text)
|
||||||
|
if result.def != nil {
|
||||||
|
text += s(maxLength - stepLength + 1)
|
||||||
|
text += blackb("# " + result.def.definitionID())
|
||||||
}
|
}
|
||||||
if res.typ == pending {
|
fmt.Fprintln(f.out, text)
|
||||||
|
|
||||||
|
if table := result.step.Argument.GetDataTable(); table != nil {
|
||||||
|
f.printTable(table, cyan)
|
||||||
|
}
|
||||||
|
|
||||||
|
if docString := astStep.GetDocString(); docString != nil {
|
||||||
|
f.printDocString(docString)
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.err != nil {
|
||||||
|
fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", result.err)))
|
||||||
|
}
|
||||||
|
|
||||||
|
if result.status == pending {
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+yellow("TODO: write pending definition"))
|
fmt.Fprintln(f.out, s(f.indent*3)+yellow("TODO: write pending definition"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) isBackgroundStep(step *gherkin.Step) bool {
|
func (f *pretty) printDocString(docString *messages.GherkinDocument_Feature_Step_DocString) {
|
||||||
if f.feature.Background == nil {
|
var ct string
|
||||||
return false
|
|
||||||
|
if len(docString.MediaType) > 0 {
|
||||||
|
ct = " " + cyan(docString.MediaType)
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, bstep := range f.feature.Background.Steps {
|
fmt.Fprintln(f.out, s(f.indent*3)+cyan(docString.Delimiter)+ct)
|
||||||
if bstep.Location.Line == step.Location.Line {
|
|
||||||
return true
|
for _, ln := range strings.Split(docString.Content, "\n") {
|
||||||
}
|
fmt.Fprintln(f.out, s(f.indent*3)+cyan(ln))
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
|
fmt.Fprintln(f.out, s(f.indent*3)+cyan(docString.Delimiter))
|
||||||
}
|
}
|
||||||
|
|
||||||
// print table with aligned table cells
|
// print table with aligned table cells
|
||||||
func (f *pretty) printTable(t *gherkin.DataTable, c colors.ColorFunc) {
|
// @TODO: need to make example header cells bold
|
||||||
var l = longest(t, c)
|
func (f *pretty) printTable(t *messages.PickleStepArgument_PickleTable, c colors.ColorFunc) {
|
||||||
|
maxColLengths := maxColLengths(t, c)
|
||||||
var cols = make([]string, len(t.Rows[0].Cells))
|
var cols = make([]string, len(t.Rows[0].Cells))
|
||||||
|
|
||||||
for _, row := range t.Rows {
|
for _, row := range t.Rows {
|
||||||
for i, cell := range row.Cells {
|
for i, cell := range row.Cells {
|
||||||
val := c(cell.Value)
|
val := c(cell.Value)
|
||||||
ln := utf8.RuneCountInString(val)
|
colLength := utf8.RuneCountInString(val)
|
||||||
cols[i] = val + s(l[i]-ln)
|
cols[i] = val + s(maxColLengths[i]-colLength)
|
||||||
}
|
}
|
||||||
|
|
||||||
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cols, " | ")+" |")
|
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cols, " | ")+" |")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) Passed(step *gherkin.Step, match *StepDef) {
|
|
||||||
f.basefmt.Passed(step, match)
|
|
||||||
f.printStepKind(f.passed[len(f.passed)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *pretty) Skipped(step *gherkin.Step, match *StepDef) {
|
|
||||||
f.basefmt.Skipped(step, match)
|
|
||||||
f.printStepKind(f.skipped[len(f.skipped)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *pretty) Undefined(step *gherkin.Step, match *StepDef) {
|
|
||||||
f.basefmt.Undefined(step, match)
|
|
||||||
f.printStepKind(f.undefined[len(f.undefined)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *pretty) Failed(step *gherkin.Step, match *StepDef, err error) {
|
|
||||||
f.basefmt.Failed(step, match, err)
|
|
||||||
f.printStepKind(f.failed[len(f.failed)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
func (f *pretty) Pending(step *gherkin.Step, match *StepDef) {
|
|
||||||
f.basefmt.Pending(step, match)
|
|
||||||
f.printStepKind(f.pending[len(f.pending)-1])
|
|
||||||
}
|
|
||||||
|
|
||||||
// longest gives a list of longest columns of all rows in Table
|
// longest gives a list of longest columns of all rows in Table
|
||||||
func longest(tbl interface{}, clrs ...colors.ColorFunc) []int {
|
func maxColLengths(t *messages.PickleStepArgument_PickleTable, clrs ...colors.ColorFunc) []int {
|
||||||
var rows []*gherkin.TableRow
|
if t == nil {
|
||||||
switch t := tbl.(type) {
|
return []int{}
|
||||||
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))
|
longest := make([]int, len(t.Rows[0].Cells))
|
||||||
for _, row := range rows {
|
for _, row := range t.Rows {
|
||||||
for i, cell := range row.Cells {
|
for i, cell := range row.Cells {
|
||||||
for _, c := range clrs {
|
for _, c := range clrs {
|
||||||
ln := utf8.RuneCountInString(c(cell.Value))
|
ln := utf8.RuneCountInString(c(cell.Value))
|
||||||
|
@ -458,35 +398,71 @@ func longest(tbl interface{}, clrs ...colors.ColorFunc) []int {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return longest
|
return longest
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) longestStep(steps []*gherkin.Step, base int) int {
|
func longestExampleRow(t *messages.GherkinDocument_Feature_Scenario_Examples, clrs ...colors.ColorFunc) []int {
|
||||||
ret := base
|
if t == nil {
|
||||||
for _, step := range steps {
|
return []int{}
|
||||||
length := f.length(step)
|
}
|
||||||
if length > ret {
|
|
||||||
ret = length
|
longest := make([]int, len(t.TableHeader.Cells))
|
||||||
|
for i, cell := range t.TableHeader.Cells {
|
||||||
|
for _, c := range clrs {
|
||||||
|
ln := utf8.RuneCountInString(c(cell.Value))
|
||||||
|
if longest[i] < ln {
|
||||||
|
longest[i] = ln
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ln := utf8.RuneCountInString(cell.Value)
|
||||||
|
if longest[i] < ln {
|
||||||
|
longest[i] = ln
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return ret
|
|
||||||
|
for _, row := range t.TableBody {
|
||||||
|
for i, cell := range row.Cells {
|
||||||
|
for _, c := range clrs {
|
||||||
|
ln := utf8.RuneCountInString(c(cell.Value))
|
||||||
|
if longest[i] < ln {
|
||||||
|
longest[i] = ln
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ln := utf8.RuneCountInString(cell.Value)
|
||||||
|
if longest[i] < ln {
|
||||||
|
longest[i] = ln
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return longest
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *pretty) longestStep(steps []*messages.GherkinDocument_Feature_Step, pickleLength int) int {
|
||||||
|
max := pickleLength
|
||||||
|
|
||||||
|
for _, step := range steps {
|
||||||
|
length := f.lengthPickleStep(step.Keyword, step.Text)
|
||||||
|
if length > max {
|
||||||
|
max = length
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return max
|
||||||
}
|
}
|
||||||
|
|
||||||
// a line number representation in feature file
|
// a line number representation in feature file
|
||||||
func (f *pretty) line(loc *gherkin.Location) string {
|
func (f *pretty) line(loc *messages.Location) string {
|
||||||
return blackb(fmt.Sprintf("# %s:%d", f.features[len(f.features)-1].Path, loc.Line))
|
return " " + blackb(fmt.Sprintf("# %s:%d", f.lastFeature().Path, loc.Line))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *pretty) length(node interface{}) int {
|
func (f *pretty) lengthPickleStep(keyword, text string) int {
|
||||||
switch t := node.(type) {
|
return f.indent*2 + utf8.RuneCountInString(strings.TrimSpace(keyword)+" "+text)
|
||||||
case *gherkin.Background:
|
}
|
||||||
return f.indent + utf8.RuneCountInString(strings.TrimSpace(t.Keyword)+": "+t.Name)
|
|
||||||
case *gherkin.Step:
|
func (f *pretty) lengthPickle(keyword, name string) int {
|
||||||
return f.indent*2 + utf8.RuneCountInString(strings.TrimSpace(t.Keyword)+" "+t.Text)
|
return f.indent + utf8.RuneCountInString(strings.TrimSpace(keyword)+": "+name)
|
||||||
case *gherkin.Scenario:
|
|
||||||
return f.indent + utf8.RuneCountInString(strings.TrimSpace(t.Keyword)+": "+t.Name)
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
return f.indent + utf8.RuneCountInString(strings.TrimSpace(t.Keyword)+": "+t.Name)
|
|
||||||
}
|
|
||||||
panic(fmt.Sprintf("unexpected node %T to determine length", node))
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ import (
|
||||||
"math"
|
"math"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
|
@ -37,21 +37,39 @@ func (f *progress) Summary() {
|
||||||
fmt.Fprintf(f.out, " %d\n", *f.steps)
|
fmt.Fprintf(f.out, " %d\n", *f.steps)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fmt.Fprintln(f.out, "")
|
|
||||||
|
|
||||||
if len(f.failed) > 0 {
|
var failedStepsOutput []string
|
||||||
fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n")
|
for _, sr := range f.findStepResults(failed) {
|
||||||
for _, fail := range f.failed {
|
if sr.status == failed {
|
||||||
fmt.Fprintln(f.out, s(2)+red(fail.scenarioDesc())+blackb(" # "+fail.scenarioLine()))
|
sc := f.findScenario(sr.owner.AstNodeIds[0])
|
||||||
fmt.Fprintln(f.out, s(4)+red(strings.TrimSpace(fail.step.Keyword)+" "+fail.step.Text)+blackb(" # "+fail.line()))
|
scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, sr.owner.Name)
|
||||||
fmt.Fprintln(f.out, s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n")
|
scenarioLine := fmt.Sprintf("%s:%d", sr.owner.Uri, sc.Location.Line)
|
||||||
|
|
||||||
|
step := f.findStep(sr.step.AstNodeIds[0])
|
||||||
|
stepDesc := strings.TrimSpace(step.Keyword) + " " + sr.step.Text
|
||||||
|
stepLine := fmt.Sprintf("%s:%d", sr.owner.Uri, step.Location.Line)
|
||||||
|
|
||||||
|
failedStepsOutput = append(
|
||||||
|
failedStepsOutput,
|
||||||
|
s(2)+red(scenarioDesc)+blackb(" # "+scenarioLine),
|
||||||
|
s(4)+red(stepDesc)+blackb(" # "+stepLine),
|
||||||
|
s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", sr.err)),
|
||||||
|
"",
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if len(failedStepsOutput) > 0 {
|
||||||
|
fmt.Fprintln(f.out, "\n\n--- "+red("Failed steps:")+"\n")
|
||||||
|
fmt.Fprint(f.out, strings.Join(failedStepsOutput, "\n"))
|
||||||
|
}
|
||||||
|
fmt.Fprintln(f.out, "")
|
||||||
|
|
||||||
f.basefmt.Summary()
|
f.basefmt.Summary()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) step(res *stepResult) {
|
func (f *progress) step(res *stepResult) {
|
||||||
switch res.typ {
|
switch res.status {
|
||||||
case passed:
|
case passed:
|
||||||
fmt.Fprint(f.out, green("."))
|
fmt.Fprint(f.out, green("."))
|
||||||
case skipped:
|
case skipped:
|
||||||
|
@ -71,44 +89,49 @@ func (f *progress) step(res *stepResult) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
|
func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Passed(step, match)
|
f.basefmt.Passed(pickle, step, match)
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
f.step(f.passed[len(f.passed)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Skipped(step *gherkin.Step, match *StepDef) {
|
func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Skipped(step, match)
|
f.basefmt.Skipped(pickle, step, match)
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
f.step(f.skipped[len(f.skipped)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Undefined(step *gherkin.Step, match *StepDef) {
|
func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Undefined(step, match)
|
f.basefmt.Undefined(pickle, step, match)
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
f.step(f.undefined[len(f.undefined)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Failed(step *gherkin.Step, match *StepDef, err error) {
|
func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||||
f.basefmt.Failed(step, match, err)
|
f.basefmt.Failed(pickle, step, match, err)
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
f.step(f.failed[len(f.failed)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Pending(step *gherkin.Step, match *StepDef) {
|
func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||||
f.basefmt.Pending(step, match)
|
f.basefmt.Pending(pickle, step, match)
|
||||||
|
|
||||||
f.lock.Lock()
|
f.lock.Lock()
|
||||||
defer f.lock.Unlock()
|
defer f.lock.Unlock()
|
||||||
f.step(f.pending[len(f.pending)-1])
|
|
||||||
|
f.step(f.lastStepResult())
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *progress) Sync(cf ConcurrentFormatter) {
|
func (f *progress) Sync(cf ConcurrentFormatter) {
|
||||||
|
|
|
@ -3,28 +3,42 @@ package godog
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var basicGherkinFeature = `
|
||||||
|
Feature: basic
|
||||||
|
|
||||||
|
Scenario: passing scenario
|
||||||
|
When one
|
||||||
|
Then two
|
||||||
|
`
|
||||||
|
|
||||||
func TestProgressFormatterOutput(t *testing.T) {
|
func TestProgressFormatterOutput(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature))
|
const path = "any.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(sampleGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{
|
features: []*feature{{
|
||||||
Path: "any.feature",
|
GherkinDocument: gd,
|
||||||
Feature: feat,
|
pickles: pickles,
|
||||||
Content: []byte(sampleGherkinFeature),
|
Path: path,
|
||||||
|
Content: []byte(sampleGherkinFeature),
|
||||||
}},
|
}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^passing$`, func() error { return nil })
|
s.Step(`^passing$`, func() error { return nil })
|
||||||
|
@ -67,61 +81,52 @@ func FeatureContext(s *godog.Suite) {
|
||||||
s.Step(` + "`^next undefined$`" + `, nextUndefined)
|
s.Step(` + "`^next undefined$`" + `, nextUndefined)
|
||||||
}`
|
}`
|
||||||
|
|
||||||
|
require.True(t, r.run())
|
||||||
|
|
||||||
expected = trimAllLines(expected)
|
expected = trimAllLines(expected)
|
||||||
|
|
||||||
r.run()
|
|
||||||
|
|
||||||
actual := trimAllLines(buf.String())
|
actual := trimAllLines(buf.String())
|
||||||
|
|
||||||
shouldMatchOutput(expected, actual, t)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
}
|
||||||
|
|
||||||
var basicGherkinFeature = `
|
|
||||||
Feature: basic
|
|
||||||
|
|
||||||
Scenario: passing scenario
|
|
||||||
When one
|
|
||||||
Then two
|
|
||||||
`
|
|
||||||
|
|
||||||
func TestProgressFormatterWhenStepPanics(t *testing.T) {
|
func TestProgressFormatterWhenStepPanics(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^one$`, func() error { return nil })
|
s.Step(`^one$`, func() error { return nil })
|
||||||
s.Step(`^two$`, func() error { panic("omg") })
|
s.Step(`^two$`, func() error { panic("omg") })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.run() {
|
require.True(t, r.run())
|
||||||
t.Fatal("the suite should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
out := buf.String()
|
actual := buf.String()
|
||||||
if idx := strings.Index(out, "godog/fmt_progress_test.go:100"); idx == -1 {
|
assert.Contains(t, actual, "godog/fmt_progress_test.go:107")
|
||||||
t.Fatalf("expected to find panic stacktrace, actual:\n%s", out)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
|
func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^sub1$`, func() error { return nil })
|
s.Step(`^sub1$`, func() error { return nil })
|
||||||
s.Step(`^sub-sub$`, func() error { return nil })
|
s.Step(`^sub-sub$`, func() error { return nil })
|
||||||
|
@ -131,22 +136,22 @@ func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.run() {
|
assert.False(t, r.run())
|
||||||
t.Fatal("the suite should have passed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
|
func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "some.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat, Path: "some.feature"}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles, Path: path}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^sub1$`, func() error { return nil })
|
s.Step(`^sub1$`, func() error { return nil })
|
||||||
s.Step(`^sub-sub$`, func() error { return fmt.Errorf("errored") })
|
s.Step(`^sub-sub$`, func() error { return fmt.Errorf("errored") })
|
||||||
|
@ -156,9 +161,7 @@ func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.run() {
|
require.True(t, r.run())
|
||||||
t.Fatal("the suite should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `
|
expected := `
|
||||||
.F 2
|
.F 2
|
||||||
|
@ -178,48 +181,21 @@ Error: sub2: sub-sub: errored
|
||||||
|
|
||||||
expected = trimAllLines(expected)
|
expected = trimAllLines(expected)
|
||||||
actual := trimAllLines(buf.String())
|
actual := trimAllLines(buf.String())
|
||||||
|
assert.Equal(t, expected, actual)
|
||||||
shouldMatchOutput(expected, actual, t)
|
|
||||||
}
|
|
||||||
|
|
||||||
func shouldMatchOutput(expected, actual string, t *testing.T) {
|
|
||||||
act := []byte(actual)
|
|
||||||
exp := []byte(expected)
|
|
||||||
|
|
||||||
if len(act) != len(exp) {
|
|
||||||
t.Fatalf("content lengths do not match, expected: %d, actual %d, actual output:\n%s", len(exp), len(act), actual)
|
|
||||||
}
|
|
||||||
|
|
||||||
for i := 0; i < len(exp); i++ {
|
|
||||||
if act[i] == exp[i] {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
cpe := make([]byte, len(exp))
|
|
||||||
copy(cpe, exp)
|
|
||||||
e := append(exp[:i], '^')
|
|
||||||
e = append(e, cpe[i:]...)
|
|
||||||
|
|
||||||
cpa := make([]byte, len(act))
|
|
||||||
copy(cpa, act)
|
|
||||||
a := append(act[:i], '^')
|
|
||||||
a = append(a, cpa[i:]...)
|
|
||||||
|
|
||||||
t.Fatalf("expected output does not match:\n%s\n\n%s", string(a), string(e))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
|
func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^sub1$`, func() error { return nil })
|
s.Step(`^sub1$`, func() error { return nil })
|
||||||
s.Step(`^sub-sub$`, func() error { return nil })
|
s.Step(`^sub-sub$`, func() error { return nil })
|
||||||
|
@ -229,22 +205,22 @@ func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.run() {
|
assert.True(t, r.run())
|
||||||
t.Fatal("the suite should have failed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgressFormatterMultistepTemplates(t *testing.T) {
|
func TestProgressFormatterMultistepTemplates(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
}
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^sub-sub$`, func() error { return nil })
|
s.Step(`^sub-sub$`, func() error { return nil })
|
||||||
s.Step(`^substep$`, func() Steps { return Steps{"sub-sub", `unavailable "John" cost 5`, "one", "three"} })
|
s.Step(`^substep$`, func() Steps { return Steps{"sub-sub", `unavailable "John" cost 5`, "one", "three"} })
|
||||||
|
@ -253,9 +229,7 @@ func TestProgressFormatterMultistepTemplates(t *testing.T) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.run() {
|
require.False(t, r.run())
|
||||||
t.Fatal("the suite should have passed")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `
|
expected := `
|
||||||
.U 2
|
.U 2
|
||||||
|
@ -287,14 +261,13 @@ func FeatureContext(s *godog.Suite) {
|
||||||
`
|
`
|
||||||
|
|
||||||
expected = trimAllLines(expected)
|
expected = trimAllLines(expected)
|
||||||
|
|
||||||
actual := trimAllLines(buf.String())
|
actual := trimAllLines(buf.String())
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("expected output does not match: %s", actual)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgressFormatterWhenMultiStepHasArgument(t *testing.T) {
|
func TestProgressFormatterWhenMultiStepHasArgument(t *testing.T) {
|
||||||
|
const path = "any.feature"
|
||||||
|
|
||||||
var featureSource = `
|
var featureSource = `
|
||||||
Feature: basic
|
Feature: basic
|
||||||
|
@ -306,26 +279,28 @@ Feature: basic
|
||||||
text
|
text
|
||||||
"""
|
"""
|
||||||
`
|
`
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(featureSource))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(featureSource), (&messages.Incrementing{}).NewId)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
|
var buf bytes.Buffer
|
||||||
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", ioutil.Discard),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^one$`, func() error { return nil })
|
s.Step(`^one$`, func() error { return nil })
|
||||||
s.Step(`^two:$`, func(doc *gherkin.DocString) Steps { return Steps{"one"} })
|
s.Step(`^two:$`, func(doc *messages.PickleStepArgument_PickleDocString) Steps { return Steps{"one"} })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if r.run() {
|
assert.False(t, r.run())
|
||||||
t.Fatal("the suite should have passed")
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestProgressFormatterWhenMultiStepHasStepWithArgument(t *testing.T) {
|
func TestProgressFormatterWhenMultiStepHasStepWithArgument(t *testing.T) {
|
||||||
|
const path = "any.feature"
|
||||||
|
|
||||||
var featureSource = `
|
var featureSource = `
|
||||||
Feature: basic
|
Feature: basic
|
||||||
|
@ -334,10 +309,10 @@ Feature: basic
|
||||||
When one
|
When one
|
||||||
Then two`
|
Then two`
|
||||||
|
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(featureSource))
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(featureSource), (&messages.Incrementing{}).NewId)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("unexpected error: %v", err)
|
|
||||||
}
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
var subStep = `three:
|
var subStep = `three:
|
||||||
"""
|
"""
|
||||||
|
@ -348,17 +323,15 @@ Feature: basic
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", w),
|
fmt: progressFunc("progress", w),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^one$`, func() error { return nil })
|
s.Step(`^one$`, func() error { return nil })
|
||||||
s.Step(`^two$`, func() Steps { return Steps{subStep} })
|
s.Step(`^two$`, func() Steps { return Steps{subStep} })
|
||||||
s.Step(`^three:$`, func(doc *gherkin.DocString) error { return nil })
|
s.Step(`^three:$`, func(doc *messages.PickleStepArgument_PickleDocString) error { return nil })
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
if !r.run() {
|
require.True(t, r.run())
|
||||||
t.Fatal("the suite should have failed")
|
|
||||||
}
|
|
||||||
|
|
||||||
expected := `
|
expected := `
|
||||||
.F 2
|
.F 2
|
||||||
|
@ -366,8 +339,8 @@ Feature: basic
|
||||||
|
|
||||||
--- Failed steps:
|
--- Failed steps:
|
||||||
|
|
||||||
Scenario: passing scenario # :4
|
Scenario: passing scenario # any.feature:4
|
||||||
Then two # :6
|
Then two # any.feature:6
|
||||||
Error: nested steps cannot be multiline and have table or content body argument
|
Error: nested steps cannot be multiline and have table or content body argument
|
||||||
|
|
||||||
|
|
||||||
|
@ -378,7 +351,6 @@ Feature: basic
|
||||||
|
|
||||||
expected = trimAllLines(expected)
|
expected = trimAllLines(expected)
|
||||||
actual := trimAllLines(buf.String())
|
actual := trimAllLines(buf.String())
|
||||||
if actual != expected {
|
|
||||||
t.Fatalf("expected output does not match: %s", actual)
|
assert.Equal(t, expected, actual)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -21,16 +21,16 @@
|
||||||
|
|
||||||
--- <red>Failed steps:</red>
|
--- <red>Failed steps:</red>
|
||||||
|
|
||||||
<red>Scenario Outline: outline</red><bold-black> # formatter-tests/features/scenario_outline.feature:5</bold-black>
|
<red>Scenario Outline: outline</red> <bold-black># formatter-tests/features/scenario_outline.feature:5</bold-black>
|
||||||
<red>Then odd 2 and even 0 number</red><bold-black> # formatter-tests/features/scenario_outline.feature:8</bold-black>
|
<red>Then odd 2 and even 0 number</red> <bold-black># formatter-tests/features/scenario_outline.feature:8</bold-black>
|
||||||
<red>Error: </red><bold-red>2 is not odd</bold-red>
|
<red>Error: </red><bold-red>2 is not odd</bold-red>
|
||||||
|
|
||||||
<red>Scenario Outline: outline</red><bold-black> # formatter-tests/features/scenario_outline.feature:5</bold-black>
|
<red>Scenario Outline: outline</red> <bold-black># formatter-tests/features/scenario_outline.feature:5</bold-black>
|
||||||
<red>Then odd 3 and even 11 number</red><bold-black> # formatter-tests/features/scenario_outline.feature:8</bold-black>
|
<red>Then odd 3 and even 11 number</red> <bold-black># formatter-tests/features/scenario_outline.feature:8</bold-black>
|
||||||
<red>Error: </red><bold-red>11 is not even</bold-red>
|
<red>Error: </red><bold-red>11 is not even</bold-red>
|
||||||
|
|
||||||
<red>Scenario Outline: outline</red><bold-black> # formatter-tests/features/scenario_outline.feature:5</bold-black>
|
<red>Scenario Outline: outline</red> <bold-black># formatter-tests/features/scenario_outline.feature:5</bold-black>
|
||||||
<red>Then odd 3 and even 9 number</red><bold-black> # formatter-tests/features/scenario_outline.feature:8</bold-black>
|
<red>Then odd 3 and even 9 number</red> <bold-black># formatter-tests/features/scenario_outline.feature:8</bold-black>
|
||||||
<red>Error: </red><bold-red>9 is not even</bold-red>
|
<red>Error: </red><bold-red>9 is not even</bold-red>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,8 @@
|
||||||
|
|
||||||
--- <red>Failed steps:</red>
|
--- <red>Failed steps:</red>
|
||||||
|
|
||||||
<red>Scenario: failing</red><bold-black> # formatter-tests/features/some_scenarions_including_failing.feature:3</bold-black>
|
<red>Scenario: failing</red> <bold-black># formatter-tests/features/some_scenarions_including_failing.feature:3</bold-black>
|
||||||
<red>When failing step</red><bold-black> # formatter-tests/features/some_scenarions_including_failing.feature:5</bold-black>
|
<red>When failing step</red> <bold-black># formatter-tests/features/some_scenarions_including_failing.feature:5</bold-black>
|
||||||
<red>Error: </red><bold-red>step failed</bold-red>
|
<red>Error: </red><bold-red>step failed</bold-red>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -10,17 +10,16 @@
|
||||||
<cyan>Then</cyan> <cyan>passing step</cyan> <bold-black># formatters_print_test.go:65 -> passingStepDef</bold-black>
|
<cyan>Then</cyan> <cyan>passing step</cyan> <bold-black># formatters_print_test.go:65 -> passingStepDef</bold-black>
|
||||||
|
|
||||||
<bold-white>Scenario:</bold-white> two <bold-black># formatter-tests/features/two_scenarios_with_background_fail.feature:11</bold-black>
|
<bold-white>Scenario:</bold-white> two <bold-black># formatter-tests/features/two_scenarios_with_background_fail.feature:11</bold-black>
|
||||||
<bold-red>step failed</bold-red>
|
|
||||||
<cyan>Then</cyan> <cyan>passing step</cyan> <bold-black># formatters_print_test.go:65 -> passingStepDef</bold-black>
|
<cyan>Then</cyan> <cyan>passing step</cyan> <bold-black># formatters_print_test.go:65 -> passingStepDef</bold-black>
|
||||||
|
|
||||||
--- <red>Failed steps:</red>
|
--- <red>Failed steps:</red>
|
||||||
|
|
||||||
<red>Scenario: one</red><bold-black> # formatter-tests/features/two_scenarios_with_background_fail.feature:7</bold-black>
|
<red>Scenario: one</red> <bold-black># formatter-tests/features/two_scenarios_with_background_fail.feature:7</bold-black>
|
||||||
<red>And failing step</red><bold-black> # formatter-tests/features/two_scenarios_with_background_fail.feature:5</bold-black>
|
<red>And failing step</red> <bold-black># formatter-tests/features/two_scenarios_with_background_fail.feature:5</bold-black>
|
||||||
<red>Error: </red><bold-red>step failed</bold-red>
|
<red>Error: </red><bold-red>step failed</bold-red>
|
||||||
|
|
||||||
<red>Scenario: two</red><bold-black> # formatter-tests/features/two_scenarios_with_background_fail.feature:11</bold-black>
|
<red>Scenario: two</red> <bold-black># formatter-tests/features/two_scenarios_with_background_fail.feature:11</bold-black>
|
||||||
<red>And failing step</red><bold-black> # formatter-tests/features/two_scenarios_with_background_fail.feature:5</bold-black>
|
<red>And failing step</red> <bold-black># formatter-tests/features/two_scenarios_with_background_fail.feature:5</bold-black>
|
||||||
<red>Error: </red><bold-red>step failed</bold-red>
|
<red>Error: </red><bold-red>step failed</bold-red>
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -8,13 +8,14 @@ import (
|
||||||
"path"
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrintingFormatters(t *testing.T) {
|
func TestPrintingFormatters(t *testing.T) {
|
||||||
features, err := parseFeatures("", []string{"formatter-tests"})
|
features, err := parseFeatures("", []string{"formatter-tests"})
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatalf("failed to parse formatter features: %v", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
out := &tagColorWriter{w: &buf}
|
out := &tagColorWriter{w: &buf}
|
||||||
|
@ -44,19 +45,18 @@ func TestPrintingFormatters(t *testing.T) {
|
||||||
suite.features = []*feature{feat} // set the feature
|
suite.features = []*feature{feat} // set the feature
|
||||||
|
|
||||||
expectedOutput, err := ioutil.ReadFile(expectOutputPath)
|
expectedOutput, err := ioutil.ReadFile(expectOutputPath)
|
||||||
if err != nil {
|
require.NoError(t, err)
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
suite.run()
|
suite.run()
|
||||||
suite.fmt.Summary()
|
suite.fmt.Summary()
|
||||||
|
|
||||||
expected := string(expectedOutput)
|
expected := string(expectedOutput)
|
||||||
actual := buf.String()
|
expected = trimAllLines(expected)
|
||||||
|
|
||||||
if actual != expected {
|
actual := buf.String()
|
||||||
t.Fatalf("%s does not match to:\n%s", expectOutputPath, actual)
|
actual = trimAllLines(actual)
|
||||||
}
|
|
||||||
|
assert.Equalf(t, expected, actual, "expected: [%s], actual: [%s]", expected, actual)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
os.Setenv("GODOG_TESTED_PACKAGE", pkg)
|
os.Setenv("GODOG_TESTED_PACKAGE", pkg)
|
||||||
|
|
36
gherkin.go
36
gherkin.go
|
@ -1,36 +0,0 @@
|
||||||
package godog
|
|
||||||
|
|
||||||
import "github.com/cucumber/godog/gherkin"
|
|
||||||
|
|
||||||
// examples is a helper func to cast gherkin.Examples
|
|
||||||
// or gherkin.BaseExamples if its empty
|
|
||||||
// @TODO: this should go away with gherkin update
|
|
||||||
func examples(ex interface{}) (*gherkin.Examples, bool) {
|
|
||||||
t, ok := ex.(*gherkin.Examples)
|
|
||||||
return t, ok
|
|
||||||
}
|
|
||||||
|
|
||||||
// means there are no scenarios or they do not have steps
|
|
||||||
func isEmptyFeature(ft *gherkin.Feature) bool {
|
|
||||||
for _, def := range ft.ScenarioDefinitions {
|
|
||||||
if !isEmptyScenario(def) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
// means scenario dooes not have steps
|
|
||||||
func isEmptyScenario(def interface{}) bool {
|
|
||||||
switch t := def.(type) {
|
|
||||||
case *gherkin.Scenario:
|
|
||||||
if len(t.Steps) > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
if len(t.Steps) > 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true
|
|
||||||
}
|
|
|
@ -1,21 +0,0 @@
|
||||||
The MIT License (MIT)
|
|
||||||
|
|
||||||
Copyright (c) 2014-2016 Cucumber Ltd, Gaspar Nagy
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in
|
|
||||||
all copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
||||||
THE SOFTWARE.
|
|
|
@ -1,3 +0,0 @@
|
||||||
[](http://travis-ci.org/cucumber/gherkin-go)
|
|
||||||
|
|
||||||
Gherkin parser/compiler for Go. Please see [Gherkin](https://github.com/cucumber/gherkin) for details.
|
|
|
@ -1,95 +0,0 @@
|
||||||
package gherkin
|
|
||||||
|
|
||||||
type Location struct {
|
|
||||||
Line int `json:"line"`
|
|
||||||
Column int `json:"column"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Node struct {
|
|
||||||
Location *Location `json:"location,omitempty"`
|
|
||||||
Type string `json:"type"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Feature struct {
|
|
||||||
Node
|
|
||||||
Tags []*Tag `json:"tags"`
|
|
||||||
Language string `json:"language,omitempty"`
|
|
||||||
Keyword string `json:"keyword"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Background *Background `json:"background,omitempty"`
|
|
||||||
ScenarioDefinitions []interface{} `json:"scenarioDefinitions"`
|
|
||||||
Comments []*Comment `json:"comments"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Comment struct {
|
|
||||||
Node
|
|
||||||
Text string `json:"text"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Tag struct {
|
|
||||||
Node
|
|
||||||
Name string `json:"name"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Background struct {
|
|
||||||
ScenarioDefinition
|
|
||||||
}
|
|
||||||
|
|
||||||
type Scenario struct {
|
|
||||||
ScenarioDefinition
|
|
||||||
Tags []*Tag `json:"tags"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScenarioOutline struct {
|
|
||||||
ScenarioDefinition
|
|
||||||
Tags []*Tag `json:"tags"`
|
|
||||||
Examples []*Examples `json:"examples,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Examples struct {
|
|
||||||
Node
|
|
||||||
Tags []*Tag `json:"tags"`
|
|
||||||
Keyword string `json:"keyword"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
TableHeader *TableRow `json:"tableHeader"`
|
|
||||||
TableBody []*TableRow `json:"tableBody"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TableRow struct {
|
|
||||||
Node
|
|
||||||
Cells []*TableCell `json:"cells"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type TableCell struct {
|
|
||||||
Node
|
|
||||||
Value string `json:"value"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type ScenarioDefinition struct {
|
|
||||||
Node
|
|
||||||
Keyword string `json:"keyword"`
|
|
||||||
Name string `json:"name"`
|
|
||||||
Description string `json:"description,omitempty"`
|
|
||||||
Steps []*Step `json:"steps"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type Step struct {
|
|
||||||
Node
|
|
||||||
Keyword string `json:"keyword"`
|
|
||||||
Text string `json:"text"`
|
|
||||||
Argument interface{} `json:"argument,omitempty"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DocString struct {
|
|
||||||
Node
|
|
||||||
ContentType string `json:"contentType,omitempty"`
|
|
||||||
Content string `json:"content"`
|
|
||||||
Delimitter string `json:"-"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type DataTable struct {
|
|
||||||
Node
|
|
||||||
Rows []*TableRow `json:"rows"`
|
|
||||||
}
|
|
|
@ -1,378 +0,0 @@
|
||||||
package gherkin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type AstBuilder interface {
|
|
||||||
Builder
|
|
||||||
GetFeature() *Feature
|
|
||||||
}
|
|
||||||
|
|
||||||
type astBuilder struct {
|
|
||||||
stack []*astNode
|
|
||||||
comments []*Comment
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) Reset() {
|
|
||||||
t.comments = []*Comment{}
|
|
||||||
t.stack = []*astNode{}
|
|
||||||
t.push(newAstNode(RuleType_None))
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) GetFeature() *Feature {
|
|
||||||
res := t.currentNode().getSingle(RuleType_Feature)
|
|
||||||
if val, ok := res.(*Feature); ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type astNode struct {
|
|
||||||
ruleType RuleType
|
|
||||||
subNodes map[RuleType][]interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *astNode) add(rt RuleType, obj interface{}) {
|
|
||||||
a.subNodes[rt] = append(a.subNodes[rt], obj)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *astNode) getSingle(rt RuleType) interface{} {
|
|
||||||
if val, ok := a.subNodes[rt]; ok {
|
|
||||||
for i := range val {
|
|
||||||
return val[i]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *astNode) getItems(rt RuleType) []interface{} {
|
|
||||||
var res []interface{}
|
|
||||||
if val, ok := a.subNodes[rt]; ok {
|
|
||||||
for i := range val {
|
|
||||||
res = append(res, val[i])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return res
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *astNode) getToken(tt TokenType) *Token {
|
|
||||||
if val, ok := a.getSingle(tt.RuleType()).(*Token); ok {
|
|
||||||
return val
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *astNode) getTokens(tt TokenType) []*Token {
|
|
||||||
var items = a.getItems(tt.RuleType())
|
|
||||||
var tokens []*Token
|
|
||||||
for i := range items {
|
|
||||||
if val, ok := items[i].(*Token); ok {
|
|
||||||
tokens = append(tokens, val)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return tokens
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) currentNode() *astNode {
|
|
||||||
if len(t.stack) > 0 {
|
|
||||||
return t.stack[len(t.stack)-1]
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func newAstNode(rt RuleType) *astNode {
|
|
||||||
return &astNode{
|
|
||||||
ruleType: rt,
|
|
||||||
subNodes: make(map[RuleType][]interface{}),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewAstBuilder() AstBuilder {
|
|
||||||
builder := new(astBuilder)
|
|
||||||
builder.comments = []*Comment{}
|
|
||||||
builder.push(newAstNode(RuleType_None))
|
|
||||||
return builder
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) push(n *astNode) {
|
|
||||||
t.stack = append(t.stack, n)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) pop() *astNode {
|
|
||||||
x := t.stack[len(t.stack)-1]
|
|
||||||
t.stack = t.stack[:len(t.stack)-1]
|
|
||||||
return x
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) Build(tok *Token) (bool, error) {
|
|
||||||
if tok.Type == TokenType_Comment {
|
|
||||||
comment := new(Comment)
|
|
||||||
comment.Type = "Comment"
|
|
||||||
comment.Location = astLocation(tok)
|
|
||||||
comment.Text = tok.Text
|
|
||||||
t.comments = append(t.comments, comment)
|
|
||||||
} else {
|
|
||||||
t.currentNode().add(tok.Type.RuleType(), tok)
|
|
||||||
}
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
func (t *astBuilder) StartRule(r RuleType) (bool, error) {
|
|
||||||
t.push(newAstNode(r))
|
|
||||||
return true, nil
|
|
||||||
}
|
|
||||||
func (t *astBuilder) EndRule(r RuleType) (bool, error) {
|
|
||||||
node := t.pop()
|
|
||||||
transformedNode, err := t.transformNode(node)
|
|
||||||
t.currentNode().add(node.ruleType, transformedNode)
|
|
||||||
return true, err
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *astBuilder) transformNode(node *astNode) (interface{}, error) {
|
|
||||||
switch node.ruleType {
|
|
||||||
|
|
||||||
case RuleType_Step:
|
|
||||||
stepLine := node.getToken(TokenType_StepLine)
|
|
||||||
step := new(Step)
|
|
||||||
step.Type = "Step"
|
|
||||||
step.Location = astLocation(stepLine)
|
|
||||||
step.Keyword = stepLine.Keyword
|
|
||||||
step.Text = stepLine.Text
|
|
||||||
step.Argument = node.getSingle(RuleType_DataTable)
|
|
||||||
if step.Argument == nil {
|
|
||||||
step.Argument = node.getSingle(RuleType_DocString)
|
|
||||||
}
|
|
||||||
return step, nil
|
|
||||||
|
|
||||||
case RuleType_DocString:
|
|
||||||
separatorToken := node.getToken(TokenType_DocStringSeparator)
|
|
||||||
contentType := separatorToken.Text
|
|
||||||
lineTokens := node.getTokens(TokenType_Other)
|
|
||||||
var text string
|
|
||||||
for i := range lineTokens {
|
|
||||||
if i > 0 {
|
|
||||||
text += "\n"
|
|
||||||
}
|
|
||||||
text += lineTokens[i].Text
|
|
||||||
}
|
|
||||||
ds := new(DocString)
|
|
||||||
ds.Type = "DocString"
|
|
||||||
ds.Location = astLocation(separatorToken)
|
|
||||||
ds.ContentType = contentType
|
|
||||||
ds.Content = text
|
|
||||||
ds.Delimitter = DOCSTRING_SEPARATOR // TODO: remember separator
|
|
||||||
return ds, nil
|
|
||||||
|
|
||||||
case RuleType_DataTable:
|
|
||||||
rows, err := astTableRows(node)
|
|
||||||
dt := new(DataTable)
|
|
||||||
dt.Type = "DataTable"
|
|
||||||
dt.Location = rows[0].Location
|
|
||||||
dt.Rows = rows
|
|
||||||
return dt, err
|
|
||||||
|
|
||||||
case RuleType_Background:
|
|
||||||
backgroundLine := node.getToken(TokenType_BackgroundLine)
|
|
||||||
description, _ := node.getSingle(RuleType_Description).(string)
|
|
||||||
bg := new(Background)
|
|
||||||
bg.Type = "Background"
|
|
||||||
bg.Location = astLocation(backgroundLine)
|
|
||||||
bg.Keyword = backgroundLine.Keyword
|
|
||||||
bg.Name = backgroundLine.Text
|
|
||||||
bg.Description = description
|
|
||||||
bg.Steps = astSteps(node)
|
|
||||||
return bg, nil
|
|
||||||
|
|
||||||
case RuleType_Scenario_Definition:
|
|
||||||
tags := astTags(node)
|
|
||||||
scenarioNode, _ := node.getSingle(RuleType_Scenario).(*astNode)
|
|
||||||
if scenarioNode != nil {
|
|
||||||
scenarioLine := scenarioNode.getToken(TokenType_ScenarioLine)
|
|
||||||
description, _ := scenarioNode.getSingle(RuleType_Description).(string)
|
|
||||||
sc := new(Scenario)
|
|
||||||
sc.Type = "Scenario"
|
|
||||||
sc.Tags = tags
|
|
||||||
sc.Location = astLocation(scenarioLine)
|
|
||||||
sc.Keyword = scenarioLine.Keyword
|
|
||||||
sc.Name = scenarioLine.Text
|
|
||||||
sc.Description = description
|
|
||||||
sc.Steps = astSteps(scenarioNode)
|
|
||||||
return sc, nil
|
|
||||||
} else {
|
|
||||||
scenarioOutlineNode, ok := node.getSingle(RuleType_ScenarioOutline).(*astNode)
|
|
||||||
if !ok {
|
|
||||||
panic("Internal grammar error")
|
|
||||||
}
|
|
||||||
scenarioOutlineLine := scenarioOutlineNode.getToken(TokenType_ScenarioOutlineLine)
|
|
||||||
description, _ := scenarioOutlineNode.getSingle(RuleType_Description).(string)
|
|
||||||
sc := new(ScenarioOutline)
|
|
||||||
sc.Type = "ScenarioOutline"
|
|
||||||
sc.Tags = tags
|
|
||||||
sc.Location = astLocation(scenarioOutlineLine)
|
|
||||||
sc.Keyword = scenarioOutlineLine.Keyword
|
|
||||||
sc.Name = scenarioOutlineLine.Text
|
|
||||||
sc.Description = description
|
|
||||||
sc.Steps = astSteps(scenarioOutlineNode)
|
|
||||||
sc.Examples = astExamples(scenarioOutlineNode)
|
|
||||||
return sc, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
case RuleType_Examples_Definition:
|
|
||||||
tags := astTags(node)
|
|
||||||
examplesNode, _ := node.getSingle(RuleType_Examples).(*astNode)
|
|
||||||
examplesLine := examplesNode.getToken(TokenType_ExamplesLine)
|
|
||||||
description, _ := examplesNode.getSingle(RuleType_Description).(string)
|
|
||||||
allRows, err := astTableRows(examplesNode)
|
|
||||||
ex := new(Examples)
|
|
||||||
ex.Type = "Examples"
|
|
||||||
ex.Tags = tags
|
|
||||||
ex.Location = astLocation(examplesLine)
|
|
||||||
ex.Keyword = examplesLine.Keyword
|
|
||||||
ex.Name = examplesLine.Text
|
|
||||||
ex.Description = description
|
|
||||||
ex.TableHeader = allRows[0]
|
|
||||||
ex.TableBody = allRows[1:]
|
|
||||||
return ex, err
|
|
||||||
|
|
||||||
case RuleType_Description:
|
|
||||||
lineTokens := node.getTokens(TokenType_Other)
|
|
||||||
// Trim trailing empty lines
|
|
||||||
end := len(lineTokens)
|
|
||||||
for end > 0 && strings.TrimSpace(lineTokens[end-1].Text) == "" {
|
|
||||||
end--
|
|
||||||
}
|
|
||||||
var desc []string
|
|
||||||
for i := range lineTokens[0:end] {
|
|
||||||
desc = append(desc, lineTokens[i].Text)
|
|
||||||
}
|
|
||||||
return strings.Join(desc, "\n"), nil
|
|
||||||
|
|
||||||
case RuleType_Feature:
|
|
||||||
header, ok := node.getSingle(RuleType_Feature_Header).(*astNode)
|
|
||||||
if !ok {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
tags := astTags(header)
|
|
||||||
featureLine := header.getToken(TokenType_FeatureLine)
|
|
||||||
if featureLine == nil {
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
background, _ := node.getSingle(RuleType_Background).(*Background)
|
|
||||||
scenarioDefinitions := node.getItems(RuleType_Scenario_Definition)
|
|
||||||
if scenarioDefinitions == nil {
|
|
||||||
scenarioDefinitions = []interface{}{}
|
|
||||||
}
|
|
||||||
description, _ := header.getSingle(RuleType_Description).(string)
|
|
||||||
|
|
||||||
feat := new(Feature)
|
|
||||||
feat.Type = "Feature"
|
|
||||||
feat.Tags = tags
|
|
||||||
feat.Location = astLocation(featureLine)
|
|
||||||
feat.Language = featureLine.GherkinDialect
|
|
||||||
feat.Keyword = featureLine.Keyword
|
|
||||||
feat.Name = featureLine.Text
|
|
||||||
feat.Description = description
|
|
||||||
feat.Background = background
|
|
||||||
feat.ScenarioDefinitions = scenarioDefinitions
|
|
||||||
feat.Comments = t.comments
|
|
||||||
return feat, nil
|
|
||||||
}
|
|
||||||
return node, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func astLocation(t *Token) *Location {
|
|
||||||
return &Location{
|
|
||||||
Line: t.Location.Line,
|
|
||||||
Column: t.Location.Column,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func astTableRows(t *astNode) (rows []*TableRow, err error) {
|
|
||||||
rows = []*TableRow{}
|
|
||||||
tokens := t.getTokens(TokenType_TableRow)
|
|
||||||
for i := range tokens {
|
|
||||||
row := new(TableRow)
|
|
||||||
row.Type = "TableRow"
|
|
||||||
row.Location = astLocation(tokens[i])
|
|
||||||
row.Cells = astTableCells(tokens[i])
|
|
||||||
rows = append(rows, row)
|
|
||||||
}
|
|
||||||
err = ensureCellCount(rows)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func ensureCellCount(rows []*TableRow) error {
|
|
||||||
if len(rows) <= 1 {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
cellCount := len(rows[0].Cells)
|
|
||||||
for i := range rows {
|
|
||||||
if cellCount != len(rows[i].Cells) {
|
|
||||||
return &parseError{"inconsistent cell count within the table", &Location{
|
|
||||||
Line: rows[i].Location.Line,
|
|
||||||
Column: rows[i].Location.Column,
|
|
||||||
}}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func astTableCells(t *Token) (cells []*TableCell) {
|
|
||||||
cells = []*TableCell{}
|
|
||||||
for i := range t.Items {
|
|
||||||
item := t.Items[i]
|
|
||||||
cell := new(TableCell)
|
|
||||||
cell.Type = "TableCell"
|
|
||||||
cell.Location = &Location{
|
|
||||||
Line: t.Location.Line,
|
|
||||||
Column: item.Column,
|
|
||||||
}
|
|
||||||
cell.Value = item.Text
|
|
||||||
cells = append(cells, cell)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func astSteps(t *astNode) (steps []*Step) {
|
|
||||||
steps = []*Step{}
|
|
||||||
tokens := t.getItems(RuleType_Step)
|
|
||||||
for i := range tokens {
|
|
||||||
step, _ := tokens[i].(*Step)
|
|
||||||
steps = append(steps, step)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func astExamples(t *astNode) (examples []*Examples) {
|
|
||||||
examples = []*Examples{}
|
|
||||||
tokens := t.getItems(RuleType_Examples_Definition)
|
|
||||||
for i := range tokens {
|
|
||||||
example, _ := tokens[i].(*Examples)
|
|
||||||
examples = append(examples, example)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func astTags(node *astNode) (tags []*Tag) {
|
|
||||||
tags = []*Tag{}
|
|
||||||
tagsNode, ok := node.getSingle(RuleType_Tags).(*astNode)
|
|
||||||
if !ok {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
tokens := tagsNode.getTokens(TokenType_TagLine)
|
|
||||||
for i := range tokens {
|
|
||||||
token := tokens[i]
|
|
||||||
for k := range token.Items {
|
|
||||||
item := token.Items[k]
|
|
||||||
tag := new(Tag)
|
|
||||||
tag.Type = "Tag"
|
|
||||||
tag.Location = &Location{
|
|
||||||
Line: token.Location.Line,
|
|
||||||
Column: item.Column,
|
|
||||||
}
|
|
||||||
tag.Name = item.Text
|
|
||||||
tags = append(tags, tag)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
|
@ -1,47 +0,0 @@
|
||||||
package gherkin
|
|
||||||
|
|
||||||
type GherkinDialect struct {
|
|
||||||
Language string
|
|
||||||
Name string
|
|
||||||
Native string
|
|
||||||
Keywords map[string][]string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GherkinDialect) FeatureKeywords() []string {
|
|
||||||
return g.Keywords["feature"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GherkinDialect) ScenarioKeywords() []string {
|
|
||||||
return g.Keywords["scenario"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GherkinDialect) StepKeywords() []string {
|
|
||||||
result := g.Keywords["given"]
|
|
||||||
result = append(result, g.Keywords["when"]...)
|
|
||||||
result = append(result, g.Keywords["then"]...)
|
|
||||||
result = append(result, g.Keywords["and"]...)
|
|
||||||
result = append(result, g.Keywords["but"]...)
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GherkinDialect) BackgroundKeywords() []string {
|
|
||||||
return g.Keywords["background"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GherkinDialect) ScenarioOutlineKeywords() []string {
|
|
||||||
return g.Keywords["scenarioOutline"]
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *GherkinDialect) ExamplesKeywords() []string {
|
|
||||||
return g.Keywords["examples"]
|
|
||||||
}
|
|
||||||
|
|
||||||
type GherkinDialectProvider interface {
|
|
||||||
GetDialect(language string) *GherkinDialect
|
|
||||||
}
|
|
||||||
|
|
||||||
type gherkinDialectMap map[string]*GherkinDialect
|
|
||||||
|
|
||||||
func (g gherkinDialectMap) GetDialect(language string) *GherkinDialect {
|
|
||||||
return g[language]
|
|
||||||
}
|
|
Различия файлов не показаны, т.к. их слишком много
Показать различия
|
@ -1,137 +0,0 @@
|
||||||
package gherkin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"bufio"
|
|
||||||
"fmt"
|
|
||||||
"io"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Parser interface {
|
|
||||||
StopAtFirstError(b bool)
|
|
||||||
Parse(s Scanner, m Matcher) (err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
The scanner reads a gherkin doc (typically read from a .feature file) and creates a token for
|
|
||||||
each line. The tokens are passed to the parser, which outputs an AST (Abstract Syntax Tree).
|
|
||||||
|
|
||||||
If the scanner sees a # language header, it will reconfigure itself dynamically to look for
|
|
||||||
Gherkin keywords for the associated language. The keywords are defined in gherkin-languages.json.
|
|
||||||
*/
|
|
||||||
type Scanner interface {
|
|
||||||
Scan() (line *Line, atEof bool, err error)
|
|
||||||
}
|
|
||||||
|
|
||||||
type Builder interface {
|
|
||||||
Build(*Token) (bool, error)
|
|
||||||
StartRule(RuleType) (bool, error)
|
|
||||||
EndRule(RuleType) (bool, error)
|
|
||||||
Reset()
|
|
||||||
}
|
|
||||||
|
|
||||||
type Token struct {
|
|
||||||
Type TokenType
|
|
||||||
Keyword string
|
|
||||||
Text string
|
|
||||||
Items []*LineSpan
|
|
||||||
GherkinDialect string
|
|
||||||
Indent string
|
|
||||||
Location *Location
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *Token) IsEOF() bool {
|
|
||||||
return t.Type == TokenType_EOF
|
|
||||||
}
|
|
||||||
func (t *Token) String() string {
|
|
||||||
return fmt.Sprintf("%s: %s/%s", t.Type.Name(), t.Keyword, t.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
type LineSpan struct {
|
|
||||||
Column int
|
|
||||||
Text string
|
|
||||||
}
|
|
||||||
|
|
||||||
func (l *LineSpan) String() string {
|
|
||||||
return fmt.Sprintf("%d:%s", l.Column, l.Text)
|
|
||||||
}
|
|
||||||
|
|
||||||
type parser struct {
|
|
||||||
builder Builder
|
|
||||||
stopAtFirstError bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewParser(b Builder) Parser {
|
|
||||||
return &parser{
|
|
||||||
builder: b,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (p *parser) StopAtFirstError(b bool) {
|
|
||||||
p.stopAtFirstError = b
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewScanner(r io.Reader) Scanner {
|
|
||||||
return &scanner{
|
|
||||||
s: bufio.NewScanner(r),
|
|
||||||
line: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type scanner struct {
|
|
||||||
s *bufio.Scanner
|
|
||||||
line int
|
|
||||||
}
|
|
||||||
|
|
||||||
func (t *scanner) Scan() (line *Line, atEof bool, err error) {
|
|
||||||
scanning := t.s.Scan()
|
|
||||||
if !scanning {
|
|
||||||
err = t.s.Err()
|
|
||||||
if err == nil {
|
|
||||||
atEof = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if err == nil {
|
|
||||||
t.line += 1
|
|
||||||
str := t.s.Text()
|
|
||||||
line = &Line{str, t.line, strings.TrimLeft(str, " \t"), atEof}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
type Line struct {
|
|
||||||
LineText string
|
|
||||||
LineNumber int
|
|
||||||
TrimmedLineText string
|
|
||||||
AtEof bool
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Line) Indent() int {
|
|
||||||
return len(g.LineText) - len(g.TrimmedLineText)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Line) IsEmpty() bool {
|
|
||||||
return len(g.TrimmedLineText) == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Line) IsEof() bool {
|
|
||||||
return g.AtEof
|
|
||||||
}
|
|
||||||
|
|
||||||
func (g *Line) StartsWith(prefix string) bool {
|
|
||||||
return strings.HasPrefix(g.TrimmedLineText, prefix)
|
|
||||||
}
|
|
||||||
|
|
||||||
func ParseFeature(in io.Reader) (feature *Feature, err error) {
|
|
||||||
|
|
||||||
builder := NewAstBuilder()
|
|
||||||
parser := NewParser(builder)
|
|
||||||
parser.StopAtFirstError(false)
|
|
||||||
matcher := NewMatcher(GherkinDialectsBuildin())
|
|
||||||
|
|
||||||
scanner := NewScanner(in)
|
|
||||||
|
|
||||||
err = parser.Parse(scanner, matcher)
|
|
||||||
|
|
||||||
return builder.GetFeature(), err
|
|
||||||
}
|
|
|
@ -1,270 +0,0 @@
|
||||||
package gherkin
|
|
||||||
|
|
||||||
import (
|
|
||||||
"regexp"
|
|
||||||
"strings"
|
|
||||||
"unicode/utf8"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
DEFAULT_DIALECT = "en"
|
|
||||||
COMMENT_PREFIX = "#"
|
|
||||||
TAG_PREFIX = "@"
|
|
||||||
TITLE_KEYWORD_SEPARATOR = ":"
|
|
||||||
TABLE_CELL_SEPARATOR = '|'
|
|
||||||
ESCAPE_CHAR = '\\'
|
|
||||||
ESCAPED_NEWLINE = 'n'
|
|
||||||
DOCSTRING_SEPARATOR = "\"\"\""
|
|
||||||
DOCSTRING_ALTERNATIVE_SEPARATOR = "```"
|
|
||||||
)
|
|
||||||
|
|
||||||
type matcher struct {
|
|
||||||
gdp GherkinDialectProvider
|
|
||||||
default_lang string
|
|
||||||
lang string
|
|
||||||
dialect *GherkinDialect
|
|
||||||
activeDocStringSeparator string
|
|
||||||
indentToRemove int
|
|
||||||
languagePattern *regexp.Regexp
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewMatcher(gdp GherkinDialectProvider) Matcher {
|
|
||||||
return &matcher{
|
|
||||||
gdp: gdp,
|
|
||||||
default_lang: DEFAULT_DIALECT,
|
|
||||||
lang: DEFAULT_DIALECT,
|
|
||||||
dialect: gdp.GetDialect(DEFAULT_DIALECT),
|
|
||||||
languagePattern: regexp.MustCompile("^\\s*#\\s*language\\s*:\\s*([a-zA-Z\\-_]+)\\s*$"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewLanguageMatcher(gdp GherkinDialectProvider, language string) Matcher {
|
|
||||||
return &matcher{
|
|
||||||
gdp: gdp,
|
|
||||||
default_lang: language,
|
|
||||||
lang: language,
|
|
||||||
dialect: gdp.GetDialect(language),
|
|
||||||
languagePattern: regexp.MustCompile("^\\s*#\\s*language\\s*:\\s*([a-zA-Z\\-_]+)\\s*$"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) Reset() {
|
|
||||||
m.indentToRemove = 0
|
|
||||||
m.activeDocStringSeparator = ""
|
|
||||||
if m.lang != "en" {
|
|
||||||
m.dialect = m.gdp.GetDialect(m.default_lang)
|
|
||||||
m.lang = "en"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) newTokenAtLocation(line, index int) (token *Token) {
|
|
||||||
column := index + 1
|
|
||||||
token = new(Token)
|
|
||||||
token.GherkinDialect = m.lang
|
|
||||||
token.Location = &Location{line, column}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchEOF(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
if line.IsEof() {
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_EOF
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchEmpty(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
if line.IsEmpty() {
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_Empty
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchComment(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
if line.StartsWith(COMMENT_PREFIX) {
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, 0), true
|
|
||||||
token.Type = TokenType_Comment
|
|
||||||
token.Text = line.LineText
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchTagLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
if line.StartsWith(TAG_PREFIX) {
|
|
||||||
var tags []*LineSpan
|
|
||||||
var column = line.Indent()
|
|
||||||
splits := strings.Split(line.TrimmedLineText, TAG_PREFIX)
|
|
||||||
for i := range splits {
|
|
||||||
txt := strings.Trim(splits[i], " ")
|
|
||||||
if txt != "" {
|
|
||||||
tags = append(tags, &LineSpan{column, TAG_PREFIX + txt})
|
|
||||||
}
|
|
||||||
column = column + len(splits[i]) + 1
|
|
||||||
}
|
|
||||||
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_TagLine
|
|
||||||
token.Items = tags
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) matchTitleLine(line *Line, tokenType TokenType, keywords []string) (ok bool, token *Token, err error) {
|
|
||||||
for i := range keywords {
|
|
||||||
keyword := keywords[i]
|
|
||||||
if line.StartsWith(keyword + TITLE_KEYWORD_SEPARATOR) {
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = tokenType
|
|
||||||
token.Keyword = keyword
|
|
||||||
token.Text = strings.Trim(line.TrimmedLineText[len(keyword)+1:], " ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchFeatureLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
return m.matchTitleLine(line, TokenType_FeatureLine, m.dialect.FeatureKeywords())
|
|
||||||
}
|
|
||||||
func (m *matcher) MatchBackgroundLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
return m.matchTitleLine(line, TokenType_BackgroundLine, m.dialect.BackgroundKeywords())
|
|
||||||
}
|
|
||||||
func (m *matcher) MatchScenarioLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
return m.matchTitleLine(line, TokenType_ScenarioLine, m.dialect.ScenarioKeywords())
|
|
||||||
}
|
|
||||||
func (m *matcher) MatchScenarioOutlineLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
return m.matchTitleLine(line, TokenType_ScenarioOutlineLine, m.dialect.ScenarioOutlineKeywords())
|
|
||||||
}
|
|
||||||
func (m *matcher) MatchExamplesLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
return m.matchTitleLine(line, TokenType_ExamplesLine, m.dialect.ExamplesKeywords())
|
|
||||||
}
|
|
||||||
func (m *matcher) MatchStepLine(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
keywords := m.dialect.StepKeywords()
|
|
||||||
for i := range keywords {
|
|
||||||
keyword := keywords[i]
|
|
||||||
if line.StartsWith(keyword) {
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_StepLine
|
|
||||||
token.Keyword = keyword
|
|
||||||
token.Text = strings.Trim(line.TrimmedLineText[len(keyword):], " ")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchDocStringSeparator(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
if m.activeDocStringSeparator != "" {
|
|
||||||
if line.StartsWith(m.activeDocStringSeparator) {
|
|
||||||
// close
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_DocStringSeparator
|
|
||||||
|
|
||||||
m.indentToRemove = 0
|
|
||||||
m.activeDocStringSeparator = ""
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if line.StartsWith(DOCSTRING_SEPARATOR) {
|
|
||||||
m.activeDocStringSeparator = DOCSTRING_SEPARATOR
|
|
||||||
} else if line.StartsWith(DOCSTRING_ALTERNATIVE_SEPARATOR) {
|
|
||||||
m.activeDocStringSeparator = DOCSTRING_ALTERNATIVE_SEPARATOR
|
|
||||||
}
|
|
||||||
if m.activeDocStringSeparator != "" {
|
|
||||||
// open
|
|
||||||
contentType := line.TrimmedLineText[len(m.activeDocStringSeparator):]
|
|
||||||
m.indentToRemove = line.Indent()
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_DocStringSeparator
|
|
||||||
token.Text = contentType
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchTableRow(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
var firstChar, firstPos = utf8.DecodeRuneInString(line.TrimmedLineText)
|
|
||||||
if firstChar == TABLE_CELL_SEPARATOR {
|
|
||||||
var cells []*LineSpan
|
|
||||||
var cell []rune
|
|
||||||
var startCol = line.Indent() + 2 // column where the current cell started
|
|
||||||
// start after the first separator, it's not included in the cell
|
|
||||||
for i, w, col := firstPos, 0, startCol; i < len(line.TrimmedLineText); i += w {
|
|
||||||
var char rune
|
|
||||||
char, w = utf8.DecodeRuneInString(line.TrimmedLineText[i:])
|
|
||||||
if char == TABLE_CELL_SEPARATOR {
|
|
||||||
// append current cell
|
|
||||||
txt := string(cell)
|
|
||||||
txtTrimmed := strings.TrimLeft(txt, " ")
|
|
||||||
ind := len(txt) - len(txtTrimmed)
|
|
||||||
cells = append(cells, &LineSpan{startCol + ind, strings.TrimRight(txtTrimmed, " ")})
|
|
||||||
// start building next
|
|
||||||
cell = make([]rune, 0)
|
|
||||||
startCol = col + 1
|
|
||||||
} else if char == ESCAPE_CHAR {
|
|
||||||
// skip this character but count the column
|
|
||||||
i += w
|
|
||||||
col++
|
|
||||||
char, w = utf8.DecodeRuneInString(line.TrimmedLineText[i:])
|
|
||||||
if char == ESCAPED_NEWLINE {
|
|
||||||
cell = append(cell, '\n')
|
|
||||||
} else {
|
|
||||||
if char != TABLE_CELL_SEPARATOR && char != ESCAPE_CHAR {
|
|
||||||
cell = append(cell, ESCAPE_CHAR)
|
|
||||||
}
|
|
||||||
cell = append(cell, char)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cell = append(cell, char)
|
|
||||||
}
|
|
||||||
col++
|
|
||||||
}
|
|
||||||
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_TableRow
|
|
||||||
token.Items = cells
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchLanguage(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
matches := m.languagePattern.FindStringSubmatch(line.TrimmedLineText)
|
|
||||||
if len(matches) > 0 {
|
|
||||||
lang := matches[1]
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
|
||||||
token.Type = TokenType_Language
|
|
||||||
token.Text = lang
|
|
||||||
|
|
||||||
dialect := m.gdp.GetDialect(lang)
|
|
||||||
if dialect == nil {
|
|
||||||
err = &parseError{"Language not supported: " + lang, token.Location}
|
|
||||||
} else {
|
|
||||||
m.lang = lang
|
|
||||||
m.dialect = dialect
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) MatchOther(line *Line) (ok bool, token *Token, err error) {
|
|
||||||
token, ok = m.newTokenAtLocation(line.LineNumber, 0), true
|
|
||||||
token.Type = TokenType_Other
|
|
||||||
|
|
||||||
element := line.LineText
|
|
||||||
txt := strings.TrimLeft(element, " ")
|
|
||||||
|
|
||||||
if len(element)-len(txt) > m.indentToRemove {
|
|
||||||
token.Text = m.unescapeDocString(element[m.indentToRemove:])
|
|
||||||
} else {
|
|
||||||
token.Text = m.unescapeDocString(txt)
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *matcher) unescapeDocString(text string) string {
|
|
||||||
if m.activeDocStringSeparator != "" {
|
|
||||||
return strings.Replace(text, "\\\"\\\"\\\"", "\"\"\"", -1)
|
|
||||||
} else {
|
|
||||||
return text
|
|
||||||
}
|
|
||||||
}
|
|
2270
gherkin/parser.go
2270
gherkin/parser.go
Различия файлов не показаны, т.к. их слишком много
Показать различия
6
go.mod
6
go.mod
|
@ -2,4 +2,8 @@ module github.com/cucumber/godog
|
||||||
|
|
||||||
go 1.13
|
go 1.13
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.4.0
|
require (
|
||||||
|
github.com/cucumber/gherkin-go/v9 v9.2.0
|
||||||
|
github.com/cucumber/messages-go/v9 v9.0.3
|
||||||
|
github.com/stretchr/testify v1.4.0
|
||||||
|
)
|
||||||
|
|
28
go.sum
28
go.sum
|
@ -1,11 +1,33 @@
|
||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
github.com/aslakhellesoy/gox v1.0.100/go.mod h1:AJl542QsKKG96COVsv0N74HHzVQgDIQPceVUh1aeU2M=
|
||||||
|
github.com/cucumber/gherkin-go/v9 v9.2.0 h1:vxpzP4JtfNSDGH4s0u4TIxv+RaX533MCD+XNakz5kLY=
|
||||||
|
github.com/cucumber/gherkin-go/v9 v9.2.0/go.mod h1:W/+Z5yOowYWXRMlC6lJvM9LFDAFfsicZ1sstjPKfWWQ=
|
||||||
|
github.com/cucumber/messages-go/v9 v9.0.3 h1:xXYjyj2aUOdkakEJAQIvP+1Bn2gOQNN+pY5pCRZQZzI=
|
||||||
|
github.com/cucumber/messages-go/v9 v9.0.3/go.mod h1:TICon2O2emBWMY1eeQvog6b+zK5c+puAFO6avjzC/JA=
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible h1:y12jRkkFxsd7GpqdSZ+/KCs/fJbqpEXSGd4+jfEaewE=
|
||||||
|
github.com/gofrs/uuid v3.2.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM=
|
||||||
|
github.com/gogo/protobuf v1.3.1 h1:DqDEcV5aeaTmdFBePNpYsp3FlcVH/2ISVVM9Qf8PSls=
|
||||||
|
github.com/gogo/protobuf v1.3.1/go.mod h1:SlYgWuQ5SjCEi6WLHjHCa1yvBfUnHcTbrrZtXPKa29o=
|
||||||
|
github.com/hashicorp/go-version v1.0.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
|
||||||
|
github.com/kisielk/errcheck v1.2.0/go.mod h1:/BMXB+zMLi60iA8Vv6Ksmxu/1UDYcXs4uQLJ+jE2L00=
|
||||||
|
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
|
||||||
|
github.com/kr/pretty v0.2.0 h1:s5hAObm+yFO5uHYt5dYjxi2rXrsnmRpJx4OYvIWUaQs=
|
||||||
|
github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7 h1:VUgggvou5XRW9mHwD/yXxIYSMtY0zoKQf/v226p2nyo=
|
||||||
|
gopkg.in/yaml.v2 v2.2.7/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
|
41
run_test.go
41
run_test.go
|
@ -10,11 +10,12 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func okStep() error {
|
func okStep() error {
|
||||||
|
@ -59,12 +60,16 @@ func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
|
func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
|
|
||||||
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", ioutil.Discard),
|
fmt: progressFunc("progress", ioutil.Discard),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^one$`, func() error { return nil })
|
s.Step(`^one$`, func() error { return nil })
|
||||||
s.Step(`^two$`, func() error { return ErrPending })
|
s.Step(`^two$`, func() error { return ErrPending })
|
||||||
|
@ -78,12 +83,16 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {
|
func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
|
|
||||||
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", ioutil.Discard),
|
fmt: progressFunc("progress", ioutil.Discard),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^one$`, func() error { return nil })
|
s.Step(`^one$`, func() error { return nil })
|
||||||
// two - is undefined
|
// two - is undefined
|
||||||
|
@ -97,12 +106,16 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestShouldFailOnError(t *testing.T) {
|
func TestShouldFailOnError(t *testing.T) {
|
||||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
const path = "any.feature"
|
||||||
|
|
||||||
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
r := runner{
|
r := runner{
|
||||||
fmt: progressFunc("progress", ioutil.Discard),
|
fmt: progressFunc("progress", ioutil.Discard),
|
||||||
features: []*feature{&feature{Feature: feat}},
|
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||||
initializer: func(s *Suite) {
|
initializer: func(s *Suite) {
|
||||||
s.Step(`^one$`, func() error { return nil })
|
s.Step(`^one$`, func() error { return nil })
|
||||||
s.Step(`^two$`, func() error { return fmt.Errorf("error") })
|
s.Step(`^two$`, func() error { return fmt.Errorf("error") })
|
||||||
|
@ -243,11 +256,10 @@ type succeedRunTestCase struct {
|
||||||
filename string // expected output file
|
filename string // expected output file
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestSucceedRun(t *testing.T) {
|
func TestConcurrencyRun(t *testing.T) {
|
||||||
testCases := []succeedRunTestCase{
|
testCases := []succeedRunTestCase{
|
||||||
{format: "progress", concurrency: 4, filename: "fixtures/progress_output.txt"},
|
{format: "progress", concurrency: 4, filename: "fixtures/progress_output.txt"},
|
||||||
{format: "junit", concurrency: 4, filename: "fixtures/junit_output.xml"},
|
{format: "junit", concurrency: 4, filename: "fixtures/junit_output.xml"},
|
||||||
{format: "cucumber", concurrency: 2, filename: "fixtures/cucumber_output.json"},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for _, tc := range testCases {
|
for _, tc := range testCases {
|
||||||
|
@ -265,7 +277,7 @@ func TestSucceedRun(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func testSucceedRun(t *testing.T, format string, concurrency int, expectedOutput string) {
|
func testSucceedRun(t *testing.T, format string, concurrency int, expected string) {
|
||||||
output := new(bytes.Buffer)
|
output := new(bytes.Buffer)
|
||||||
|
|
||||||
opt := Options{
|
opt := Options{
|
||||||
|
@ -282,11 +294,12 @@ func testSucceedRun(t *testing.T, format string, concurrency int, expectedOutput
|
||||||
b, err := ioutil.ReadAll(output)
|
b, err := ioutil.ReadAll(output)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
actual := strings.TrimSpace(string(b))
|
|
||||||
|
|
||||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||||
expectedOutput = suiteCtxReg.ReplaceAllString(expectedOutput, `suite_context.go:0`)
|
|
||||||
|
expected = suiteCtxReg.ReplaceAllString(expected, `suite_context.go:0`)
|
||||||
|
|
||||||
|
actual := strings.TrimSpace(string(b))
|
||||||
actual = suiteCtxReg.ReplaceAllString(actual, `suite_context.go:0`)
|
actual = suiteCtxReg.ReplaceAllString(actual, `suite_context.go:0`)
|
||||||
|
|
||||||
assert.Equalf(t, expectedOutput, actual, "[%s]", actual)
|
assert.Equalf(t, expected, actual, "[%s]", actual)
|
||||||
}
|
}
|
||||||
|
|
47
stepdef.go
47
stepdef.go
|
@ -10,7 +10,7 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
||||||
|
@ -31,7 +31,7 @@ var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
||||||
// will result in main step failure.
|
// will result in main step failure.
|
||||||
type Steps []string
|
type Steps []string
|
||||||
|
|
||||||
// StepDef is a registered step definition
|
// StepDefinition is a registered step definition
|
||||||
// contains a StepHandler and regexp which
|
// contains a StepHandler and regexp which
|
||||||
// is used to match a step. Args which
|
// is used to match a step. Args which
|
||||||
// were matched by last executed step
|
// were matched by last executed step
|
||||||
|
@ -39,7 +39,7 @@ type Steps []string
|
||||||
// This structure is passed to the formatter
|
// This structure is passed to the formatter
|
||||||
// when step is matched and is either failed
|
// when step is matched and is either failed
|
||||||
// or successful
|
// or successful
|
||||||
type StepDef struct {
|
type StepDefinition struct {
|
||||||
args []interface{}
|
args []interface{}
|
||||||
hv reflect.Value
|
hv reflect.Value
|
||||||
Expr *regexp.Regexp
|
Expr *regexp.Regexp
|
||||||
|
@ -50,7 +50,7 @@ type StepDef struct {
|
||||||
undefined []string
|
undefined []string
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *StepDef) definitionID() string {
|
func (sd *StepDefinition) definitionID() string {
|
||||||
ptr := sd.hv.Pointer()
|
ptr := sd.hv.Pointer()
|
||||||
f := runtime.FuncForPC(ptr)
|
f := runtime.FuncForPC(ptr)
|
||||||
file, line := f.FileLine(ptr)
|
file, line := f.FileLine(ptr)
|
||||||
|
@ -80,7 +80,7 @@ func (sd *StepDef) definitionID() string {
|
||||||
|
|
||||||
// run a step with the matched arguments using
|
// run a step with the matched arguments using
|
||||||
// reflect
|
// reflect
|
||||||
func (sd *StepDef) run() interface{} {
|
func (sd *StepDefinition) run() interface{} {
|
||||||
typ := sd.hv.Type()
|
typ := sd.hv.Type()
|
||||||
if len(sd.args) < typ.NumIn() {
|
if len(sd.args) < typ.NumIn() {
|
||||||
return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.args))
|
return fmt.Errorf("func expects %d arguments, which is more than %d matched from step", typ.NumIn(), len(sd.args))
|
||||||
|
@ -168,20 +168,32 @@ func (sd *StepDef) run() interface{} {
|
||||||
case reflect.Ptr:
|
case reflect.Ptr:
|
||||||
arg := sd.args[i]
|
arg := sd.args[i]
|
||||||
switch param.Elem().String() {
|
switch param.Elem().String() {
|
||||||
case "gherkin.DocString":
|
case "messages.PickleStepArgument_PickleDocString":
|
||||||
v, ok := arg.(*gherkin.DocString)
|
if v, ok := arg.(*messages.PickleStepArgument); ok {
|
||||||
if !ok {
|
values = append(values, reflect.ValueOf(v.GetDocString()))
|
||||||
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg)
|
break
|
||||||
}
|
}
|
||||||
values = append(values, reflect.ValueOf(v))
|
|
||||||
case "gherkin.DataTable":
|
if v, ok := arg.(*messages.PickleStepArgument_PickleDocString); ok {
|
||||||
v, ok := arg.(*gherkin.DataTable)
|
values = append(values, reflect.ValueOf(v))
|
||||||
if !ok {
|
break
|
||||||
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg)
|
|
||||||
}
|
}
|
||||||
values = append(values, reflect.ValueOf(v))
|
|
||||||
|
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleDocString`, i, arg, arg)
|
||||||
|
case "messages.PickleStepArgument_PickleTable":
|
||||||
|
if v, ok := arg.(*messages.PickleStepArgument); ok {
|
||||||
|
values = append(values, reflect.ValueOf(v.GetDataTable()))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if v, ok := arg.(*messages.PickleStepArgument_PickleTable); ok {
|
||||||
|
values = append(values, reflect.ValueOf(v))
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *messages.PickleStepArgument_PickleTable`, i, arg, arg)
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("the argument %d type %T is not supported", i, arg)
|
return fmt.Errorf("the argument %d type %T is not supported %s", i, arg, param.Elem().String())
|
||||||
}
|
}
|
||||||
case reflect.Slice:
|
case reflect.Slice:
|
||||||
switch param {
|
switch param {
|
||||||
|
@ -198,10 +210,11 @@ func (sd *StepDef) run() interface{} {
|
||||||
return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind())
|
return fmt.Errorf("the argument %d type %s is not supported", i, param.Kind())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return sd.hv.Call(values)[0].Interface()
|
return sd.hv.Call(values)[0].Interface()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (sd *StepDef) shouldBeString(idx int) (string, error) {
|
func (sd *StepDefinition) shouldBeString(idx int) (string, error) {
|
||||||
arg := sd.args[idx]
|
arg := sd.args[idx]
|
||||||
s, ok := arg.(string)
|
s, ok := arg.(string)
|
||||||
if !ok {
|
if !ok {
|
||||||
|
|
|
@ -5,13 +5,13 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestShouldSupportIntTypes(t *testing.T) {
|
func TestShouldSupportIntTypes(t *testing.T) {
|
||||||
fn := func(a int64, b int32, c int16, d int8) error { return nil }
|
fn := func(a int64, b int32, c int16, d int8) error { return nil }
|
||||||
|
|
||||||
def := &StepDef{
|
def := &StepDefinition{
|
||||||
Handler: fn,
|
Handler: fn,
|
||||||
hv: reflect.ValueOf(fn),
|
hv: reflect.ValueOf(fn),
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ func TestShouldSupportIntTypes(t *testing.T) {
|
||||||
func TestShouldSupportFloatTypes(t *testing.T) {
|
func TestShouldSupportFloatTypes(t *testing.T) {
|
||||||
fn := func(a float64, b float32) error { return nil }
|
fn := func(a float64, b float32) error { return nil }
|
||||||
|
|
||||||
def := &StepDef{
|
def := &StepDefinition{
|
||||||
Handler: fn,
|
Handler: fn,
|
||||||
hv: reflect.ValueOf(fn),
|
hv: reflect.ValueOf(fn),
|
||||||
}
|
}
|
||||||
|
@ -48,12 +48,12 @@ func TestShouldSupportFloatTypes(t *testing.T) {
|
||||||
|
|
||||||
func TestShouldNotSupportOtherPointerTypesThanGherkin(t *testing.T) {
|
func TestShouldNotSupportOtherPointerTypesThanGherkin(t *testing.T) {
|
||||||
fn1 := func(a *int) error { return nil }
|
fn1 := func(a *int) error { return nil }
|
||||||
fn2 := func(a *gherkin.DocString) error { return nil }
|
fn2 := func(a *messages.PickleStepArgument_PickleDocString) error { return nil }
|
||||||
fn3 := func(a *gherkin.DataTable) error { return nil }
|
fn3 := func(a *messages.PickleStepArgument_PickleTable) error { return nil }
|
||||||
|
|
||||||
def1 := &StepDef{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{(*int)(nil)}}
|
def1 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{(*int)(nil)}}
|
||||||
def2 := &StepDef{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{(*gherkin.DocString)(nil)}}
|
def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{&messages.PickleStepArgument_PickleDocString{}}}
|
||||||
def3 := &StepDef{Handler: fn3, hv: reflect.ValueOf(fn3), args: []interface{}{(*gherkin.DataTable)(nil)}}
|
def3 := &StepDefinition{Handler: fn3, hv: reflect.ValueOf(fn3), args: []interface{}{(*messages.PickleStepArgument_PickleTable)(nil)}}
|
||||||
|
|
||||||
if err := def1.run(); err == nil {
|
if err := def1.run(); err == nil {
|
||||||
t.Fatalf("expected conversion error, but got none")
|
t.Fatalf("expected conversion error, but got none")
|
||||||
|
@ -70,8 +70,8 @@ func TestShouldSupportOnlyByteSlice(t *testing.T) {
|
||||||
fn1 := func(a []byte) error { return nil }
|
fn1 := func(a []byte) error { return nil }
|
||||||
fn2 := func(a []string) error { return nil }
|
fn2 := func(a []string) error { return nil }
|
||||||
|
|
||||||
def1 := &StepDef{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}}
|
def1 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}}
|
||||||
def2 := &StepDef{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{[]string{}}}
|
def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{[]string{}}}
|
||||||
|
|
||||||
if err := def1.run(); err != nil {
|
if err := def1.run(); err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
|
@ -83,7 +83,7 @@ func TestShouldSupportOnlyByteSlice(t *testing.T) {
|
||||||
|
|
||||||
func TestUnexpectedArguments(t *testing.T) {
|
func TestUnexpectedArguments(t *testing.T) {
|
||||||
fn := func(a, b int) error { return nil }
|
fn := func(a, b int) error { return nil }
|
||||||
def := &StepDef{Handler: fn, hv: reflect.ValueOf(fn)}
|
def := &StepDefinition{Handler: fn, hv: reflect.ValueOf(fn)}
|
||||||
|
|
||||||
def.args = []interface{}{"1"}
|
def.args = []interface{}{"1"}
|
||||||
if err := def.run(); err == nil {
|
if err := def.run(); err == nil {
|
||||||
|
@ -97,7 +97,7 @@ func TestUnexpectedArguments(t *testing.T) {
|
||||||
|
|
||||||
// @TODO maybe we should support duration
|
// @TODO maybe we should support duration
|
||||||
// fn2 := func(err time.Duration) error { return nil }
|
// fn2 := func(err time.Duration) error { return nil }
|
||||||
// def = &StepDef{Handler: fn2, hv: reflect.ValueOf(fn2)}
|
// def = &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2)}
|
||||||
|
|
||||||
// def.args = []interface{}{"1"}
|
// def.args = []interface{}{"1"}
|
||||||
// if err := def.run(); err == nil {
|
// if err := def.run(); err == nil {
|
||||||
|
|
471
suite.go
471
suite.go
|
@ -4,7 +4,6 @@ import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"math/rand"
|
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -15,16 +14,17 @@ import (
|
||||||
"time"
|
"time"
|
||||||
"unicode/utf8"
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/cucumber/godog/gherkin"
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
|
var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
var typeOfBytes = reflect.TypeOf([]byte(nil))
|
var typeOfBytes = reflect.TypeOf([]byte(nil))
|
||||||
|
|
||||||
type feature struct {
|
type feature struct {
|
||||||
*gherkin.Feature
|
*messages.GherkinDocument
|
||||||
|
pickles []*messages.Pickle
|
||||||
Scenarios []*scenario
|
pickleResults []*pickleResult
|
||||||
|
|
||||||
time time.Time
|
time time.Time
|
||||||
Content []byte `json:"-"`
|
Content []byte `json:"-"`
|
||||||
|
@ -32,47 +32,118 @@ type feature struct {
|
||||||
order int
|
order int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (f feature) findScenario(astScenarioID string) *messages.GherkinDocument_Feature_Scenario {
|
||||||
|
for _, child := range f.GherkinDocument.Feature.Children {
|
||||||
|
if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID {
|
||||||
|
return sc
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f feature) findBackground(astScenarioID string) *messages.GherkinDocument_Feature_Background {
|
||||||
|
var bg *messages.GherkinDocument_Feature_Background
|
||||||
|
|
||||||
|
for _, child := range f.GherkinDocument.Feature.Children {
|
||||||
|
if tmp := child.GetBackground(); tmp != nil {
|
||||||
|
bg = tmp
|
||||||
|
}
|
||||||
|
|
||||||
|
if sc := child.GetScenario(); sc != nil && sc.Id == astScenarioID {
|
||||||
|
return bg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f feature) findExample(exampleAstID string) (*messages.GherkinDocument_Feature_Scenario_Examples, *messages.GherkinDocument_Feature_TableRow) {
|
||||||
|
for _, child := range f.GherkinDocument.Feature.Children {
|
||||||
|
if sc := child.GetScenario(); sc != nil {
|
||||||
|
for _, example := range sc.Examples {
|
||||||
|
for _, row := range example.TableBody {
|
||||||
|
if row.Id == exampleAstID {
|
||||||
|
return example, row
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f feature) findStep(astStepID string) *messages.GherkinDocument_Feature_Step {
|
||||||
|
for _, child := range f.GherkinDocument.Feature.Children {
|
||||||
|
if sc := child.GetScenario(); sc != nil {
|
||||||
|
for _, step := range sc.GetSteps() {
|
||||||
|
if step.Id == astStepID {
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bg := child.GetBackground(); bg != nil {
|
||||||
|
for _, step := range bg.GetSteps() {
|
||||||
|
if step.Id == astStepID {
|
||||||
|
return step
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (f feature) startedAt() time.Time {
|
func (f feature) startedAt() time.Time {
|
||||||
return f.time
|
return f.time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f feature) finishedAt() time.Time {
|
func (f feature) finishedAt() time.Time {
|
||||||
if len(f.Scenarios) == 0 {
|
if len(f.pickleResults) == 0 {
|
||||||
return f.startedAt()
|
return f.startedAt()
|
||||||
}
|
}
|
||||||
|
|
||||||
return f.Scenarios[len(f.Scenarios)-1].finishedAt()
|
return f.pickleResults[len(f.pickleResults)-1].finishedAt()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f feature) appendStepResult(s *stepResult) {
|
func (f feature) appendStepResult(s *stepResult) {
|
||||||
scenario := f.Scenarios[len(f.Scenarios)-1]
|
pickles := f.pickleResults[len(f.pickleResults)-1]
|
||||||
scenario.Steps = append(scenario.Steps, s)
|
pickles.stepResults = append(pickles.stepResults, s)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f feature) lastPickleResult() *pickleResult {
|
||||||
|
return f.pickleResults[len(f.pickleResults)-1]
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f feature) lastStepResult() *stepResult {
|
||||||
|
last := f.lastPickleResult()
|
||||||
|
return last.stepResults[len(last.stepResults)-1]
|
||||||
}
|
}
|
||||||
|
|
||||||
type sortByName []*feature
|
type sortByName []*feature
|
||||||
|
|
||||||
func (s sortByName) Len() int { return len(s) }
|
func (s sortByName) Len() int { return len(s) }
|
||||||
func (s sortByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
func (s sortByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name }
|
||||||
func (s sortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
func (s sortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
type scenario struct {
|
type pickleResult struct {
|
||||||
Name string
|
Name string
|
||||||
OutlineName string
|
|
||||||
ExampleNo int
|
|
||||||
time time.Time
|
time time.Time
|
||||||
Steps []*stepResult
|
stepResults []*stepResult
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s scenario) startedAt() time.Time {
|
func (s pickleResult) startedAt() time.Time {
|
||||||
return s.time
|
return s.time
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s scenario) finishedAt() time.Time {
|
func (s pickleResult) finishedAt() time.Time {
|
||||||
if len(s.Steps) == 0 {
|
if len(s.stepResults) == 0 {
|
||||||
return s.startedAt()
|
return s.startedAt()
|
||||||
}
|
}
|
||||||
|
|
||||||
return s.Steps[len(s.Steps)-1].time
|
return s.stepResults[len(s.stepResults)-1].time
|
||||||
}
|
}
|
||||||
|
|
||||||
// ErrUndefined is returned in case if step definition was not found
|
// ErrUndefined is returned in case if step definition was not found
|
||||||
|
@ -94,7 +165,7 @@ var ErrPending = fmt.Errorf("step implementation is pending")
|
||||||
// executions are catching panic error since it may
|
// executions are catching panic error since it may
|
||||||
// be a context specific error.
|
// be a context specific error.
|
||||||
type Suite struct {
|
type Suite struct {
|
||||||
steps []*StepDef
|
steps []*StepDefinition
|
||||||
features []*feature
|
features []*feature
|
||||||
fmt Formatter
|
fmt Formatter
|
||||||
|
|
||||||
|
@ -105,16 +176,16 @@ type Suite struct {
|
||||||
|
|
||||||
// suite event handlers
|
// suite event handlers
|
||||||
beforeSuiteHandlers []func()
|
beforeSuiteHandlers []func()
|
||||||
beforeFeatureHandlers []func(*gherkin.Feature)
|
beforeFeatureHandlers []func(*messages.GherkinDocument)
|
||||||
beforeScenarioHandlers []func(interface{})
|
beforeScenarioHandlers []func(*messages.Pickle)
|
||||||
beforeStepHandlers []func(*gherkin.Step)
|
beforeStepHandlers []func(*messages.Pickle_PickleStep)
|
||||||
afterStepHandlers []func(*gherkin.Step, error)
|
afterStepHandlers []func(*messages.Pickle_PickleStep, error)
|
||||||
afterScenarioHandlers []func(interface{}, error)
|
afterScenarioHandlers []func(*messages.Pickle, error)
|
||||||
afterFeatureHandlers []func(*gherkin.Feature)
|
afterFeatureHandlers []func(*messages.GherkinDocument)
|
||||||
afterSuiteHandlers []func()
|
afterSuiteHandlers []func()
|
||||||
}
|
}
|
||||||
|
|
||||||
// Step allows to register a *StepDef in Godog
|
// Step allows to register a *StepDefinition in Godog
|
||||||
// feature suite, the definition will be applied
|
// feature suite, the definition will be applied
|
||||||
// to all steps matching the given Regexp expr.
|
// to all steps matching the given Regexp expr.
|
||||||
//
|
//
|
||||||
|
@ -126,7 +197,7 @@ type Suite struct {
|
||||||
// the same step, then only the first matched handler
|
// the same step, then only the first matched handler
|
||||||
// will be applied.
|
// will be applied.
|
||||||
//
|
//
|
||||||
// If none of the *StepDef is matched, then
|
// If none of the *StepDefinition is matched, then
|
||||||
// ErrUndefined error will be returned when
|
// ErrUndefined error will be returned when
|
||||||
// running steps.
|
// running steps.
|
||||||
func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||||
|
@ -153,7 +224,7 @@ func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||||
panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut()))
|
panic(fmt.Sprintf("expected handler to return only one value, but it has: %d", typ.NumOut()))
|
||||||
}
|
}
|
||||||
|
|
||||||
def := &StepDef{
|
def := &StepDefinition{
|
||||||
Handler: stepFunc,
|
Handler: stepFunc,
|
||||||
Expr: regex,
|
Expr: regex,
|
||||||
hv: v,
|
hv: v,
|
||||||
|
@ -200,31 +271,28 @@ func (s *Suite) BeforeSuite(fn func()) {
|
||||||
// scenario to restart it.
|
// scenario to restart it.
|
||||||
//
|
//
|
||||||
// Use it wisely and avoid sharing state between scenarios.
|
// Use it wisely and avoid sharing state between scenarios.
|
||||||
func (s *Suite) BeforeFeature(fn func(*gherkin.Feature)) {
|
func (s *Suite) BeforeFeature(fn func(*messages.GherkinDocument)) {
|
||||||
s.beforeFeatureHandlers = append(s.beforeFeatureHandlers, fn)
|
s.beforeFeatureHandlers = append(s.beforeFeatureHandlers, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeScenario registers a function or method
|
// BeforeScenario registers a function or method
|
||||||
// to be run before every scenario or scenario outline.
|
// to be run before every pickle.
|
||||||
//
|
|
||||||
// The interface argument may be *gherkin.Scenario
|
|
||||||
// or *gherkin.ScenarioOutline
|
|
||||||
//
|
//
|
||||||
// It is a good practice to restore the default state
|
// It is a good practice to restore the default state
|
||||||
// before every scenario so it would be isolated from
|
// before every scenario so it would be isolated from
|
||||||
// any kind of state.
|
// any kind of state.
|
||||||
func (s *Suite) BeforeScenario(fn func(interface{})) {
|
func (s *Suite) BeforeScenario(fn func(*messages.Pickle)) {
|
||||||
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn)
|
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// BeforeStep registers a function or method
|
// BeforeStep registers a function or method
|
||||||
// to be run before every scenario
|
// to be run before every step.
|
||||||
func (s *Suite) BeforeStep(fn func(*gherkin.Step)) {
|
func (s *Suite) BeforeStep(fn func(*messages.Pickle_PickleStep)) {
|
||||||
s.beforeStepHandlers = append(s.beforeStepHandlers, fn)
|
s.beforeStepHandlers = append(s.beforeStepHandlers, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterStep registers an function or method
|
// AfterStep registers an function or method
|
||||||
// to be run after every scenario
|
// to be run after every step.
|
||||||
//
|
//
|
||||||
// It may be convenient to return a different kind of error
|
// It may be convenient to return a different kind of error
|
||||||
// in order to print more state details which may help
|
// in order to print more state details which may help
|
||||||
|
@ -232,22 +300,19 @@ func (s *Suite) BeforeStep(fn func(*gherkin.Step)) {
|
||||||
//
|
//
|
||||||
// In some cases, for example when running a headless
|
// In some cases, for example when running a headless
|
||||||
// browser, to take a screenshot after failure.
|
// browser, to take a screenshot after failure.
|
||||||
func (s *Suite) AfterStep(fn func(*gherkin.Step, error)) {
|
func (s *Suite) AfterStep(fn func(*messages.Pickle_PickleStep, error)) {
|
||||||
s.afterStepHandlers = append(s.afterStepHandlers, fn)
|
s.afterStepHandlers = append(s.afterStepHandlers, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterScenario registers an function or method
|
// AfterScenario registers an function or method
|
||||||
// to be run after every scenario or scenario outline
|
// to be run after every pickle.
|
||||||
//
|
func (s *Suite) AfterScenario(fn func(*messages.Pickle, error)) {
|
||||||
// The interface argument may be *gherkin.Scenario
|
|
||||||
// or *gherkin.ScenarioOutline
|
|
||||||
func (s *Suite) AfterScenario(fn func(interface{}, error)) {
|
|
||||||
s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn)
|
s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
// AfterFeature registers a function or method
|
// AfterFeature registers a function or method
|
||||||
// to be run once after feature executed all scenarios.
|
// to be run once after feature executed all scenarios.
|
||||||
func (s *Suite) AfterFeature(fn func(*gherkin.Feature)) {
|
func (s *Suite) AfterFeature(fn func(*messages.GherkinDocument)) {
|
||||||
s.afterFeatureHandlers = append(s.afterFeatureHandlers, fn)
|
s.afterFeatureHandlers = append(s.afterFeatureHandlers, fn)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -276,7 +341,7 @@ func (s *Suite) run() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
func (s *Suite) matchStep(step *messages.Pickle_PickleStep) *StepDefinition {
|
||||||
def := s.matchStepText(step.Text)
|
def := s.matchStepText(step.Text)
|
||||||
if def != nil && step.Argument != nil {
|
if def != nil && step.Argument != nil {
|
||||||
def.args = append(def.args, step.Argument)
|
def.args = append(def.args, step.Argument)
|
||||||
|
@ -284,14 +349,14 @@ func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
||||||
return def
|
return def
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep, prevStepErr error) (err error) {
|
||||||
// run before step handlers
|
// run before step handlers
|
||||||
for _, f := range s.beforeStepHandlers {
|
for _, f := range s.beforeStepHandlers {
|
||||||
f(step)
|
f(step)
|
||||||
}
|
}
|
||||||
|
|
||||||
match := s.matchStep(step)
|
match := s.matchStep(step)
|
||||||
s.fmt.Defined(step, match)
|
s.fmt.Defined(pickle, step, match)
|
||||||
|
|
||||||
// user multistep definitions may panic
|
// user multistep definitions may panic
|
||||||
defer func() {
|
defer func() {
|
||||||
|
@ -312,11 +377,11 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
|
|
||||||
switch err {
|
switch err {
|
||||||
case nil:
|
case nil:
|
||||||
s.fmt.Passed(step, match)
|
s.fmt.Passed(pickle, step, match)
|
||||||
case ErrPending:
|
case ErrPending:
|
||||||
s.fmt.Pending(step, match)
|
s.fmt.Pending(pickle, step, match)
|
||||||
default:
|
default:
|
||||||
s.fmt.Failed(step, match, err)
|
s.fmt.Failed(pickle, step, match, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
// run after step handlers
|
// run after step handlers
|
||||||
|
@ -329,7 +394,7 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
return err
|
return err
|
||||||
} else if len(undef) > 0 {
|
} else if len(undef) > 0 {
|
||||||
if match != nil {
|
if match != nil {
|
||||||
match = &StepDef{
|
match = &StepDefinition{
|
||||||
args: match.args,
|
args: match.args,
|
||||||
hv: match.hv,
|
hv: match.hv,
|
||||||
Expr: match.Expr,
|
Expr: match.Expr,
|
||||||
|
@ -338,12 +403,12 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||||
undefined: undef,
|
undefined: undef,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
s.fmt.Undefined(step, match)
|
s.fmt.Undefined(pickle, step, match)
|
||||||
return ErrUndefined
|
return ErrUndefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if prevStepErr != nil {
|
if prevStepErr != nil {
|
||||||
s.fmt.Skipped(step, match)
|
s.fmt.Skipped(pickle, step, match)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,7 +473,7 @@ func (s *Suite) maybeSubSteps(result interface{}) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) matchStepText(text string) *StepDef {
|
func (s *Suite) matchStepText(text string) *StepDefinition {
|
||||||
for _, h := range s.steps {
|
for _, h := range s.steps {
|
||||||
if m := h.Expr.FindStringSubmatch(text); len(m) > 0 {
|
if m := h.Expr.FindStringSubmatch(text); len(m) > 0 {
|
||||||
var args []interface{}
|
var args []interface{}
|
||||||
|
@ -418,7 +483,7 @@ func (s *Suite) matchStepText(text string) *StepDef {
|
||||||
|
|
||||||
// since we need to assign arguments
|
// since we need to assign arguments
|
||||||
// better to copy the step definition
|
// better to copy the step definition
|
||||||
return &StepDef{
|
return &StepDefinition{
|
||||||
args: args,
|
args: args,
|
||||||
hv: h.hv,
|
hv: h.hv,
|
||||||
Expr: h.Expr,
|
Expr: h.Expr,
|
||||||
|
@ -430,9 +495,9 @@ func (s *Suite) matchStepText(text string) *StepDef {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runSteps(steps []*gherkin.Step) (err error) {
|
func (s *Suite) runSteps(pickle *messages.Pickle, steps []*messages.Pickle_PickleStep) (err error) {
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
stepErr := s.runStep(step, err)
|
stepErr := s.runStep(pickle, step, err)
|
||||||
switch stepErr {
|
switch stepErr {
|
||||||
case ErrUndefined:
|
case ErrUndefined:
|
||||||
// do not overwrite failed error
|
// do not overwrite failed error
|
||||||
|
@ -449,110 +514,6 @@ func (s *Suite) runSteps(steps []*gherkin.Step) (err error) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runOutline(outline *gherkin.ScenarioOutline, b *gherkin.Background) (failErr error) {
|
|
||||||
s.fmt.Node(outline)
|
|
||||||
|
|
||||||
for _, ex := range outline.Examples {
|
|
||||||
example, hasExamples := examples(ex)
|
|
||||||
if !hasExamples {
|
|
||||||
// @TODO: may need to print empty example node, but
|
|
||||||
// for backward compatibility, cannot cast to *gherkin.ExamplesBase
|
|
||||||
// at the moment
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
s.fmt.Node(example)
|
|
||||||
placeholders := example.TableHeader.Cells
|
|
||||||
groups := example.TableBody
|
|
||||||
|
|
||||||
for _, group := range groups {
|
|
||||||
if !isEmptyScenario(outline) {
|
|
||||||
for _, f := range s.beforeScenarioHandlers {
|
|
||||||
f(outline)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
var steps []*gherkin.Step
|
|
||||||
for _, outlineStep := range outline.Steps {
|
|
||||||
text := outlineStep.Text
|
|
||||||
for i, placeholder := range placeholders {
|
|
||||||
text = strings.Replace(text, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
|
|
||||||
}
|
|
||||||
|
|
||||||
// translate argument
|
|
||||||
arg := outlineStep.Argument
|
|
||||||
switch t := outlineStep.Argument.(type) {
|
|
||||||
case *gherkin.DataTable:
|
|
||||||
tbl := &gherkin.DataTable{
|
|
||||||
Node: t.Node,
|
|
||||||
Rows: make([]*gherkin.TableRow, len(t.Rows)),
|
|
||||||
}
|
|
||||||
for i, row := range t.Rows {
|
|
||||||
cells := make([]*gherkin.TableCell, len(row.Cells))
|
|
||||||
for j, cell := range row.Cells {
|
|
||||||
trans := cell.Value
|
|
||||||
for i, placeholder := range placeholders {
|
|
||||||
trans = strings.Replace(trans, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
|
|
||||||
}
|
|
||||||
cells[j] = &gherkin.TableCell{
|
|
||||||
Node: cell.Node,
|
|
||||||
Value: trans,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
tbl.Rows[i] = &gherkin.TableRow{
|
|
||||||
Node: row.Node,
|
|
||||||
Cells: cells,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
arg = tbl
|
|
||||||
case *gherkin.DocString:
|
|
||||||
trans := t.Content
|
|
||||||
for i, placeholder := range placeholders {
|
|
||||||
trans = strings.Replace(trans, "<"+placeholder.Value+">", group.Cells[i].Value, -1)
|
|
||||||
}
|
|
||||||
arg = &gherkin.DocString{
|
|
||||||
Node: t.Node,
|
|
||||||
Content: trans,
|
|
||||||
ContentType: t.ContentType,
|
|
||||||
Delimitter: t.Delimitter,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// clone a step
|
|
||||||
step := &gherkin.Step{
|
|
||||||
Node: outlineStep.Node,
|
|
||||||
Text: text,
|
|
||||||
Keyword: outlineStep.Keyword,
|
|
||||||
Argument: arg,
|
|
||||||
}
|
|
||||||
steps = append(steps, step)
|
|
||||||
}
|
|
||||||
|
|
||||||
// run example table row
|
|
||||||
s.fmt.Node(group)
|
|
||||||
|
|
||||||
if b != nil {
|
|
||||||
steps = append(b.Steps, steps...)
|
|
||||||
}
|
|
||||||
|
|
||||||
err := s.runSteps(steps)
|
|
||||||
|
|
||||||
if !isEmptyScenario(outline) {
|
|
||||||
for _, f := range s.afterScenarioHandlers {
|
|
||||||
f(outline, err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if s.shouldFail(err) {
|
|
||||||
failErr = err
|
|
||||||
if s.stopOnFailure {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *Suite) shouldFail(err error) bool {
|
func (s *Suite) shouldFail(err error) bool {
|
||||||
if err == nil {
|
if err == nil {
|
||||||
return false
|
return false
|
||||||
|
@ -566,46 +527,24 @@ func (s *Suite) shouldFail(err error) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runFeature(f *feature) {
|
func (s *Suite) runFeature(f *feature) {
|
||||||
if !isEmptyFeature(f.Feature) {
|
if !isEmptyFeature(f.pickles) {
|
||||||
for _, fn := range s.beforeFeatureHandlers {
|
for _, fn := range s.beforeFeatureHandlers {
|
||||||
fn(f.Feature)
|
fn(f.GherkinDocument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
s.fmt.Feature(f.Feature, f.Path, f.Content)
|
s.fmt.Feature(f.GherkinDocument, f.Path, f.Content)
|
||||||
|
|
||||||
// make a local copy of the feature scenario defenitions,
|
|
||||||
// then shuffle it if we are randomizing scenarios
|
|
||||||
scenarios := make([]interface{}, len(f.ScenarioDefinitions))
|
|
||||||
if s.randomSeed != 0 {
|
|
||||||
r := rand.New(rand.NewSource(s.randomSeed))
|
|
||||||
perm := r.Perm(len(f.ScenarioDefinitions))
|
|
||||||
for i, v := range perm {
|
|
||||||
scenarios[v] = f.ScenarioDefinitions[i]
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
copy(scenarios, f.ScenarioDefinitions)
|
|
||||||
}
|
|
||||||
|
|
||||||
defer func() {
|
defer func() {
|
||||||
if !isEmptyFeature(f.Feature) {
|
if !isEmptyFeature(f.pickles) {
|
||||||
for _, fn := range s.afterFeatureHandlers {
|
for _, fn := range s.afterFeatureHandlers {
|
||||||
fn(f.Feature)
|
fn(f.GherkinDocument)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
for _, scenario := range scenarios {
|
for _, pickle := range f.pickles {
|
||||||
var err error
|
err := s.runPickle(pickle)
|
||||||
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 s.shouldFail(err) {
|
if s.shouldFail(err) {
|
||||||
s.failed = true
|
s.failed = true
|
||||||
if s.stopOnFailure {
|
if s.stopOnFailure {
|
||||||
|
@ -615,31 +554,35 @@ func (s *Suite) runFeature(f *feature) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *Suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) {
|
func isEmptyFeature(pickles []*messages.Pickle) bool {
|
||||||
if isEmptyScenario(scenario) {
|
for _, pickle := range pickles {
|
||||||
s.fmt.Node(scenario)
|
if len(pickle.Steps) > 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *Suite) runPickle(pickle *messages.Pickle) (err error) {
|
||||||
|
if len(pickle.Steps) == 0 {
|
||||||
|
s.fmt.Pickle(pickle)
|
||||||
return ErrUndefined
|
return ErrUndefined
|
||||||
}
|
}
|
||||||
|
|
||||||
// run before scenario handlers
|
// run before scenario handlers
|
||||||
for _, f := range s.beforeScenarioHandlers {
|
for _, f := range s.beforeScenarioHandlers {
|
||||||
f(scenario)
|
f(pickle)
|
||||||
}
|
}
|
||||||
|
|
||||||
s.fmt.Node(scenario)
|
s.fmt.Pickle(pickle)
|
||||||
|
|
||||||
// background
|
|
||||||
steps := scenario.Steps
|
|
||||||
if b != nil {
|
|
||||||
steps = append(b.Steps, steps...)
|
|
||||||
}
|
|
||||||
|
|
||||||
// scenario
|
// scenario
|
||||||
err = s.runSteps(steps)
|
err = s.runSteps(pickle, pickle.Steps)
|
||||||
|
|
||||||
// run after scenario handlers
|
// run after scenario handlers
|
||||||
for _, f := range s.afterScenarioHandlers {
|
for _, f := range s.afterScenarioHandlers {
|
||||||
f(scenario, err)
|
f(pickle, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
@ -686,15 +629,18 @@ func parseFeatureFile(path string) (*feature, error) {
|
||||||
defer reader.Close()
|
defer reader.Close()
|
||||||
|
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
ft, err := gherkin.ParseFeature(io.TeeReader(reader, &buf))
|
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), (&messages.Incrementing{}).NewId)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("%s - %v", path, err)
|
return nil, fmt.Errorf("%s - %v", path, err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pickles := gherkin.Pickles(*gherkinDocument, path, (&messages.Incrementing{}).NewId)
|
||||||
|
|
||||||
return &feature{
|
return &feature{
|
||||||
Path: path,
|
GherkinDocument: gherkinDocument,
|
||||||
Feature: ft,
|
pickles: pickles,
|
||||||
Content: buf.Bytes(),
|
Content: buf.Bytes(),
|
||||||
|
Path: path,
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -724,9 +670,8 @@ func parseFeatureDir(dir string) ([]*feature, error) {
|
||||||
|
|
||||||
func parsePath(path string) ([]*feature, error) {
|
func parsePath(path string) ([]*feature, error) {
|
||||||
var features []*feature
|
var features []*feature
|
||||||
// check if line number is specified
|
|
||||||
var line int
|
path, line := extractFeaturePathLine(path)
|
||||||
path, line = extractFeaturePathLine(path)
|
|
||||||
|
|
||||||
fi, err := os.Stat(path)
|
fi, err := os.Stat(path)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -743,20 +688,16 @@ func parsePath(path string) ([]*feature, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// filter scenario by line number
|
// filter scenario by line number
|
||||||
var scenarios []interface{}
|
var pickles []*messages.Pickle
|
||||||
for _, def := range ft.ScenarioDefinitions {
|
for _, pickle := range ft.pickles {
|
||||||
var ln int
|
sc := ft.findScenario(pickle.AstNodeIds[0])
|
||||||
switch t := def.(type) {
|
|
||||||
case *gherkin.Scenario:
|
if line == -1 || uint32(line) == sc.Location.Line {
|
||||||
ln = t.Location.Line
|
pickles = append(pickles, pickle)
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
ln = t.Location.Line
|
|
||||||
}
|
|
||||||
if line == -1 || ln == line {
|
|
||||||
scenarios = append(scenarios, def)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ft.ScenarioDefinitions = scenarios
|
ft.pickles = pickles
|
||||||
|
|
||||||
return append(features, ft), nil
|
return append(features, ft), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -784,6 +725,7 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) {
|
||||||
byPath[ft.Path] = ft
|
byPath[ft.Path] = ft
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return filterFeatures(filter, byPath), nil
|
return filterFeatures(filter, byPath), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -795,7 +737,7 @@ func (s sortByOrderGiven) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||||
|
|
||||||
func filterFeatures(tags string, collected map[string]*feature) (features []*feature) {
|
func filterFeatures(tags string, collected map[string]*feature) (features []*feature) {
|
||||||
for _, ft := range collected {
|
for _, ft := range collected {
|
||||||
applyTagFilter(tags, ft.Feature)
|
applyTagFilter(tags, ft)
|
||||||
features = append(features, ft)
|
features = append(features, ft)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -804,81 +746,23 @@ func filterFeatures(tags string, collected map[string]*feature) (features []*fea
|
||||||
return features
|
return features
|
||||||
}
|
}
|
||||||
|
|
||||||
func applyTagFilter(tags string, ft *gherkin.Feature) {
|
func applyTagFilter(tags string, ft *feature) {
|
||||||
if len(tags) == 0 {
|
if len(tags) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
var scenarios []interface{}
|
var pickles []*messages.Pickle
|
||||||
for _, scenario := range ft.ScenarioDefinitions {
|
for _, pickle := range ft.pickles {
|
||||||
switch t := scenario.(type) {
|
if matchesTags(tags, pickle.Tags) {
|
||||||
case *gherkin.ScenarioOutline:
|
pickles = append(pickles, pickle)
|
||||||
var allExamples []*gherkin.Examples
|
|
||||||
for _, examples := range t.Examples {
|
|
||||||
if matchesTags(tags, allTags(ft, t, examples)) {
|
|
||||||
allExamples = append(allExamples, examples)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
t.Examples = allExamples
|
|
||||||
if len(t.Examples) > 0 {
|
|
||||||
scenarios = append(scenarios, scenario)
|
|
||||||
}
|
|
||||||
case *gherkin.Scenario:
|
|
||||||
if matchesTags(tags, allTags(ft, t)) {
|
|
||||||
scenarios = append(scenarios, scenario)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
ft.ScenarioDefinitions = scenarios
|
|
||||||
}
|
|
||||||
|
|
||||||
func allTags(nodes ...interface{}) []string {
|
ft.pickles = pickles
|
||||||
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
|
// based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters
|
||||||
func matchesTags(filter string, tags []string) (ok bool) {
|
func matchesTags(filter string, tags []*messages.Pickle_PickleTag) (ok bool) {
|
||||||
ok = true
|
ok = true
|
||||||
for _, andTags := range strings.Split(filter, "&&") {
|
for _, andTags := range strings.Split(filter, "&&") {
|
||||||
var okComma bool
|
var okComma bool
|
||||||
|
@ -895,3 +779,14 @@ func matchesTags(filter string, tags []string) (ok bool) {
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func hasTag(tags []*messages.Pickle_PickleTag, tag string) bool {
|
||||||
|
for _, t := range tags {
|
||||||
|
tName := strings.Replace(t.Name, "@", "", -1)
|
||||||
|
|
||||||
|
if tName == tag {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -12,8 +12,10 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"github.com/cucumber/gherkin-go/v9"
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
"github.com/cucumber/godog/gherkin"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// SuiteContext provides steps for godog suite execution and
|
// SuiteContext provides steps for godog suite execution and
|
||||||
|
@ -112,7 +114,7 @@ type suiteContext struct {
|
||||||
out bytes.Buffer
|
out bytes.Buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
|
func (s *suiteContext) ResetBeforeEachScenario(*messages.Pickle) {
|
||||||
// reset whole suite with the state
|
// reset whole suite with the state
|
||||||
s.out.Reset()
|
s.out.Reset()
|
||||||
s.paths = []string{}
|
s.paths = []string{}
|
||||||
|
@ -128,7 +130,7 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for _, feat := range s.testedSuite.features {
|
for _, feat := range s.testedSuite.features {
|
||||||
applyTagFilter(tags, feat.Feature)
|
applyTagFilter(tags, feat)
|
||||||
}
|
}
|
||||||
s.testedSuite.fmt = testFormatterFunc("godog", &s.out)
|
s.testedSuite.fmt = testFormatterFunc("godog", &s.out)
|
||||||
s.testedSuite.run()
|
s.testedSuite.run()
|
||||||
|
@ -151,7 +153,7 @@ func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) thereShouldBeEventsFired(doc *gherkin.DocString) error {
|
func (s *suiteContext) thereShouldBeEventsFired(doc *messages.PickleStepArgument_PickleDocString) error {
|
||||||
actual := strings.Split(strings.TrimSpace(s.out.String()), "\n")
|
actual := strings.Split(strings.TrimSpace(s.out.String()), "\n")
|
||||||
expect := strings.Split(strings.TrimSpace(doc.Content), "\n")
|
expect := strings.Split(strings.TrimSpace(doc.Content), "\n")
|
||||||
if len(expect) != len(actual) {
|
if len(expect) != len(actual) {
|
||||||
|
@ -184,7 +186,7 @@ func (s *suiteContext) cleanupSnippet(snip string) string {
|
||||||
return strings.Join(lines, "\n")
|
return strings.Join(lines, "\n")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *gherkin.DocString) error {
|
func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *messages.PickleStepArgument_PickleDocString) error {
|
||||||
f, ok := s.testedSuite.fmt.(*testFormatter)
|
f, ok := s.testedSuite.fmt.(*testFormatter)
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
|
return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
|
||||||
|
@ -197,7 +199,7 @@ func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *gherkin.DocString)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.DocString) error {
|
func (s *suiteContext) followingStepsShouldHave(status string, steps *messages.PickleStepArgument_PickleDocString) error {
|
||||||
var expected = strings.Split(steps.Content, "\n")
|
var expected = strings.Split(steps.Content, "\n")
|
||||||
var actual, unmatched, matched []string
|
var actual, unmatched, matched []string
|
||||||
|
|
||||||
|
@ -207,23 +209,23 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.Do
|
||||||
}
|
}
|
||||||
switch status {
|
switch status {
|
||||||
case "passed":
|
case "passed":
|
||||||
for _, st := range f.passed {
|
for _, st := range f.findStepResults(passed) {
|
||||||
actual = append(actual, st.step.Text)
|
actual = append(actual, st.step.Text)
|
||||||
}
|
}
|
||||||
case "failed":
|
case "failed":
|
||||||
for _, st := range f.failed {
|
for _, st := range f.findStepResults(failed) {
|
||||||
actual = append(actual, st.step.Text)
|
actual = append(actual, st.step.Text)
|
||||||
}
|
}
|
||||||
case "skipped":
|
case "skipped":
|
||||||
for _, st := range f.skipped {
|
for _, st := range f.findStepResults(skipped) {
|
||||||
actual = append(actual, st.step.Text)
|
actual = append(actual, st.step.Text)
|
||||||
}
|
}
|
||||||
case "undefined":
|
case "undefined":
|
||||||
for _, st := range f.undefined {
|
for _, st := range f.findStepResults(undefined) {
|
||||||
actual = append(actual, st.step.Text)
|
actual = append(actual, st.step.Text)
|
||||||
}
|
}
|
||||||
case "pending":
|
case "pending":
|
||||||
for _, st := range f.pending {
|
for _, st := range f.findStepResults(pending) {
|
||||||
actual = append(actual, st.step.Text)
|
actual = append(actual, st.step.Text)
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
@ -268,19 +270,23 @@ func (s *suiteContext) allStepsShouldHave(status string) error {
|
||||||
return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
|
return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
|
||||||
}
|
}
|
||||||
|
|
||||||
total := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending)
|
total := len(f.findStepResults(passed)) +
|
||||||
|
len(f.findStepResults(failed)) +
|
||||||
|
len(f.findStepResults(skipped)) +
|
||||||
|
len(f.findStepResults(undefined)) +
|
||||||
|
len(f.findStepResults(pending))
|
||||||
var actual int
|
var actual int
|
||||||
switch status {
|
switch status {
|
||||||
case "passed":
|
case "passed":
|
||||||
actual = len(f.passed)
|
actual = len(f.findStepResults(passed))
|
||||||
case "failed":
|
case "failed":
|
||||||
actual = len(f.failed)
|
actual = len(f.findStepResults(failed))
|
||||||
case "skipped":
|
case "skipped":
|
||||||
actual = len(f.skipped)
|
actual = len(f.findStepResults(skipped))
|
||||||
case "undefined":
|
case "undefined":
|
||||||
actual = len(f.undefined)
|
actual = len(f.findStepResults(undefined))
|
||||||
case "pending":
|
case "pending":
|
||||||
actual = len(f.pending)
|
actual = len(f.findStepResults(pending))
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unexpected step status wanted: %s", status)
|
return fmt.Errorf("unexpected step status wanted: %s", status)
|
||||||
}
|
}
|
||||||
|
@ -298,22 +304,22 @@ func (s *suiteContext) iAmListeningToSuiteEvents() error {
|
||||||
s.testedSuite.AfterSuite(func() {
|
s.testedSuite.AfterSuite(func() {
|
||||||
s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}})
|
s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}})
|
||||||
})
|
})
|
||||||
s.testedSuite.BeforeFeature(func(ft *gherkin.Feature) {
|
s.testedSuite.BeforeFeature(func(ft *messages.GherkinDocument) {
|
||||||
s.events = append(s.events, &firedEvent{"BeforeFeature", []interface{}{ft}})
|
s.events = append(s.events, &firedEvent{"BeforeFeature", []interface{}{ft}})
|
||||||
})
|
})
|
||||||
s.testedSuite.AfterFeature(func(ft *gherkin.Feature) {
|
s.testedSuite.AfterFeature(func(ft *messages.GherkinDocument) {
|
||||||
s.events = append(s.events, &firedEvent{"AfterFeature", []interface{}{ft}})
|
s.events = append(s.events, &firedEvent{"AfterFeature", []interface{}{ft}})
|
||||||
})
|
})
|
||||||
s.testedSuite.BeforeScenario(func(scenario interface{}) {
|
s.testedSuite.BeforeScenario(func(pickle *messages.Pickle) {
|
||||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
|
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{pickle}})
|
||||||
})
|
})
|
||||||
s.testedSuite.AfterScenario(func(scenario interface{}, err error) {
|
s.testedSuite.AfterScenario(func(pickle *messages.Pickle, err error) {
|
||||||
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
|
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{pickle, err}})
|
||||||
})
|
})
|
||||||
s.testedSuite.BeforeStep(func(step *gherkin.Step) {
|
s.testedSuite.BeforeStep(func(step *messages.Pickle_PickleStep) {
|
||||||
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
|
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
|
||||||
})
|
})
|
||||||
s.testedSuite.AfterStep(func(step *gherkin.Step, err error) {
|
s.testedSuite.AfterStep(func(step *messages.Pickle_PickleStep, err error) {
|
||||||
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, err}})
|
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, err}})
|
||||||
})
|
})
|
||||||
return nil
|
return nil
|
||||||
|
@ -324,9 +330,10 @@ func (s *suiteContext) aFailingStep() error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// parse a given feature file body as a feature
|
// parse a given feature file body as a feature
|
||||||
func (s *suiteContext) aFeatureFile(name string, body *gherkin.DocString) error {
|
func (s *suiteContext) aFeatureFile(path string, body *messages.PickleStepArgument_PickleDocString) error {
|
||||||
ft, err := gherkin.ParseFeature(strings.NewReader(body.Content))
|
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(body.Content), (&messages.Incrementing{}).NewId)
|
||||||
s.testedSuite.features = append(s.testedSuite.features, &feature{Feature: ft, Path: name})
|
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||||
|
s.testedSuite.features = append(s.testedSuite.features, &feature{GherkinDocument: gd, pickles: pickles, Path: path})
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -354,7 +361,7 @@ func (s *suiteContext) theSuiteShouldHave(state string) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *gherkin.DocString) error {
|
func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *messages.PickleStepArgument_PickleDocString) error {
|
||||||
if len(s.testedSuite.features) != num {
|
if len(s.testedSuite.features) != num {
|
||||||
return fmt.Errorf("expected %d features to be parsed, but have %d", num, len(s.testedSuite.features))
|
return fmt.Errorf("expected %d features to be parsed, but have %d", num, len(s.testedSuite.features))
|
||||||
}
|
}
|
||||||
|
@ -399,7 +406,7 @@ func (s *suiteContext) iRunFeatureSuite() error {
|
||||||
func (s *suiteContext) numScenariosRegistered(expected int) (err error) {
|
func (s *suiteContext) numScenariosRegistered(expected int) (err error) {
|
||||||
var num int
|
var num int
|
||||||
for _, ft := range s.testedSuite.features {
|
for _, ft := range s.testedSuite.features {
|
||||||
num += len(ft.ScenarioDefinitions)
|
num += len(ft.pickles)
|
||||||
}
|
}
|
||||||
if num != expected {
|
if num != expected {
|
||||||
err = fmt.Errorf("expected %d scenarios to be registered, but got %d", expected, num)
|
err = fmt.Errorf("expected %d scenarios to be registered, but got %d", expected, num)
|
||||||
|
@ -429,9 +436,7 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(expected string) err
|
||||||
|
|
||||||
var name string
|
var name string
|
||||||
switch t := event.args[0].(type) {
|
switch t := event.args[0].(type) {
|
||||||
case *gherkin.Scenario:
|
case *messages.Pickle:
|
||||||
name = t.Name
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
name = t.Name
|
name = t.Name
|
||||||
}
|
}
|
||||||
if name == expected {
|
if name == expected {
|
||||||
|
@ -448,7 +453,7 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(expected string) err
|
||||||
return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, expected, `"`+strings.Join(found, `", "`)+`"`)
|
return fmt.Errorf(`expected "%s" scenario, but got these fired %s`, expected, `"`+strings.Join(found, `", "`)+`"`)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.DataTable) error {
|
func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *messages.PickleStepArgument_PickleTable) error {
|
||||||
if len(tbl.Rows[0].Cells) != 2 {
|
if len(tbl.Rows[0].Cells) != 2 {
|
||||||
return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0].Cells))
|
return fmt.Errorf("expected two columns for event table row, got: %d", len(tbl.Rows[0].Cells))
|
||||||
}
|
}
|
||||||
|
@ -465,7 +470,7 @@ func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.Data
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
|
func (s *suiteContext) theRenderJSONWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
|
||||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||||
|
|
||||||
expectedString := docstring.Content
|
expectedString := docstring.Content
|
||||||
|
@ -489,7 +494,7 @@ func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) theRenderOutputWillBe(docstring *gherkin.DocString) error {
|
func (s *suiteContext) theRenderOutputWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
|
||||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||||
suiteCtxFuncReg := regexp.MustCompile(`github.com/cucumber/godog.SuiteContext.func(\d+)`)
|
suiteCtxFuncReg := regexp.MustCompile(`github.com/cucumber/godog.SuiteContext.func(\d+)`)
|
||||||
|
|
||||||
|
@ -508,7 +513,7 @@ func (s *suiteContext) theRenderOutputWillBe(docstring *gherkin.DocString) error
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *suiteContext) theRenderXMLWillBe(docstring *gherkin.DocString) error {
|
func (s *suiteContext) theRenderXMLWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
|
||||||
expectedString := docstring.Content
|
expectedString := docstring.Content
|
||||||
actualString := s.out.String()
|
actualString := s.out.String()
|
||||||
|
|
||||||
|
@ -523,28 +528,23 @@ func (s *suiteContext) theRenderXMLWillBe(docstring *gherkin.DocString) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
if !reflect.DeepEqual(expected, actual) {
|
if !reflect.DeepEqual(expected, actual) {
|
||||||
return fmt.Errorf("expected xml does not match actual: %s", actualString)
|
return fmt.Errorf("expected json does not match actual: %s", actualString)
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type testFormatter struct {
|
type testFormatter struct {
|
||||||
*basefmt
|
*basefmt
|
||||||
scenarios []interface{}
|
pickles []*messages.Pickle
|
||||||
}
|
}
|
||||||
|
|
||||||
func testFormatterFunc(suite string, out io.Writer) Formatter {
|
func testFormatterFunc(suite string, out io.Writer) Formatter {
|
||||||
return &testFormatter{basefmt: newBaseFmt(suite, out)}
|
return &testFormatter{basefmt: newBaseFmt(suite, out)}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testFormatter) Node(node interface{}) {
|
func (f *testFormatter) Pickle(p *messages.Pickle) {
|
||||||
f.basefmt.Node(node)
|
f.basefmt.Pickle(p)
|
||||||
switch t := node.(type) {
|
f.pickles = append(f.pickles, p)
|
||||||
case *gherkin.Scenario:
|
|
||||||
f.scenarios = append(f.scenarios, t)
|
|
||||||
case *gherkin.ScenarioOutline:
|
|
||||||
f.scenarios = append(f.scenarios, t)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (f *testFormatter) Summary() {}
|
func (f *testFormatter) Summary() {}
|
||||||
|
|
|
@ -2,28 +2,32 @@ package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/cucumber/messages-go/v9"
|
||||||
)
|
)
|
||||||
|
|
||||||
func assertNotMatchesTagFilter(tags []string, filter string, t *testing.T) {
|
func assertNotMatchesTagFilter(tags []*messages.Pickle_PickleTag, filter string, t *testing.T) {
|
||||||
if matchesTags(filter, tags) {
|
if matchesTags(filter, tags) {
|
||||||
t.Errorf(`expected tags: %v not to match tag filter "%s", but it did`, tags, filter)
|
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) {
|
func assertMatchesTagFilter(tags []*messages.Pickle_PickleTag, filter string, t *testing.T) {
|
||||||
if !matchesTags(filter, tags) {
|
if !matchesTags(filter, tags) {
|
||||||
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, tags, filter)
|
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, tags, filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTagFilter(t *testing.T) {
|
func TestTagFilter(t *testing.T) {
|
||||||
assertMatchesTagFilter([]string{"wip"}, "@wip", t)
|
assertMatchesTagFilter([]*tag{{Name: "wip"}}, "@wip", t)
|
||||||
assertMatchesTagFilter([]string{}, "~@wip", t)
|
assertMatchesTagFilter([]*tag{}, "~@wip", t)
|
||||||
assertMatchesTagFilter([]string{"one", "two"}, "@two,@three", t)
|
assertMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@two,@three", t)
|
||||||
assertMatchesTagFilter([]string{"one", "two"}, "@one&&@two", t)
|
assertMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one&&@two", t)
|
||||||
assertMatchesTagFilter([]string{"one", "two"}, "one && two", t)
|
assertMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "one && two", t)
|
||||||
|
|
||||||
assertNotMatchesTagFilter([]string{}, "@wip", t)
|
assertNotMatchesTagFilter([]*tag{}, "@wip", t)
|
||||||
assertNotMatchesTagFilter([]string{"one", "two"}, "@one&&~@two", t)
|
assertNotMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one&&~@two", t)
|
||||||
assertNotMatchesTagFilter([]string{"one", "two"}, "@one && ~@two", t)
|
assertNotMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one && ~@two", t)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type tag = messages.Pickle_PickleTag
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче