Broke out some code from massive files into new files

Этот коммит содержится в:
Fredrik Lönnblad 2020-06-13 06:43:02 +02:00 коммит произвёл Fredrik Lönnblad
родитель a57f082852
коммит 25b1915272
9 изменённых файлов: 648 добавлений и 611 удалений

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

@ -0,0 +1,96 @@
package godog
import (
"time"
"github.com/cucumber/messages-go/v10"
)
type feature struct {
*messages.GherkinDocument
pickles []*messages.Pickle
time time.Time
content []byte
order int
}
type sortFeaturesByName []*feature
func (s sortFeaturesByName) Len() int { return len(s) }
func (s sortFeaturesByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name }
func (s sortFeaturesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type sortFeaturesByOrder []*feature
func (s sortFeaturesByOrder) Len() int { return len(s) }
func (s sortFeaturesByOrder) Less(i, j int) bool { return s[i].order < s[j].order }
func (s sortFeaturesByOrder) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
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
}

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

@ -1,20 +1,12 @@
package godog
import (
"bytes"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
"github.com/cucumber/messages-go/v10"
"github.com/cucumber/godog/colors"
)
type registeredFormatter struct {
@ -99,301 +91,27 @@ type storageFormatter interface {
// suite name and io.Writer to record output
type FormatterFunc func(string, io.Writer) Formatter
type stepResultStatus int
const (
passed stepResultStatus = iota
failed
skipped
undefined
pending
)
func (st stepResultStatus) clr() colors.ColorFunc {
switch st {
case passed:
return green
case failed:
return red
case skipped:
return cyan
default:
return yellow
}
}
func (st stepResultStatus) String() string {
switch st {
case passed:
return "passed"
case failed:
return "failed"
case skipped:
return "skipped"
case undefined:
return "undefined"
case pending:
return "pending"
default:
return "unknown"
}
}
type pickleStepResult struct {
Status stepResultStatus
finishedAt time.Time
err error
PickleID string
PickleStepID string
def *StepDefinition
}
func newStepResult(pickleID, pickleStepID string, match *StepDefinition) pickleStepResult {
return pickleStepResult{finishedAt: timeNowFunc(), PickleID: pickleID, PickleStepID: pickleStepID, def: match}
}
func newBaseFmt(suite string, out io.Writer) *basefmt {
return &basefmt{
suiteName: suite,
startedAt: timeNowFunc(),
indent: 2,
out: out,
lock: new(sync.Mutex),
}
}
type basefmt struct {
suiteName string
out io.Writer
owner interface{}
indent int
storage *storage
startedAt time.Time
firstFeature *bool
lock *sync.Mutex
}
func (f *basefmt) setStorage(st *storage) {
f.storage = st
}
func (f *basefmt) TestRunStarted() {
f.lock.Lock()
defer f.lock.Unlock()
firstFeature := true
f.firstFeature = &firstFeature
}
func (f *basefmt) Pickle(p *messages.Pickle) {}
func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {}
func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) {
f.lock.Lock()
defer f.lock.Unlock()
*f.firstFeature = false
}
func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
}
func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Summary() {
var totalSc, passedSc, undefinedSc int
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int
pickleResults := f.storage.mustGetPickleResults()
for _, pr := range pickleResults {
var prStatus stepResultStatus
totalSc++
pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pr.PickleID)
if len(pickleStepResults) == 0 {
prStatus = undefined
}
for _, sr := range pickleStepResults {
totalSt++
switch sr.Status {
case passed:
prStatus = passed
passedSt++
case failed:
prStatus = failed
failedSt++
case skipped:
skippedSt++
case undefined:
prStatus = undefined
undefinedSt++
case pending:
prStatus = pending
pendingSt++
}
}
if prStatus == passed {
passedSc++
} else if prStatus == undefined {
undefinedSc++
}
}
var steps, parts, scenarios []string
if passedSt > 0 {
steps = append(steps, green(fmt.Sprintf("%d passed", passedSt)))
}
if failedSt > 0 {
parts = append(parts, red(fmt.Sprintf("%d failed", failedSt)))
steps = append(steps, red(fmt.Sprintf("%d failed", failedSt)))
}
if pendingSt > 0 {
parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt)))
steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt)))
}
if undefinedSt > 0 {
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt)))
} else if undefinedSc > 0 {
// there may be some scenarios without steps
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
}
if skippedSt > 0 {
steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt)))
}
if passedSc > 0 {
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc)))
}
scenarios = append(scenarios, parts...)
elapsed := timeNowFunc().Sub(f.startedAt)
fmt.Fprintln(f.out, "")
if totalSc == 0 {
fmt.Fprintln(f.out, "No scenarios")
} else {
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", totalSc, strings.Join(scenarios, ", ")))
}
if totalSt == 0 {
fmt.Fprintln(f.out, "No steps")
} else {
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", totalSt, strings.Join(steps, ", ")))
}
elapsedString := elapsed.String()
if elapsed.Nanoseconds() == 0 {
// go 1.5 and 1.6 prints 0 instead of 0s, if duration is zero.
elapsedString = "0s"
}
fmt.Fprintln(f.out, elapsedString)
// prints used randomization seed
seed, err := strconv.ParseInt(os.Getenv("GODOG_SEED"), 10, 64)
if err == nil && seed != 0 {
fmt.Fprintln(f.out, "")
fmt.Fprintln(f.out, "Randomized with seed:", colors.Yellow(seed))
}
if text := f.snippets(); text != "" {
fmt.Fprintln(f.out, "")
fmt.Fprintln(f.out, yellow("You can implement step definitions for undefined steps with these snippets:"))
fmt.Fprintln(f.out, yellow(text))
}
}
func (f *basefmt) Sync(cf ConcurrentFormatter) {
if source, ok := cf.(*basefmt); ok {
f.lock = source.lock
f.firstFeature = source.firstFeature
}
}
func (f *basefmt) Copy(cf ConcurrentFormatter) {}
func (f *basefmt) snippets() string {
undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined)
if len(undefinedStepResults) == 0 {
return ""
}
var index int
var snips []undefinedSnippet
// build snippets
for _, u := range undefinedStepResults {
pickleStep := f.storage.mustGetPickleStep(u.PickleStepID)
steps := []string{pickleStep.Text}
arg := pickleStep.Argument
if u.def != nil {
steps = u.def.undefined
arg = nil
}
for _, step := range steps {
expr := snippetExprCleanup.ReplaceAllString(step, "\\$1")
expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)")
expr = snippetExprQuoted.ReplaceAllString(expr, "$1\"([^\"]*)\"$2")
expr = "^" + strings.TrimSpace(expr) + "$"
name := snippetNumbers.ReplaceAllString(step, " ")
name = snippetExprQuoted.ReplaceAllString(name, " ")
name = strings.TrimSpace(snippetMethodName.ReplaceAllString(name, ""))
var words []string
for i, w := range strings.Split(name, " ") {
switch {
case i != 0:
w = strings.Title(w)
case len(w) > 0:
w = string(unicode.ToLower(rune(w[0]))) + w[1:]
}
words = append(words, w)
}
name = strings.Join(words, "")
if len(name) == 0 {
index++
name = fmt.Sprintf("StepDefinitioninition%d", index)
}
var found bool
for _, snip := range snips {
if snip.Expr == expr {
found = true
break
}
}
if !found {
snips = append(snips, undefinedSnippet{Method: name, Expr: expr, argument: arg})
}
}
}
sort.Sort(snippetSortByMethod(snips))
var buf bytes.Buffer
if err := undefinedSnippetsTpl.Execute(&buf, snips); err != nil {
panic(err)
}
// there may be trailing spaces
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 printStepDefinitions(steps []*StepDefinition, w io.Writer) {
var longest int
for _, def := range steps {
n := utf8.RuneCountInString(def.Expr.String())
if longest < n {
longest = n
}
}
for _, def := range steps {
n := utf8.RuneCountInString(def.Expr.String())
location := def.definitionID()
spaces := strings.Repeat(" ", longest-n)
fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location))
}
if len(steps) == 0 {
fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..")
}
}

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

@ -0,0 +1,258 @@
package godog
import (
"bytes"
"fmt"
"io"
"os"
"sort"
"strconv"
"strings"
"sync"
"time"
"unicode"
"github.com/cucumber/messages-go/v10"
"github.com/cucumber/godog/colors"
)
func newBaseFmt(suite string, out io.Writer) *basefmt {
return &basefmt{
suiteName: suite,
startedAt: timeNowFunc(),
indent: 2,
out: out,
lock: new(sync.Mutex),
}
}
type basefmt struct {
suiteName string
out io.Writer
owner interface{}
indent int
storage *storage
startedAt time.Time
firstFeature *bool
lock *sync.Mutex
}
func (f *basefmt) setStorage(st *storage) {
f.storage = st
}
func (f *basefmt) TestRunStarted() {
f.lock.Lock()
defer f.lock.Unlock()
firstFeature := true
f.firstFeature = &firstFeature
}
func (f *basefmt) Pickle(p *messages.Pickle) {}
func (f *basefmt) Defined(*messages.Pickle, *messages.Pickle_PickleStep, *StepDefinition) {}
func (f *basefmt) Feature(ft *messages.GherkinDocument, p string, c []byte) {
f.lock.Lock()
defer f.lock.Unlock()
*f.firstFeature = false
}
func (f *basefmt) Passed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Skipped(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Undefined(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Failed(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition, err error) {
}
func (f *basefmt) Pending(pickle *messages.Pickle, step *messages.Pickle_PickleStep, match *StepDefinition) {
}
func (f *basefmt) Summary() {
var totalSc, passedSc, undefinedSc int
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int
pickleResults := f.storage.mustGetPickleResults()
for _, pr := range pickleResults {
var prStatus stepResultStatus
totalSc++
pickleStepResults := f.storage.mustGetPickleStepResultsByPickleID(pr.PickleID)
if len(pickleStepResults) == 0 {
prStatus = undefined
}
for _, sr := range pickleStepResults {
totalSt++
switch sr.Status {
case passed:
prStatus = passed
passedSt++
case failed:
prStatus = failed
failedSt++
case skipped:
skippedSt++
case undefined:
prStatus = undefined
undefinedSt++
case pending:
prStatus = pending
pendingSt++
}
}
if prStatus == passed {
passedSc++
} else if prStatus == undefined {
undefinedSc++
}
}
var steps, parts, scenarios []string
if passedSt > 0 {
steps = append(steps, green(fmt.Sprintf("%d passed", passedSt)))
}
if failedSt > 0 {
parts = append(parts, red(fmt.Sprintf("%d failed", failedSt)))
steps = append(steps, red(fmt.Sprintf("%d failed", failedSt)))
}
if pendingSt > 0 {
parts = append(parts, yellow(fmt.Sprintf("%d pending", pendingSt)))
steps = append(steps, yellow(fmt.Sprintf("%d pending", pendingSt)))
}
if undefinedSt > 0 {
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
steps = append(steps, yellow(fmt.Sprintf("%d undefined", undefinedSt)))
} else if undefinedSc > 0 {
// there may be some scenarios without steps
parts = append(parts, yellow(fmt.Sprintf("%d undefined", undefinedSc)))
}
if skippedSt > 0 {
steps = append(steps, cyan(fmt.Sprintf("%d skipped", skippedSt)))
}
if passedSc > 0 {
scenarios = append(scenarios, green(fmt.Sprintf("%d passed", passedSc)))
}
scenarios = append(scenarios, parts...)
elapsed := timeNowFunc().Sub(f.startedAt)
fmt.Fprintln(f.out, "")
if totalSc == 0 {
fmt.Fprintln(f.out, "No scenarios")
} else {
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", totalSc, strings.Join(scenarios, ", ")))
}
if totalSt == 0 {
fmt.Fprintln(f.out, "No steps")
} else {
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", totalSt, strings.Join(steps, ", ")))
}
elapsedString := elapsed.String()
if elapsed.Nanoseconds() == 0 {
// go 1.5 and 1.6 prints 0 instead of 0s, if duration is zero.
elapsedString = "0s"
}
fmt.Fprintln(f.out, elapsedString)
// prints used randomization seed
seed, err := strconv.ParseInt(os.Getenv("GODOG_SEED"), 10, 64)
if err == nil && seed != 0 {
fmt.Fprintln(f.out, "")
fmt.Fprintln(f.out, "Randomized with seed:", colors.Yellow(seed))
}
if text := f.snippets(); text != "" {
fmt.Fprintln(f.out, "")
fmt.Fprintln(f.out, yellow("You can implement step definitions for undefined steps with these snippets:"))
fmt.Fprintln(f.out, yellow(text))
}
}
func (f *basefmt) Sync(cf ConcurrentFormatter) {
if source, ok := cf.(*basefmt); ok {
f.lock = source.lock
f.firstFeature = source.firstFeature
}
}
func (f *basefmt) Copy(cf ConcurrentFormatter) {}
func (f *basefmt) snippets() string {
undefinedStepResults := f.storage.mustGetPickleStepResultsByStatus(undefined)
if len(undefinedStepResults) == 0 {
return ""
}
var index int
var snips []undefinedSnippet
// build snippets
for _, u := range undefinedStepResults {
pickleStep := f.storage.mustGetPickleStep(u.PickleStepID)
steps := []string{pickleStep.Text}
arg := pickleStep.Argument
if u.def != nil {
steps = u.def.undefined
arg = nil
}
for _, step := range steps {
expr := snippetExprCleanup.ReplaceAllString(step, "\\$1")
expr = snippetNumbers.ReplaceAllString(expr, "(\\d+)")
expr = snippetExprQuoted.ReplaceAllString(expr, "$1\"([^\"]*)\"$2")
expr = "^" + strings.TrimSpace(expr) + "$"
name := snippetNumbers.ReplaceAllString(step, " ")
name = snippetExprQuoted.ReplaceAllString(name, " ")
name = strings.TrimSpace(snippetMethodName.ReplaceAllString(name, ""))
var words []string
for i, w := range strings.Split(name, " ") {
switch {
case i != 0:
w = strings.Title(w)
case len(w) > 0:
w = string(unicode.ToLower(rune(w[0]))) + w[1:]
}
words = append(words, w)
}
name = strings.Join(words, "")
if len(name) == 0 {
index++
name = fmt.Sprintf("StepDefinitioninition%d", index)
}
var found bool
for _, snip := range snips {
if snip.Expr == expr {
found = true
break
}
}
if !found {
snips = append(snips, undefinedSnippet{Method: name, Expr: expr, argument: arg})
}
}
}
sort.Sort(snippetSortByMethod(snips))
var buf bytes.Buffer
if err := undefinedSnippetsTpl.Execute(&buf, snips); err != nil {
panic(err)
}
// there may be trailing spaces
return strings.Replace(buf.String(), " \n", "\n", -1)
}

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

@ -0,0 +1,169 @@
package godog
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"regexp"
"sort"
"strconv"
"strings"
"github.com/cucumber/gherkin-go/v11"
"github.com/cucumber/messages-go/v10"
)
var pathLineRe = regexp.MustCompile(`:([\d]+)$`)
func extractFeaturePathLine(p string) (string, int) {
line := -1
retPath := p
if m := pathLineRe.FindStringSubmatch(p); len(m) > 0 {
if i, err := strconv.Atoi(m[1]); err == nil {
line = i
retPath = p[:strings.LastIndexByte(p, ':')]
}
}
return retPath, line
}
func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) {
reader, err := os.Open(path)
if err != nil {
return nil, err
}
defer reader.Close()
var buf bytes.Buffer
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
if err != nil {
return nil, fmt.Errorf("%s - %v", path, err)
}
gherkinDocument.Uri = path
pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc)
f := feature{GherkinDocument: gherkinDocument, pickles: pickles, content: buf.Bytes()}
return &f, nil
}
func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) {
var features []*feature
return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if !strings.HasSuffix(p, ".feature") {
return nil
}
feat, err := parseFeatureFile(p, newIDFunc)
if err != nil {
return err
}
features = append(features, feat)
return nil
})
}
func parsePath(path string) ([]*feature, error) {
var features []*feature
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)
}
ft, err := parseFeatureFile(path, newIDFunc)
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)
}
}
ft.pickles = pickles
return append(features, ft), nil
}
func parseFeatures(filter string, paths []string) ([]*feature, error) {
var order int
uniqueFeatureURI := make(map[string]*feature)
for _, path := range paths {
feats, err := parsePath(path)
switch {
case os.IsNotExist(err):
return nil, fmt.Errorf(`feature path "%s" is not available`, path)
case os.IsPermission(err):
return nil, fmt.Errorf(`feature path "%s" is not accessible`, path)
case err != nil:
return nil, err
}
for _, ft := range feats {
if _, duplicate := uniqueFeatureURI[ft.Uri]; duplicate {
continue
}
ft.order = order
order++
uniqueFeatureURI[ft.Uri] = ft
}
}
return filterFeatures(filter, uniqueFeatureURI), nil
}
func filterFeatures(tags string, collected map[string]*feature) (features []*feature) {
for _, ft := range collected {
ft.pickles = applyTagFilter(tags, ft.pickles)
if ft.Feature != nil {
features = append(features, ft)
}
}
sort.Sort(sortFeaturesByOrder(features))
return features
}
func applyTagFilter(tags string, pickles []*messages.Pickle) (result []*messages.Pickle) {
if len(tags) == 0 {
return pickles
}
for _, pickle := range pickles {
if matchesTags(tags, pickle.Tags) {
result = append(result, pickle)
}
}
return
}

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

@ -0,0 +1,77 @@
package godog
import (
"time"
"github.com/cucumber/godog/colors"
)
type pickleResult struct {
PickleID string
StartedAt time.Time
}
type pickleStepResult struct {
Status stepResultStatus
finishedAt time.Time
err error
PickleID string
PickleStepID string
def *StepDefinition
}
func newStepResult(pickleID, pickleStepID string, match *StepDefinition) pickleStepResult {
return pickleStepResult{finishedAt: timeNowFunc(), PickleID: pickleID, PickleStepID: pickleStepID, def: match}
}
type sortPickleStepResultsByPickleStepID []pickleStepResult
func (s sortPickleStepResultsByPickleStepID) Len() int { return len(s) }
func (s sortPickleStepResultsByPickleStepID) Less(i, j int) bool {
iID := mustConvertStringToInt(s[i].PickleStepID)
jID := mustConvertStringToInt(s[j].PickleStepID)
return iID < jID
}
func (s sortPickleStepResultsByPickleStepID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type stepResultStatus int
const (
passed stepResultStatus = iota
failed
skipped
undefined
pending
)
func (st stepResultStatus) clr() colors.ColorFunc {
switch st {
case passed:
return green
case failed:
return red
case skipped:
return cyan
default:
return yellow
}
}
func (st stepResultStatus) String() string {
switch st {
case passed:
return "passed"
case failed:
return "failed"
case skipped:
return "skipped"
case undefined:
return "undefined"
case pending:
return "pending"
default:
return "unknown"
}
}

2
run.go
Просмотреть файл

@ -199,7 +199,7 @@ func runWithOptions(suite string, runner runner, opt Options) int {
if opt.ShowStepDefinitions {
s := &Suite{}
runner.initializer(s)
s.printStepDefinitions(output)
printStepDefinitions(s.steps, output)
return exitOptionError
}

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

@ -34,7 +34,8 @@ func TestPrintsStepDefinitions(t *testing.T) {
for _, step := range steps {
s.Step(step, okStep)
}
s.printStepDefinitions(w)
printStepDefinitions(s.steps, w)
out := buf.String()
ref := `okStep`
@ -52,7 +53,8 @@ func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) {
var buf bytes.Buffer
w := colors.Uncolored(&buf)
s := &Suite{}
s.printStepDefinitions(w)
printStepDefinitions(s.steps, w)
out := strings.TrimSpace(buf.String())
assert.Equal(t, "there were no contexts registered, could not find any step definition..", out)

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

@ -1,143 +1,17 @@
package godog
import (
"bytes"
"fmt"
"io"
"os"
"path/filepath"
"reflect"
"regexp"
"sort"
"strconv"
"strings"
"time"
"unicode/utf8"
"github.com/cucumber/gherkin-go/v11"
"github.com/cucumber/messages-go/v10"
)
var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
var typeOfBytes = reflect.TypeOf([]byte(nil))
type feature struct {
*messages.GherkinDocument
pickles []*messages.Pickle
time time.Time
content []byte
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
}
type sortFeaturesByName []*feature
func (s sortFeaturesByName) Len() int { return len(s) }
func (s sortFeaturesByName) Less(i, j int) bool { return s[i].Feature.Name < s[j].Feature.Name }
func (s sortFeaturesByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type sortPicklesByID []*messages.Pickle
func (s sortPicklesByID) Len() int { return len(s) }
func (s sortPicklesByID) Less(i, j int) bool {
iID := mustConvertStringToInt(s[i].Id)
jID := mustConvertStringToInt(s[j].Id)
return iID < jID
}
func (s sortPicklesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
type sortPickleStepResultsByPickleStepID []pickleStepResult
func (s sortPickleStepResultsByPickleStepID) Len() int { return len(s) }
func (s sortPickleStepResultsByPickleStepID) Less(i, j int) bool {
iID := mustConvertStringToInt(s[i].PickleStepID)
jID := mustConvertStringToInt(s[j].PickleStepID)
return iID < jID
}
func (s sortPickleStepResultsByPickleStepID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func mustConvertStringToInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}
type pickleResult struct {
PickleID string
StartedAt time.Time
}
// ErrUndefined is returned in case if step definition was not found
var ErrUndefined = fmt.Errorf("step is undefined")
@ -654,181 +528,3 @@ func (s *Suite) runPickle(pickle *messages.Pickle) (err error) {
return
}
func (s *Suite) printStepDefinitions(w io.Writer) {
var longest int
for _, def := range s.steps {
n := utf8.RuneCountInString(def.Expr.String())
if longest < n {
longest = n
}
}
for _, def := range s.steps {
n := utf8.RuneCountInString(def.Expr.String())
location := def.definitionID()
spaces := strings.Repeat(" ", longest-n)
fmt.Fprintln(w, yellow(def.Expr.String())+spaces, blackb("# "+location))
}
if len(s.steps) == 0 {
fmt.Fprintln(w, "there were no contexts registered, could not find any step definition..")
}
}
var pathLineRe = regexp.MustCompile(`:([\d]+)$`)
func extractFeaturePathLine(p string) (string, int) {
line := -1
retPath := p
if m := pathLineRe.FindStringSubmatch(p); len(m) > 0 {
if i, err := strconv.Atoi(m[1]); err == nil {
line = i
retPath = p[:strings.LastIndexByte(p, ':')]
}
}
return retPath, line
}
func parseFeatureFile(path string, newIDFunc func() string) (*feature, error) {
reader, err := os.Open(path)
if err != nil {
return nil, err
}
defer reader.Close()
var buf bytes.Buffer
gherkinDocument, err := gherkin.ParseGherkinDocument(io.TeeReader(reader, &buf), newIDFunc)
if err != nil {
return nil, fmt.Errorf("%s - %v", path, err)
}
gherkinDocument.Uri = path
pickles := gherkin.Pickles(*gherkinDocument, path, newIDFunc)
f := feature{GherkinDocument: gherkinDocument, pickles: pickles, content: buf.Bytes()}
return &f, nil
}
func parseFeatureDir(dir string, newIDFunc func() string) ([]*feature, error) {
var features []*feature
return features, filepath.Walk(dir, func(p string, f os.FileInfo, err error) error {
if err != nil {
return err
}
if f.IsDir() {
return nil
}
if !strings.HasSuffix(p, ".feature") {
return nil
}
feat, err := parseFeatureFile(p, newIDFunc)
if err != nil {
return err
}
features = append(features, feat)
return nil
})
}
func parsePath(path string) ([]*feature, error) {
var features []*feature
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)
}
ft, err := parseFeatureFile(path, newIDFunc)
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)
}
}
ft.pickles = pickles
return append(features, ft), nil
}
func parseFeatures(filter string, paths []string) ([]*feature, error) {
var order int
uniqueFeatureURI := make(map[string]*feature)
for _, path := range paths {
feats, err := parsePath(path)
switch {
case os.IsNotExist(err):
return nil, fmt.Errorf(`feature path "%s" is not available`, path)
case os.IsPermission(err):
return nil, fmt.Errorf(`feature path "%s" is not accessible`, path)
case err != nil:
return nil, err
}
for _, ft := range feats {
if _, duplicate := uniqueFeatureURI[ft.Uri]; duplicate {
continue
}
ft.order = order
order++
uniqueFeatureURI[ft.Uri] = ft
}
}
return filterFeatures(filter, uniqueFeatureURI), nil
}
type sortFeaturesByOrder []*feature
func (s sortFeaturesByOrder) Len() int { return len(s) }
func (s sortFeaturesByOrder) Less(i, j int) bool { return s[i].order < s[j].order }
func (s sortFeaturesByOrder) 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 {
ft.pickles = applyTagFilter(tags, ft.pickles)
if ft.Feature != nil {
features = append(features, ft)
}
}
sort.Sort(sortFeaturesByOrder(features))
return features
}
func applyTagFilter(tags string, pickles []*messages.Pickle) (result []*messages.Pickle) {
if len(tags) == 0 {
return pickles
}
for _, pickle := range pickles {
if matchesTags(tags, pickle.Tags) {
result = append(result, pickle)
}
}
return
}

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

@ -1,10 +1,12 @@
package godog
import (
"strconv"
"strings"
"time"
"github.com/cucumber/godog/colors"
"github.com/cucumber/messages-go/v10"
)
var (
@ -37,3 +39,22 @@ func trimAllLines(s string) string {
}
return strings.Join(lines, "\n")
}
type sortPicklesByID []*messages.Pickle
func (s sortPicklesByID) Len() int { return len(s) }
func (s sortPicklesByID) Less(i, j int) bool {
iID := mustConvertStringToInt(s[i].Id)
jID := mustConvertStringToInt(s[j].Id)
return iID < jID
}
func (s sortPicklesByID) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
func mustConvertStringToInt(s string) int {
i, err := strconv.Atoi(s)
if err != nil {
panic(err)
}
return i
}