Merge branch 'release/v0.9.0' into release/v0.9.0-rc1

Этот коммит содержится в:
Fredrik Lönnblad 2020-03-09 15:07:49 -03:00
родитель e0ed649cd9 572b13d2db
коммит 9c2d83a29a
44 изменённых файлов: 1333 добавлений и 13349 удалений

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

@ -23,7 +23,6 @@ commands:
description: "Run go vet"
steps:
- run: go vet github.com/cucumber/godog
- run: go vet github.com/cucumber/godog/gherkin
- run: go vet github.com/cucumber/godog/colors
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
import (
"github.com/cucumber/gherkin-go/v9"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
)
type apiFeature struct {
@ -71,7 +71,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
return godog.ErrPending
}
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) error {
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) error {
return godog.ErrPending
}
@ -98,8 +98,8 @@ import (
"net/http"
"net/http/httptest"
"github.com/cucumber/gherkin-go/v9"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
)
type apiFeature struct {
@ -142,7 +142,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
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 data interface{}
if err = json.Unmarshal([]byte(body.Content), &data); err != nil {

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

@ -8,14 +8,14 @@ import (
"reflect"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
type apiFeature struct {
resp *httptest.ResponseRecorder
}
func (a *apiFeature) resetResponse(interface{}) {
func (a *apiFeature) resetResponse(*messages.Pickle) {
a.resp = httptest.NewRecorder()
}
@ -51,7 +51,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
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{}
// re-encode expected response

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

@ -11,7 +11,7 @@ import (
txdb "github.com/DATA-DOG/go-txdb"
"github.com/cucumber/godog"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
func init() {
@ -24,7 +24,7 @@ type apiFeature struct {
resp *httptest.ResponseRecorder
}
func (a *apiFeature) resetResponse(interface{}) {
func (a *apiFeature) resetResponse(*messages.Pickle) {
a.resp = httptest.NewRecorder()
if a.db != nil {
a.db.Close()
@ -71,7 +71,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
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{}
// re-encode expected response
@ -91,7 +91,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err er
return nil
}
func (a *apiFeature) thereAreUsers(users *gherkin.DataTable) error {
func (a *apiFeature) thereAreUsers(users *messages.PickleStepArgument_PickleTable) error {
var fields []string
var marks []string
head := users.Rows[0].Cells

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

@ -9,6 +9,7 @@ import (
"github.com/cucumber/godog"
"github.com/cucumber/godog/colors"
messages "github.com/cucumber/messages-go/v9"
)
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(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
s.BeforeScenario(func(interface{}) {
s.BeforeScenario(func(*messages.Pickle) {
Godogs = 0 // clean the state before every scenario
})
}

60
builder_go112_test.go Обычный файл
Просмотреть файл

@ -0,0 +1,60 @@
// +build go1.12
// +build !go1.13
package godog
import (
"bytes"
"os"
"path/filepath"
"testing"
)
func TestGodogBuildWithVendoredGodogAndMod(t *testing.T) {
gopath := filepath.Join(os.TempDir(), "_gpc")
dir := filepath.Join(gopath, "src", "godogs")
err := buildTestPackage(dir, map[string]string{
"godogs.feature": builderFeatureFile,
"godogs.go": builderMainCodeFile,
"godogs_test.go": builderTestFile,
"go.mod": builderModFile,
})
if err != nil {
os.RemoveAll(gopath)
t.Fatal(err)
}
defer os.RemoveAll(gopath)
pkg := filepath.Join(dir, "vendor", "github.com", "cucumber")
if err := os.MkdirAll(pkg, 0755); err != nil {
t.Fatal(err)
}
prevDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
// symlink godog package
if err := os.Symlink(prevDir, filepath.Join(pkg, "godog")); err != nil {
t.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(prevDir)
cmd := buildTestCommand(t, "godogs.feature")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(envVarsWithoutGopath(), "GOPATH="+gopath)
if err := cmd.Run(); err != nil {
t.Log(stdout.String())
t.Log(stderr.String())
t.Fatal(err)
}
}

54
builder_go113_test.go Обычный файл
Просмотреть файл

@ -0,0 +1,54 @@
// +build go1.13
package godog
import (
"bytes"
"os"
"os/exec"
"path/filepath"
"testing"
)
func TestGodogBuildWithVendoredGodogAndMod(t *testing.T) {
gopath := filepath.Join(os.TempDir(), "_gpc")
dir := filepath.Join(gopath, "src", "godogs")
err := buildTestPackage(dir, map[string]string{
"godogs.feature": builderFeatureFile,
"godogs.go": builderMainCodeFile,
"godogs_test.go": builderTestFile,
"go.mod": builderModFile,
})
if err != nil {
os.RemoveAll(gopath)
t.Fatal(err)
}
defer os.RemoveAll(gopath)
prevDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
if err = exec.Command("go", "mod", "vendor").Run(); err != nil {
t.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(prevDir)
cmd := buildTestCommand(t, "godogs.feature")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(envVarsWithoutGopath(), "GOPATH="+gopath)
if err := cmd.Run(); err != nil {
t.Log(stdout.String())
t.Log(stderr.String())
t.Fatal(err)
}
}

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

@ -310,55 +310,6 @@ func TestGodogBuildWithinGopath(t *testing.T) {
}
}
func TestGodogBuildWithVendoredGodogAndMod(t *testing.T) {
gopath := filepath.Join(os.TempDir(), "_gpc")
dir := filepath.Join(gopath, "src", "godogs")
err := buildTestPackage(dir, map[string]string{
"godogs.feature": builderFeatureFile,
"godogs.go": builderMainCodeFile,
"godogs_test.go": builderTestFile,
"go.mod": builderModFile,
})
if err != nil {
os.RemoveAll(gopath)
t.Fatal(err)
}
defer os.RemoveAll(gopath)
pkg := filepath.Join(dir, "vendor", "github.com", "cucumber")
if err := os.MkdirAll(pkg, 0755); err != nil {
t.Fatal(err)
}
prevDir, err := os.Getwd()
if err != nil {
t.Fatal(err)
}
// symlink godog package
if err := os.Symlink(prevDir, filepath.Join(pkg, "godog")); err != nil {
t.Fatal(err)
}
if err := os.Chdir(dir); err != nil {
t.Fatal(err)
}
defer os.Chdir(prevDir)
cmd := buildTestCommand(t, "godogs.feature")
var stdout, stderr bytes.Buffer
cmd.Stdout = &stdout
cmd.Stderr = &stderr
cmd.Env = append(envVarsWithoutGopath(), "GOPATH="+gopath)
if err := cmd.Run(); err != nil {
t.Log(stdout.String())
t.Log(stderr.String())
t.Fatal(err)
}
}
func TestGodogBuildWithVendoredGodogWithoutModule(t *testing.T) {
gopath := filepath.Join(os.TempDir(), "_gp")
dir := filepath.Join(gopath, "src", "godogs")

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

@ -181,7 +181,7 @@ func (cw *tagColorWriter) Write(p []byte) (int, error) {
}
if cw.state == outsideCsiCode {
nw, err = cw.w.Write(p[first:len(p)])
nw, err = cw.w.Write(p[first:])
r += nw
}

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

@ -409,7 +409,7 @@ func (cw *ansiColorWriter) Write(p []byte) (int, error) {
}
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
}

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

@ -40,7 +40,7 @@ Feature: load features
| feature | number |
| features/load.feature:3 | 0 |
| features/load.feature:6 | 1 |
| features/load.feature | 4 |
| features/load.feature | 6 |
Scenario: load a number of feature files
Given a feature path "features/load.feature"

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

@ -48,7 +48,7 @@ Feature: undefined step snippets
When I run feature suite
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
}

Различия файлов не показаны, т.к. их слишком много Показать различия

433
fmt.go
Просмотреть файл

@ -15,7 +15,8 @@ import (
"unicode"
"github.com/cucumber/godog/colors"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
// some snippet formatting regexps
@ -43,7 +44,7 @@ var undefinedSnippetsTpl = template.Must(template.New("snippets").Funcs(snippetH
type undefinedSnippet struct {
Method string
Expr string
argument interface{} // gherkin step argument
argument *messages.PickleStepArgument
}
type registeredFormatter struct {
@ -97,14 +98,14 @@ func AvailableFormatters() map[string]string {
// formatters needs to be registered with a
// godog.Format function call
type Formatter interface {
Feature(*gherkin.Feature, string, []byte)
Node(interface{})
Defined(*gherkin.Step, *StepDef)
Failed(*gherkin.Step, *StepDef, error)
Passed(*gherkin.Step, *StepDef)
Skipped(*gherkin.Step, *StepDef)
Undefined(*gherkin.Step, *StepDef)
Pending(*gherkin.Step, *StepDef)
Feature(*messages.GherkinDocument, string, []byte)
Pickle(*messages.Pickle)
Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
Failed(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition, error)
Passed(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
Skipped(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
Undefined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
Pending(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition)
Summary()
}
@ -120,17 +121,17 @@ type ConcurrentFormatter interface {
// suite name and io.Writer to record output
type FormatterFunc func(string, io.Writer) Formatter
type stepType int
type stepResultStatus int
const (
passed stepType = iota
passed stepResultStatus = iota
failed
skipped
undefined
pending
)
func (st stepType) clr() colors.ColorFunc {
func (st stepResultStatus) clr() colors.ColorFunc {
switch st {
case passed:
return green
@ -143,7 +144,7 @@ func (st stepType) clr() colors.ColorFunc {
}
}
func (st stepType) String() string {
func (st stepResultStatus) String() string {
switch st {
case passed:
return "passed"
@ -161,65 +162,17 @@ func (st stepType) String() string {
}
type stepResult struct {
typ stepType
feature *feature
owner interface{}
step *gherkin.Step
status stepResultStatus
time time.Time
def *StepDef
err error
owner *messages.Pickle
step *messages.Pickle_PickleStep
def *StepDefinition
}
func (f stepResult) line() string {
return fmt.Sprintf("%s:%d", f.feature.Path, f.step.Location.Line)
}
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 newStepResult(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) *stepResult {
return &stepResult{time: timeNowFunc(), owner: pickle, step: step, def: match}
}
func newBaseFmt(suite string, out io.Writer) *basefmt {
@ -241,215 +194,203 @@ type basefmt struct {
started time.Time
features []*feature
failed []*stepResult
passed []*stepResult
skipped []*stepResult
undefined []*stepResult
pending []*stepResult
lock *sync.Mutex
}
func (f *basefmt) Node(n interface{}) {
func (f *basefmt) lastFeature() *feature {
return f.features[len(f.features)-1]
}
func (f *basefmt) lastStepResult() *stepResult {
return f.lastFeature().lastStepResult()
}
func (f *basefmt) findScenario(scenarioAstID string) *messages.GherkinDocument_Feature_Scenario {
for _, ft := range f.features {
if sc := ft.findScenario(scenarioAstID); sc != nil {
return sc
}
}
panic("Couldn't find scenario for AST ID: " + scenarioAstID)
}
func (f *basefmt) findBackground(scenarioAstID string) *messages.GherkinDocument_Feature_Background {
for _, ft := range f.features {
if bg := ft.findBackground(scenarioAstID); bg != nil {
return bg
}
}
return nil
}
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
}
}
panic("Couldn't find step for AST ID: " + stepAstID)
}
func (f *basefmt) Pickle(p *messages.Pickle) {
f.lock.Lock()
defer f.lock.Unlock()
switch t := n.(type) {
case *gherkin.Scenario:
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]
lastExample := feature.Scenarios[len(feature.Scenarios)-1]
newExample := scenario{OutlineName: lastExample.OutlineName, ExampleNo: lastExample.ExampleNo + 1, time: timeNowFunc()}
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)
}
}
feature.pickleResults = append(feature.pickleResults, &pickleResult{Name: p.Name, time: timeNowFunc()})
}
func (f *basefmt) Defined(*gherkin.Step, *StepDef) {
f.lock.Lock()
defer f.lock.Unlock()
}
func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {}
func (f *basefmt) Feature(ft *gherkin.Feature, p string, c []byte) {
func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) {
f.lock.Lock()
defer f.lock.Unlock()
f.features = append(f.features, &feature{Path: p, Feature: ft, time: timeNowFunc()})
f.features = append(f.features, &feature{Path: p, GherkinDocument: ft, time: timeNowFunc()})
}
func (f *basefmt) Passed(step *gherkin.Step, match *StepDef) {
func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
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)
s := newStepResult(pickle, step, match)
s.status = passed
f.lastFeature().appendStepResult(s)
}
func (f *basefmt) Skipped(step *gherkin.Step, match *StepDef) {
func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.lock.Lock()
defer f.lock.Unlock()
s := &stepResult{
owner: f.owner,
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)
s := newStepResult(pickle, step, match)
s.status = skipped
f.lastFeature().appendStepResult(s)
}
func (f *basefmt) Undefined(step *gherkin.Step, match *StepDef) {
func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.lock.Lock()
defer f.lock.Unlock()
s := &stepResult{
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)
s := newStepResult(pickle, step, match)
s.status = undefined
f.lastFeature().appendStepResult(s)
}
func (f *basefmt) Failed(step *gherkin.Step, match *StepDef, err error) {
func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
f.lock.Lock()
defer f.lock.Unlock()
s := &stepResult{
owner: f.owner,
feature: f.features[len(f.features)-1],
step: step,
def: match,
err: err,
typ: failed,
time: timeNowFunc(),
}
f.failed = append(f.failed, s)
f.features[len(f.features)-1].appendStepResult(s)
s := newStepResult(pickle, step, match)
s.status = failed
s.err = err
f.lastFeature().appendStepResult(s)
}
func (f *basefmt) Pending(step *gherkin.Step, match *StepDef) {
func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.lock.Lock()
defer f.lock.Unlock()
s := &stepResult{
owner: f.owner,
feature: f.features[len(f.features)-1],
step: step,
def: match,
typ: pending,
time: timeNowFunc(),
}
f.pending = append(f.pending, s)
f.features[len(f.features)-1].appendStepResult(s)
s := newStepResult(pickle, step, match)
s.status = pending
f.lastFeature().appendStepResult(s)
}
func (f *basefmt) Summary() {
var total, passed, undefined int
for _, ft := range f.features {
for _, def := range ft.ScenarioDefinitions {
switch t := def.(type) {
case *gherkin.Scenario:
total++
if len(t.Steps) == 0 {
undefined++
var totalSc, passedSc, undefinedSc int
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int
for _, feat := range f.features {
for _, pr := range feat.pickleResults {
var prStatus stepResultStatus
totalSc++
if len(pr.stepResults) == 0 {
prStatus = undefined
}
case *gherkin.ScenarioOutline:
for _, ex := range t.Examples {
total += len(ex.TableBody)
if len(t.Steps) == 0 {
undefined += len(ex.TableBody)
for _, sr := range pr.stepResults {
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 {
passedSc++
} else if prStatus == undefined {
undefinedSc++
}
}
}
passed = total - undefined
var owner interface{}
for _, undef := range f.undefined {
if owner != undef.owner {
undefined++
owner = undef.owner
}
}
var steps, parts, scenarios []string
nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending)
if len(f.passed) > 0 {
steps = append(steps, green(fmt.Sprintf("%d passed", len(f.passed))))
if passedSt > 0 {
steps = append(steps, green(fmt.Sprintf("%d passed", passedSt)))
}
if len(f.failed) > 0 {
passed -= len(f.failed)
parts = append(parts, red(fmt.Sprintf("%d failed", len(f.failed))))
steps = append(steps, parts[len(parts)-1])
if failedSt > 0 {
parts = append(parts, red(fmt.Sprintf("%d failed", failedSt)))
steps = append(steps, red(fmt.Sprintf("%d failed", failedSt)))
}
if len(f.pending) > 0 {
passed -= len(f.pending)
parts = append(parts, yellow(fmt.Sprintf("%d pending", len(f.pending))))
steps = append(steps, yellow(fmt.Sprintf("%d pending", len(f.pending))))
if pendingSt > 0 {
parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt)))
steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt)))
}
if len(f.undefined) > 0 {
passed -= undefined
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefined)))
steps = append(steps, yellow(fmt.Sprintf("%d undefined", len(f.undefined))))
} else if undefined > 0 {
if undefinedSt > 0 {
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt)))
} else if undefinedSc > 0 {
// 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 {
steps = append(steps, cyan(fmt.Sprintf("%d skipped", len(f.skipped))))
if skippedSt > 0 {
steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt)))
}
if passed > 0 {
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passed)))
if passedSc > 0 {
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc)))
}
scenarios = append(scenarios, parts...)
elapsed := timeNowFunc().Sub(f.started)
fmt.Fprintln(f.out, "")
if total == 0 {
if totalSc == 0 {
fmt.Fprintln(f.out, "No scenarios")
} 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")
} 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()
@ -484,21 +425,6 @@ func (f *basefmt) Copy(cf ConcurrentFormatter) {
for _, v := range source.features {
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())
}
}
if s.argument != nil {
switch s.argument.(type) {
case *gherkin.DocString:
args = append(args, "*gherkin.DocString")
case *gherkin.DataTable:
args = append(args, "*gherkin.DataTable")
if s.argument.GetDocString() != nil {
args = append(args, "*messages.PickleStepArgument_PickleDocString")
}
if s.argument.GetDataTable() != nil {
args = append(args, "*messages.PickleStepArgument_PickleTable")
}
}
@ -550,15 +477,30 @@ func (s *undefinedSnippet) Args() (ret string) {
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 {
if len(f.undefined) == 0 {
undefinedStepResults := f.findStepResults(undefined)
if len(undefinedStepResults) == 0 {
return ""
}
var index int
var snips []*undefinedSnippet
// build snippets
for _, u := range f.undefined {
for _, u := range undefinedStepResults {
steps := []string{u.step.Text}
arg := u.step.Argument
if u.def != nil {
@ -587,7 +529,7 @@ func (f *basefmt) snippets() string {
name = strings.Join(words, "")
if len(name) == 0 {
index++
name = fmt.Sprintf("stepDefinition%d", index)
name = fmt.Sprintf("StepDefinitioninition%d", index)
}
var found bool
@ -611,25 +553,6 @@ func (f *basefmt) snippets() string {
return strings.Replace(buf.String(), " \n", "\n", -1)
}
func (f *basefmt) isLastStep(s *gherkin.Step) bool {
ft := f.features[len(f.features)-1]
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
func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool {
return pickle.Steps[len(pickle.Steps)-1].Id == step.Id
}

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

@ -15,11 +15,10 @@ import (
"encoding/json"
"fmt"
"io"
"strconv"
"strings"
"time"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
func init() {
@ -107,7 +106,7 @@ type cukefmt struct {
// it restricts this formatter to run only in synchronous single
// threaded execution. Unless running a copy of formatter for each feature
path string
stat stepType // last step status, before skipped
status stepResultStatus // last step status, before skipped
ID string // current test id.
results []cukeFeatureJSON // structure that represent cuke results
curStep *cukeStep // track the current step
@ -122,115 +121,86 @@ type cukefmt struct {
// of the example name inorder to build id fields.
}
func (f *cukefmt) Node(n interface{}) {
f.basefmt.Node(n)
func (f *cukefmt) Pickle(pickle *messages.Pickle) {
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
// append the name associated with the example as part of the id.
case *gherkin.Examples:
f.curExampleName = makeID(t.Name)
f.curRow = 2 // there can be more than one example set per outline so reset row count.
// cucumber counts the header row as an example when creating the id.
// store any example level tags in a temp location.
f.curExampleTags = make([]cukeTag, len(t.Tags))
for idx, element := range t.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))
// apply feature level tags
if len(f.curOutline.Tags) > 0 {
copy(f.curOutline.Tags, f.curFeature.Tags)
// apply outline level tags.
for idx, element := range t.Tags {
f.curOutline.Tags[idx+len(f.curFeature.Tags)].Line = element.Location.Line
f.curOutline.Tags[idx+len(f.curFeature.Tags)].Name = element.Name
}
}
// 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.Name = pickle.Name
f.curElement.Line = int(scenario.Location.Line)
f.curElement.Description = scenario.Description
f.curElement.Keyword = scenario.Keyword
f.curElement.ID = f.curFeature.ID + ";" + makeID(pickle.Name)
f.curElement.Type = "scenario"
f.curElement.Tags = make([]cukeTag, len(t.Tags)+len(f.curFeature.Tags))
f.curElement.Tags = make([]cukeTag, len(scenario.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
for idx, element := range scenario.Tags {
f.curElement.Tags[idx+len(f.curFeature.Tags)].Line = int(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]
if len(pickle.AstNodeIds) == 1 {
return
}
// copy in example level tags.
f.curElement.Tags = append(f.curElement.Tags, f.curExampleTags...)
example, _ := f.findExample(pickle.AstNodeIds[1])
// apply example level tags.
for _, tag := range example.Tags {
tag := cukeTag{Line: int(tag.Location.Line), Name: tag.Name}
f.curElement.Tags = append(f.curElement.Tags, tag)
}
examples := scenario.GetExamples()
if len(examples) > 0 {
rowID := pickle.AstNodeIds[1]
for _, example := range examples {
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)
}
}
}
}
}
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.ID = makeID(ft.Name)
f.ID = makeID(gd.Feature.Name)
f.results = append(f.results, cukeFeatureJSON{})
f.curFeature = &f.results[len(f.results)-1]
f.curFeature.URI = p
f.curFeature.Name = ft.Name
f.curFeature.Keyword = ft.Keyword
f.curFeature.Line = ft.Location.Line
f.curFeature.Description = ft.Description
f.curFeature.Name = gd.Feature.Name
f.curFeature.Keyword = gd.Feature.Keyword
f.curFeature.Line = int(gd.Feature.Location.Line)
f.curFeature.Description = gd.Feature.Description
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 {
f.curFeature.Tags[idx].Line = element.Location.Line
for idx, element := range gd.Feature.Tags {
f.curFeature.Tags[idx].Line = int(element.Location.Line)
f.curFeature.Tags[idx].Name = element.Name
}
f.curFeature.Comments = make([]cukeComment, len(ft.Comments))
for idx, comment := range ft.Comments {
f.curFeature.Comments = make([]cukeComment, len(gd.Comments))
for idx, comment := range gd.Comments {
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 +214,43 @@ func (f *cukefmt) Summary() {
}
func (f *cukefmt) step(res *stepResult) {
// determine if test case has finished
switch t := f.owner.(type) {
case *gherkin.TableRow:
d := int(timeNowFunc().Sub(f.startTime).Nanoseconds())
f.curStep.Result.Duration = &d
f.curStep.Line = t.Location.Line
f.curStep.Result.Status = res.typ.String()
f.curStep.Result.Status = res.status.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.curElement.Steps = append(f.curElement.Steps, cukeStep{})
f.curStep = &f.curElement.Steps[len(f.curElement.Steps)-1]
f.curStep.Name = step.Text
f.curStep.Line = step.Location.Line
f.curStep.Keyword = step.Keyword
step := f.findStep(pickleStep.AstNodeIds[0])
if _, ok := step.Argument.(*gherkin.DocString); ok {
f.curStep.Docstring = &cukeDocstring{}
f.curStep.Docstring.ContentType = strings.TrimSpace(step.Argument.(*gherkin.DocString).ContentType)
f.curStep.Docstring.Line = step.Argument.(*gherkin.DocString).Location.Line
f.curStep.Docstring.Value = step.Argument.(*gherkin.DocString).Content
line := step.Location.Line
if len(pickle.AstNodeIds) == 2 {
_, row := f.findExample(pickle.AstNodeIds[1])
line = row.Location.Line
}
if _, ok := step.Argument.(*gherkin.DataTable); ok {
dataTable := step.Argument.(*gherkin.DataTable)
f.curStep.Name = pickleStep.Text
f.curStep.Line = int(line)
f.curStep.Keyword = step.Keyword
f.curStep.DataTable = make([]*cukeDataTableRow, len(dataTable.Rows))
for i, row := range dataTable.Rows {
arg := pickleStep.Argument
if arg.GetDocString() != nil && step.GetDocString() != nil {
f.curStep.Docstring = &cukeDocstring{}
f.curStep.Docstring.ContentType = strings.TrimSpace(arg.GetDocString().MediaType)
f.curStep.Docstring.Line = int(step.GetDocString().Location.Line)
f.curStep.Docstring.Value = arg.GetDocString().Content
}
if arg.GetDataTable() != nil {
f.curStep.DataTable = make([]*cukeDataTableRow, len(arg.GetDataTable().Rows))
for i, row := range arg.GetDataTable().Rows {
cells := make([]string, len(row.Cells))
for j, cell := range row.Cells {
cells[j] = cell.Value
@ -300,42 +264,47 @@ func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) {
}
}
func (f *cukefmt) Passed(step *gherkin.Step, match *StepDef) {
f.basefmt.Passed(step, match)
f.stat = passed
f.step(f.passed[len(f.passed)-1])
func (f *cukefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Passed(pickle, step, match)
f.status = passed
f.step(f.lastStepResult())
}
func (f *cukefmt) Skipped(step *gherkin.Step, match *StepDef) {
f.basefmt.Skipped(step, match)
f.step(f.skipped[len(f.skipped)-1])
func (f *cukefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Skipped(pickle, step, match)
f.step(f.lastStepResult())
// no duration reported for skipped.
f.curStep.Result.Duration = nil
}
func (f *cukefmt) Undefined(step *gherkin.Step, match *StepDef) {
f.basefmt.Undefined(step, match)
f.stat = undefined
f.step(f.undefined[len(f.undefined)-1])
func (f *cukefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Undefined(pickle, step, match)
f.status = undefined
f.step(f.lastStepResult())
// 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
}
func (f *cukefmt) Failed(step *gherkin.Step, match *StepDef, err error) {
f.basefmt.Failed(step, match, err)
f.stat = failed
f.step(f.failed[len(f.failed)-1])
func (f *cukefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
f.basefmt.Failed(pickle, step, match, err)
f.status = failed
f.step(f.lastStepResult())
}
func (f *cukefmt) Pending(step *gherkin.Step, match *StepDef) {
f.stat = pending
f.basefmt.Pending(step, match)
f.step(f.pending[len(f.pending)-1])
func (f *cukefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Pending(pickle, step, match)
f.status = pending
f.step(f.lastStepResult())
// 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
}

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

@ -5,7 +5,7 @@ import (
"fmt"
"io"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
const nanoSec = 1000000
@ -41,7 +41,7 @@ type events struct {
// it restricts this formatter to run only in synchronous single
// threaded execution. Unless running a copy of formatter for each feature
path string
stat stepType // last step status, before skipped
status stepResultStatus // last step status, before skipped
outlineSteps int // number of current outline scenario steps
}
@ -53,25 +53,8 @@ func (f *events) event(ev interface{}) {
fmt.Fprintln(f.out, string(data))
}
func (f *events) Node(n interface{}) {
f.basefmt.Node(n)
var id string
var undefined bool
switch t := n.(type) {
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
}
func (f *events) Pickle(pickle *messages.Pickle) {
f.basefmt.Pickle(pickle)
f.event(&struct {
Event string `json:"event"`
@ -79,11 +62,11 @@ func (f *events) Node(n interface{}) {
Timestamp int64 `json:"timestamp"`
}{
"TestCaseStarted",
id,
f.scenarioLocation(pickle.AstNodeIds),
timeNowFunc().UnixNano() / nanoSec,
})
if undefined {
if len(pickle.Steps) == 0 {
// @TODO: is status undefined or passed? when there are no steps
// for this scenario
f.event(&struct {
@ -93,14 +76,14 @@ func (f *events) Node(n interface{}) {
Status string `json:"status"`
}{
"TestCaseFinished",
id,
f.scenarioLocation(pickle.AstNodeIds),
timeNowFunc().UnixNano() / nanoSec,
"undefined",
})
}
}
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.path = p
f.event(&struct {
@ -109,7 +92,7 @@ func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
Source string `json:"source"`
}{
"TestSource",
fmt.Sprintf("%s:%d", p, ft.Location.Line),
fmt.Sprintf("%s:%d", p, ft.Feature.Location.Line),
string(c),
})
}
@ -117,10 +100,10 @@ func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
func (f *events) Summary() {
// @TODO: determine status
status := passed
if len(f.failed) > 0 {
if len(f.findStepResults(failed)) > 0 {
status = failed
} else if len(f.passed) == 0 {
if len(f.undefined) > len(f.pending) {
} else if len(f.findStepResults(passed)) == 0 {
if len(f.findStepResults(undefined)) > len(f.findStepResults(pending)) {
status = undefined
} else {
status = pending
@ -148,6 +131,8 @@ func (f *events) Summary() {
}
func (f *events) step(res *stepResult) {
step := f.findStep(res.step.AstNodeIds[0])
var errMsg string
if res.err != nil {
errMsg = res.err.Error()
@ -160,25 +145,13 @@ func (f *events) step(res *stepResult) {
Summary string `json:"summary,omitempty"`
}{
"TestStepFinished",
fmt.Sprintf("%s:%d", f.path, res.step.Location.Line),
fmt.Sprintf("%s:%d", f.path, step.Location.Line),
timeNowFunc().UnixNano() / nanoSec,
res.typ.String(),
res.status.String(),
errMsg,
})
// determine if test case has finished
var finished bool
var line int
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 isLastStep(res.owner, res.step) {
f.event(&struct {
Event string `json:"event"`
Location string `json:"location"`
@ -186,16 +159,18 @@ func (f *events) step(res *stepResult) {
Status string `json:"status"`
}{
"TestCaseFinished",
fmt.Sprintf("%s:%d", f.path, line),
f.scenarioLocation(res.owner.AstNodeIds),
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 {
m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]
m := def.Expr.FindStringSubmatchIndex(pickleStep.Text)[2:]
var args [][2]int
for i := 0; i < len(m)/2; i++ {
pair := m[i : i*2+2]
@ -233,31 +208,47 @@ func (f *events) Defined(step *gherkin.Step, def *StepDef) {
})
}
func (f *events) Passed(step *gherkin.Step, match *StepDef) {
f.basefmt.Passed(step, match)
f.stat = passed
f.step(f.passed[len(f.passed)-1])
func (f *events) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Passed(pickle, step, match)
f.status = passed
f.step(f.lastStepResult())
}
func (f *events) Skipped(step *gherkin.Step, match *StepDef) {
f.basefmt.Skipped(step, match)
f.step(f.skipped[len(f.skipped)-1])
func (f *events) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Skipped(pickle, step, match)
f.step(f.lastStepResult())
}
func (f *events) Undefined(step *gherkin.Step, match *StepDef) {
f.basefmt.Undefined(step, match)
f.stat = undefined
f.step(f.undefined[len(f.undefined)-1])
func (f *events) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Undefined(pickle, step, match)
f.status = undefined
f.step(f.lastStepResult())
}
func (f *events) Failed(step *gherkin.Step, match *StepDef, err error) {
f.basefmt.Failed(step, match, err)
f.stat = failed
f.step(f.failed[len(f.failed)-1])
func (f *events) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
f.basefmt.Failed(pickle, step, match, err)
f.status = failed
f.step(f.lastStepResult())
}
func (f *events) Pending(step *gherkin.Step, match *StepDef) {
f.stat = pending
f.basefmt.Pending(step, match)
f.step(f.pending[len(f.pending)-1])
func (f *events) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Pending(pickle, step, match)
f.status = pending
f.step(f.lastStepResult())
}
func (f *events) scenarioLocation(astNodeIds []string) string {
scenario := f.findScenario(astNodeIds[0])
line := scenario.Location.Line
if len(astNodeIds) == 2 {
_, row := f.findExample(astNodeIds[1])
line = row.Location.Line
}
return fmt.Sprintf("%s:%d", f.path, line)
}

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

@ -64,45 +64,55 @@ func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*f
for idx, feat := range features {
ts := junitTestSuite{
Name: feat.Name,
Name: feat.GherkinDocument.Feature.Name,
Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()),
TestCases: make([]*junitTestCase, len(feat.Scenarios)),
TestCases: make([]*junitTestCase, len(feat.pickleResults)),
}
for idx, scenario := range feat.Scenarios {
tc := junitTestCase{
Name: scenario.Name,
Time: junitTimeDuration(scenario.startedAt(), scenario.finishedAt()),
var testcaseNames = make(map[string]int)
for _, pickleResult := range feat.pickleResults {
testcaseNames[pickleResult.Name] = testcaseNames[pickleResult.Name] + 1
}
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++
suite.Tests++
for _, step := range scenario.Steps {
switch step.typ {
for _, stepResult := range pickleResult.stepResults {
switch stepResult.status {
case passed:
tc.Status = passed.String()
case failed:
tc.Status = failed.String()
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:
tc.Error = append(tc.Error, &junitError{
Type: "skipped",
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
Message: fmt.Sprintf("Step %s", stepResult.step.Text),
})
case undefined:
tc.Status = undefined.String()
tc.Error = append(tc.Error, &junitError{
Type: "undefined",
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
Message: fmt.Sprintf("Step %s", stepResult.step.Text),
})
case pending:
tc.Status = pending.String()
tc.Error = append(tc.Error, &junitError{
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"
"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/gherkin"
)
var sampleGherkinFeature = `
@ -49,18 +53,21 @@ Feature: junit formatter
`
func TestJUnitFormatterOutput(t *testing.T) {
feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "any.feature"
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
w := colors.Uncolored(&buf)
s := &Suite{
fmt: junitFunc("junit", w),
features: []*feature{&feature{
Path: "any.feature",
Feature: feat,
features: []*feature{{
GherkinDocument: gd,
pickles: pickles,
Path: path,
Content: []byte(sampleGherkinFeature),
}},
}
@ -151,19 +158,18 @@ func TestJUnitFormatterOutput(t *testing.T) {
},
}},
}
s.run()
s.fmt.Summary()
var exp bytes.Buffer
if _, err = io.WriteString(&exp, xml.Header); err != nil {
t.Fatalf("unexpected error: %v", err)
}
_, err = io.WriteString(&exp, xml.Header)
require.NoError(t, err)
enc := xml.NewEncoder(&exp)
enc.Indent("", " ")
if err = enc.Encode(expected); err != nil {
t.Fatalf("unexpected error: %v", err)
}
if buf.String() != exp.String() {
t.Fatalf("expected output does not match: %s", buf.String())
}
err = enc.Encode(expected)
require.NoError(t, err)
assert.Equal(t, exp.String(), buf.String())
}

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

@ -3,13 +3,13 @@ package godog
import (
"fmt"
"io"
"math"
"regexp"
"strings"
"unicode/utf8"
"github.com/cucumber/messages-go/v9"
"github.com/cucumber/godog/colors"
"github.com/cucumber/godog/gherkin"
)
func init() {
@ -25,79 +25,59 @@ var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>")
// a built in default pretty formatter
type pretty struct {
*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) {
if len(f.features) != 0 {
// not a first feature, add a newline
fmt.Fprintln(f.out, "")
func (f *pretty) Feature(gd *messages.GherkinDocument, p string, c []byte) {
f.basefmt.Feature(gd, p, c)
f.printFeature(gd.Feature)
}
// 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) != "" {
for _, line := range strings.Split(ft.Description, "\n") {
}
func (f *pretty) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
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) > 1 {
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))
}
}
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 {
@ -108,342 +88,302 @@ func keywordAndName(keyword, name string) string {
return title
}
func (f *pretty) printUndefinedScenario(sc interface{}) {
if f.bgSteps > 0 {
bg := f.feature.Background
f.commentPos = f.longestStep(bg.Steps, f.length(bg))
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(bg.Keyword, bg.Name))
func (f *pretty) scenarioLengths(scenarioAstID string) (scenarioHeaderLength int, maxLength int) {
astScenario := f.findScenario(scenarioAstID)
astBackground := f.findBackground(scenarioAstID)
for _, step := range bg.Steps {
f.bgSteps--
f.printStep(step, nil, colors.Cyan)
}
scenarioHeaderLength = f.lengthPickle(astScenario.Keyword, astScenario.Name)
maxLength = f.longestStep(astScenario.Steps, scenarioHeaderLength)
if astBackground != nil {
maxLength = f.longestStep(astBackground.Steps, maxLength)
}
switch t := sc.(type) {
case *gherkin.Scenario:
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)
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)
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)
}
for _, example := range t.Examples {
max := longest(example, cyan)
f.printExampleHeader(example, max)
for _, row := range example.TableBody {
f.printExampleRow(row, max, cyan)
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*2) + cyan(strings.TrimSpace(step.Keyword)) + " " + cyan(step.Text)
fmt.Fprintln(f.out, text)
}
}
// do not print scenario headers and examples multiple times
if len(astScenario.Examples) > 0 {
exampleTable, exampleRow := f.findExample(pickle.AstNodeIds[1])
firstExampleRow := exampleTable.TableBody[0].Id == exampleRow.Id
firstExamplesTable := astScenario.Examples[0].Location.Line == exampleTable.Location.Line
if !(firstExamplesTable && firstExampleRow) {
return
}
}
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
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")
for _, fail := range f.failed {
fmt.Fprintln(f.out, s(2)+red(fail.scenarioDesc())+blackb(" # "+fail.scenarioLine()))
fmt.Fprintln(f.out, s(4)+red(strings.TrimSpace(fail.step.Keyword)+" "+fail.step.Text)+blackb(" # "+fail.line()))
fmt.Fprintln(f.out, s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n")
for _, fail := range failedStepResults {
astScenario := f.findScenario(fail.owner.AstNodeIds[0])
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(f.indent)+red(scenarioDesc)+f.line(astScenario.Location))
fmt.Fprintln(f.out, s(f.indent*2)+red(stepDesc)+f.line(astStep.Location))
fmt.Fprintln(f.out, s(f.indent*3)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n")
}
}
f.basefmt.Summary()
}
func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
var msg string
var clr colors.ColorFunc
func (f *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps int) {
var errorMsg string
var clr = green
ex := outline.Examples[f.outlineNumExample]
example, hasExamples := examples(ex)
if !hasExamples {
astScenario := f.findScenario(pickle.AstNodeIds[0])
scenarioHeaderLength, maxLength := f.scenarioLengths(pickle.AstNodeIds[0])
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
return
}
firstExample := f.outlineNumExamples == len(example.TableBody)
printSteps := firstExample && f.outlineNumExample == 0
lastStep := len(f.lastFeature().lastPickleResult().stepResults) == len(pickle.Steps)
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
switch {
case res.typ == failed:
msg = res.err.Error()
clr = res.typ.clr()
case res.typ == undefined || res.typ == pending:
clr = res.typ.clr()
case res.typ == skipped && clr == nil:
case result.status == failed:
errorMsg = result.err.Error()
clr = result.status.clr()
case result.status == undefined || result.status == pending:
clr = result.status.clr()
case result.status == skipped && clr == nil:
clr = cyan
}
if printSteps && i >= f.totalBgSteps {
if firstExamplesTable && printExampleHeader {
// in first example, we need to print steps
var text string
ostep := outline.Steps[i-f.totalBgSteps]
if res.def != nil {
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
astStep := f.findStep(result.step.AstNodeIds[0])
if result.def != nil {
if m := outlinePlaceholderRegexp.FindAllStringIndex(astStep.Text, -1); len(m) > 0 {
var pos int
for i := 0; i < len(m); i++ {
pair := m[i]
text += cyan(ostep.Text[pos:pair[0]])
text += cyanb(ostep.Text[pair[0]:pair[1]])
text += cyan(astStep.Text[pos:pair[0]])
text += cyanb(astStep.Text[pair[0]:pair[1]])
pos = pair[1]
}
text += cyan(ostep.Text[pos:len(ostep.Text)])
text += cyan(astStep.Text[pos:len(astStep.Text)])
} 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 {
text = cyan(ostep.Text)
text = cyan(astStep.Text)
}
// 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
// @TODO: need to make example header cells bold
switch t := ostep.Argument.(type) {
case *gherkin.DataTable:
f.printTable(t, cyan)
case *gherkin.DocString:
var ct string
if len(t.ContentType) > 0 {
ct = " " + cyan(t.ContentType)
if table := result.step.Argument.GetDataTable(); table != nil {
f.printTable(table, cyan)
}
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 docString := astStep.GetDocString(); docString != nil {
f.printDocString(docString)
}
}
}
if clr == nil {
clr = green
}
max := longestExampleRow(exampleTable, clr, cyan)
max := longest(example, clr, cyan)
// an example table header
if firstExample {
f.printExampleHeader(example, max)
if printExampleHeader {
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
row := example.TableBody[len(example.TableBody)-f.outlineNumExamples]
f.printExampleRow(row, max, clr)
f.printTableRow(exampleRow, max, clr)
// if there is an error
if msg != "" {
fmt.Fprintln(f.out, s(f.indent*4)+redb(msg))
if errorMsg != "" {
fmt.Fprintln(f.out, s(f.indent*4)+redb(errorMsg))
}
}
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))
for i, cell := range row.Cells {
val := clr(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) printExampleHeader(example *gherkin.Examples, max []int) {
cells := make([]string, len(example.TableHeader.Cells))
// 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) printTableHeader(row *messages.GherkinDocument_Feature_TableRow, max []int) {
f.printTableRow(row, max, cyan)
}
func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c colors.ColorFunc) {
text := s(f.indent*2) + c(strings.TrimSpace(step.Keyword)) + " "
switch {
case def != nil:
if m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]; len(m) > 0 {
var pos, i int
for pos, i = 0, 0; i < len(m); i++ {
if m[i] == -1 {
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)
func (f *pretty) printStep(result *stepResult) {
astBackground := f.findBackground(result.owner.AstNodeIds[0])
astScenario := f.findScenario(result.owner.AstNodeIds[0])
astStep := f.findStep(result.step.AstNodeIds[0])
var backgroundSteps int
if astBackground != nil {
backgroundSteps = len(astBackground.Steps)
}
fmt.Fprintln(f.out, text)
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))
}
}
astBackgroundStep := backgroundSteps > 0 && backgroundSteps >= len(f.lastFeature().lastPickleResult().stepResults)
func (f *pretty) printStepKind(res *stepResult) {
f.steps--
if f.outline != nil {
f.outlineSteps = append(f.outlineSteps, res)
}
var bgStep bool
bg := f.feature.Background
// if has not printed background yet
switch {
// first background step
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 {
if len(f.lastFeature().pickleResults) > 1 {
return
}
if !f.isBackgroundStep(res.step) || bgStep {
f.printStep(res.step, res.def, res.typ.clr())
firstExecutedBackgroundStep := astBackground != nil && len(f.lastFeature().lastPickleResult().stepResults) == 1
if firstExecutedBackgroundStep {
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name))
}
if res.err != nil {
fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", res.err)))
}
if res.typ == pending {
if !astBackgroundStep && len(astScenario.Examples) > 0 {
f.printOutlineExample(result.owner, backgroundSteps)
return
}
scenarioHeaderLength, maxLength := f.scenarioLengths(result.owner.AstNodeIds[0])
stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text)
firstExecutedScenarioStep := len(f.lastFeature().lastPickleResult().stepResults) == backgroundSteps+1
if !astBackgroundStep && firstExecutedScenarioStep {
f.printScenarioHeader(astScenario, maxLength-scenarioHeaderLength)
}
text := s(f.indent*2) + 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())
}
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"))
}
}
func (f *pretty) isBackgroundStep(step *gherkin.Step) bool {
if f.feature.Background == nil {
return false
func (f *pretty) printDocString(docString *messages.GherkinDocument_Feature_Step_DocString) {
var ct string
if len(docString.MediaType) > 0 {
ct = " " + cyan(docString.MediaType)
}
for _, bstep := range f.feature.Background.Steps {
if bstep.Location.Line == step.Location.Line {
return true
fmt.Fprintln(f.out, s(f.indent*3)+cyan(docString.Delimiter)+ct)
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
func (f *pretty) printTable(t *gherkin.DataTable, c colors.ColorFunc) {
var l = longest(t, c)
// @TODO: need to make example header cells bold
func (f *pretty) printTable(t *messages.PickleStepArgument_PickleTable, c colors.ColorFunc) {
maxColLengths := maxColLengths(t, c)
var cols = make([]string, len(t.Rows[0].Cells))
for _, row := range t.Rows {
for i, cell := range row.Cells {
val := c(cell.Value)
ln := utf8.RuneCountInString(val)
cols[i] = val + s(l[i]-ln)
colLength := utf8.RuneCountInString(val)
cols[i] = val + s(maxColLengths[i]-colLength)
}
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
func longest(tbl interface{}, clrs ...colors.ColorFunc) []int {
var rows []*gherkin.TableRow
switch t := tbl.(type) {
case *gherkin.Examples:
rows = append(rows, t.TableHeader)
rows = append(rows, t.TableBody...)
case *gherkin.DataTable:
rows = append(rows, t.Rows...)
func maxColLengths(t *messages.PickleStepArgument_PickleTable, clrs ...colors.ColorFunc) []int {
if t == nil {
return []int{}
}
longest := make([]int, len(rows[0].Cells))
for _, row := range rows {
longest := make([]int, len(t.Rows[0].Cells))
for _, row := range t.Rows {
for i, cell := range row.Cells {
for _, c := range clrs {
ln := utf8.RuneCountInString(c(cell.Value))
@ -458,35 +398,71 @@ func longest(tbl interface{}, clrs ...colors.ColorFunc) []int {
}
}
}
return longest
}
func (f *pretty) longestStep(steps []*gherkin.Step, base int) int {
ret := base
func longestExampleRow(t *messages.GherkinDocument_Feature_Scenario_Examples, clrs ...colors.ColorFunc) []int {
if t == nil {
return []int{}
}
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
}
}
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.length(step)
if length > ret {
ret = length
length := f.lengthPickleStep(step.Keyword, step.Text)
if length > max {
max = length
}
}
return ret
return max
}
// a line number representation in feature file
func (f *pretty) line(loc *gherkin.Location) string {
return blackb(fmt.Sprintf("# %s:%d", f.features[len(f.features)-1].Path, loc.Line))
func (f *pretty) line(loc *messages.Location) string {
return " " + blackb(fmt.Sprintf("# %s:%d", f.lastFeature().Path, loc.Line))
}
func (f *pretty) length(node interface{}) int {
switch t := node.(type) {
case *gherkin.Background:
return f.indent + utf8.RuneCountInString(strings.TrimSpace(t.Keyword)+": "+t.Name)
case *gherkin.Step:
return f.indent*2 + utf8.RuneCountInString(strings.TrimSpace(t.Keyword)+" "+t.Text)
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))
func (f *pretty) lengthPickleStep(keyword, text string) int {
return f.indent*2 + utf8.RuneCountInString(strings.TrimSpace(keyword)+" "+text)
}
func (f *pretty) lengthPickle(keyword, name string) int {
return f.indent + utf8.RuneCountInString(strings.TrimSpace(keyword)+": "+name)
}

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

@ -6,7 +6,7 @@ import (
"math"
"strings"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
func init() {
@ -37,21 +37,39 @@ func (f *progress) Summary() {
fmt.Fprintf(f.out, " %d\n", *f.steps)
}
}
var failedStepsOutput []string
for _, sr := range f.findStepResults(failed) {
if sr.status == failed {
sc := f.findScenario(sr.owner.AstNodeIds[0])
scenarioDesc := fmt.Sprintf("%s: %s", sc.Keyword, sr.owner.Name)
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, "")
if len(f.failed) > 0 {
fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\n")
for _, fail := range f.failed {
fmt.Fprintln(f.out, s(2)+red(fail.scenarioDesc())+blackb(" # "+fail.scenarioLine()))
fmt.Fprintln(f.out, s(4)+red(strings.TrimSpace(fail.step.Keyword)+" "+fail.step.Text)+blackb(" # "+fail.line()))
fmt.Fprintln(f.out, s(6)+red("Error: ")+redb(fmt.Sprintf("%+v", fail.err))+"\n")
}
}
f.basefmt.Summary()
}
func (f *progress) step(res *stepResult) {
switch res.typ {
switch res.status {
case passed:
fmt.Fprint(f.out, green("."))
case skipped:
@ -71,44 +89,49 @@ func (f *progress) step(res *stepResult) {
}
}
func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
f.basefmt.Passed(step, match)
func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Passed(pickle, step, match)
f.lock.Lock()
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) {
f.basefmt.Skipped(step, match)
func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Skipped(pickle, step, match)
f.lock.Lock()
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) {
f.basefmt.Undefined(step, match)
func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Undefined(pickle, step, match)
f.lock.Lock()
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) {
f.basefmt.Failed(step, match, err)
func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
f.basefmt.Failed(pickle, step, match, err)
f.lock.Lock()
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) {
f.basefmt.Pending(step, match)
func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
f.basefmt.Pending(pickle, step, match)
f.lock.Lock()
defer f.lock.Unlock()
f.step(f.pending[len(f.pending)-1])
f.step(f.lastStepResult())
}
func (f *progress) Sync(cf ConcurrentFormatter) {

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

@ -3,27 +3,41 @@ package godog
import (
"bytes"
"fmt"
"io/ioutil"
"strings"
"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/gherkin"
)
var basicGherkinFeature = `
Feature: basic
Scenario: passing scenario
When one
Then two
`
func TestProgressFormatterOutput(t *testing.T) {
feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "any.feature"
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
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{
Path: "any.feature",
Feature: feat,
features: []*feature{{
GherkinDocument: gd,
pickles: pickles,
Path: path,
Content: []byte(sampleGherkinFeature),
}},
initializer: func(s *Suite) {
@ -67,61 +81,52 @@ func FeatureContext(s *godog.Suite) {
s.Step(` + "`^next undefined$`" + `, nextUndefined)
}`
require.True(t, r.run())
expected = trimAllLines(expected)
r.run()
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) {
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "any.feature"
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
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^one$`, func() error { return nil })
s.Step(`^two$`, func() error { panic("omg") })
},
}
if !r.run() {
t.Fatal("the suite should have failed")
}
require.True(t, r.run())
out := buf.String()
if idx := strings.Index(out, "godog/fmt_progress_test.go:100"); idx == -1 {
t.Fatalf("expected to find panic stacktrace, actual:\n%s", out)
}
actual := buf.String()
assert.Contains(t, actual, "godog/fmt_progress_test.go:107")
}
func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "any.feature"
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
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^sub1$`, func() error { return nil })
s.Step(`^sub-sub$`, func() error { return nil })
@ -131,22 +136,22 @@ func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
},
}
if r.run() {
t.Fatal("the suite should have passed")
}
assert.False(t, r.run())
}
func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "some.feature"
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
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{Feature: feat, Path: "some.feature"}},
features: []*feature{{GherkinDocument: gd, pickles: pickles, Path: path}},
initializer: func(s *Suite) {
s.Step(`^sub1$`, func() error { return nil })
s.Step(`^sub-sub$`, func() error { return fmt.Errorf("errored") })
@ -156,9 +161,7 @@ func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
},
}
if !r.run() {
t.Fatal("the suite should have failed")
}
require.True(t, r.run())
expected := `
.F 2
@ -178,48 +181,21 @@ Error: sub2: sub-sub: errored
expected = trimAllLines(expected)
actual := trimAllLines(buf.String())
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))
}
assert.Equal(t, expected, actual)
}
func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "any.feature"
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
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^sub1$`, func() error { return nil })
s.Step(`^sub-sub$`, func() error { return nil })
@ -229,22 +205,22 @@ func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
},
}
if !r.run() {
t.Fatal("the suite should have failed")
}
assert.True(t, r.run())
}
func TestProgressFormatterMultistepTemplates(t *testing.T) {
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
if err != nil {
t.Fatalf("unexpected error: %v", err)
}
const path = "any.feature"
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
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^sub-sub$`, func() error { return nil })
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() {
t.Fatal("the suite should have passed")
}
require.False(t, r.run())
expected := `
.U 2
@ -287,14 +261,13 @@ func FeatureContext(s *godog.Suite) {
`
expected = trimAllLines(expected)
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) {
const path = "any.feature"
var featureSource = `
Feature: basic
@ -306,26 +279,28 @@ Feature: basic
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{
fmt: progressFunc("progress", ioutil.Discard),
features: []*feature{&feature{Feature: feat}},
fmt: progressFunc("progress", w),
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
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() {
t.Fatal("the suite should have passed")
}
assert.False(t, r.run())
}
func TestProgressFormatterWhenMultiStepHasStepWithArgument(t *testing.T) {
const path = "any.feature"
var featureSource = `
Feature: basic
@ -334,10 +309,10 @@ Feature: basic
When one
Then two`
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 subStep = `three:
"""
@ -348,17 +323,15 @@ Feature: basic
w := colors.Uncolored(&buf)
r := runner{
fmt: progressFunc("progress", w),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^one$`, func() error { return nil })
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() {
t.Fatal("the suite should have failed")
}
require.True(t, r.run())
expected := `
.F 2
@ -366,8 +339,8 @@ Feature: basic
--- Failed steps:
Scenario: passing scenario # :4
Then two # :6
Scenario: passing scenario # any.feature:4
Then two # any.feature:6
Error: nested steps cannot be multiline and have table or content body argument
@ -378,7 +351,6 @@ Feature: basic
expected = trimAllLines(expected)
actual := trimAllLines(buf.String())
if actual != expected {
t.Fatalf("expected output does not match: %s", actual)
}
assert.Equal(t, expected, actual)
}

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

@ -1,5 +1,5 @@
{"event":"TestRunStarted","version":"0.1.0","timestamp":-6795364578871,"suite":"events"}
{"event":"TestSource","location":"formatter-tests/features/scenario_outline.feature:2","source":"@outline @tag\nFeature: outline\n\n @scenario\n Scenario Outline: outline\n Given passing step\n When passing step\n Then odd \u003codd\u003e and even \u003ceven\u003e number\n\n @tagged\n Examples: tagged\n | odd | even |\n | 1 | 2 |\n | 2 | 0 |\n | 3 | 11 |\n\n @tag2\n Examples:\n | odd | even |\n | 1 | 14 |\n | 3 | 9 |\n\n"}
{"event":"TestSource","location":"formatter-tests/features/scenario_outline.feature:2","source":"@outline @tag\nFeature: outline\n\n @scenario\n Scenario Outline: outline\n Given passing step\n When passing step\n Then odd \u003codd\u003e and even \u003ceven\u003e number\n\n @tagged\n Examples: tagged\n | odd | even |\n | 1 | 2 |\n | 2 | 0 |\n | 3 | 11 |\n\n @tag2\n Examples:\n | odd | even |\n | 1 | 14 |\n | 3 | 9 |\n"}
{"event":"TestCaseStarted","location":"formatter-tests/features/scenario_outline.feature:13","timestamp":-6795364578871}
{"event":"StepDefinitionFound","location":"formatter-tests/features/scenario_outline.feature:6","definition_id":"formatters_print_test.go:63 -\u003e passingStepDef","arguments":[]}
{"event":"TestStepStarted","location":"formatter-tests/features/scenario_outline.feature:6","timestamp":-6795364578871}

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

@ -19,4 +19,3 @@ Feature: outline
| odd | even |
| 1 | 14 |
| 3 | 9 |

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

@ -21,16 +21,16 @@
--- <red>Failed steps:</red>
<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>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>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>Then odd 3 and even 11 number</red><bold-black> # formatter-tests/features/scenario_outline.feature:8</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>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>Then odd 3 and even 9 number</red><bold-black> # formatter-tests/features/scenario_outline.feature:8</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>Error: </red><bold-red>9 is not even</bold-red>

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

@ -17,8 +17,8 @@
--- <red>Failed steps:</red>
<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>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>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:63 -> passingStepDef</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:63 -> passingStepDef</bold-black>
--- <red>Failed steps:</red>
<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>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>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>And failing step</red><bold-black> # formatter-tests/features/two_scenarios_with_background_fail.feature:5</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>Error: </red><bold-red>step failed</bold-red>

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

@ -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 @@
[![Build Status](https://secure.travis-ci.org/cucumber/gherkin-go.svg)](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
}
}

Различия файлов не показаны, т.к. их слишком много Показать различия

6
go.mod
Просмотреть файл

@ -3,9 +3,7 @@ module github.com/cucumber/godog
go 1.13
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/kr/pretty v0.2.0 // indirect
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
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect
gopkg.in/yaml.v2 v2.2.7 // indirect
)

17
go.sum
Просмотреть файл

@ -1,22 +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.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/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
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/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 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 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
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=

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

@ -10,11 +10,12 @@ import (
"strings"
"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/gherkin"
)
func okStep() error {
@ -59,12 +60,16 @@ func TestPrintsNoStepDefinitionsIfNoneFound(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)
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
r := runner{
fmt: progressFunc("progress", ioutil.Discard),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^one$`, func() error { return nil })
s.Step(`^two$`, func() error { return ErrPending })
@ -78,12 +83,16 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(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)
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
r := runner{
fmt: progressFunc("progress", ioutil.Discard),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^one$`, func() error { return nil })
// two - is undefined
@ -97,12 +106,16 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(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)
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
r := runner{
fmt: progressFunc("progress", ioutil.Discard),
features: []*feature{&feature{Feature: feat}},
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
initializer: func(s *Suite) {
s.Step(`^one$`, func() error { return nil })
s.Step(`^two$`, func() error { return fmt.Errorf("error") })
@ -243,11 +256,10 @@ type succeedRunTestCase struct {
filename string // expected output file
}
func TestSucceedRun(t *testing.T) {
func TestConcurrencyRun(t *testing.T) {
testCases := []succeedRunTestCase{
{format: "progress", concurrency: 4, filename: "fixtures/progress_output.txt"},
{format: "junit", concurrency: 4, filename: "fixtures/junit_output.xml"},
{format: "cucumber", concurrency: 2, filename: "fixtures/cucumber_output.json"},
}
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)
opt := Options{
@ -282,11 +294,12 @@ func testSucceedRun(t *testing.T, format string, concurrency int, expectedOutput
b, err := ioutil.ReadAll(output)
require.NoError(t, err)
actual := strings.TrimSpace(string(b))
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`)
assert.Equalf(t, expectedOutput, actual, "[%s]", actual)
assert.Equalf(t, expected, actual, "[%s]", actual)
}

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

@ -10,7 +10,7 @@ import (
"strconv"
"strings"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
@ -31,7 +31,7 @@ var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
// will result in main step failure.
type Steps []string
// StepDef is a registered step definition
// StepDefinition is a registered step definition
// contains a StepHandler and regexp which
// is used to match a step. Args which
// were matched by last executed step
@ -39,7 +39,7 @@ type Steps []string
// This structure is passed to the formatter
// when step is matched and is either failed
// or successful
type StepDef struct {
type StepDefinition struct {
args []interface{}
hv reflect.Value
Expr *regexp.Regexp
@ -50,7 +50,7 @@ type StepDef struct {
undefined []string
}
func (sd *StepDef) definitionID() string {
func (sd *StepDefinition) definitionID() string {
ptr := sd.hv.Pointer()
f := runtime.FuncForPC(ptr)
file, line := f.FileLine(ptr)
@ -80,7 +80,7 @@ func (sd *StepDef) definitionID() string {
// run a step with the matched arguments using
// reflect
func (sd *StepDef) run() interface{} {
func (sd *StepDefinition) run() interface{} {
typ := sd.hv.Type()
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))
@ -168,20 +168,32 @@ func (sd *StepDef) run() interface{} {
case reflect.Ptr:
arg := sd.args[i]
switch param.Elem().String() {
case "gherkin.DocString":
v, ok := arg.(*gherkin.DocString)
if !ok {
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg)
case "messages.PickleStepArgument_PickleDocString":
if v, ok := arg.(*messages.PickleStepArgument); ok {
values = append(values, reflect.ValueOf(v.GetDocString()))
break
}
if v, ok := arg.(*messages.PickleStepArgument_PickleDocString); ok {
values = append(values, reflect.ValueOf(v))
case "gherkin.DataTable":
v, ok := arg.(*gherkin.DataTable)
if !ok {
return fmt.Errorf(`cannot convert argument %d: "%v" of type "%T" to *gherkin.DocString`, i, arg, arg)
break
}
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:
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:
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 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]
s, ok := arg.(string)
if !ok {

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

@ -5,13 +5,13 @@ import (
"strings"
"testing"
"github.com/cucumber/godog/gherkin"
"github.com/cucumber/messages-go/v9"
)
func TestShouldSupportIntTypes(t *testing.T) {
fn := func(a int64, b int32, c int16, d int8) error { return nil }
def := &StepDef{
def := &StepDefinition{
Handler: fn,
hv: reflect.ValueOf(fn),
}
@ -30,7 +30,7 @@ func TestShouldSupportIntTypes(t *testing.T) {
func TestShouldSupportFloatTypes(t *testing.T) {
fn := func(a float64, b float32) error { return nil }
def := &StepDef{
def := &StepDefinition{
Handler: fn,
hv: reflect.ValueOf(fn),
}
@ -48,12 +48,12 @@ func TestShouldSupportFloatTypes(t *testing.T) {
func TestShouldNotSupportOtherPointerTypesThanGherkin(t *testing.T) {
fn1 := func(a *int) error { return nil }
fn2 := func(a *gherkin.DocString) error { return nil }
fn3 := func(a *gherkin.DataTable) error { return nil }
fn2 := func(a *messages.PickleStepArgument_PickleDocString) error { return nil }
fn3 := func(a *messages.PickleStepArgument_PickleTable) error { return nil }
def1 := &StepDef{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{(*int)(nil)}}
def2 := &StepDef{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{(*gherkin.DocString)(nil)}}
def3 := &StepDef{Handler: fn3, hv: reflect.ValueOf(fn3), args: []interface{}{(*gherkin.DataTable)(nil)}}
def1 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{(*int)(nil)}}
def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{&messages.PickleStepArgument_PickleDocString{}}}
def3 := &StepDefinition{Handler: fn3, hv: reflect.ValueOf(fn3), args: []interface{}{(*messages.PickleStepArgument_PickleTable)(nil)}}
if err := def1.run(); err == nil {
t.Fatalf("expected conversion error, but got none")
@ -70,8 +70,8 @@ func TestShouldSupportOnlyByteSlice(t *testing.T) {
fn1 := func(a []byte) error { return nil }
fn2 := func(a []string) error { return nil }
def1 := &StepDef{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}}
def2 := &StepDef{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{[]string{}}}
def1 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}}
def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{[]string{}}}
if err := def1.run(); err != nil {
t.Fatalf("unexpected error: %v", err)
@ -83,7 +83,7 @@ func TestShouldSupportOnlyByteSlice(t *testing.T) {
func TestUnexpectedArguments(t *testing.T) {
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"}
if err := def.run(); err == nil {
@ -97,7 +97,7 @@ func TestUnexpectedArguments(t *testing.T) {
// @TODO maybe we should support duration
// 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"}
// if err := def.run(); err == nil {

481
suite.go
Просмотреть файл

@ -4,7 +4,6 @@ import (
"bytes"
"fmt"
"io"
"math/rand"
"os"
"path/filepath"
"reflect"
@ -15,16 +14,17 @@ import (
"time"
"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 typeOfBytes = reflect.TypeOf([]byte(nil))
type feature struct {
*gherkin.Feature
Scenarios []*scenario
*messages.GherkinDocument
pickles []*messages.Pickle
pickleResults []*pickleResult
time time.Time
Content []byte `json:"-"`
@ -32,47 +32,118 @@ type feature struct {
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 {
return f.time
}
func (f feature) finishedAt() time.Time {
if len(f.Scenarios) == 0 {
if len(f.pickleResults) == 0 {
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) {
scenario := f.Scenarios[len(f.Scenarios)-1]
scenario.Steps = append(scenario.Steps, s)
pickles := f.pickleResults[len(f.pickleResults)-1]
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
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] }
type scenario struct {
type pickleResult struct {
Name string
OutlineName string
ExampleNo int
time time.Time
Steps []*stepResult
stepResults []*stepResult
}
func (s scenario) startedAt() time.Time {
func (s pickleResult) startedAt() time.Time {
return s.time
}
func (s scenario) finishedAt() time.Time {
if len(s.Steps) == 0 {
func (s pickleResult) finishedAt() time.Time {
if len(s.stepResults) == 0 {
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
@ -94,7 +165,7 @@ var ErrPending = fmt.Errorf("step implementation is pending")
// executions are catching panic error since it may
// be a context specific error.
type Suite struct {
steps []*StepDef
steps []*StepDefinition
features []*feature
fmt Formatter
@ -105,16 +176,16 @@ type Suite struct {
// suite event handlers
beforeSuiteHandlers []func()
beforeFeatureHandlers []func(*gherkin.Feature)
beforeScenarioHandlers []func(interface{})
beforeStepHandlers []func(*gherkin.Step)
afterStepHandlers []func(*gherkin.Step, error)
afterScenarioHandlers []func(interface{}, error)
afterFeatureHandlers []func(*gherkin.Feature)
beforeFeatureHandlers []func(*messages.GherkinDocument)
beforeScenarioHandlers []func(*messages.Pickle)
beforeStepHandlers []func(*messages.Pickle_PickleStep)
afterStepHandlers []func(*messages.Pickle_PickleStep, error)
afterScenarioHandlers []func(*messages.Pickle, error)
afterFeatureHandlers []func(*messages.GherkinDocument)
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
// to all steps matching the given Regexp expr.
//
@ -126,7 +197,7 @@ type Suite struct {
// the same step, then only the first matched handler
// 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
// running steps.
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()))
}
def := &StepDef{
def := &StepDefinition{
Handler: stepFunc,
Expr: regex,
hv: v,
@ -203,31 +274,28 @@ func (s *Suite) BeforeSuite(fn func()) {
//
// Deprecated: BeforeFeature will be removed. Depending on
// your usecase, do setup in BeforeSuite or BeforeScenario.
func (s *Suite) BeforeFeature(fn func(*gherkin.Feature)) {
func (s *Suite) BeforeFeature(fn func(*messages.GherkinDocument)) {
s.beforeFeatureHandlers = append(s.beforeFeatureHandlers, fn)
}
// BeforeScenario registers a function or method
// to be run before every scenario or scenario outline.
//
// The interface argument may be *gherkin.Scenario
// or *gherkin.ScenarioOutline
// to be run before every pickle.
//
// It is a good practice to restore the default state
// before every scenario so it would be isolated from
// any kind of state.
func (s *Suite) BeforeScenario(fn func(interface{})) {
func (s *Suite) BeforeScenario(fn func(*messages.Pickle)) {
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn)
}
// BeforeStep registers a function or method
// to be run before every scenario
func (s *Suite) BeforeStep(fn func(*gherkin.Step)) {
// to be run before every step.
func (s *Suite) BeforeStep(fn func(*messages.Pickle_PickleStep)) {
s.beforeStepHandlers = append(s.beforeStepHandlers, fn)
}
// 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
// in order to print more state details which may help
@ -235,16 +303,13 @@ func (s *Suite) BeforeStep(fn func(*gherkin.Step)) {
//
// In some cases, for example when running a headless
// 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)
}
// AfterScenario registers an function or method
// to be run after every scenario or scenario outline
//
// The interface argument may be *gherkin.Scenario
// or *gherkin.ScenarioOutline
func (s *Suite) AfterScenario(fn func(interface{}, error)) {
// to be run after every pickle.
func (s *Suite) AfterScenario(fn func(*messages.Pickle, error)) {
s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn)
}
@ -254,7 +319,7 @@ func (s *Suite) AfterScenario(fn func(interface{}, error)) {
// Deprecated: AfterFeature will be removed. Depending on
// your usecase, do cleanup and teardowns in AfterScenario
// or AfterSuite.
func (s *Suite) AfterFeature(fn func(*gherkin.Feature)) {
func (s *Suite) AfterFeature(fn func(*messages.GherkinDocument)) {
s.afterFeatureHandlers = append(s.afterFeatureHandlers, fn)
}
@ -283,7 +348,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)
if def != nil && step.Argument != nil {
def.args = append(def.args, step.Argument)
@ -291,14 +356,14 @@ func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
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
for _, f := range s.beforeStepHandlers {
f(step)
}
match := s.matchStep(step)
s.fmt.Defined(step, match)
s.fmt.Defined(pickle, step, match)
// user multistep definitions may panic
defer func() {
@ -319,11 +384,11 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
switch err {
case nil:
s.fmt.Passed(step, match)
s.fmt.Passed(pickle, step, match)
case ErrPending:
s.fmt.Pending(step, match)
s.fmt.Pending(pickle, step, match)
default:
s.fmt.Failed(step, match, err)
s.fmt.Failed(pickle, step, match, err)
}
// run after step handlers
@ -336,7 +401,7 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
return err
} else if len(undef) > 0 {
if match != nil {
match = &StepDef{
match = &StepDefinition{
args: match.args,
hv: match.hv,
Expr: match.Expr,
@ -345,12 +410,12 @@ func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
undefined: undef,
}
}
s.fmt.Undefined(step, match)
s.fmt.Undefined(pickle, step, match)
return ErrUndefined
}
if prevStepErr != nil {
s.fmt.Skipped(step, match)
s.fmt.Skipped(pickle, step, match)
return nil
}
@ -415,7 +480,7 @@ func (s *Suite) maybeSubSteps(result interface{}) error {
return nil
}
func (s *Suite) matchStepText(text string) *StepDef {
func (s *Suite) matchStepText(text string) *StepDefinition {
for _, h := range s.steps {
if m := h.Expr.FindStringSubmatch(text); len(m) > 0 {
var args []interface{}
@ -425,7 +490,7 @@ func (s *Suite) matchStepText(text string) *StepDef {
// since we need to assign arguments
// better to copy the step definition
return &StepDef{
return &StepDefinition{
args: args,
hv: h.hv,
Expr: h.Expr,
@ -437,9 +502,9 @@ func (s *Suite) matchStepText(text string) *StepDef {
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 {
stepErr := s.runStep(step, err)
stepErr := s.runStep(pickle, step, err)
switch stepErr {
case ErrUndefined:
// do not overwrite failed error
@ -456,110 +521,6 @@ func (s *Suite) runSteps(steps []*gherkin.Step) (err error) {
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 {
if err == nil {
return false
@ -573,46 +534,24 @@ func (s *Suite) shouldFail(err error) bool {
}
func (s *Suite) runFeature(f *feature) {
if !isEmptyFeature(f.Feature) {
if !isEmptyFeature(f.pickles) {
for _, fn := range s.beforeFeatureHandlers {
fn(f.Feature)
fn(f.GherkinDocument)
}
}
s.fmt.Feature(f.Feature, 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)
}
s.fmt.Feature(f.GherkinDocument, f.Path, f.Content)
defer func() {
if !isEmptyFeature(f.Feature) {
if !isEmptyFeature(f.pickles) {
for _, fn := range s.afterFeatureHandlers {
fn(f.Feature)
fn(f.GherkinDocument)
}
}
}()
for _, scenario := range scenarios {
var err error
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)
}
for _, pickle := range f.pickles {
err := s.runPickle(pickle)
if s.shouldFail(err) {
s.failed = true
if s.stopOnFailure {
@ -622,31 +561,35 @@ func (s *Suite) runFeature(f *feature) {
}
}
func (s *Suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) {
if isEmptyScenario(scenario) {
s.fmt.Node(scenario)
func isEmptyFeature(pickles []*messages.Pickle) bool {
for _, pickle := range pickles {
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
}
// run before scenario handlers
for _, f := range s.beforeScenarioHandlers {
f(scenario)
f(pickle)
}
s.fmt.Node(scenario)
// background
steps := scenario.Steps
if b != nil {
steps = append(b.Steps, steps...)
}
s.fmt.Pickle(pickle)
// scenario
err = s.runSteps(steps)
err = s.runSteps(pickle, pickle.Steps)
// run after scenario handlers
for _, f := range s.afterScenarioHandlers {
f(scenario, err)
f(pickle, err)
}
return
@ -685,7 +628,7 @@ func extractFeaturePathLine(p string) (string, int) {
return retPath, line
}
func parseFeatureFile(path string) (*feature, error) {
func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) {
reader, err := os.Open(path)
if err != nil {
return nil, err
@ -693,19 +636,22 @@ func parseFeatureFile(path string) (*feature, error) {
defer reader.Close()
var buf bytes.Buffer
ft, err := gherkin.ParseFeature(io.TeeReader(reader, &buf))
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
if err != nil {
return nil, fmt.Errorf("%s - %v", path, err)
}
pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc)
return &feature{
Path: path,
Feature: ft,
GherkinDocument: gherkinDocument,
pickles: pickles,
Content: buf.Bytes(),
Path: path,
}, nil
}
func parseFeatureDir(dir string) ([]*feature, error) {
func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) {
var features []*feature
return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
if err != nil {
@ -720,7 +666,7 @@ func parseFeatureDir(dir string) ([]*feature, error) {
return nil
}
feat, err := parseFeatureFile(p)
feat, err := parseFeatureFile(p, newIDFunc)
if err != nil {
return err
}
@ -731,39 +677,36 @@ func parseFeatureDir(dir string) ([]*feature, error) {
func parsePath(path string) ([]*feature, error) {
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)
if err != nil {
return features, err
}
newIDFunc := (&messages.Incrementing{}).NewId
if fi.IsDir() {
return parseFeatureDir(path)
return parseFeatureDir(path, newIDFunc)
}
ft, err := parseFeatureFile(path)
ft, err := parseFeatureFile(path, newIDFunc)
if err != nil {
return features, err
}
// filter scenario by line number
var scenarios []interface{}
for _, def := range ft.ScenarioDefinitions {
var ln int
switch t := def.(type) {
case *gherkin.Scenario:
ln = t.Location.Line
case *gherkin.ScenarioOutline:
ln = t.Location.Line
}
if line == -1 || ln == line {
scenarios = append(scenarios, def)
var pickles []*messages.Pickle
for _, pickle := range ft.pickles {
sc := ft.findScenario(pickle.AstNodeIds[0])
if line == -1 || uint32(line) == sc.Location.Line {
pickles = append(pickles, pickle)
}
}
ft.ScenarioDefinitions = scenarios
ft.pickles = pickles
return append(features, ft), nil
}
@ -791,6 +734,7 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) {
byPath[ft.Path] = ft
}
}
return filterFeatures(filter, byPath), nil
}
@ -802,7 +746,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) {
for _, ft := range collected {
applyTagFilter(tags, ft.Feature)
applyTagFilter(tags, ft)
features = append(features, ft)
}
@ -811,81 +755,23 @@ func filterFeatures(tags string, collected map[string]*feature) (features []*fea
return features
}
func applyTagFilter(tags string, ft *gherkin.Feature) {
func applyTagFilter(tags string, ft *feature) {
if len(tags) == 0 {
return
}
var scenarios []interface{}
for _, scenario := range ft.ScenarioDefinitions {
switch t := scenario.(type) {
case *gherkin.ScenarioOutline:
var allExamples []*gherkin.Examples
for _, examples := range t.Examples {
if matchesTags(tags, allTags(ft, t, examples)) {
allExamples = append(allExamples, examples)
var pickles []*messages.Pickle
for _, pickle := range ft.pickles {
if matchesTags(tags, pickle.Tags) {
pickles = append(pickles, pickle)
}
}
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 {
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
ft.pickles = pickles
}
// 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
for _, andTags := range strings.Split(filter, "&&") {
var okComma bool
@ -902,3 +788,14 @@ func matchesTags(filter string, tags []string) (ok bool) {
}
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"
"strings"
"github.com/cucumber/gherkin-go/v9"
"github.com/cucumber/messages-go/v9"
"github.com/cucumber/godog/colors"
"github.com/cucumber/godog/gherkin"
)
// SuiteContext provides steps for godog suite execution and
@ -112,7 +114,7 @@ type suiteContext struct {
out bytes.Buffer
}
func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
func (s *suiteContext) ResetBeforeEachScenario(*messages.Pickle) {
// reset whole suite with the state
s.out.Reset()
s.paths = []string{}
@ -128,7 +130,7 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error {
return err
}
for _, feat := range s.testedSuite.features {
applyTagFilter(tags, feat.Feature)
applyTagFilter(tags, feat)
}
s.testedSuite.fmt = testFormatterFunc("godog", &s.out)
s.testedSuite.run()
@ -151,7 +153,7 @@ func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error {
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")
expect := strings.Split(strings.TrimSpace(doc.Content), "\n")
if len(expect) != len(actual) {
@ -184,7 +186,7 @@ func (s *suiteContext) cleanupSnippet(snip string) string {
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)
if !ok {
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
}
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 actual, unmatched, matched []string
@ -207,23 +209,23 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.Do
}
switch status {
case "passed":
for _, st := range f.passed {
for _, st := range f.findStepResults(passed) {
actual = append(actual, st.step.Text)
}
case "failed":
for _, st := range f.failed {
for _, st := range f.findStepResults(failed) {
actual = append(actual, st.step.Text)
}
case "skipped":
for _, st := range f.skipped {
for _, st := range f.findStepResults(skipped) {
actual = append(actual, st.step.Text)
}
case "undefined":
for _, st := range f.undefined {
for _, st := range f.findStepResults(undefined) {
actual = append(actual, st.step.Text)
}
case "pending":
for _, st := range f.pending {
for _, st := range f.findStepResults(pending) {
actual = append(actual, st.step.Text)
}
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)
}
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
switch status {
case "passed":
actual = len(f.passed)
actual = len(f.findStepResults(passed))
case "failed":
actual = len(f.failed)
actual = len(f.findStepResults(failed))
case "skipped":
actual = len(f.skipped)
actual = len(f.findStepResults(skipped))
case "undefined":
actual = len(f.undefined)
actual = len(f.findStepResults(undefined))
case "pending":
actual = len(f.pending)
actual = len(f.findStepResults(pending))
default:
return fmt.Errorf("unexpected step status wanted: %s", status)
}
@ -298,22 +304,22 @@ func (s *suiteContext) iAmListeningToSuiteEvents() error {
s.testedSuite.AfterSuite(func() {
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.testedSuite.AfterFeature(func(ft *gherkin.Feature) {
s.testedSuite.AfterFeature(func(ft *messages.GherkinDocument) {
s.events = append(s.events, &firedEvent{"AfterFeature", []interface{}{ft}})
})
s.testedSuite.BeforeScenario(func(scenario interface{}) {
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
s.testedSuite.BeforeScenario(func(pickle *messages.Pickle) {
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{pickle}})
})
s.testedSuite.AfterScenario(func(scenario interface{}, err error) {
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
s.testedSuite.AfterScenario(func(pickle *messages.Pickle, err error) {
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.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}})
})
return nil
@ -324,9 +330,10 @@ func (s *suiteContext) aFailingStep() error {
}
// parse a given feature file body as a feature
func (s *suiteContext) aFeatureFile(name string, body *gherkin.DocString) error {
ft, err := gherkin.ParseFeature(strings.NewReader(body.Content))
s.testedSuite.features = append(s.testedSuite.features, &feature{Feature: ft, Path: name})
func (s *suiteContext) aFeatureFile(path string, body *messages.PickleStepArgument_PickleDocString) error {
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(body.Content), (&messages.Incrementing{}).NewId)
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
s.testedSuite.features = append(s.testedSuite.features, &feature{GherkinDocument: gd, pickles: pickles, Path: path})
return err
}
@ -354,7 +361,7 @@ func (s *suiteContext) theSuiteShouldHave(state string) error {
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 {
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) {
var num int
for _, ft := range s.testedSuite.features {
num += len(ft.ScenarioDefinitions)
num += len(ft.pickles)
}
if num != expected {
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
switch t := event.args[0].(type) {
case *gherkin.Scenario:
name = t.Name
case *gherkin.ScenarioOutline:
case *messages.Pickle:
name = t.Name
}
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, `", "`)+`"`)
}
func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.DataTable) error {
func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *messages.PickleStepArgument_PickleTable) error {
if len(tbl.Rows[0].Cells) != 2 {
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
}
func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
func (s *suiteContext) theRenderJSONWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
expectedString := docstring.Content
@ -489,7 +494,7 @@ func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
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+`)
suiteCtxFuncReg := regexp.MustCompile(`github.com/cucumber/godog.SuiteContext.func(\d+)`)
@ -508,7 +513,7 @@ func (s *suiteContext) theRenderOutputWillBe(docstring *gherkin.DocString) error
return nil
}
func (s *suiteContext) theRenderXMLWillBe(docstring *gherkin.DocString) error {
func (s *suiteContext) theRenderXMLWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
expectedString := docstring.Content
actualString := s.out.String()
@ -523,28 +528,23 @@ func (s *suiteContext) theRenderXMLWillBe(docstring *gherkin.DocString) error {
}
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
}
type testFormatter struct {
*basefmt
scenarios []interface{}
pickles []*messages.Pickle
}
func testFormatterFunc(suite string, out io.Writer) Formatter {
return &testFormatter{basefmt: newBaseFmt(suite, out)}
}
func (f *testFormatter) Node(node interface{}) {
f.basefmt.Node(node)
switch t := node.(type) {
case *gherkin.Scenario:
f.scenarios = append(f.scenarios, t)
case *gherkin.ScenarioOutline:
f.scenarios = append(f.scenarios, t)
}
func (f *testFormatter) Pickle(p *messages.Pickle) {
f.basefmt.Pickle(p)
f.pickles = append(f.pickles, p)
}
func (f *testFormatter) Summary() {}

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

@ -2,28 +2,32 @@ package godog
import (
"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) {
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) {
t.Errorf(`expected tags: %v to match tag filter "%s", but it did not`, tags, filter)
}
}
func TestTagFilter(t *testing.T) {
assertMatchesTagFilter([]string{"wip"}, "@wip", t)
assertMatchesTagFilter([]string{}, "~@wip", t)
assertMatchesTagFilter([]string{"one", "two"}, "@two,@three", t)
assertMatchesTagFilter([]string{"one", "two"}, "@one&&@two", t)
assertMatchesTagFilter([]string{"one", "two"}, "one && two", t)
assertMatchesTagFilter([]*tag{{Name: "wip"}}, "@wip", t)
assertMatchesTagFilter([]*tag{}, "~@wip", t)
assertMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@two,@three", t)
assertMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one&&@two", t)
assertMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "one && two", t)
assertNotMatchesTagFilter([]string{}, "@wip", t)
assertNotMatchesTagFilter([]string{"one", "two"}, "@one&&~@two", t)
assertNotMatchesTagFilter([]string{"one", "two"}, "@one && ~@two", t)
assertNotMatchesTagFilter([]*tag{}, "@wip", t)
assertNotMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one&&~@two", t)
assertNotMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one && ~@two", t)
}
type tag = messages.Pickle_PickleTag