320 строки
7,9 КиБ
Go
320 строки
7,9 КиБ
Go
package formatters
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"io"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"unicode"
|
|
|
|
"github.com/cucumber/messages-go/v16"
|
|
|
|
"github.com/cucumber/godog/colors"
|
|
"github.com/cucumber/godog/formatters"
|
|
"github.com/cucumber/godog/internal/models"
|
|
"github.com/cucumber/godog/internal/storage"
|
|
"github.com/cucumber/godog/internal/utils"
|
|
)
|
|
|
|
// BaseFormatterFunc implements the FormatterFunc for the base formatter.
|
|
func BaseFormatterFunc(suite string, out io.Writer) formatters.Formatter {
|
|
return NewBase(suite, out)
|
|
}
|
|
|
|
// NewBase creates a new base formatter.
|
|
func NewBase(suite string, out io.Writer) *Base {
|
|
return &Base{
|
|
suiteName: suite,
|
|
indent: 2,
|
|
out: out,
|
|
Lock: new(sync.Mutex),
|
|
}
|
|
}
|
|
|
|
// Base is a base formatter.
|
|
type Base struct {
|
|
suiteName string
|
|
out io.Writer
|
|
indent int
|
|
|
|
Storage *storage.Storage
|
|
Lock *sync.Mutex
|
|
}
|
|
|
|
// SetStorage assigns gherkin data storage.
|
|
func (f *Base) SetStorage(st *storage.Storage) {
|
|
f.Lock.Lock()
|
|
defer f.Lock.Unlock()
|
|
|
|
f.Storage = st
|
|
}
|
|
|
|
// TestRunStarted is triggered on test start.
|
|
func (f *Base) TestRunStarted() {}
|
|
|
|
// Feature receives gherkin document.
|
|
func (f *Base) Feature(*messages.GherkinDocument, string, []byte) {}
|
|
|
|
// Pickle receives scenario.
|
|
func (f *Base) Pickle(*messages.Pickle) {}
|
|
|
|
// Defined receives step definition.
|
|
func (f *Base) Defined(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {
|
|
}
|
|
|
|
// Passed captures passed step.
|
|
func (f *Base) Passed(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {}
|
|
|
|
// Skipped captures skipped step.
|
|
func (f *Base) Skipped(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {
|
|
}
|
|
|
|
// Undefined captures undefined step.
|
|
func (f *Base) Undefined(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {
|
|
}
|
|
|
|
// Failed captures failed step.
|
|
func (f *Base) Failed(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition, error) {
|
|
}
|
|
|
|
// Pending captures pending step.
|
|
func (f *Base) Pending(*messages.Pickle, *messages.PickleStep, *formatters.StepDefinition) {
|
|
}
|
|
|
|
// Summary renders summary information.
|
|
func (f *Base) Summary() {
|
|
var totalSc, passedSc, undefinedSc int
|
|
var totalSt, passedSt, failedSt, skippedSt, pendingSt, undefinedSt int
|
|
|
|
pickleResults := f.Storage.MustGetPickleResults()
|
|
for _, pr := range pickleResults {
|
|
var prStatus models.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...)
|
|
|
|
testRunStartedAt := f.Storage.MustGetTestRunStarted().StartedAt
|
|
elapsed := utils.TimeNowFunc().Sub(testRunStartedAt)
|
|
|
|
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))
|
|
}
|
|
}
|
|
|
|
// Snippets returns code suggestions for undefined steps.
|
|
func (f *Base) 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:
|
|
r := []rune(w)
|
|
// tut
|
|
w = string(unicode.ToLower(r[0])) + string(r[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,
|
|
argument_names: f.get_argument_names(&u, pickleStep),
|
|
})
|
|
}
|
|
}
|
|
}
|
|
|
|
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 (f *Base) get_argument_names(undefResult *models.PickleStepResult, pickleStep *messages.PickleStep) (arg_names map[int]string) {
|
|
step := f.find_undef_step(undefResult, pickleStep)
|
|
if step == nil {
|
|
return
|
|
}
|
|
|
|
arg_names = map[int]string{}
|
|
|
|
args := snippetExprAnyParam.FindAllString(step.Text, -1)
|
|
for i, a := range args {
|
|
a = strings.TrimSpace(a)
|
|
|
|
if len(a) < 5 {
|
|
continue
|
|
}
|
|
|
|
parts := strings.Split(a, "\"")
|
|
if len(parts) < 3 {
|
|
continue
|
|
}
|
|
a = parts[1]
|
|
|
|
if a[0] != '<' {
|
|
continue
|
|
}
|
|
|
|
a = a[1 : len(a)-1]
|
|
|
|
// fmt.Printf("\ta: %v: %v\n", a, parts)
|
|
|
|
arg_names[i] = a
|
|
}
|
|
|
|
return
|
|
}
|
|
func (f *Base) find_undef_step(undefResult *models.PickleStepResult, pickleStep *messages.PickleStep) *messages.Step {
|
|
pickle := f.Storage.MustGetPickle(undefResult.PickleID)
|
|
if pickle == nil {
|
|
return nil
|
|
}
|
|
|
|
feat := f.Storage.MustGetFeature(pickle.Uri)
|
|
if feat == nil {
|
|
return nil
|
|
}
|
|
|
|
if len(pickleStep.AstNodeIds) == 0 {
|
|
return nil
|
|
}
|
|
|
|
step_id := pickleStep.AstNodeIds[0]
|
|
|
|
return feat.FindStep(step_id)
|
|
}
|