Broke out some code from massive files into new files
Этот коммит содержится в:
родитель
a57f082852
коммит
25b1915272
9 изменённых файлов: 648 добавлений и 611 удалений
96
feature.go
Обычный файл
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
326
fmt.go
|
@ -1,20 +1,12 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"os"
|
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"unicode/utf8"
|
||||||
"time"
|
|
||||||
"unicode"
|
|
||||||
|
|
||||||
"github.com/cucumber/messages-go/v10"
|
"github.com/cucumber/messages-go/v10"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type registeredFormatter struct {
|
type registeredFormatter struct {
|
||||||
|
@ -99,301 +91,27 @@ type storageFormatter interface {
|
||||||
// suite name and io.Writer to record output
|
// suite name and io.Writer to record output
|
||||||
type FormatterFunc func(string, io.Writer) Formatter
|
type FormatterFunc func(string, io.Writer) Formatter
|
||||||
|
|
||||||
type 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 {
|
func isLastStep(pickle *messages.Pickle, step *messages.Pickle_PickleStep) bool {
|
||||||
return pickle.Steps[len(pickle.Steps)-1].Id == step.Id
|
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
Обычный файл
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
Обычный файл
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
Обычный файл
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
2
run.go
|
@ -199,7 +199,7 @@ func runWithOptions(suite string, runner runner, opt Options) int {
|
||||||
if opt.ShowStepDefinitions {
|
if opt.ShowStepDefinitions {
|
||||||
s := &Suite{}
|
s := &Suite{}
|
||||||
runner.initializer(s)
|
runner.initializer(s)
|
||||||
s.printStepDefinitions(output)
|
printStepDefinitions(s.steps, output)
|
||||||
return exitOptionError
|
return exitOptionError
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -34,7 +34,8 @@ func TestPrintsStepDefinitions(t *testing.T) {
|
||||||
for _, step := range steps {
|
for _, step := range steps {
|
||||||
s.Step(step, okStep)
|
s.Step(step, okStep)
|
||||||
}
|
}
|
||||||
s.printStepDefinitions(w)
|
|
||||||
|
printStepDefinitions(s.steps, w)
|
||||||
|
|
||||||
out := buf.String()
|
out := buf.String()
|
||||||
ref := `okStep`
|
ref := `okStep`
|
||||||
|
@ -52,7 +53,8 @@ func TestPrintsNoStepDefinitionsIfNoneFound(t *testing.T) {
|
||||||
var buf bytes.Buffer
|
var buf bytes.Buffer
|
||||||
w := colors.Uncolored(&buf)
|
w := colors.Uncolored(&buf)
|
||||||
s := &Suite{}
|
s := &Suite{}
|
||||||
s.printStepDefinitions(w)
|
|
||||||
|
printStepDefinitions(s.steps, w)
|
||||||
|
|
||||||
out := strings.TrimSpace(buf.String())
|
out := strings.TrimSpace(buf.String())
|
||||||
assert.Equal(t, "there were no contexts registered, could not find any step definition..", out)
|
assert.Equal(t, "there were no contexts registered, could not find any step definition..", out)
|
||||||
|
|
304
suite.go
304
suite.go
|
@ -1,143 +1,17 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"reflect"
|
"reflect"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
|
||||||
"unicode/utf8"
|
|
||||||
|
|
||||||
"github.com/cucumber/gherkin-go/v11"
|
|
||||||
"github.com/cucumber/messages-go/v10"
|
"github.com/cucumber/messages-go/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
|
var errorInterface = reflect.TypeOf((*error)(nil)).Elem()
|
||||||
var typeOfBytes = reflect.TypeOf([]byte(nil))
|
var typeOfBytes = reflect.TypeOf([]byte(nil))
|
||||||
|
|
||||||
type feature struct {
|
|
||||||
*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
|
// ErrUndefined is returned in case if step definition was not found
|
||||||
var ErrUndefined = fmt.Errorf("step is undefined")
|
var ErrUndefined = fmt.Errorf("step is undefined")
|
||||||
|
|
||||||
|
@ -654,181 +528,3 @@ func (s *Suite) runPickle(pickle *messages.Pickle) (err error) {
|
||||||
|
|
||||||
return
|
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
|
|
||||||
}
|
|
||||||
|
|
21
utils.go
21
utils.go
|
@ -1,10 +1,12 @@
|
||||||
package godog
|
package godog
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/cucumber/godog/colors"
|
"github.com/cucumber/godog/colors"
|
||||||
|
"github.com/cucumber/messages-go/v10"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
@ -37,3 +39,22 @@ func trimAllLines(s string) string {
|
||||||
}
|
}
|
||||||
return strings.Join(lines, "\n")
|
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
|
||||||
|
}
|
||||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче