Merge branch 'master' into release/v0.9.0-rc1
Этот коммит содержится в:
коммит
b2f20ce589
45 изменённых файлов: 13362 добавлений и 1368 удалений
|
@ -23,6 +23,7 @@ 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"
|
||||
|
|
35
README.md
35
README.md
|
@ -156,36 +156,12 @@ console output snippets in order to test our feature requirements:
|
|||
package main
|
||||
|
||||
import (
|
||||
"flag"
|
||||
"fmt"
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"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)}
|
||||
|
||||
func init() {
|
||||
godog.BindFlags("godog.", flag.CommandLine, &opt)
|
||||
}
|
||||
|
||||
func TestMain(m *testing.M) {
|
||||
flag.Parse()
|
||||
opt.Paths = flag.Args()
|
||||
|
||||
status := godog.RunWithOptions("godogs", func(s *godog.Suite) {
|
||||
FeatureContext(s)
|
||||
}, opt)
|
||||
|
||||
if st := m.Run(); st > status {
|
||||
status = st
|
||||
}
|
||||
os.Exit(status)
|
||||
}
|
||||
|
||||
func thereAreGodogs(available int) error {
|
||||
Godogs = available
|
||||
return nil
|
||||
|
@ -354,6 +330,17 @@ func TestMain(m *testing.M) {
|
|||
|
||||
Now when running `go test -v` it will use **pretty** format.
|
||||
|
||||
### Tags
|
||||
|
||||
If you want to filter scenarios by tags, you can use the
|
||||
`-t=<expression>` or `--tags=<expression>` where `<expression>`
|
||||
is one of the following:
|
||||
|
||||
- `@wip` - run all scenarios with wip tag
|
||||
- `~@wip` - exclude all scenarios with wip tag
|
||||
- `@wip && ~@new` - run wip scenarios, but exclude new
|
||||
- `@wip,@undone` - run wip or undone scenarios
|
||||
|
||||
### Configure common options for godog CLI
|
||||
|
||||
There are no global options or configuration files. Alias your common or
|
||||
|
|
|
@ -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 *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) 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 *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err 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/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
type apiFeature struct {
|
||||
resp *httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (a *apiFeature) resetResponse(*messages.Pickle) {
|
||||
func (a *apiFeature) resetResponse(interface{}) {
|
||||
a.resp = httptest.NewRecorder()
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,7 @@ func (a *apiFeature) theResponseCodeShouldBe(code int) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgument_PickleDocString) (err error) {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (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/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -24,7 +24,7 @@ type apiFeature struct {
|
|||
resp *httptest.ResponseRecorder
|
||||
}
|
||||
|
||||
func (a *apiFeature) resetResponse(*messages.Pickle) {
|
||||
func (a *apiFeature) resetResponse(interface{}) {
|
||||
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 *messages.PickleStepArgument_PickleDocString) (err error) {
|
||||
func (a *apiFeature) theResponseShouldMatchJSON(body *gherkin.DocString) (err error) {
|
||||
var expected, actual interface{}
|
||||
|
||||
// re-encode expected response
|
||||
|
@ -91,7 +91,7 @@ func (a *apiFeature) theResponseShouldMatchJSON(body *messages.PickleStepArgumen
|
|||
return nil
|
||||
}
|
||||
|
||||
func (a *apiFeature) thereAreUsers(users *messages.PickleStepArgument_PickleTable) error {
|
||||
func (a *apiFeature) thereAreUsers(users *gherkin.DataTable) error {
|
||||
var fields []string
|
||||
var marks []string
|
||||
head := users.Rows[0].Cells
|
||||
|
|
|
@ -9,7 +9,6 @@ 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)}
|
||||
|
@ -57,7 +56,7 @@ func FeatureContext(s *godog.Suite) {
|
|||
s.Step(`^I eat (\d+)$`, iEat)
|
||||
s.Step(`^there should be (\d+) remaining$`, thereShouldBeRemaining)
|
||||
|
||||
s.BeforeScenario(func(*messages.Pickle) {
|
||||
s.BeforeScenario(func(interface{}) {
|
||||
Godogs = 0 // clean the state before every scenario
|
||||
})
|
||||
}
|
||||
|
|
|
@ -1,60 +0,0 @@
|
|||
// +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)
|
||||
}
|
||||
}
|
|
@ -1,54 +0,0 @@
|
|||
// +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,6 +310,55 @@ 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:])
|
||||
nw, err = cw.w.Write(p[first:len(p)])
|
||||
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:])
|
||||
nw, err = cw.w.Write(p[first:len(p)])
|
||||
r += nw
|
||||
}
|
||||
|
||||
|
|
|
@ -40,7 +40,7 @@ Feature: load features
|
|||
| feature | number |
|
||||
| features/load.feature:3 | 0 |
|
||||
| features/load.feature:6 | 1 |
|
||||
| features/load.feature | 6 |
|
||||
| features/load.feature | 4 |
|
||||
|
||||
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 *messages.PickleStepArgument_PickleTable) error {
|
||||
func iSendRequestToWith(arg1, arg2 string, arg3 *gherkin.DataTable) error {
|
||||
return godog.ErrPending
|
||||
}
|
||||
|
||||
|
|
5640
fixtures/cucumber_output.json
Обычный файл
5640
fixtures/cucumber_output.json
Обычный файл
Различия файлов не показаны, т.к. их слишком много
Показать различия
433
fmt.go
433
fmt.go
|
@ -15,8 +15,7 @@ import (
|
|||
"unicode"
|
||||
|
||||
"github.com/cucumber/godog/colors"
|
||||
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
// some snippet formatting regexps
|
||||
|
@ -44,7 +43,7 @@ var undefinedSnippetsTpl = template.Must(template.New("snippets").Funcs(snippetH
|
|||
type undefinedSnippet struct {
|
||||
Method string
|
||||
Expr string
|
||||
argument *messages.PickleStepArgument
|
||||
argument interface{} // gherkin step argument
|
||||
}
|
||||
|
||||
type registeredFormatter struct {
|
||||
|
@ -98,14 +97,14 @@ func AvailableFormatters() map[string]string {
|
|||
// formatters needs to be registered with a
|
||||
// godog.Format function call
|
||||
type Formatter interface {
|
||||
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)
|
||||
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)
|
||||
Summary()
|
||||
}
|
||||
|
||||
|
@ -121,17 +120,17 @@ type ConcurrentFormatter interface {
|
|||
// suite name and io.Writer to record output
|
||||
type FormatterFunc func(string, io.Writer) Formatter
|
||||
|
||||
type stepResultStatus int
|
||||
type stepType int
|
||||
|
||||
const (
|
||||
passed stepResultStatus = iota
|
||||
passed stepType = iota
|
||||
failed
|
||||
skipped
|
||||
undefined
|
||||
pending
|
||||
)
|
||||
|
||||
func (st stepResultStatus) clr() colors.ColorFunc {
|
||||
func (st stepType) clr() colors.ColorFunc {
|
||||
switch st {
|
||||
case passed:
|
||||
return green
|
||||
|
@ -144,7 +143,7 @@ func (st stepResultStatus) clr() colors.ColorFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (st stepResultStatus) String() string {
|
||||
func (st stepType) String() string {
|
||||
switch st {
|
||||
case passed:
|
||||
return "passed"
|
||||
|
@ -162,17 +161,65 @@ func (st stepResultStatus) String() string {
|
|||
}
|
||||
|
||||
type stepResult struct {
|
||||
status stepResultStatus
|
||||
typ stepType
|
||||
feature *feature
|
||||
owner interface{}
|
||||
step *gherkin.Step
|
||||
time time.Time
|
||||
def *StepDef
|
||||
err error
|
||||
|
||||
owner *messages.Pickle
|
||||
step *messages.Pickle_PickleStep
|
||||
def *StepDefinition
|
||||
}
|
||||
|
||||
func newStepResult(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) *stepResult {
|
||||
return &stepResult{time: timeNowFunc(), owner: pickle, step: step, def: match}
|
||||
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 newBaseFmt(suite string, out io.Writer) *basefmt {
|
||||
|
@ -194,203 +241,215 @@ type basefmt struct {
|
|||
|
||||
started time.Time
|
||||
features []*feature
|
||||
failed []*stepResult
|
||||
passed []*stepResult
|
||||
skipped []*stepResult
|
||||
undefined []*stepResult
|
||||
pending []*stepResult
|
||||
|
||||
lock *sync.Mutex
|
||||
}
|
||||
|
||||
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) {
|
||||
func (f *basefmt) Node(n interface{}) {
|
||||
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]
|
||||
feature.pickleResults = append(feature.pickleResults, &pickleResult{Name: p.Name, time: timeNowFunc()})
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {}
|
||||
func (f *basefmt) Defined(*gherkin.Step, *StepDef) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
}
|
||||
|
||||
func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) {
|
||||
func (f *basefmt) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.features = append(f.features, &feature{Path: p, GherkinDocument: ft, time: timeNowFunc()})
|
||||
f.features = append(f.features, &feature{Path: p, Feature: ft, time: timeNowFunc()})
|
||||
}
|
||||
|
||||
func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
func (f *basefmt) Passed(step *gherkin.Step, match *StepDef) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
s := newStepResult(pickle, step, match)
|
||||
s.status = passed
|
||||
f.lastFeature().appendStepResult(s)
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
func (f *basefmt) Skipped(step *gherkin.Step, match *StepDef) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
s := newStepResult(pickle, step, match)
|
||||
s.status = skipped
|
||||
f.lastFeature().appendStepResult(s)
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
func (f *basefmt) Undefined(step *gherkin.Step, match *StepDef) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
s := newStepResult(pickle, step, match)
|
||||
s.status = undefined
|
||||
f.lastFeature().appendStepResult(s)
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||
func (f *basefmt) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
s := newStepResult(pickle, step, match)
|
||||
s.status = failed
|
||||
s.err = err
|
||||
f.lastFeature().appendStepResult(s)
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
func (f *basefmt) Pending(step *gherkin.Step, match *StepDef) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
s := newStepResult(pickle, step, match)
|
||||
s.status = pending
|
||||
f.lastFeature().appendStepResult(s)
|
||||
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)
|
||||
}
|
||||
|
||||
func (f *basefmt) Summary() {
|
||||
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
|
||||
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++
|
||||
}
|
||||
|
||||
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++
|
||||
case *gherkin.ScenarioOutline:
|
||||
for _, ex := range t.Examples {
|
||||
total += len(ex.TableBody)
|
||||
if len(t.Steps) == 0 {
|
||||
undefined += len(ex.TableBody)
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
if passedSt > 0 {
|
||||
steps = append(steps, green(fmt.Sprintf("%d passed", passedSt)))
|
||||
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 failedSt > 0 {
|
||||
parts = append(parts, red(fmt.Sprintf("%d failed", failedSt)))
|
||||
steps = append(steps, red(fmt.Sprintf("%d failed", failedSt)))
|
||||
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 pendingSt > 0 {
|
||||
parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt)))
|
||||
steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt)))
|
||||
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 undefinedSt > 0 {
|
||||
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
|
||||
steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt)))
|
||||
} else if undefinedSc > 0 {
|
||||
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 {
|
||||
// there may be some scenarios without steps
|
||||
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
|
||||
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefined)))
|
||||
}
|
||||
if skippedSt > 0 {
|
||||
steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt)))
|
||||
if len(f.skipped) > 0 {
|
||||
steps = append(steps, cyan(fmt.Sprintf("%d skipped", len(f.skipped))))
|
||||
}
|
||||
if passedSc > 0 {
|
||||
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc)))
|
||||
if passed > 0 {
|
||||
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passed)))
|
||||
}
|
||||
scenarios = append(scenarios, parts...)
|
||||
elapsed := timeNowFunc().Sub(f.started)
|
||||
|
||||
fmt.Fprintln(f.out, "")
|
||||
|
||||
if totalSc == 0 {
|
||||
if total == 0 {
|
||||
fmt.Fprintln(f.out, "No scenarios")
|
||||
} else {
|
||||
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", totalSc, strings.Join(scenarios, ", ")))
|
||||
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", total, strings.Join(scenarios, ", ")))
|
||||
}
|
||||
|
||||
if totalSt == 0 {
|
||||
if nsteps == 0 {
|
||||
fmt.Fprintln(f.out, "No steps")
|
||||
} else {
|
||||
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", totalSt, strings.Join(steps, ", ")))
|
||||
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
||||
}
|
||||
|
||||
elapsedString := elapsed.String()
|
||||
|
@ -425,6 +484,21 @@ 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -455,13 +529,12 @@ func (s *undefinedSnippet) Args() (ret string) {
|
|||
args = append(args, reflect.String.String())
|
||||
}
|
||||
}
|
||||
|
||||
if s.argument != nil {
|
||||
if s.argument.GetDocString() != nil {
|
||||
args = append(args, "*messages.PickleStepArgument_PickleDocString")
|
||||
}
|
||||
if s.argument.GetDataTable() != nil {
|
||||
args = append(args, "*messages.PickleStepArgument_PickleTable")
|
||||
switch s.argument.(type) {
|
||||
case *gherkin.DocString:
|
||||
args = append(args, "*gherkin.DocString")
|
||||
case *gherkin.DataTable:
|
||||
args = append(args, "*gherkin.DataTable")
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -477,30 +550,15 @@ 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 {
|
||||
undefinedStepResults := f.findStepResults(undefined)
|
||||
if len(undefinedStepResults) == 0 {
|
||||
if len(f.undefined) == 0 {
|
||||
return ""
|
||||
}
|
||||
|
||||
var index int
|
||||
var snips []*undefinedSnippet
|
||||
// build snippets
|
||||
for _, u := range undefinedStepResults {
|
||||
for _, u := range f.undefined {
|
||||
steps := []string{u.step.Text}
|
||||
arg := u.step.Argument
|
||||
if u.def != nil {
|
||||
|
@ -529,7 +587,7 @@ func (f *basefmt) snippets() string {
|
|||
name = strings.Join(words, "")
|
||||
if len(name) == 0 {
|
||||
index++
|
||||
name = fmt.Sprintf("StepDefinitioninition%d", index)
|
||||
name = fmt.Sprintf("stepDefinition%d", index)
|
||||
}
|
||||
|
||||
var found bool
|
||||
|
@ -553,6 +611,25 @@ func (f *basefmt) snippets() string {
|
|||
return strings.Replace(buf.String(), " \n", "\n", -1)
|
||||
}
|
||||
|
||||
func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool {
|
||||
return pickle.Steps[len(pickle.Steps)-1].Id == step.Id
|
||||
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
|
||||
}
|
||||
|
|
221
fmt_cucumber.go
221
fmt_cucumber.go
|
@ -15,10 +15,11 @@ import (
|
|||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -106,7 +107,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
|
||||
status stepResultStatus // last step status, before skipped
|
||||
stat stepType // last step status, before skipped
|
||||
ID string // current test id.
|
||||
results []cukeFeatureJSON // structure that represent cuke results
|
||||
curStep *cukeStep // track the current step
|
||||
|
@ -121,86 +122,115 @@ type cukefmt struct {
|
|||
// of the example name inorder to build id fields.
|
||||
}
|
||||
|
||||
func (f *cukefmt) Pickle(pickle *messages.Pickle) {
|
||||
f.basefmt.Pickle(pickle)
|
||||
func (f *cukefmt) Node(n interface{}) {
|
||||
f.basefmt.Node(n)
|
||||
|
||||
scenario := f.findScenario(pickle.AstNodeIds[0])
|
||||
switch t := n.(type) {
|
||||
|
||||
// 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 = 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.Name = t.Name
|
||||
f.curElement.Line = t.Location.Line
|
||||
f.curElement.Description = t.Description
|
||||
f.curElement.Keyword = t.Keyword
|
||||
f.curElement.ID = f.curFeature.ID + ";" + makeID(t.Name)
|
||||
f.curElement.Type = "scenario"
|
||||
|
||||
f.curElement.Tags = make([]cukeTag, len(scenario.Tags)+len(f.curFeature.Tags))
|
||||
f.curElement.Tags = make([]cukeTag, len(t.Tags)+len(f.curFeature.Tags))
|
||||
|
||||
if len(f.curElement.Tags) > 0 {
|
||||
// apply feature level tags
|
||||
copy(f.curElement.Tags, f.curFeature.Tags)
|
||||
|
||||
// apply scenario level tags.
|
||||
for idx, element := range scenario.Tags {
|
||||
f.curElement.Tags[idx+len(f.curFeature.Tags)].Line = int(element.Location.Line)
|
||||
for idx, element := range t.Tags {
|
||||
f.curElement.Tags[idx+len(f.curFeature.Tags)].Line = element.Location.Line
|
||||
f.curElement.Tags[idx+len(f.curFeature.Tags)].Name = element.Name
|
||||
}
|
||||
}
|
||||
|
||||
if len(pickle.AstNodeIds) == 1 {
|
||||
return
|
||||
}
|
||||
// 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]
|
||||
|
||||
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)
|
||||
}
|
||||
// copy in example level tags.
|
||||
f.curElement.Tags = append(f.curElement.Tags, f.curExampleTags...)
|
||||
|
||||
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(gd *messages.GherkinDocument, p string, c []byte) {
|
||||
f.basefmt.Feature(gd, p, c)
|
||||
func (f *cukefmt) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||
|
||||
f.basefmt.Feature(ft, p, c)
|
||||
f.path = p
|
||||
f.ID = makeID(gd.Feature.Name)
|
||||
f.ID = makeID(ft.Name)
|
||||
f.results = append(f.results, cukeFeatureJSON{})
|
||||
|
||||
f.curFeature = &f.results[len(f.results)-1]
|
||||
f.curFeature.URI = p
|
||||
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.Name = ft.Name
|
||||
f.curFeature.Keyword = ft.Keyword
|
||||
f.curFeature.Line = ft.Location.Line
|
||||
f.curFeature.Description = ft.Description
|
||||
f.curFeature.ID = f.ID
|
||||
f.curFeature.Tags = make([]cukeTag, len(gd.Feature.Tags))
|
||||
f.curFeature.Tags = make([]cukeTag, len(ft.Tags))
|
||||
|
||||
for idx, element := range gd.Feature.Tags {
|
||||
f.curFeature.Tags[idx].Line = int(element.Location.Line)
|
||||
for idx, element := range ft.Tags {
|
||||
f.curFeature.Tags[idx].Line = element.Location.Line
|
||||
f.curFeature.Tags[idx].Name = element.Name
|
||||
}
|
||||
|
||||
f.curFeature.Comments = make([]cukeComment, len(gd.Comments))
|
||||
for idx, comment := range gd.Comments {
|
||||
f.curFeature.Comments = make([]cukeComment, len(ft.Comments))
|
||||
for idx, comment := range ft.Comments {
|
||||
f.curFeature.Comments[idx].Value = strings.TrimSpace(comment.Text)
|
||||
f.curFeature.Comments[idx].Line = int(comment.Location.Line)
|
||||
f.curFeature.Comments[idx].Line = comment.Location.Line
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -214,43 +244,49 @@ 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.Result.Status = res.status.String()
|
||||
f.curStep.Line = t.Location.Line
|
||||
f.curStep.Result.Status = res.typ.String()
|
||||
if res.err != nil {
|
||||
f.curStep.Result.Error = res.err.Error()
|
||||
}
|
||||
case *gherkin.Scenario:
|
||||
d := int(timeNowFunc().Sub(f.startTime).Nanoseconds())
|
||||
f.curStep.Result.Duration = &d
|
||||
f.curStep.Result.Status = res.typ.String()
|
||||
if res.err != nil {
|
||||
f.curStep.Result.Error = res.err.Error()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (f *cukefmt) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep, def *StepDefinition) {
|
||||
func (f *cukefmt) Defined(step *gherkin.Step, def *StepDef) {
|
||||
|
||||
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]
|
||||
|
||||
step := f.findStep(pickleStep.AstNodeIds[0])
|
||||
|
||||
line := step.Location.Line
|
||||
if len(pickle.AstNodeIds) == 2 {
|
||||
_, row := f.findExample(pickle.AstNodeIds[1])
|
||||
line = row.Location.Line
|
||||
}
|
||||
|
||||
f.curStep.Name = pickleStep.Text
|
||||
f.curStep.Line = int(line)
|
||||
f.curStep.Name = step.Text
|
||||
f.curStep.Line = step.Location.Line
|
||||
f.curStep.Keyword = step.Keyword
|
||||
|
||||
arg := pickleStep.Argument
|
||||
|
||||
if arg.GetDocString() != nil && step.GetDocString() != nil {
|
||||
if _, ok := step.Argument.(*gherkin.DocString); ok {
|
||||
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
|
||||
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
|
||||
}
|
||||
|
||||
if arg.GetDataTable() != nil {
|
||||
f.curStep.DataTable = make([]*cukeDataTableRow, len(arg.GetDataTable().Rows))
|
||||
for i, row := range arg.GetDataTable().Rows {
|
||||
if _, ok := step.Argument.(*gherkin.DataTable); ok {
|
||||
dataTable := step.Argument.(*gherkin.DataTable)
|
||||
|
||||
f.curStep.DataTable = make([]*cukeDataTableRow, len(dataTable.Rows))
|
||||
for i, row := range dataTable.Rows {
|
||||
cells := make([]string, len(row.Cells))
|
||||
for j, cell := range row.Cells {
|
||||
cells[j] = cell.Value
|
||||
|
@ -264,47 +300,42 @@ func (f *cukefmt) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_P
|
|||
}
|
||||
}
|
||||
|
||||
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) 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) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
f.basefmt.Skipped(pickle, step, match)
|
||||
|
||||
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])
|
||||
|
||||
// no duration reported for skipped.
|
||||
f.curStep.Result.Duration = nil
|
||||
}
|
||||
|
||||
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())
|
||||
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])
|
||||
|
||||
// the location for undefined is the feature file location not the step file.
|
||||
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, f.findStep(step.AstNodeIds[0]).Location.Line)
|
||||
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, step.Location.Line)
|
||||
f.curStep.Result.Duration = nil
|
||||
}
|
||||
|
||||
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) 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) 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 *cukefmt) Pending(step *gherkin.Step, match *StepDef) {
|
||||
f.stat = pending
|
||||
f.basefmt.Pending(step, match)
|
||||
f.step(f.pending[len(f.pending)-1])
|
||||
|
||||
// the location for pending is the feature file location not the step file.
|
||||
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, f.findStep(step.AstNodeIds[0]).Location.Line)
|
||||
f.curStep.Match.Location = fmt.Sprintf("%s:%d", f.path, step.Location.Line)
|
||||
f.curStep.Result.Duration = nil
|
||||
}
|
||||
|
|
125
fmt_events.go
125
fmt_events.go
|
@ -5,7 +5,7 @@ import (
|
|||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
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
|
||||
status stepResultStatus // last step status, before skipped
|
||||
stat stepType // last step status, before skipped
|
||||
outlineSteps int // number of current outline scenario steps
|
||||
}
|
||||
|
||||
|
@ -53,8 +53,25 @@ func (f *events) event(ev interface{}) {
|
|||
fmt.Fprintln(f.out, string(data))
|
||||
}
|
||||
|
||||
func (f *events) Pickle(pickle *messages.Pickle) {
|
||||
f.basefmt.Pickle(pickle)
|
||||
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
|
||||
}
|
||||
|
||||
f.event(&struct {
|
||||
Event string `json:"event"`
|
||||
|
@ -62,11 +79,11 @@ func (f *events) Pickle(pickle *messages.Pickle) {
|
|||
Timestamp int64 `json:"timestamp"`
|
||||
}{
|
||||
"TestCaseStarted",
|
||||
f.scenarioLocation(pickle.AstNodeIds),
|
||||
id,
|
||||
timeNowFunc().UnixNano() / nanoSec,
|
||||
})
|
||||
|
||||
if len(pickle.Steps) == 0 {
|
||||
if undefined {
|
||||
// @TODO: is status undefined or passed? when there are no steps
|
||||
// for this scenario
|
||||
f.event(&struct {
|
||||
|
@ -76,14 +93,14 @@ func (f *events) Pickle(pickle *messages.Pickle) {
|
|||
Status string `json:"status"`
|
||||
}{
|
||||
"TestCaseFinished",
|
||||
f.scenarioLocation(pickle.AstNodeIds),
|
||||
id,
|
||||
timeNowFunc().UnixNano() / nanoSec,
|
||||
"undefined",
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) {
|
||||
func (f *events) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||
f.basefmt.Feature(ft, p, c)
|
||||
f.path = p
|
||||
f.event(&struct {
|
||||
|
@ -92,7 +109,7 @@ func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) {
|
|||
Source string `json:"source"`
|
||||
}{
|
||||
"TestSource",
|
||||
fmt.Sprintf("%s:%d", p, ft.Feature.Location.Line),
|
||||
fmt.Sprintf("%s:%d", p, ft.Location.Line),
|
||||
string(c),
|
||||
})
|
||||
}
|
||||
|
@ -100,10 +117,10 @@ func (f *events) Feature(ft *messages.GherkinDocument, p string, c []byte) {
|
|||
func (f *events) Summary() {
|
||||
// @TODO: determine status
|
||||
status := passed
|
||||
if len(f.findStepResults(failed)) > 0 {
|
||||
if len(f.failed) > 0 {
|
||||
status = failed
|
||||
} else if len(f.findStepResults(passed)) == 0 {
|
||||
if len(f.findStepResults(undefined)) > len(f.findStepResults(pending)) {
|
||||
} else if len(f.passed) == 0 {
|
||||
if len(f.undefined) > len(f.pending) {
|
||||
status = undefined
|
||||
} else {
|
||||
status = pending
|
||||
|
@ -131,8 +148,6 @@ 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()
|
||||
|
@ -145,13 +160,25 @@ func (f *events) step(res *stepResult) {
|
|||
Summary string `json:"summary,omitempty"`
|
||||
}{
|
||||
"TestStepFinished",
|
||||
fmt.Sprintf("%s:%d", f.path, step.Location.Line),
|
||||
fmt.Sprintf("%s:%d", f.path, res.step.Location.Line),
|
||||
timeNowFunc().UnixNano() / nanoSec,
|
||||
res.status.String(),
|
||||
res.typ.String(),
|
||||
errMsg,
|
||||
})
|
||||
|
||||
if isLastStep(res.owner, res.step) {
|
||||
// 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 {
|
||||
f.event(&struct {
|
||||
Event string `json:"event"`
|
||||
Location string `json:"location"`
|
||||
|
@ -159,18 +186,16 @@ func (f *events) step(res *stepResult) {
|
|||
Status string `json:"status"`
|
||||
}{
|
||||
"TestCaseFinished",
|
||||
f.scenarioLocation(res.owner.AstNodeIds),
|
||||
fmt.Sprintf("%s:%d", f.path, line),
|
||||
timeNowFunc().UnixNano() / nanoSec,
|
||||
f.status.String(),
|
||||
f.stat.String(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func (f *events) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_PickleStep, def *StepDefinition) {
|
||||
step := f.findStep(pickleStep.AstNodeIds[0])
|
||||
|
||||
func (f *events) Defined(step *gherkin.Step, def *StepDef) {
|
||||
if def != nil {
|
||||
m := def.Expr.FindStringSubmatchIndex(pickleStep.Text)[2:]
|
||||
m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]
|
||||
var args [][2]int
|
||||
for i := 0; i < len(m)/2; i++ {
|
||||
pair := m[i : i*2+2]
|
||||
|
@ -208,47 +233,31 @@ func (f *events) Defined(pickle *messages.Pickle, pickleStep *messages.Pickle_Pi
|
|||
})
|
||||
}
|
||||
|
||||
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) 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) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
f.basefmt.Skipped(pickle, step, match)
|
||||
|
||||
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) 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) 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) 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) 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) 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)
|
||||
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])
|
||||
}
|
||||
|
|
34
fmt_junit.go
34
fmt_junit.go
|
@ -64,55 +64,45 @@ func buildJUNITPackageSuite(suiteName string, startedAt time.Time, features []*f
|
|||
|
||||
for idx, feat := range features {
|
||||
ts := junitTestSuite{
|
||||
Name: feat.GherkinDocument.Feature.Name,
|
||||
Name: feat.Name,
|
||||
Time: junitTimeDuration(feat.startedAt(), feat.finishedAt()),
|
||||
TestCases: make([]*junitTestCase, len(feat.pickleResults)),
|
||||
TestCases: make([]*junitTestCase, len(feat.Scenarios)),
|
||||
}
|
||||
|
||||
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])
|
||||
for idx, scenario := range feat.Scenarios {
|
||||
tc := junitTestCase{
|
||||
Name: scenario.Name,
|
||||
Time: junitTimeDuration(scenario.startedAt(), scenario.finishedAt()),
|
||||
}
|
||||
|
||||
ts.Tests++
|
||||
suite.Tests++
|
||||
|
||||
for _, stepResult := range pickleResult.stepResults {
|
||||
switch stepResult.status {
|
||||
for _, step := range scenario.Steps {
|
||||
switch step.typ {
|
||||
case passed:
|
||||
tc.Status = passed.String()
|
||||
case failed:
|
||||
tc.Status = failed.String()
|
||||
tc.Failure = &junitFailure{
|
||||
Message: fmt.Sprintf("Step %s: %s", stepResult.step.Text, stepResult.err),
|
||||
Message: fmt.Sprintf("%s %s: %s", step.step.Type, step.step.Text, step.err),
|
||||
}
|
||||
case skipped:
|
||||
tc.Error = append(tc.Error, &junitError{
|
||||
Type: "skipped",
|
||||
Message: fmt.Sprintf("Step %s", stepResult.step.Text),
|
||||
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
|
||||
})
|
||||
case undefined:
|
||||
tc.Status = undefined.String()
|
||||
tc.Error = append(tc.Error, &junitError{
|
||||
Type: "undefined",
|
||||
Message: fmt.Sprintf("Step %s", stepResult.step.Text),
|
||||
Message: fmt.Sprintf("%s %s", step.step.Type, step.step.Text),
|
||||
})
|
||||
case pending:
|
||||
tc.Status = pending.String()
|
||||
tc.Error = append(tc.Error, &junitError{
|
||||
Type: "pending",
|
||||
Message: fmt.Sprintf("Step %s: TODO: write pending definition", stepResult.step.Text),
|
||||
Message: fmt.Sprintf("%s %s: TODO: write pending definition", step.step.Type, step.step.Text),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,12 +8,8 @@ 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 = `
|
||||
|
@ -53,21 +49,18 @@ Feature: junit formatter
|
|||
`
|
||||
|
||||
func TestJUnitFormatterOutput(t *testing.T) {
|
||||
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)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
s := &Suite{
|
||||
fmt: junitFunc("junit", w),
|
||||
features: []*feature{{
|
||||
GherkinDocument: gd,
|
||||
pickles: pickles,
|
||||
Path: path,
|
||||
features: []*feature{&feature{
|
||||
Path: "any.feature",
|
||||
Feature: feat,
|
||||
Content: []byte(sampleGherkinFeature),
|
||||
}},
|
||||
}
|
||||
|
@ -158,18 +151,19 @@ func TestJUnitFormatterOutput(t *testing.T) {
|
|||
},
|
||||
}},
|
||||
}
|
||||
|
||||
s.run()
|
||||
s.fmt.Summary()
|
||||
|
||||
var exp bytes.Buffer
|
||||
_, err = io.WriteString(&exp, xml.Header)
|
||||
require.NoError(t, err)
|
||||
|
||||
if _, err = io.WriteString(&exp, xml.Header); err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
enc := xml.NewEncoder(&exp)
|
||||
enc.Indent("", " ")
|
||||
err = enc.Encode(expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, exp.String(), buf.String())
|
||||
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())
|
||||
}
|
||||
}
|
||||
|
|
636
fmt_pretty.go
636
fmt_pretty.go
|
@ -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,59 +25,79 @@ 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(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
|
||||
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) 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") {
|
||||
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") {
|
||||
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 {
|
||||
|
@ -88,302 +108,342 @@ func keywordAndName(keyword, name string) string {
|
|||
return title
|
||||
}
|
||||
|
||||
func (f *pretty) scenarioLengths(scenarioAstID string) (scenarioHeaderLength int, maxLength int) {
|
||||
astScenario := f.findScenario(scenarioAstID)
|
||||
astBackground := f.findBackground(scenarioAstID)
|
||||
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))
|
||||
|
||||
scenarioHeaderLength = f.lengthPickle(astScenario.Keyword, astScenario.Name)
|
||||
maxLength = f.longestStep(astScenario.Steps, scenarioHeaderLength)
|
||||
|
||||
if astBackground != nil {
|
||||
maxLength = f.longestStep(astBackground.Steps, maxLength)
|
||||
for _, step := range bg.Steps {
|
||||
f.bgSteps--
|
||||
f.printStep(step, nil, colors.Cyan)
|
||||
}
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
fmt.Fprintln(f.out, "\n"+text)
|
||||
}
|
||||
|
||||
func (f *pretty) printUndefinedPickle(pickle *messages.Pickle) {
|
||||
astScenario := f.findScenario(pickle.AstNodeIds[0])
|
||||
astBackground := f.findBackground(pickle.AstNodeIds[0])
|
||||
|
||||
scenarioHeaderLength, maxLength := f.scenarioLengths(pickle.AstNodeIds[0])
|
||||
|
||||
if astBackground != nil {
|
||||
fmt.Fprintln(f.out, "\n"+s(f.indent)+keywordAndName(astBackground.Keyword, astBackground.Name))
|
||||
for _, step := range astBackground.Steps {
|
||||
text := s(f.indent*2) + cyan(strings.TrimSpace(step.Keyword)) + " " + cyan(step.Text)
|
||||
fmt.Fprintln(f.out, text)
|
||||
for _, example := range t.Examples {
|
||||
max := longest(example, cyan)
|
||||
f.printExampleHeader(example, max)
|
||||
for _, row := range example.TableBody {
|
||||
f.printExampleRow(row, max, cyan)
|
||||
}
|
||||
}
|
||||
|
||||
// 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() {
|
||||
failedStepResults := f.findStepResults(failed)
|
||||
if len(failedStepResults) > 0 {
|
||||
if len(f.failed) > 0 {
|
||||
fmt.Fprintln(f.out, "\n--- "+red("Failed steps:")+"\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")
|
||||
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 *pretty) printOutlineExample(pickle *messages.Pickle, backgroundSteps int) {
|
||||
var errorMsg string
|
||||
var clr = green
|
||||
func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
||||
var msg string
|
||||
var clr colors.ColorFunc
|
||||
|
||||
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 {
|
||||
ex := outline.Examples[f.outlineNumExample]
|
||||
example, hasExamples := examples(ex)
|
||||
if !hasExamples {
|
||||
// do not print empty examples
|
||||
return
|
||||
}
|
||||
|
||||
lastStep := len(f.lastFeature().lastPickleResult().stepResults) == len(pickle.Steps)
|
||||
if !lastStep {
|
||||
// do not print examples unless all steps has finished
|
||||
return
|
||||
}
|
||||
firstExample := f.outlineNumExamples == len(example.TableBody)
|
||||
printSteps := firstExample && f.outlineNumExample == 0
|
||||
|
||||
for _, result := range f.lastFeature().lastPickleResult().stepResults {
|
||||
for i, res := range f.outlineSteps {
|
||||
// determine example row status
|
||||
switch {
|
||||
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:
|
||||
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:
|
||||
clr = cyan
|
||||
}
|
||||
|
||||
if firstExamplesTable && printExampleHeader {
|
||||
if printSteps && i >= f.totalBgSteps {
|
||||
// in first example, we need to print steps
|
||||
var text string
|
||||
|
||||
astStep := f.findStep(result.step.AstNodeIds[0])
|
||||
|
||||
if result.def != nil {
|
||||
if m := outlinePlaceholderRegexp.FindAllStringIndex(astStep.Text, -1); len(m) > 0 {
|
||||
ostep := outline.Steps[i-f.totalBgSteps]
|
||||
if res.def != nil {
|
||||
if m := outlinePlaceholderRegexp.FindAllStringIndex(ostep.Text, -1); len(m) > 0 {
|
||||
var pos int
|
||||
for i := 0; i < len(m); i++ {
|
||||
pair := m[i]
|
||||
text += cyan(astStep.Text[pos:pair[0]])
|
||||
text += cyanb(astStep.Text[pair[0]:pair[1]])
|
||||
text += cyan(ostep.Text[pos:pair[0]])
|
||||
text += cyanb(ostep.Text[pair[0]:pair[1]])
|
||||
pos = pair[1]
|
||||
}
|
||||
text += cyan(astStep.Text[pos:len(astStep.Text)])
|
||||
text += cyan(ostep.Text[pos:len(ostep.Text)])
|
||||
} else {
|
||||
text = cyan(astStep.Text)
|
||||
text = cyan(ostep.Text)
|
||||
}
|
||||
|
||||
_, maxLength := f.scenarioLengths(result.owner.AstNodeIds[0])
|
||||
stepLength := f.lengthPickleStep(astStep.Keyword, astStep.Text)
|
||||
|
||||
text += s(maxLength - stepLength)
|
||||
text += " " + blackb("# "+result.def.definitionID())
|
||||
text += s(f.commentPos-f.length(ostep)+1) + blackb(fmt.Sprintf("# %s", res.def.definitionID()))
|
||||
} else {
|
||||
text = cyan(astStep.Text)
|
||||
text = cyan(ostep.Text)
|
||||
}
|
||||
// print the step outline
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(astStep.Keyword))+" "+text)
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+cyan(strings.TrimSpace(ostep.Keyword))+" "+text)
|
||||
|
||||
if table := result.step.Argument.GetDataTable(); table != nil {
|
||||
f.printTable(table, cyan)
|
||||
// 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 docString := astStep.GetDocString(); docString != nil {
|
||||
f.printDocString(docString)
|
||||
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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
max := longestExampleRow(exampleTable, clr, cyan)
|
||||
if clr == nil {
|
||||
clr = green
|
||||
}
|
||||
|
||||
max := longest(example, clr, cyan)
|
||||
// an example table header
|
||||
if printExampleHeader {
|
||||
fmt.Fprintln(f.out, "")
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+keywordAndName(exampleTable.Keyword, exampleTable.Name))
|
||||
|
||||
f.printTableHeader(exampleTable.TableHeader, max)
|
||||
if firstExample {
|
||||
f.printExampleHeader(example, max)
|
||||
}
|
||||
|
||||
f.printTableRow(exampleRow, max, clr)
|
||||
// an example table row
|
||||
row := example.TableBody[len(example.TableBody)-f.outlineNumExamples]
|
||||
f.printExampleRow(row, max, clr)
|
||||
|
||||
if errorMsg != "" {
|
||||
fmt.Fprintln(f.out, s(f.indent*4)+redb(errorMsg))
|
||||
// if there is an error
|
||||
if msg != "" {
|
||||
fmt.Fprintln(f.out, s(f.indent*4)+redb(msg))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *pretty) printTableRow(row *messages.GherkinDocument_Feature_TableRow, max []int, clr colors.ColorFunc) {
|
||||
func (f *pretty) printExampleRow(row *gherkin.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) printTableHeader(row *messages.GherkinDocument_Feature_TableRow, max []int) {
|
||||
f.printTableRow(row, max, cyan)
|
||||
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) 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)
|
||||
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)
|
||||
}
|
||||
|
||||
astBackgroundStep := backgroundSteps > 0 && backgroundSteps >= len(f.lastFeature().lastPickleResult().stepResults)
|
||||
|
||||
if astBackgroundStep {
|
||||
if len(f.lastFeature().pickleResults) > 1 {
|
||||
return
|
||||
}
|
||||
|
||||
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 !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)
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
if table := result.step.Argument.GetDataTable(); table != nil {
|
||||
f.printTable(table, cyan)
|
||||
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--
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if docString := astStep.GetDocString(); docString != nil {
|
||||
f.printDocString(docString)
|
||||
if !f.isBackgroundStep(res.step) || bgStep {
|
||||
f.printStep(res.step, res.def, res.typ.clr())
|
||||
}
|
||||
|
||||
if result.err != nil {
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", result.err)))
|
||||
if res.err != nil {
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+redb(fmt.Sprintf("%+v", res.err)))
|
||||
}
|
||||
|
||||
if result.status == pending {
|
||||
if res.typ == pending {
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+yellow("TODO: write pending definition"))
|
||||
}
|
||||
}
|
||||
|
||||
func (f *pretty) printDocString(docString *messages.GherkinDocument_Feature_Step_DocString) {
|
||||
var ct string
|
||||
|
||||
if len(docString.MediaType) > 0 {
|
||||
ct = " " + cyan(docString.MediaType)
|
||||
func (f *pretty) isBackgroundStep(step *gherkin.Step) bool {
|
||||
if f.feature.Background == nil {
|
||||
return false
|
||||
}
|
||||
|
||||
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))
|
||||
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))
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// print table with aligned table cells
|
||||
// @TODO: need to make example header cells bold
|
||||
func (f *pretty) printTable(t *messages.PickleStepArgument_PickleTable, c colors.ColorFunc) {
|
||||
maxColLengths := maxColLengths(t, c)
|
||||
func (f *pretty) printTable(t *gherkin.DataTable, c colors.ColorFunc) {
|
||||
var l = longest(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)
|
||||
colLength := utf8.RuneCountInString(val)
|
||||
cols[i] = val + s(maxColLengths[i]-colLength)
|
||||
ln := utf8.RuneCountInString(val)
|
||||
cols[i] = val + s(l[i]-ln)
|
||||
}
|
||||
|
||||
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 maxColLengths(t *messages.PickleStepArgument_PickleTable, clrs ...colors.ColorFunc) []int {
|
||||
if t == nil {
|
||||
return []int{}
|
||||
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...)
|
||||
}
|
||||
|
||||
longest := make([]int, len(t.Rows[0].Cells))
|
||||
for _, row := range t.Rows {
|
||||
longest := make([]int, len(rows[0].Cells))
|
||||
for _, row := range rows {
|
||||
for i, cell := range row.Cells {
|
||||
for _, c := range clrs {
|
||||
ln := utf8.RuneCountInString(c(cell.Value))
|
||||
|
@ -398,71 +458,35 @@ func maxColLengths(t *messages.PickleStepArgument_PickleTable, clrs ...colors.Co
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
return longest
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
func (f *pretty) longestStep(steps []*gherkin.Step, base int) int {
|
||||
ret := base
|
||||
for _, step := range steps {
|
||||
length := f.lengthPickleStep(step.Keyword, step.Text)
|
||||
if length > max {
|
||||
max = length
|
||||
length := f.length(step)
|
||||
if length > ret {
|
||||
ret = length
|
||||
}
|
||||
}
|
||||
|
||||
return max
|
||||
return ret
|
||||
}
|
||||
|
||||
// a line number representation in feature file
|
||||
func (f *pretty) line(loc *messages.Location) string {
|
||||
return " " + blackb(fmt.Sprintf("# %s:%d", f.lastFeature().Path, loc.Line))
|
||||
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) 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)
|
||||
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))
|
||||
}
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"math"
|
||||
"strings"
|
||||
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
|
@ -37,39 +37,21 @@ 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.status {
|
||||
switch res.typ {
|
||||
case passed:
|
||||
fmt.Fprint(f.out, green("."))
|
||||
case skipped:
|
||||
|
@ -89,49 +71,44 @@ func (f *progress) step(res *stepResult) {
|
|||
}
|
||||
}
|
||||
|
||||
func (f *progress) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
f.basefmt.Passed(pickle, step, match)
|
||||
func (f *progress) Passed(step *gherkin.Step, match *StepDef) {
|
||||
f.basefmt.Passed(step, match)
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.step(f.lastStepResult())
|
||||
f.step(f.passed[len(f.passed)-1])
|
||||
}
|
||||
|
||||
func (f *progress) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
f.basefmt.Skipped(pickle, step, match)
|
||||
func (f *progress) Skipped(step *gherkin.Step, match *StepDef) {
|
||||
f.basefmt.Skipped(step, match)
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.step(f.lastStepResult())
|
||||
f.step(f.skipped[len(f.skipped)-1])
|
||||
}
|
||||
|
||||
func (f *progress) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
f.basefmt.Undefined(pickle, step, match)
|
||||
func (f *progress) Undefined(step *gherkin.Step, match *StepDef) {
|
||||
f.basefmt.Undefined(step, match)
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.step(f.lastStepResult())
|
||||
f.step(f.undefined[len(f.undefined)-1])
|
||||
}
|
||||
|
||||
func (f *progress) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
|
||||
f.basefmt.Failed(pickle, step, match, err)
|
||||
func (f *progress) Failed(step *gherkin.Step, match *StepDef, err error) {
|
||||
f.basefmt.Failed(step, match, err)
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.step(f.lastStepResult())
|
||||
f.step(f.failed[len(f.failed)-1])
|
||||
}
|
||||
|
||||
func (f *progress) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
|
||||
f.basefmt.Pending(pickle, step, match)
|
||||
func (f *progress) Pending(step *gherkin.Step, match *StepDef) {
|
||||
f.basefmt.Pending(step, match)
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
f.step(f.lastStepResult())
|
||||
f.step(f.pending[len(f.pending)-1])
|
||||
}
|
||||
|
||||
func (f *progress) Sync(cf ConcurrentFormatter) {
|
||||
|
|
|
@ -3,41 +3,27 @@ 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) {
|
||||
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)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(sampleGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{
|
||||
GherkinDocument: gd,
|
||||
pickles: pickles,
|
||||
Path: path,
|
||||
features: []*feature{&feature{
|
||||
Path: "any.feature",
|
||||
Feature: feat,
|
||||
Content: []byte(sampleGherkinFeature),
|
||||
}},
|
||||
initializer: func(s *Suite) {
|
||||
|
@ -81,52 +67,61 @@ func FeatureContext(s *godog.Suite) {
|
|||
s.Step(` + "`^next undefined$`" + `, nextUndefined)
|
||||
}`
|
||||
|
||||
require.True(t, r.run())
|
||||
|
||||
expected = trimAllLines(expected)
|
||||
|
||||
r.run()
|
||||
|
||||
actual := trimAllLines(buf.String())
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
shouldMatchOutput(expected, actual, t)
|
||||
}
|
||||
|
||||
var basicGherkinFeature = `
|
||||
Feature: basic
|
||||
|
||||
Scenario: passing scenario
|
||||
When one
|
||||
Then two
|
||||
`
|
||||
|
||||
func TestProgressFormatterWhenStepPanics(t *testing.T) {
|
||||
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)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^one$`, func() error { return nil })
|
||||
s.Step(`^two$`, func() error { panic("omg") })
|
||||
},
|
||||
}
|
||||
|
||||
require.True(t, r.run())
|
||||
if !r.run() {
|
||||
t.Fatal("the suite should have failed")
|
||||
}
|
||||
|
||||
actual := buf.String()
|
||||
assert.Contains(t, actual, "godog/fmt_progress_test.go:107")
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
|
||||
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)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^sub1$`, func() error { return nil })
|
||||
s.Step(`^sub-sub$`, func() error { return nil })
|
||||
|
@ -136,22 +131,22 @@ func TestProgressFormatterWithPassingMultisteps(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
assert.False(t, r.run())
|
||||
if r.run() {
|
||||
t.Fatal("the suite should have passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
|
||||
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)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles, Path: path}},
|
||||
features: []*feature{&feature{Feature: feat, Path: "some.feature"}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^sub1$`, func() error { return nil })
|
||||
s.Step(`^sub-sub$`, func() error { return fmt.Errorf("errored") })
|
||||
|
@ -161,7 +156,9 @@ func TestProgressFormatterWithFailingMultisteps(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
require.True(t, r.run())
|
||||
if !r.run() {
|
||||
t.Fatal("the suite should have failed")
|
||||
}
|
||||
|
||||
expected := `
|
||||
.F 2
|
||||
|
@ -181,21 +178,48 @@ Error: sub2: sub-sub: errored
|
|||
|
||||
expected = trimAllLines(expected)
|
||||
actual := trimAllLines(buf.String())
|
||||
assert.Equal(t, expected, actual)
|
||||
|
||||
shouldMatchOutput(expected, actual, t)
|
||||
}
|
||||
|
||||
func shouldMatchOutput(expected, actual string, t *testing.T) {
|
||||
act := []byte(actual)
|
||||
exp := []byte(expected)
|
||||
|
||||
if len(act) != len(exp) {
|
||||
t.Fatalf("content lengths do not match, expected: %d, actual %d, actual output:\n%s", len(exp), len(act), actual)
|
||||
}
|
||||
|
||||
for i := 0; i < len(exp); i++ {
|
||||
if act[i] == exp[i] {
|
||||
continue
|
||||
}
|
||||
|
||||
cpe := make([]byte, len(exp))
|
||||
copy(cpe, exp)
|
||||
e := append(exp[:i], '^')
|
||||
e = append(e, cpe[i:]...)
|
||||
|
||||
cpa := make([]byte, len(act))
|
||||
copy(cpa, act)
|
||||
a := append(act[:i], '^')
|
||||
a = append(a, cpa[i:]...)
|
||||
|
||||
t.Fatalf("expected output does not match:\n%s\n\n%s", string(a), string(e))
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||
require.NoError(t, err)
|
||||
|
||||
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^sub1$`, func() error { return nil })
|
||||
s.Step(`^sub-sub$`, func() error { return nil })
|
||||
|
@ -205,22 +229,22 @@ func TestProgressFormatterWithPanicInMultistep(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
assert.True(t, r.run())
|
||||
if !r.run() {
|
||||
t.Fatal("the suite should have failed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressFormatterMultistepTemplates(t *testing.T) {
|
||||
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)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var buf bytes.Buffer
|
||||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
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"} })
|
||||
|
@ -229,7 +253,9 @@ func TestProgressFormatterMultistepTemplates(t *testing.T) {
|
|||
},
|
||||
}
|
||||
|
||||
require.False(t, r.run())
|
||||
if r.run() {
|
||||
t.Fatal("the suite should have passed")
|
||||
}
|
||||
|
||||
expected := `
|
||||
.U 2
|
||||
|
@ -261,13 +287,14 @@ func FeatureContext(s *godog.Suite) {
|
|||
`
|
||||
|
||||
expected = trimAllLines(expected)
|
||||
actual := trimAllLines(buf.String())
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
actual := trimAllLines(buf.String())
|
||||
if actual != expected {
|
||||
t.Fatalf("expected output does not match: %s", actual)
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressFormatterWhenMultiStepHasArgument(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
|
||||
var featureSource = `
|
||||
Feature: basic
|
||||
|
@ -279,28 +306,26 @@ 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", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
fmt: progressFunc("progress", ioutil.Discard),
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^one$`, func() error { return nil })
|
||||
s.Step(`^two:$`, func(doc *messages.PickleStepArgument_PickleDocString) Steps { return Steps{"one"} })
|
||||
s.Step(`^two:$`, func(doc *gherkin.DocString) Steps { return Steps{"one"} })
|
||||
},
|
||||
}
|
||||
|
||||
assert.False(t, r.run())
|
||||
if r.run() {
|
||||
t.Fatal("the suite should have passed")
|
||||
}
|
||||
}
|
||||
|
||||
func TestProgressFormatterWhenMultiStepHasStepWithArgument(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
|
||||
var featureSource = `
|
||||
Feature: basic
|
||||
|
@ -309,10 +334,10 @@ Feature: basic
|
|||
When one
|
||||
Then two`
|
||||
|
||||
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(featureSource), (&messages.Incrementing{}).NewId)
|
||||
require.NoError(t, err)
|
||||
|
||||
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(featureSource))
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
var subStep = `three:
|
||||
"""
|
||||
|
@ -323,15 +348,17 @@ Feature: basic
|
|||
w := colors.Uncolored(&buf)
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", w),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^one$`, func() error { return nil })
|
||||
s.Step(`^two$`, func() Steps { return Steps{subStep} })
|
||||
s.Step(`^three:$`, func(doc *messages.PickleStepArgument_PickleDocString) error { return nil })
|
||||
s.Step(`^three:$`, func(doc *gherkin.DocString) error { return nil })
|
||||
},
|
||||
}
|
||||
|
||||
require.True(t, r.run())
|
||||
if !r.run() {
|
||||
t.Fatal("the suite should have failed")
|
||||
}
|
||||
|
||||
expected := `
|
||||
.F 2
|
||||
|
@ -339,8 +366,8 @@ Feature: basic
|
|||
|
||||
--- Failed steps:
|
||||
|
||||
Scenario: passing scenario # any.feature:4
|
||||
Then two # any.feature:6
|
||||
Scenario: passing scenario # :4
|
||||
Then two # :6
|
||||
Error: nested steps cannot be multiline and have table or content body argument
|
||||
|
||||
|
||||
|
@ -351,6 +378,7 @@ Feature: basic
|
|||
|
||||
expected = trimAllLines(expected)
|
||||
actual := trimAllLines(buf.String())
|
||||
|
||||
assert.Equal(t, expected, actual)
|
||||
if actual != expected {
|
||||
t.Fatalf("expected output does not match: %s", 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"}
|
||||
{"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":"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,3 +19,4 @@ 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,16 +10,17 @@
|
|||
<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>
|
||||
|
||||
|
||||
|
|
36
gherkin.go
Обычный файл
36
gherkin.go
Обычный файл
|
@ -0,0 +1,36 @@
|
|||
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
|
||||
}
|
21
gherkin/LICENSE
Обычный файл
21
gherkin/LICENSE
Обычный файл
|
@ -0,0 +1,21 @@
|
|||
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.
|
3
gherkin/README.md
Обычный файл
3
gherkin/README.md
Обычный файл
|
@ -0,0 +1,3 @@
|
|||
[](http://travis-ci.org/cucumber/gherkin-go)
|
||||
|
||||
Gherkin parser/compiler for Go. Please see [Gherkin](https://github.com/cucumber/gherkin) for details.
|
95
gherkin/ast.go
Обычный файл
95
gherkin/ast.go
Обычный файл
|
@ -0,0 +1,95 @@
|
|||
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"`
|
||||
}
|
378
gherkin/astbuilder.go
Обычный файл
378
gherkin/astbuilder.go
Обычный файл
|
@ -0,0 +1,378 @@
|
|||
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
|
||||
}
|
47
gherkin/dialect.go
Обычный файл
47
gherkin/dialect.go
Обычный файл
|
@ -0,0 +1,47 @@
|
|||
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]
|
||||
}
|
2988
gherkin/dialects_builtin.go
Обычный файл
2988
gherkin/dialects_builtin.go
Обычный файл
Различия файлов не показаны, т.к. их слишком много
Показать различия
137
gherkin/gherkin.go
Обычный файл
137
gherkin/gherkin.go
Обычный файл
|
@ -0,0 +1,137 @@
|
|||
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
|
||||
}
|
270
gherkin/matcher.go
Обычный файл
270
gherkin/matcher.go
Обычный файл
|
@ -0,0 +1,270 @@
|
|||
package gherkin
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strings"
|
||||
"unicode/utf8"
|
||||
)
|
||||
|
||||
const (
|
||||
DEFAULT_DIALECT = "en"
|
||||
COMMENT_PREFIX = "#"
|
||||
TAG_PREFIX = "@"
|
||||
TITLE_KEYWORD_SEPARATOR = ":"
|
||||
TABLE_CELL_SEPARATOR = '|'
|
||||
ESCAPE_CHAR = '\\'
|
||||
ESCAPED_NEWLINE = 'n'
|
||||
DOCSTRING_SEPARATOR = "\"\"\""
|
||||
DOCSTRING_ALTERNATIVE_SEPARATOR = "```"
|
||||
)
|
||||
|
||||
type matcher struct {
|
||||
gdp GherkinDialectProvider
|
||||
default_lang string
|
||||
lang string
|
||||
dialect *GherkinDialect
|
||||
activeDocStringSeparator string
|
||||
indentToRemove int
|
||||
languagePattern *regexp.Regexp
|
||||
}
|
||||
|
||||
func NewMatcher(gdp GherkinDialectProvider) Matcher {
|
||||
return &matcher{
|
||||
gdp: gdp,
|
||||
default_lang: DEFAULT_DIALECT,
|
||||
lang: DEFAULT_DIALECT,
|
||||
dialect: gdp.GetDialect(DEFAULT_DIALECT),
|
||||
languagePattern: regexp.MustCompile("^\\s*#\\s*language\\s*:\\s*([a-zA-Z\\-_]+)\\s*$"),
|
||||
}
|
||||
}
|
||||
|
||||
func NewLanguageMatcher(gdp GherkinDialectProvider, language string) Matcher {
|
||||
return &matcher{
|
||||
gdp: gdp,
|
||||
default_lang: language,
|
||||
lang: language,
|
||||
dialect: gdp.GetDialect(language),
|
||||
languagePattern: regexp.MustCompile("^\\s*#\\s*language\\s*:\\s*([a-zA-Z\\-_]+)\\s*$"),
|
||||
}
|
||||
}
|
||||
|
||||
func (m *matcher) Reset() {
|
||||
m.indentToRemove = 0
|
||||
m.activeDocStringSeparator = ""
|
||||
if m.lang != "en" {
|
||||
m.dialect = m.gdp.GetDialect(m.default_lang)
|
||||
m.lang = "en"
|
||||
}
|
||||
}
|
||||
|
||||
func (m *matcher) newTokenAtLocation(line, index int) (token *Token) {
|
||||
column := index + 1
|
||||
token = new(Token)
|
||||
token.GherkinDialect = m.lang
|
||||
token.Location = &Location{line, column}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchEOF(line *Line) (ok bool, token *Token, err error) {
|
||||
if line.IsEof() {
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_EOF
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchEmpty(line *Line) (ok bool, token *Token, err error) {
|
||||
if line.IsEmpty() {
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_Empty
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchComment(line *Line) (ok bool, token *Token, err error) {
|
||||
if line.StartsWith(COMMENT_PREFIX) {
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, 0), true
|
||||
token.Type = TokenType_Comment
|
||||
token.Text = line.LineText
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchTagLine(line *Line) (ok bool, token *Token, err error) {
|
||||
if line.StartsWith(TAG_PREFIX) {
|
||||
var tags []*LineSpan
|
||||
var column = line.Indent()
|
||||
splits := strings.Split(line.TrimmedLineText, TAG_PREFIX)
|
||||
for i := range splits {
|
||||
txt := strings.Trim(splits[i], " ")
|
||||
if txt != "" {
|
||||
tags = append(tags, &LineSpan{column, TAG_PREFIX + txt})
|
||||
}
|
||||
column = column + len(splits[i]) + 1
|
||||
}
|
||||
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_TagLine
|
||||
token.Items = tags
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) matchTitleLine(line *Line, tokenType TokenType, keywords []string) (ok bool, token *Token, err error) {
|
||||
for i := range keywords {
|
||||
keyword := keywords[i]
|
||||
if line.StartsWith(keyword + TITLE_KEYWORD_SEPARATOR) {
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = tokenType
|
||||
token.Keyword = keyword
|
||||
token.Text = strings.Trim(line.TrimmedLineText[len(keyword)+1:], " ")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchFeatureLine(line *Line) (ok bool, token *Token, err error) {
|
||||
return m.matchTitleLine(line, TokenType_FeatureLine, m.dialect.FeatureKeywords())
|
||||
}
|
||||
func (m *matcher) MatchBackgroundLine(line *Line) (ok bool, token *Token, err error) {
|
||||
return m.matchTitleLine(line, TokenType_BackgroundLine, m.dialect.BackgroundKeywords())
|
||||
}
|
||||
func (m *matcher) MatchScenarioLine(line *Line) (ok bool, token *Token, err error) {
|
||||
return m.matchTitleLine(line, TokenType_ScenarioLine, m.dialect.ScenarioKeywords())
|
||||
}
|
||||
func (m *matcher) MatchScenarioOutlineLine(line *Line) (ok bool, token *Token, err error) {
|
||||
return m.matchTitleLine(line, TokenType_ScenarioOutlineLine, m.dialect.ScenarioOutlineKeywords())
|
||||
}
|
||||
func (m *matcher) MatchExamplesLine(line *Line) (ok bool, token *Token, err error) {
|
||||
return m.matchTitleLine(line, TokenType_ExamplesLine, m.dialect.ExamplesKeywords())
|
||||
}
|
||||
func (m *matcher) MatchStepLine(line *Line) (ok bool, token *Token, err error) {
|
||||
keywords := m.dialect.StepKeywords()
|
||||
for i := range keywords {
|
||||
keyword := keywords[i]
|
||||
if line.StartsWith(keyword) {
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_StepLine
|
||||
token.Keyword = keyword
|
||||
token.Text = strings.Trim(line.TrimmedLineText[len(keyword):], " ")
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchDocStringSeparator(line *Line) (ok bool, token *Token, err error) {
|
||||
if m.activeDocStringSeparator != "" {
|
||||
if line.StartsWith(m.activeDocStringSeparator) {
|
||||
// close
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_DocStringSeparator
|
||||
|
||||
m.indentToRemove = 0
|
||||
m.activeDocStringSeparator = ""
|
||||
}
|
||||
return
|
||||
}
|
||||
if line.StartsWith(DOCSTRING_SEPARATOR) {
|
||||
m.activeDocStringSeparator = DOCSTRING_SEPARATOR
|
||||
} else if line.StartsWith(DOCSTRING_ALTERNATIVE_SEPARATOR) {
|
||||
m.activeDocStringSeparator = DOCSTRING_ALTERNATIVE_SEPARATOR
|
||||
}
|
||||
if m.activeDocStringSeparator != "" {
|
||||
// open
|
||||
contentType := line.TrimmedLineText[len(m.activeDocStringSeparator):]
|
||||
m.indentToRemove = line.Indent()
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_DocStringSeparator
|
||||
token.Text = contentType
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchTableRow(line *Line) (ok bool, token *Token, err error) {
|
||||
var firstChar, firstPos = utf8.DecodeRuneInString(line.TrimmedLineText)
|
||||
if firstChar == TABLE_CELL_SEPARATOR {
|
||||
var cells []*LineSpan
|
||||
var cell []rune
|
||||
var startCol = line.Indent() + 2 // column where the current cell started
|
||||
// start after the first separator, it's not included in the cell
|
||||
for i, w, col := firstPos, 0, startCol; i < len(line.TrimmedLineText); i += w {
|
||||
var char rune
|
||||
char, w = utf8.DecodeRuneInString(line.TrimmedLineText[i:])
|
||||
if char == TABLE_CELL_SEPARATOR {
|
||||
// append current cell
|
||||
txt := string(cell)
|
||||
txtTrimmed := strings.TrimLeft(txt, " ")
|
||||
ind := len(txt) - len(txtTrimmed)
|
||||
cells = append(cells, &LineSpan{startCol + ind, strings.TrimRight(txtTrimmed, " ")})
|
||||
// start building next
|
||||
cell = make([]rune, 0)
|
||||
startCol = col + 1
|
||||
} else if char == ESCAPE_CHAR {
|
||||
// skip this character but count the column
|
||||
i += w
|
||||
col++
|
||||
char, w = utf8.DecodeRuneInString(line.TrimmedLineText[i:])
|
||||
if char == ESCAPED_NEWLINE {
|
||||
cell = append(cell, '\n')
|
||||
} else {
|
||||
if char != TABLE_CELL_SEPARATOR && char != ESCAPE_CHAR {
|
||||
cell = append(cell, ESCAPE_CHAR)
|
||||
}
|
||||
cell = append(cell, char)
|
||||
}
|
||||
} else {
|
||||
cell = append(cell, char)
|
||||
}
|
||||
col++
|
||||
}
|
||||
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_TableRow
|
||||
token.Items = cells
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchLanguage(line *Line) (ok bool, token *Token, err error) {
|
||||
matches := m.languagePattern.FindStringSubmatch(line.TrimmedLineText)
|
||||
if len(matches) > 0 {
|
||||
lang := matches[1]
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, line.Indent()), true
|
||||
token.Type = TokenType_Language
|
||||
token.Text = lang
|
||||
|
||||
dialect := m.gdp.GetDialect(lang)
|
||||
if dialect == nil {
|
||||
err = &parseError{"Language not supported: " + lang, token.Location}
|
||||
} else {
|
||||
m.lang = lang
|
||||
m.dialect = dialect
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) MatchOther(line *Line) (ok bool, token *Token, err error) {
|
||||
token, ok = m.newTokenAtLocation(line.LineNumber, 0), true
|
||||
token.Type = TokenType_Other
|
||||
|
||||
element := line.LineText
|
||||
txt := strings.TrimLeft(element, " ")
|
||||
|
||||
if len(element)-len(txt) > m.indentToRemove {
|
||||
token.Text = m.unescapeDocString(element[m.indentToRemove:])
|
||||
} else {
|
||||
token.Text = m.unescapeDocString(txt)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (m *matcher) unescapeDocString(text string) string {
|
||||
if m.activeDocStringSeparator != "" {
|
||||
return strings.Replace(text, "\\\"\\\"\\\"", "\"\"\"", -1)
|
||||
} else {
|
||||
return text
|
||||
}
|
||||
}
|
2270
gherkin/parser.go
Обычный файл
2270
gherkin/parser.go
Обычный файл
Различия файлов не показаны, т.к. их слишком много
Показать различия
4
go.mod
4
go.mod
|
@ -3,7 +3,7 @@ module github.com/cucumber/godog
|
|||
go 1.13
|
||||
|
||||
require (
|
||||
github.com/cucumber/gherkin-go/v9 v9.2.0
|
||||
github.com/cucumber/messages-go/v9 v9.0.3
|
||||
github.com/DATA-DOG/go-txdb v0.1.3
|
||||
github.com/go-sql-driver/mysql v1.5.0
|
||||
github.com/stretchr/testify v1.4.0
|
||||
)
|
||||
|
|
32
go.sum
32
go.sum
|
@ -1,33 +1,15 @@
|
|||
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/DATA-DOG/go-txdb v0.1.3 h1:R4v6OuOcy2O147e2zHxU0B4NDtF+INb5R9q/CV7AEMg=
|
||||
github.com/DATA-DOG/go-txdb v0.1.3/go.mod h1:DhAhxMXZpUJVGnT+p9IbzJoRKvlArO2pkHjnGX7o0n0=
|
||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
||||
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/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs=
|
||||
github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg=
|
||||
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=
|
||||
golang.org/x/tools v0.0.0-20181030221726-6c7e314b6563/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
||||
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=
|
||||
|
|
41
run_test.go
41
run_test.go
|
@ -10,12 +10,11 @@ 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 {
|
||||
|
@ -60,16 +59,12 @@ func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
|
||||
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
require.NoError(t, err)
|
||||
|
||||
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", ioutil.Discard),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^one$`, func() error { return nil })
|
||||
s.Step(`^two$`, func() error { return ErrPending })
|
||||
|
@ -83,16 +78,12 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasPendingSteps(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
|
||||
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
require.NoError(t, err)
|
||||
|
||||
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", ioutil.Discard),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^one$`, func() error { return nil })
|
||||
// two - is undefined
|
||||
|
@ -106,16 +97,12 @@ func TestFailsOrPassesBasedOnStrictModeWhenHasUndefinedSteps(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestShouldFailOnError(t *testing.T) {
|
||||
const path = "any.feature"
|
||||
|
||||
gd, err := gherkin.ParseGherkinDocument(strings.NewReader(basicGherkinFeature), (&messages.Incrementing{}).NewId)
|
||||
feat, err := gherkin.ParseFeature(strings.NewReader(basicGherkinFeature))
|
||||
require.NoError(t, err)
|
||||
|
||||
pickles := gherkin.Pickles(*gd, path, (&messages.Incrementing{}).NewId)
|
||||
|
||||
r := runner{
|
||||
fmt: progressFunc("progress", ioutil.Discard),
|
||||
features: []*feature{{GherkinDocument: gd, pickles: pickles}},
|
||||
features: []*feature{&feature{Feature: feat}},
|
||||
initializer: func(s *Suite) {
|
||||
s.Step(`^one$`, func() error { return nil })
|
||||
s.Step(`^two$`, func() error { return fmt.Errorf("error") })
|
||||
|
@ -256,10 +243,11 @@ type succeedRunTestCase struct {
|
|||
filename string // expected output file
|
||||
}
|
||||
|
||||
func TestConcurrencyRun(t *testing.T) {
|
||||
func TestSucceedRun(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 {
|
||||
|
@ -277,7 +265,7 @@ func TestConcurrencyRun(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func testSucceedRun(t *testing.T, format string, concurrency int, expected string) {
|
||||
func testSucceedRun(t *testing.T, format string, concurrency int, expectedOutput string) {
|
||||
output := new(bytes.Buffer)
|
||||
|
||||
opt := Options{
|
||||
|
@ -294,12 +282,11 @@ func testSucceedRun(t *testing.T, format string, concurrency int, expected strin
|
|||
b, err := ioutil.ReadAll(output)
|
||||
require.NoError(t, err)
|
||||
|
||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||
|
||||
expected = suiteCtxReg.ReplaceAllString(expected, `suite_context.go:0`)
|
||||
|
||||
actual := strings.TrimSpace(string(b))
|
||||
|
||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||
expectedOutput = suiteCtxReg.ReplaceAllString(expectedOutput, `suite_context.go:0`)
|
||||
actual = suiteCtxReg.ReplaceAllString(actual, `suite_context.go:0`)
|
||||
|
||||
assert.Equalf(t, expected, actual, "[%s]", actual)
|
||||
assert.Equalf(t, expectedOutput, actual, "[%s]", actual)
|
||||
}
|
||||
|
|
43
stepdef.go
43
stepdef.go
|
@ -10,7 +10,7 @@ import (
|
|||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
||||
|
@ -31,7 +31,7 @@ var matchFuncDefRef = regexp.MustCompile(`\(([^\)]+)\)`)
|
|||
// will result in main step failure.
|
||||
type Steps []string
|
||||
|
||||
// StepDefinition is a registered step definition
|
||||
// StepDef 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 StepDefinition struct {
|
||||
type StepDef struct {
|
||||
args []interface{}
|
||||
hv reflect.Value
|
||||
Expr *regexp.Regexp
|
||||
|
@ -50,7 +50,7 @@ type StepDefinition struct {
|
|||
undefined []string
|
||||
}
|
||||
|
||||
func (sd *StepDefinition) definitionID() string {
|
||||
func (sd *StepDef) definitionID() string {
|
||||
ptr := sd.hv.Pointer()
|
||||
f := runtime.FuncForPC(ptr)
|
||||
file, line := f.FileLine(ptr)
|
||||
|
@ -80,7 +80,7 @@ func (sd *StepDefinition) definitionID() string {
|
|||
|
||||
// run a step with the matched arguments using
|
||||
// reflect
|
||||
func (sd *StepDefinition) run() interface{} {
|
||||
func (sd *StepDef) 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,32 +168,20 @@ func (sd *StepDefinition) run() interface{} {
|
|||
case reflect.Ptr:
|
||||
arg := sd.args[i]
|
||||
switch param.Elem().String() {
|
||||
case "messages.PickleStepArgument_PickleDocString":
|
||||
if v, ok := arg.(*messages.PickleStepArgument); ok {
|
||||
values = append(values, reflect.ValueOf(v.GetDocString()))
|
||||
break
|
||||
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)
|
||||
}
|
||||
|
||||
if v, ok := arg.(*messages.PickleStepArgument_PickleDocString); ok {
|
||||
values = append(values, reflect.ValueOf(v))
|
||||
break
|
||||
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)
|
||||
}
|
||||
|
||||
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 %s", i, arg, param.Elem().String())
|
||||
return fmt.Errorf("the argument %d type %T is not supported", i, arg)
|
||||
}
|
||||
case reflect.Slice:
|
||||
switch param {
|
||||
|
@ -210,11 +198,10 @@ func (sd *StepDefinition) 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 *StepDefinition) shouldBeString(idx int) (string, error) {
|
||||
func (sd *StepDef) 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/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
func TestShouldSupportIntTypes(t *testing.T) {
|
||||
fn := func(a int64, b int32, c int16, d int8) error { return nil }
|
||||
|
||||
def := &StepDefinition{
|
||||
def := &StepDef{
|
||||
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 := &StepDefinition{
|
||||
def := &StepDef{
|
||||
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 *messages.PickleStepArgument_PickleDocString) error { return nil }
|
||||
fn3 := func(a *messages.PickleStepArgument_PickleTable) error { return nil }
|
||||
fn2 := func(a *gherkin.DocString) error { return nil }
|
||||
fn3 := func(a *gherkin.DataTable) error { return 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)}}
|
||||
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)}}
|
||||
|
||||
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 := &StepDefinition{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}}
|
||||
def2 := &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2), args: []interface{}{[]string{}}}
|
||||
def1 := &StepDef{Handler: fn1, hv: reflect.ValueOf(fn1), args: []interface{}{"str"}}
|
||||
def2 := &StepDef{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 := &StepDefinition{Handler: fn, hv: reflect.ValueOf(fn)}
|
||||
def := &StepDef{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 = &StepDefinition{Handler: fn2, hv: reflect.ValueOf(fn2)}
|
||||
// def = &StepDef{Handler: fn2, hv: reflect.ValueOf(fn2)}
|
||||
|
||||
// def.args = []interface{}{"1"}
|
||||
// if err := def.run(); err == nil {
|
||||
|
|
481
suite.go
481
suite.go
|
@ -4,6 +4,7 @@ import (
|
|||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"reflect"
|
||||
|
@ -14,17 +15,16 @@ import (
|
|||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
"github.com/cucumber/gherkin-go/v9"
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
"github.com/cucumber/godog/gherkin"
|
||||
)
|
||||
|
||||
var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
|
||||
var typeOfBytes = reflect.TypeOf([]byte(nil))
|
||||
|
||||
type feature struct {
|
||||
*messages.GherkinDocument
|
||||
pickles []*messages.Pickle
|
||||
pickleResults []*pickleResult
|
||||
*gherkin.Feature
|
||||
|
||||
Scenarios []*scenario
|
||||
|
||||
time time.Time
|
||||
Content []byte `json:"-"`
|
||||
|
@ -32,118 +32,47 @@ 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.pickleResults) == 0 {
|
||||
if len(f.Scenarios) == 0 {
|
||||
return f.startedAt()
|
||||
}
|
||||
|
||||
return f.pickleResults[len(f.pickleResults)-1].finishedAt()
|
||||
return f.Scenarios[len(f.Scenarios)-1].finishedAt()
|
||||
}
|
||||
|
||||
func (f feature) appendStepResult(s *stepResult) {
|
||||
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]
|
||||
scenario := f.Scenarios[len(f.Scenarios)-1]
|
||||
scenario.Steps = append(scenario.Steps, s)
|
||||
}
|
||||
|
||||
type sortByName []*feature
|
||||
|
||||
func (s sortByName) Len() int { return len(s) }
|
||||
func (s sortByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name }
|
||||
func (s sortByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
|
||||
func (s sortByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
|
||||
|
||||
type pickleResult struct {
|
||||
type scenario struct {
|
||||
Name string
|
||||
OutlineName string
|
||||
ExampleNo int
|
||||
time time.Time
|
||||
stepResults []*stepResult
|
||||
Steps []*stepResult
|
||||
}
|
||||
|
||||
func (s pickleResult) startedAt() time.Time {
|
||||
func (s scenario) startedAt() time.Time {
|
||||
return s.time
|
||||
}
|
||||
|
||||
func (s pickleResult) finishedAt() time.Time {
|
||||
if len(s.stepResults) == 0 {
|
||||
func (s scenario) finishedAt() time.Time {
|
||||
if len(s.Steps) == 0 {
|
||||
return s.startedAt()
|
||||
}
|
||||
|
||||
return s.stepResults[len(s.stepResults)-1].time
|
||||
return s.Steps[len(s.Steps)-1].time
|
||||
}
|
||||
|
||||
// ErrUndefined is returned in case if step definition was not found
|
||||
|
@ -165,7 +94,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 []*StepDefinition
|
||||
steps []*StepDef
|
||||
features []*feature
|
||||
fmt Formatter
|
||||
|
||||
|
@ -176,16 +105,16 @@ type Suite struct {
|
|||
|
||||
// suite event handlers
|
||||
beforeSuiteHandlers []func()
|
||||
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)
|
||||
beforeFeatureHandlers []func(*gherkin.Feature)
|
||||
beforeScenarioHandlers []func(interface{})
|
||||
beforeStepHandlers []func(*gherkin.Step)
|
||||
afterStepHandlers []func(*gherkin.Step, error)
|
||||
afterScenarioHandlers []func(interface{}, error)
|
||||
afterFeatureHandlers []func(*gherkin.Feature)
|
||||
afterSuiteHandlers []func()
|
||||
}
|
||||
|
||||
// Step allows to register a *StepDefinition in Godog
|
||||
// Step allows to register a *StepDef in Godog
|
||||
// feature suite, the definition will be applied
|
||||
// to all steps matching the given Regexp expr.
|
||||
//
|
||||
|
@ -197,7 +126,7 @@ type Suite struct {
|
|||
// the same step, then only the first matched handler
|
||||
// will be applied.
|
||||
//
|
||||
// If none of the *StepDefinition is matched, then
|
||||
// If none of the *StepDef is matched, then
|
||||
// ErrUndefined error will be returned when
|
||||
// running steps.
|
||||
func (s *Suite) Step(expr interface{}, stepFunc interface{}) {
|
||||
|
@ -224,7 +153,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 := &StepDefinition{
|
||||
def := &StepDef{
|
||||
Handler: stepFunc,
|
||||
Expr: regex,
|
||||
hv: v,
|
||||
|
@ -271,28 +200,31 @@ func (s *Suite) BeforeSuite(fn func()) {
|
|||
// scenario to restart it.
|
||||
//
|
||||
// Use it wisely and avoid sharing state between scenarios.
|
||||
func (s *Suite) BeforeFeature(fn func(*messages.GherkinDocument)) {
|
||||
func (s *Suite) BeforeFeature(fn func(*gherkin.Feature)) {
|
||||
s.beforeFeatureHandlers = append(s.beforeFeatureHandlers, fn)
|
||||
}
|
||||
|
||||
// BeforeScenario registers a function or method
|
||||
// to be run before every pickle.
|
||||
// to be run before every scenario or scenario outline.
|
||||
//
|
||||
// The interface argument may be *gherkin.Scenario
|
||||
// or *gherkin.ScenarioOutline
|
||||
//
|
||||
// It is a good practice to restore the default state
|
||||
// before every scenario so it would be isolated from
|
||||
// any kind of state.
|
||||
func (s *Suite) BeforeScenario(fn func(*messages.Pickle)) {
|
||||
func (s *Suite) BeforeScenario(fn func(interface{})) {
|
||||
s.beforeScenarioHandlers = append(s.beforeScenarioHandlers, fn)
|
||||
}
|
||||
|
||||
// BeforeStep registers a function or method
|
||||
// to be run before every step.
|
||||
func (s *Suite) BeforeStep(fn func(*messages.Pickle_PickleStep)) {
|
||||
// to be run before every scenario
|
||||
func (s *Suite) BeforeStep(fn func(*gherkin.Step)) {
|
||||
s.beforeStepHandlers = append(s.beforeStepHandlers, fn)
|
||||
}
|
||||
|
||||
// AfterStep registers an function or method
|
||||
// to be run after every step.
|
||||
// to be run after every scenario
|
||||
//
|
||||
// It may be convenient to return a different kind of error
|
||||
// in order to print more state details which may help
|
||||
|
@ -300,19 +232,22 @@ func (s *Suite) BeforeStep(fn func(*messages.Pickle_PickleStep)) {
|
|||
//
|
||||
// In some cases, for example when running a headless
|
||||
// browser, to take a screenshot after failure.
|
||||
func (s *Suite) AfterStep(fn func(*messages.Pickle_PickleStep, error)) {
|
||||
func (s *Suite) AfterStep(fn func(*gherkin.Step, error)) {
|
||||
s.afterStepHandlers = append(s.afterStepHandlers, fn)
|
||||
}
|
||||
|
||||
// AfterScenario registers an function or method
|
||||
// to be run after every pickle.
|
||||
func (s *Suite) AfterScenario(fn func(*messages.Pickle, error)) {
|
||||
// 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)) {
|
||||
s.afterScenarioHandlers = append(s.afterScenarioHandlers, fn)
|
||||
}
|
||||
|
||||
// AfterFeature registers a function or method
|
||||
// to be run once after feature executed all scenarios.
|
||||
func (s *Suite) AfterFeature(fn func(*messages.GherkinDocument)) {
|
||||
func (s *Suite) AfterFeature(fn func(*gherkin.Feature)) {
|
||||
s.afterFeatureHandlers = append(s.afterFeatureHandlers, fn)
|
||||
}
|
||||
|
||||
|
@ -341,7 +276,7 @@ func (s *Suite) run() {
|
|||
}
|
||||
}
|
||||
|
||||
func (s *Suite) matchStep(step *messages.Pickle_PickleStep) *StepDefinition {
|
||||
func (s *Suite) matchStep(step *gherkin.Step) *StepDef {
|
||||
def := s.matchStepText(step.Text)
|
||||
if def != nil && step.Argument != nil {
|
||||
def.args = append(def.args, step.Argument)
|
||||
|
@ -349,14 +284,14 @@ func (s *Suite) matchStep(step *messages.Pickle_PickleStep) *StepDefinition {
|
|||
return def
|
||||
}
|
||||
|
||||
func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep, prevStepErr error) (err error) {
|
||||
func (s *Suite) runStep(step *gherkin.Step, prevStepErr error) (err error) {
|
||||
// run before step handlers
|
||||
for _, f := range s.beforeStepHandlers {
|
||||
f(step)
|
||||
}
|
||||
|
||||
match := s.matchStep(step)
|
||||
s.fmt.Defined(pickle, step, match)
|
||||
s.fmt.Defined(step, match)
|
||||
|
||||
// user multistep definitions may panic
|
||||
defer func() {
|
||||
|
@ -377,11 +312,11 @@ func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte
|
|||
|
||||
switch err {
|
||||
case nil:
|
||||
s.fmt.Passed(pickle, step, match)
|
||||
s.fmt.Passed(step, match)
|
||||
case ErrPending:
|
||||
s.fmt.Pending(pickle, step, match)
|
||||
s.fmt.Pending(step, match)
|
||||
default:
|
||||
s.fmt.Failed(pickle, step, match, err)
|
||||
s.fmt.Failed(step, match, err)
|
||||
}
|
||||
|
||||
// run after step handlers
|
||||
|
@ -394,7 +329,7 @@ func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte
|
|||
return err
|
||||
} else if len(undef) > 0 {
|
||||
if match != nil {
|
||||
match = &StepDefinition{
|
||||
match = &StepDef{
|
||||
args: match.args,
|
||||
hv: match.hv,
|
||||
Expr: match.Expr,
|
||||
|
@ -403,12 +338,12 @@ func (s *Suite) runStep(pickle *messages.Pickle, step *messages.Pickle_PickleSte
|
|||
undefined: undef,
|
||||
}
|
||||
}
|
||||
s.fmt.Undefined(pickle, step, match)
|
||||
s.fmt.Undefined(step, match)
|
||||
return ErrUndefined
|
||||
}
|
||||
|
||||
if prevStepErr != nil {
|
||||
s.fmt.Skipped(pickle, step, match)
|
||||
s.fmt.Skipped(step, match)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -473,7 +408,7 @@ func (s *Suite) maybeSubSteps(result interface{}) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Suite) matchStepText(text string) *StepDefinition {
|
||||
func (s *Suite) matchStepText(text string) *StepDef {
|
||||
for _, h := range s.steps {
|
||||
if m := h.Expr.FindStringSubmatch(text); len(m) > 0 {
|
||||
var args []interface{}
|
||||
|
@ -483,7 +418,7 @@ func (s *Suite) matchStepText(text string) *StepDefinition {
|
|||
|
||||
// since we need to assign arguments
|
||||
// better to copy the step definition
|
||||
return &StepDefinition{
|
||||
return &StepDef{
|
||||
args: args,
|
||||
hv: h.hv,
|
||||
Expr: h.Expr,
|
||||
|
@ -495,9 +430,9 @@ func (s *Suite) matchStepText(text string) *StepDefinition {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Suite) runSteps(pickle *messages.Pickle, steps []*messages.Pickle_PickleStep) (err error) {
|
||||
func (s *Suite) runSteps(steps []*gherkin.Step) (err error) {
|
||||
for _, step := range steps {
|
||||
stepErr := s.runStep(pickle, step, err)
|
||||
stepErr := s.runStep(step, err)
|
||||
switch stepErr {
|
||||
case ErrUndefined:
|
||||
// do not overwrite failed error
|
||||
|
@ -514,6 +449,110 @@ func (s *Suite) runSteps(pickle *messages.Pickle, steps []*messages.Pickle_Pickl
|
|||
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
|
||||
|
@ -527,24 +566,46 @@ func (s *Suite) shouldFail(err error) bool {
|
|||
}
|
||||
|
||||
func (s *Suite) runFeature(f *feature) {
|
||||
if !isEmptyFeature(f.pickles) {
|
||||
if !isEmptyFeature(f.Feature) {
|
||||
for _, fn := range s.beforeFeatureHandlers {
|
||||
fn(f.GherkinDocument)
|
||||
fn(f.Feature)
|
||||
}
|
||||
}
|
||||
|
||||
s.fmt.Feature(f.GherkinDocument, f.Path, f.Content)
|
||||
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)
|
||||
}
|
||||
|
||||
defer func() {
|
||||
if !isEmptyFeature(f.pickles) {
|
||||
if !isEmptyFeature(f.Feature) {
|
||||
for _, fn := range s.afterFeatureHandlers {
|
||||
fn(f.GherkinDocument)
|
||||
fn(f.Feature)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
for _, pickle := range f.pickles {
|
||||
err := s.runPickle(pickle)
|
||||
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)
|
||||
}
|
||||
if s.shouldFail(err) {
|
||||
s.failed = true
|
||||
if s.stopOnFailure {
|
||||
|
@ -554,35 +615,31 @@ func (s *Suite) runFeature(f *feature) {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
func (s *Suite) runScenario(scenario *gherkin.Scenario, b *gherkin.Background) (err error) {
|
||||
if isEmptyScenario(scenario) {
|
||||
s.fmt.Node(scenario)
|
||||
return ErrUndefined
|
||||
}
|
||||
|
||||
// run before scenario handlers
|
||||
for _, f := range s.beforeScenarioHandlers {
|
||||
f(pickle)
|
||||
f(scenario)
|
||||
}
|
||||
|
||||
s.fmt.Pickle(pickle)
|
||||
s.fmt.Node(scenario)
|
||||
|
||||
// background
|
||||
steps := scenario.Steps
|
||||
if b != nil {
|
||||
steps = append(b.Steps, steps...)
|
||||
}
|
||||
|
||||
// scenario
|
||||
err = s.runSteps(pickle, pickle.Steps)
|
||||
err = s.runSteps(steps)
|
||||
|
||||
// run after scenario handlers
|
||||
for _, f := range s.afterScenarioHandlers {
|
||||
f(pickle, err)
|
||||
f(scenario, err)
|
||||
}
|
||||
|
||||
return
|
||||
|
@ -621,7 +678,7 @@ func extractFeaturePathLine(p string) (string, int) {
|
|||
return retPath, line
|
||||
}
|
||||
|
||||
func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) {
|
||||
func parseFeatureFile(path string) (*feature, error) {
|
||||
reader, err := os.Open(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
@ -629,22 +686,19 @@ func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) {
|
|||
defer reader.Close()
|
||||
|
||||
var buf bytes.Buffer
|
||||
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
|
||||
ft, err := gherkin.ParseFeature(io.TeeReader(reader, &buf))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s - %v", path, err)
|
||||
}
|
||||
|
||||
pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc)
|
||||
|
||||
return &feature{
|
||||
GherkinDocument: gherkinDocument,
|
||||
pickles: pickles,
|
||||
Content: buf.Bytes(),
|
||||
Path: path,
|
||||
Feature: ft,
|
||||
Content: buf.Bytes(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) {
|
||||
func parseFeatureDir(dir string) ([]*feature, error) {
|
||||
var features []*feature
|
||||
return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
|
||||
if err != nil {
|
||||
|
@ -659,7 +713,7 @@ func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) {
|
|||
return nil
|
||||
}
|
||||
|
||||
feat, err := parseFeatureFile(p, newIDFunc)
|
||||
feat, err := parseFeatureFile(p)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -670,36 +724,39 @@ func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) {
|
|||
|
||||
func parsePath(path string) ([]*feature, error) {
|
||||
var features []*feature
|
||||
|
||||
path, line := extractFeaturePathLine(path)
|
||||
// check if line number is specified
|
||||
var line int
|
||||
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, newIDFunc)
|
||||
return parseFeatureDir(path)
|
||||
}
|
||||
|
||||
ft, err := parseFeatureFile(path, newIDFunc)
|
||||
ft, err := parseFeatureFile(path)
|
||||
if err != nil {
|
||||
return features, err
|
||||
}
|
||||
|
||||
// filter scenario by line number
|
||||
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)
|
||||
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)
|
||||
}
|
||||
}
|
||||
ft.pickles = pickles
|
||||
|
||||
ft.ScenarioDefinitions = scenarios
|
||||
return append(features, ft), nil
|
||||
}
|
||||
|
||||
|
@ -727,7 +784,6 @@ func parseFeatures(filter string, paths []string) ([]*feature, error) {
|
|||
byPath[ft.Path] = ft
|
||||
}
|
||||
}
|
||||
|
||||
return filterFeatures(filter, byPath), nil
|
||||
}
|
||||
|
||||
|
@ -739,7 +795,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)
|
||||
applyTagFilter(tags, ft.Feature)
|
||||
features = append(features, ft)
|
||||
}
|
||||
|
||||
|
@ -748,23 +804,81 @@ func filterFeatures(tags string, collected map[string]*feature) (features []*fea
|
|||
return features
|
||||
}
|
||||
|
||||
func applyTagFilter(tags string, ft *feature) {
|
||||
func applyTagFilter(tags string, ft *gherkin.Feature) {
|
||||
if len(tags) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
var pickles []*messages.Pickle
|
||||
for _, pickle := range ft.pickles {
|
||||
if matchesTags(tags, pickle.Tags) {
|
||||
pickles = append(pickles, pickle)
|
||||
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)
|
||||
}
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
ft.pickles = pickles
|
||||
for _, gtag := range gr {
|
||||
tag := strings.TrimSpace(gtag.Name)
|
||||
if tag[0] == '@' {
|
||||
tag = tag[1:]
|
||||
}
|
||||
copy(tmp, tags)
|
||||
var found bool
|
||||
for _, tg := range tmp {
|
||||
if tg == tag {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
tags = append(tags, tag)
|
||||
}
|
||||
}
|
||||
}
|
||||
return tags
|
||||
}
|
||||
|
||||
func hasTag(tags []string, tag string) bool {
|
||||
for _, t := range tags {
|
||||
if t == tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// based on http://behat.readthedocs.org/en/v2.5/guides/6.cli.html#gherkin-filters
|
||||
func matchesTags(filter string, tags []*messages.Pickle_PickleTag) (ok bool) {
|
||||
func matchesTags(filter string, tags []string) (ok bool) {
|
||||
ok = true
|
||||
for _, andTags := range strings.Split(filter, "&&") {
|
||||
var okComma bool
|
||||
|
@ -781,14 +895,3 @@ func matchesTags(filter string, tags []*messages.Pickle_PickleTag) (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,10 +12,8 @@ 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
|
||||
|
@ -114,7 +112,7 @@ type suiteContext struct {
|
|||
out bytes.Buffer
|
||||
}
|
||||
|
||||
func (s *suiteContext) ResetBeforeEachScenario(*messages.Pickle) {
|
||||
func (s *suiteContext) ResetBeforeEachScenario(interface{}) {
|
||||
// reset whole suite with the state
|
||||
s.out.Reset()
|
||||
s.paths = []string{}
|
||||
|
@ -130,7 +128,7 @@ func (s *suiteContext) iRunFeatureSuiteWithTags(tags string) error {
|
|||
return err
|
||||
}
|
||||
for _, feat := range s.testedSuite.features {
|
||||
applyTagFilter(tags, feat)
|
||||
applyTagFilter(tags, feat.Feature)
|
||||
}
|
||||
s.testedSuite.fmt = testFormatterFunc("godog", &s.out)
|
||||
s.testedSuite.run()
|
||||
|
@ -153,7 +151,7 @@ func (s *suiteContext) iRunFeatureSuiteWithFormatter(name string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteContext) thereShouldBeEventsFired(doc *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) thereShouldBeEventsFired(doc *gherkin.DocString) error {
|
||||
actual := strings.Split(strings.TrimSpace(s.out.String()), "\n")
|
||||
expect := strings.Split(strings.TrimSpace(doc.Content), "\n")
|
||||
if len(expect) != len(actual) {
|
||||
|
@ -186,7 +184,7 @@ func (s *suiteContext) cleanupSnippet(snip string) string {
|
|||
return strings.Join(lines, "\n")
|
||||
}
|
||||
|
||||
func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *gherkin.DocString) error {
|
||||
f, ok := s.testedSuite.fmt.(*testFormatter)
|
||||
if !ok {
|
||||
return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
|
||||
|
@ -199,7 +197,7 @@ func (s *suiteContext) theUndefinedStepSnippetsShouldBe(body *messages.PickleSte
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteContext) followingStepsShouldHave(status string, steps *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) followingStepsShouldHave(status string, steps *gherkin.DocString) error {
|
||||
var expected = strings.Split(steps.Content, "\n")
|
||||
var actual, unmatched, matched []string
|
||||
|
||||
|
@ -209,23 +207,23 @@ func (s *suiteContext) followingStepsShouldHave(status string, steps *messages.P
|
|||
}
|
||||
switch status {
|
||||
case "passed":
|
||||
for _, st := range f.findStepResults(passed) {
|
||||
for _, st := range f.passed {
|
||||
actual = append(actual, st.step.Text)
|
||||
}
|
||||
case "failed":
|
||||
for _, st := range f.findStepResults(failed) {
|
||||
for _, st := range f.failed {
|
||||
actual = append(actual, st.step.Text)
|
||||
}
|
||||
case "skipped":
|
||||
for _, st := range f.findStepResults(skipped) {
|
||||
for _, st := range f.skipped {
|
||||
actual = append(actual, st.step.Text)
|
||||
}
|
||||
case "undefined":
|
||||
for _, st := range f.findStepResults(undefined) {
|
||||
for _, st := range f.undefined {
|
||||
actual = append(actual, st.step.Text)
|
||||
}
|
||||
case "pending":
|
||||
for _, st := range f.findStepResults(pending) {
|
||||
for _, st := range f.pending {
|
||||
actual = append(actual, st.step.Text)
|
||||
}
|
||||
default:
|
||||
|
@ -270,23 +268,19 @@ func (s *suiteContext) allStepsShouldHave(status string) error {
|
|||
return fmt.Errorf("this step requires testFormatter, but there is: %T", s.testedSuite.fmt)
|
||||
}
|
||||
|
||||
total := len(f.findStepResults(passed)) +
|
||||
len(f.findStepResults(failed)) +
|
||||
len(f.findStepResults(skipped)) +
|
||||
len(f.findStepResults(undefined)) +
|
||||
len(f.findStepResults(pending))
|
||||
total := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined) + len(f.pending)
|
||||
var actual int
|
||||
switch status {
|
||||
case "passed":
|
||||
actual = len(f.findStepResults(passed))
|
||||
actual = len(f.passed)
|
||||
case "failed":
|
||||
actual = len(f.findStepResults(failed))
|
||||
actual = len(f.failed)
|
||||
case "skipped":
|
||||
actual = len(f.findStepResults(skipped))
|
||||
actual = len(f.skipped)
|
||||
case "undefined":
|
||||
actual = len(f.findStepResults(undefined))
|
||||
actual = len(f.undefined)
|
||||
case "pending":
|
||||
actual = len(f.findStepResults(pending))
|
||||
actual = len(f.pending)
|
||||
default:
|
||||
return fmt.Errorf("unexpected step status wanted: %s", status)
|
||||
}
|
||||
|
@ -304,22 +298,22 @@ func (s *suiteContext) iAmListeningToSuiteEvents() error {
|
|||
s.testedSuite.AfterSuite(func() {
|
||||
s.events = append(s.events, &firedEvent{"AfterSuite", []interface{}{}})
|
||||
})
|
||||
s.testedSuite.BeforeFeature(func(ft *messages.GherkinDocument) {
|
||||
s.testedSuite.BeforeFeature(func(ft *gherkin.Feature) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeFeature", []interface{}{ft}})
|
||||
})
|
||||
s.testedSuite.AfterFeature(func(ft *messages.GherkinDocument) {
|
||||
s.testedSuite.AfterFeature(func(ft *gherkin.Feature) {
|
||||
s.events = append(s.events, &firedEvent{"AfterFeature", []interface{}{ft}})
|
||||
})
|
||||
s.testedSuite.BeforeScenario(func(pickle *messages.Pickle) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{pickle}})
|
||||
s.testedSuite.BeforeScenario(func(scenario interface{}) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeScenario", []interface{}{scenario}})
|
||||
})
|
||||
s.testedSuite.AfterScenario(func(pickle *messages.Pickle, err error) {
|
||||
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{pickle, err}})
|
||||
s.testedSuite.AfterScenario(func(scenario interface{}, err error) {
|
||||
s.events = append(s.events, &firedEvent{"AfterScenario", []interface{}{scenario, err}})
|
||||
})
|
||||
s.testedSuite.BeforeStep(func(step *messages.Pickle_PickleStep) {
|
||||
s.testedSuite.BeforeStep(func(step *gherkin.Step) {
|
||||
s.events = append(s.events, &firedEvent{"BeforeStep", []interface{}{step}})
|
||||
})
|
||||
s.testedSuite.AfterStep(func(step *messages.Pickle_PickleStep, err error) {
|
||||
s.testedSuite.AfterStep(func(step *gherkin.Step, err error) {
|
||||
s.events = append(s.events, &firedEvent{"AfterStep", []interface{}{step, err}})
|
||||
})
|
||||
return nil
|
||||
|
@ -330,10 +324,9 @@ func (s *suiteContext) aFailingStep() error {
|
|||
}
|
||||
|
||||
// parse a given feature file body as a feature
|
||||
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})
|
||||
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})
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -361,7 +354,7 @@ func (s *suiteContext) theSuiteShouldHave(state string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) iShouldHaveNumFeatureFiles(num int, files *gherkin.DocString) error {
|
||||
if len(s.testedSuite.features) != num {
|
||||
return fmt.Errorf("expected %d features to be parsed, but have %d", num, len(s.testedSuite.features))
|
||||
}
|
||||
|
@ -406,7 +399,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.pickles)
|
||||
num += len(ft.ScenarioDefinitions)
|
||||
}
|
||||
if num != expected {
|
||||
err = fmt.Errorf("expected %d scenarios to be registered, but got %d", expected, num)
|
||||
|
@ -436,7 +429,9 @@ func (s *suiteContext) thereWasEventTriggeredBeforeScenario(expected string) err
|
|||
|
||||
var name string
|
||||
switch t := event.args[0].(type) {
|
||||
case *messages.Pickle:
|
||||
case *gherkin.Scenario:
|
||||
name = t.Name
|
||||
case *gherkin.ScenarioOutline:
|
||||
name = t.Name
|
||||
}
|
||||
if name == expected {
|
||||
|
@ -453,7 +448,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 *messages.PickleStepArgument_PickleTable) error {
|
||||
func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *gherkin.DataTable) 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))
|
||||
}
|
||||
|
@ -470,7 +465,7 @@ func (s *suiteContext) theseEventsHadToBeFiredForNumberOfTimes(tbl *messages.Pic
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteContext) theRenderJSONWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) theRenderJSONWillBe(docstring *gherkin.DocString) error {
|
||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||
|
||||
expectedString := docstring.Content
|
||||
|
@ -494,7 +489,7 @@ func (s *suiteContext) theRenderJSONWillBe(docstring *messages.PickleStepArgumen
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteContext) theRenderOutputWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) theRenderOutputWillBe(docstring *gherkin.DocString) error {
|
||||
suiteCtxReg := regexp.MustCompile(`suite_context.go:\d+`)
|
||||
suiteCtxFuncReg := regexp.MustCompile(`github.com/cucumber/godog.SuiteContext.func(\d+)`)
|
||||
|
||||
|
@ -513,7 +508,7 @@ func (s *suiteContext) theRenderOutputWillBe(docstring *messages.PickleStepArgum
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *suiteContext) theRenderXMLWillBe(docstring *messages.PickleStepArgument_PickleDocString) error {
|
||||
func (s *suiteContext) theRenderXMLWillBe(docstring *gherkin.DocString) error {
|
||||
expectedString := docstring.Content
|
||||
actualString := s.out.String()
|
||||
|
||||
|
@ -528,23 +523,28 @@ func (s *suiteContext) theRenderXMLWillBe(docstring *messages.PickleStepArgument
|
|||
}
|
||||
|
||||
if !reflect.DeepEqual(expected, actual) {
|
||||
return fmt.Errorf("expected json does not match actual: %s", actualString)
|
||||
return fmt.Errorf("expected xml does not match actual: %s", actualString)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
type testFormatter struct {
|
||||
*basefmt
|
||||
pickles []*messages.Pickle
|
||||
scenarios []interface{}
|
||||
}
|
||||
|
||||
func testFormatterFunc(suite string, out io.Writer) Formatter {
|
||||
return &testFormatter{basefmt: newBaseFmt(suite, out)}
|
||||
}
|
||||
|
||||
func (f *testFormatter) Pickle(p *messages.Pickle) {
|
||||
f.basefmt.Pickle(p)
|
||||
f.pickles = append(f.pickles, p)
|
||||
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) Summary() {}
|
||||
|
|
|
@ -2,32 +2,28 @@ package godog
|
|||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/cucumber/messages-go/v9"
|
||||
)
|
||||
|
||||
func assertNotMatchesTagFilter(tags []*messages.Pickle_PickleTag, filter string, t *testing.T) {
|
||||
func assertNotMatchesTagFilter(tags []string, 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 []*messages.Pickle_PickleTag, filter string, t *testing.T) {
|
||||
func assertMatchesTagFilter(tags []string, 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([]*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)
|
||||
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)
|
||||
|
||||
assertNotMatchesTagFilter([]*tag{}, "@wip", t)
|
||||
assertNotMatchesTagFilter([]*tag{{Name: "one"}, {Name: "two"}}, "@one&&~@two", t)
|
||||
assertNotMatchesTagFilter([]*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)
|
||||
}
|
||||
|
||||
type tag = messages.Pickle_PickleTag
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче