godog/formatter.go

252 строки
6,6 КиБ
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(),
})
}
// Formatter is an interface for feature runner
// output summary presentation
type Formatter interface {
Node(interface{})
Failed(*gherkin.Step, *stepMatchHandler, error)
Passed(*gherkin.Step, *stepMatchHandler)
Skipped(*gherkin.Step)
Undefined(*gherkin.Step)
Summary()
}
// general pretty formatter structure
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
}
// failed represents a failed step data structure
// with all necessary references
type failed struct {
step *gherkin.Step
err error
}
func (f failed) line() string {
var tok *gherkin.Token
var ft *gherkin.Feature
if f.step.Scenario != nil {
tok = f.step.Scenario.Token
ft = f.step.Scenario.Feature
} else {
tok = f.step.Background.Token
ft = f.step.Background.Feature
}
return fmt.Sprintf("%s:%d", ft.Path, tok.Line)
}
// passed represents a successful step data structure
// with all necessary references
type passed struct {
step *gherkin.Step
}
// skipped represents a skipped step data structure
// with all necessary references
type skipped struct {
step *gherkin.Step
}
// undefined represents a pending step data structure
// with all necessary references
type undefined struct {
step *gherkin.Step
}
// 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 step.Background != nil
}
return true
}
// Node takes a gherkin node for formatting
func (f *pretty) Node(node interface{}) {
switch t := node.(type) {
case *gherkin.Feature:
f.feature = t
f.doneBackground = false
f.background = nil
f.features = append(f.features, t)
fmt.Println(bcl("Feature: ", white) + t.Title + "\n")
fmt.Println(t.Description)
case *gherkin.Background:
f.background = t
fmt.Println(bcl("Background:", white) + "\n")
case *gherkin.Scenario:
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 := strings.Repeat(" ", t.Token.Indent) + bcl("Scenario: ", white) + t.Title
text += strings.Repeat(" ", f.commentPos-len(t.Token.Text)+1) + f.line(t.Token)
fmt.Println(text + "\n")
}
}
// 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)
}
// prints a single matched step
func (f *pretty) printMatchedStep(step *gherkin.Step, match *stepMatchHandler, c color) {
if !f.canPrintStep(step) {
return
}
var text string
if m := (match.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 {
text += cl(step.Text[pos:m[i]], c)
} else {
text += bcl(step.Text[pos:m[i]], c)
}
pos = m[i]
}
text += cl(step.Text[pos:len(step.Text)], c)
} else {
text = cl(step.Text, c)
}
// use reflect to get step handler function name
name := runtime.FuncForPC(reflect.ValueOf(match.handler).Pointer()).Name()
switch step.Token.Type {
case gherkin.GIVEN:
text = cl("Given", c) + " " + text
case gherkin.WHEN:
text = cl("When", c) + " " + text
case gherkin.THEN:
text = cl("Then", c) + " " + text
case gherkin.AND:
text = cl("And", c) + " " + text
case gherkin.BUT:
text = cl("But", c) + " " + text
}
text = strings.Repeat(" ", step.Token.Indent) + text
text += strings.Repeat(" ", f.commentPos-len(step.Token.Text)+1) + cl(fmt.Sprintf("# %s", name), black)
fmt.Println(text)
}
// Passed is called to represent a passed step
func (f *pretty) Passed(step *gherkin.Step, match *stepMatchHandler) {
f.printMatchedStep(step, match, green)
f.passed = append(f.passed, &passed{step})
}
// Skipped is called to represent a passed step
func (f *pretty) Skipped(step *gherkin.Step) {
if f.canPrintStep(step) {
fmt.Println(cl(step.Token.Text, cyan))
}
f.skipped = append(f.skipped, &skipped{step})
}
// Undefined is called to represent a pending step
func (f *pretty) Undefined(step *gherkin.Step) {
if f.canPrintStep(step) {
fmt.Println(cl(step.Token.Text, yellow))
}
f.undefined = append(f.undefined, &undefined{step})
}
// Failed is called to represent a failed step
func (f *pretty) Failed(step *gherkin.Step, match *stepMatchHandler, err error) {
f.printMatchedStep(step, match, red)
fmt.Println(strings.Repeat(" ", step.Token.Indent) + bcl(err, red))
f.failed = append(f.failed, &failed{step, err})
}