259 строки
6,4 КиБ
Go
259 строки
6,4 КиБ
Go
package godog
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"reflect"
|
|
"runtime"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/DATA-DOG/godog/gherkin"
|
|
)
|
|
|
|
func init() {
|
|
RegisterFormatter("pretty", "Prints every feature with runtime statuses.", &pretty{
|
|
started: time.Now(),
|
|
})
|
|
}
|
|
|
|
// a built in default pretty formatter
|
|
type pretty struct {
|
|
feature *gherkin.Feature
|
|
commentPos int
|
|
doneBackground bool
|
|
background *gherkin.Background
|
|
|
|
// summary
|
|
started time.Time
|
|
features []*gherkin.Feature
|
|
failed []*failed
|
|
passed []*passed
|
|
skipped []*skipped
|
|
undefined []*undefined
|
|
}
|
|
|
|
// a line number representation in feature file
|
|
func (f *pretty) line(tok *gherkin.Token) string {
|
|
return cl(fmt.Sprintf("# %s:%d", f.feature.Path, tok.Line), black)
|
|
}
|
|
|
|
// checks whether it should not print a background step once again
|
|
func (f *pretty) canPrintStep(step *gherkin.Step) bool {
|
|
if f.background == nil {
|
|
return true
|
|
}
|
|
|
|
if step.Background == nil {
|
|
f.doneBackground = true
|
|
return true
|
|
}
|
|
|
|
return !f.doneBackground
|
|
}
|
|
|
|
// Node takes a gherkin node for formatting
|
|
func (f *pretty) Node(node interface{}) {
|
|
switch t := node.(type) {
|
|
case *gherkin.Feature:
|
|
if f.feature != nil {
|
|
// not a first feature, add a newline
|
|
fmt.Println("")
|
|
}
|
|
f.feature = t
|
|
f.doneBackground = false
|
|
f.background = nil
|
|
f.features = append(f.features, t)
|
|
fmt.Println(bcl("Feature: ", white) + t.Title)
|
|
fmt.Println(t.Description)
|
|
case *gherkin.Background:
|
|
// determine comment position based on step length
|
|
f.commentPos = len(t.Token.Text)
|
|
for _, step := range t.Steps {
|
|
if len(step.Token.Text) > f.commentPos {
|
|
f.commentPos = len(step.Token.Text)
|
|
}
|
|
}
|
|
// do not repeat background
|
|
if !f.doneBackground {
|
|
f.background = t
|
|
fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white))
|
|
}
|
|
case *gherkin.Scenario:
|
|
// determine comment position based on step length
|
|
f.commentPos = len(t.Token.Text)
|
|
for _, step := range t.Steps {
|
|
if len(step.Token.Text) > f.commentPos {
|
|
f.commentPos = len(step.Token.Text)
|
|
}
|
|
}
|
|
text := s(t.Token.Indent) + bcl("Scenario: ", white) + t.Title
|
|
text += s(f.commentPos-len(t.Token.Text)+1) + f.line(t.Token)
|
|
fmt.Println("\n" + text)
|
|
}
|
|
}
|
|
|
|
// Summary sumarize the feature formatter output
|
|
func (f *pretty) Summary() {
|
|
// failed steps on background are not scenarios
|
|
var failedScenarios []*failed
|
|
for _, fail := range f.failed {
|
|
if fail.step.Scenario != nil {
|
|
failedScenarios = append(failedScenarios, fail)
|
|
}
|
|
}
|
|
if len(failedScenarios) > 0 {
|
|
fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n")
|
|
for _, fail := range failedScenarios {
|
|
fmt.Println(" " + cl(fail.line(), red))
|
|
}
|
|
}
|
|
var total, passed int
|
|
for _, ft := range f.features {
|
|
total += len(ft.Scenarios)
|
|
}
|
|
passed = total
|
|
|
|
var steps, parts, scenarios []string
|
|
nsteps := len(f.passed) + len(f.failed) + len(f.skipped) + len(f.undefined)
|
|
if len(f.passed) > 0 {
|
|
steps = append(steps, cl(fmt.Sprintf("%d passed", len(f.passed)), green))
|
|
}
|
|
if len(f.failed) > 0 {
|
|
passed -= len(f.failed)
|
|
parts = append(parts, cl(fmt.Sprintf("%d failed", len(f.failed)), red))
|
|
steps = append(steps, parts[len(parts)-1])
|
|
}
|
|
if len(f.skipped) > 0 {
|
|
steps = append(steps, cl(fmt.Sprintf("%d skipped", len(f.skipped)), cyan))
|
|
}
|
|
if len(f.undefined) > 0 {
|
|
passed -= len(f.undefined)
|
|
parts = append(parts, cl(fmt.Sprintf("%d undefined", len(f.undefined)), yellow))
|
|
steps = append(steps, parts[len(parts)-1])
|
|
}
|
|
if passed > 0 {
|
|
scenarios = append(scenarios, cl(fmt.Sprintf("%d passed", passed), green))
|
|
}
|
|
scenarios = append(scenarios, parts...)
|
|
elapsed := time.Since(f.started)
|
|
|
|
fmt.Println("")
|
|
if total == 0 {
|
|
fmt.Println("No scenarios")
|
|
} else {
|
|
fmt.Println(fmt.Sprintf("%d scenarios (%s)", total, strings.Join(scenarios, ", ")))
|
|
}
|
|
|
|
if nsteps == 0 {
|
|
fmt.Println("No steps")
|
|
} else {
|
|
fmt.Println(fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
|
}
|
|
fmt.Println(elapsed)
|
|
}
|
|
|
|
func (f *pretty) printStep(stepAction interface{}) {
|
|
var c color
|
|
var step *gherkin.Step
|
|
var h *stepMatchHandler
|
|
var err error
|
|
var suffix, prefix string
|
|
|
|
switch typ := stepAction.(type) {
|
|
case *passed:
|
|
step = typ.step
|
|
h = typ.handler
|
|
c = green
|
|
case *failed:
|
|
step = typ.step
|
|
h = typ.handler
|
|
err = typ.err
|
|
c = red
|
|
case *skipped:
|
|
step = typ.step
|
|
c = cyan
|
|
case *undefined:
|
|
step = typ.step
|
|
c = yellow
|
|
default:
|
|
fatal(fmt.Errorf("unexpected step type received: %T", typ))
|
|
}
|
|
|
|
if !f.canPrintStep(step) {
|
|
return
|
|
}
|
|
|
|
if h != nil {
|
|
if m := (h.expr.FindStringSubmatchIndex(step.Text))[2:]; len(m) > 0 {
|
|
var pos, i int
|
|
for pos, i = 0, 0; i < len(m); i++ {
|
|
if math.Mod(float64(i), 2) == 0 {
|
|
suffix += cl(step.Text[pos:m[i]], c)
|
|
} else {
|
|
suffix += bcl(step.Text[pos:m[i]], c)
|
|
}
|
|
pos = m[i]
|
|
}
|
|
suffix += cl(step.Text[pos:len(step.Text)], c)
|
|
} else {
|
|
suffix = cl(step.Text, c)
|
|
}
|
|
// use reflect to get step handler function name
|
|
name := runtime.FuncForPC(reflect.ValueOf(h.handler).Pointer()).Name()
|
|
suffix += s(f.commentPos-len(step.Token.Text)+1) + cl(fmt.Sprintf("# %s", name), black)
|
|
} else {
|
|
suffix = cl(step.Text, c)
|
|
}
|
|
|
|
prefix = s(step.Token.Indent)
|
|
switch step.Token.Type {
|
|
case gherkin.GIVEN:
|
|
prefix += cl("Given", c)
|
|
case gherkin.WHEN:
|
|
prefix += cl("When", c)
|
|
case gherkin.THEN:
|
|
prefix += cl("Then", c)
|
|
case gherkin.AND:
|
|
prefix += cl("And", c)
|
|
case gherkin.BUT:
|
|
prefix += cl("But", c)
|
|
}
|
|
fmt.Println(prefix, suffix)
|
|
if step.PyString != nil {
|
|
fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c))
|
|
fmt.Println(cl(step.PyString.Raw, c))
|
|
fmt.Println(s(step.Token.Indent+2) + cl(`"""`, c))
|
|
}
|
|
if err != nil {
|
|
fmt.Println(s(step.Token.Indent) + bcl(err, red))
|
|
}
|
|
}
|
|
|
|
// Passed is called to represent a passed step
|
|
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
|
|
s := &passed{step: step, handler: match}
|
|
f.printStep(s)
|
|
f.passed = append(f.passed, s)
|
|
}
|
|
|
|
// Skipped is called to represent a passed step
|
|
func (f *pretty) Skipped(step *gherkin.Step) {
|
|
s := &skipped{step: step}
|
|
f.printStep(s)
|
|
f.skipped = append(f.skipped, s)
|
|
}
|
|
|
|
// Undefined is called to represent a pending step
|
|
func (f *pretty) Undefined(step *gherkin.Step) {
|
|
s := &undefined{step: step}
|
|
f.printStep(s)
|
|
f.undefined = append(f.undefined, s)
|
|
}
|
|
|
|
// Failed is called to represent a failed step
|
|
func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) {
|
|
s := &failed{step: step, handler: match, err: err}
|
|
f.printStep(s)
|
|
f.failed = append(f.failed, s)
|
|
}
|