update usage info, finish pretty format
Этот коммит содержится в:
родитель
84c5567a10
коммит
42ca958118
4 изменённых файлов: 199 добавлений и 61 удалений
141
config.go
141
config.go
|
@ -5,55 +5,95 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
)
|
||||
|
||||
type registeredFormatter struct {
|
||||
name string
|
||||
fmt Formatter
|
||||
name string
|
||||
fmt Formatter
|
||||
description string
|
||||
}
|
||||
|
||||
var formatters []*registeredFormatter
|
||||
|
||||
// RegisterFormatter registers a feature suite output
|
||||
// Formatter for its given name
|
||||
func RegisterFormatter(name string, f Formatter) {
|
||||
// Formatter as the name and descriptiongiven.
|
||||
// Formatter is used to represent suite output
|
||||
func RegisterFormatter(name, description string, f Formatter) {
|
||||
formatters = append(formatters, ®isteredFormatter{
|
||||
name: name,
|
||||
fmt: f,
|
||||
name: name,
|
||||
fmt: f,
|
||||
description: description,
|
||||
})
|
||||
}
|
||||
|
||||
var cfg config
|
||||
var cfg *config
|
||||
|
||||
func s(n int) string {
|
||||
return strings.Repeat(" ", n)
|
||||
}
|
||||
|
||||
func init() {
|
||||
// @TODO: colorize flag help output
|
||||
flag.StringVar(&cfg.featuresPath, "features", "features", "Path to feature files")
|
||||
flag.StringVar(&cfg.formatterName, "formatter", "pretty", "Formatter name")
|
||||
cfg = &config{}
|
||||
|
||||
flag.StringVar(&cfg.format, "format", "pretty", "")
|
||||
flag.StringVar(&cfg.format, "f", "pretty", "")
|
||||
flag.Usage = func() {
|
||||
// prints an option or argument with a description, or only description
|
||||
opt := func(name, desc string) string {
|
||||
if len(name) > 0 {
|
||||
name += ":"
|
||||
}
|
||||
return s(2) + cl(name, green) + s(30-len(name)) + desc
|
||||
}
|
||||
|
||||
// --- GENERAL ---
|
||||
fmt.Println(cl("Usage:", yellow))
|
||||
fmt.Println(s(2) + "godog [options] [<paths>]\n")
|
||||
|
||||
// --- ARGUMENTS ---
|
||||
fmt.Println(cl("Arguments:", yellow))
|
||||
// --> paths
|
||||
fmt.Println(opt("paths", "Optional path(s) to execute. Can be:"))
|
||||
fmt.Println(opt("", s(4)+"- dir "+cl("(features/)", yellow)))
|
||||
fmt.Println(opt("", s(4)+"- feature "+cl("(*.feature)", yellow)))
|
||||
fmt.Println(opt("", s(4)+"- scenario at specific line "+cl("(*.feature:10)", yellow)))
|
||||
fmt.Println(opt("", "If no paths are listed, suite tries "+cl("features", yellow)+" path by default."))
|
||||
fmt.Println("")
|
||||
|
||||
// --- OPTIONS ---
|
||||
fmt.Println(cl("Options:", yellow))
|
||||
// --> format
|
||||
fmt.Println(opt("-f, --format=pretty", "How to format tests output. Available formats:"))
|
||||
for _, f := range formatters {
|
||||
fmt.Println(opt("", s(4)+"- "+cl(f.name, yellow)+": "+f.description))
|
||||
}
|
||||
fmt.Println("")
|
||||
}
|
||||
}
|
||||
|
||||
type config struct {
|
||||
featuresPath string
|
||||
formatterName string
|
||||
paths []string
|
||||
format string
|
||||
}
|
||||
|
||||
func (c config) validate() error {
|
||||
// feature path
|
||||
inf, err := os.Stat(c.featuresPath)
|
||||
if err != nil {
|
||||
return err
|
||||
func (c *config) validate() error {
|
||||
c.paths = flag.Args()
|
||||
// check the default path
|
||||
if len(c.paths) == 0 {
|
||||
inf, err := os.Stat("features")
|
||||
if err == nil && inf.IsDir() {
|
||||
c.paths = []string{"features"}
|
||||
}
|
||||
}
|
||||
if !inf.IsDir() {
|
||||
return fmt.Errorf("feature path \"%s\" is not a directory.", c.featuresPath)
|
||||
}
|
||||
|
||||
// formatter
|
||||
var found bool
|
||||
var names []string
|
||||
for _, f := range formatters {
|
||||
if f.name == c.formatterName {
|
||||
if f.name == c.format {
|
||||
found = true
|
||||
break
|
||||
}
|
||||
|
@ -61,26 +101,61 @@ func (c config) validate() error {
|
|||
}
|
||||
|
||||
if !found {
|
||||
return fmt.Errorf(`unregistered formatter name: "%s", use one of: %s`, c.formatterName, strings.Join(names, ", "))
|
||||
return fmt.Errorf(`unregistered formatter name: "%s", use one of: %s`, c.format, strings.Join(names, ", "))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c config) features() (lst []*gherkin.Feature, err error) {
|
||||
return lst, filepath.Walk(cfg.featuresPath, func(p string, f os.FileInfo, err error) error {
|
||||
if err == nil && !f.IsDir() && strings.HasSuffix(p, ".feature") {
|
||||
ft, err := gherkin.Parse(p)
|
||||
func (c *config) features() (lst []*gherkin.Feature, err error) {
|
||||
for _, pat := range c.paths {
|
||||
parts := strings.Split(pat, ":")
|
||||
path := parts[0]
|
||||
line := -1
|
||||
if len(parts) > 1 {
|
||||
line, err = strconv.Atoi(parts[1])
|
||||
if err != nil {
|
||||
return err
|
||||
return lst, fmt.Errorf("line number should follow after colon path delimiter")
|
||||
}
|
||||
lst = append(lst, ft)
|
||||
}
|
||||
return err
|
||||
})
|
||||
err = filepath.Walk(path, func(p string, f os.FileInfo, err error) error {
|
||||
if err == nil && !f.IsDir() && strings.HasSuffix(p, ".feature") {
|
||||
ft, err := gherkin.Parse(p)
|
||||
switch {
|
||||
case err == gherkin.ErrEmpty:
|
||||
// its ok, just skip it
|
||||
case err != nil:
|
||||
return err
|
||||
default:
|
||||
lst = append(lst, ft)
|
||||
}
|
||||
// filter scenario by line number
|
||||
if line != -1 {
|
||||
var scenarios []*gherkin.Scenario
|
||||
for _, s := range ft.Scenarios {
|
||||
if s.Token.Line == line {
|
||||
scenarios = append(scenarios, s)
|
||||
break
|
||||
}
|
||||
}
|
||||
ft.Scenarios = scenarios
|
||||
}
|
||||
}
|
||||
return err
|
||||
})
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (c config) formatter() Formatter {
|
||||
return &pretty{}
|
||||
func (c *config) formatter() (f Formatter) {
|
||||
for _, fmt := range formatters {
|
||||
if fmt.name == cfg.format {
|
||||
return fmt.fmt
|
||||
}
|
||||
}
|
||||
panic("formatter name had to be validated")
|
||||
}
|
||||
|
||||
func fatal(err error) {
|
||||
|
|
110
formatter.go
110
formatter.go
|
@ -6,24 +6,29 @@ import (
|
|||
"reflect"
|
||||
"runtime"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/DATA-DOG/godog/gherkin"
|
||||
)
|
||||
|
||||
func init() {
|
||||
RegisterFormatter("pretty", &pretty{})
|
||||
RegisterFormatter("pretty", "Prints every feature with runtime statuses.", &pretty{
|
||||
started: time.Now(),
|
||||
})
|
||||
}
|
||||
|
||||
// Formatter is an interface for feature runner output
|
||||
// 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)
|
||||
Pending(*gherkin.Step)
|
||||
Undefined(*gherkin.Step)
|
||||
Summary()
|
||||
}
|
||||
|
||||
// general pretty formatter structure
|
||||
type pretty struct {
|
||||
feature *gherkin.Feature
|
||||
scenario *gherkin.Scenario
|
||||
|
@ -32,13 +37,16 @@ type pretty struct {
|
|||
background *gherkin.Background
|
||||
|
||||
// summary
|
||||
features []*gherkin.Feature
|
||||
failures []*failed
|
||||
passes []*passed
|
||||
skips []*skipped
|
||||
pendings []*pending
|
||||
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 {
|
||||
feature *gherkin.Feature
|
||||
scenario *gherkin.Scenario
|
||||
|
@ -46,28 +54,36 @@ type failed struct {
|
|||
err error
|
||||
}
|
||||
|
||||
// passed represents a successful step data structure
|
||||
// with all necessary references
|
||||
type passed struct {
|
||||
feature *gherkin.Feature
|
||||
scenario *gherkin.Scenario
|
||||
step *gherkin.Step
|
||||
}
|
||||
|
||||
// skipped represents a skipped step data structure
|
||||
// with all necessary references
|
||||
type skipped struct {
|
||||
feature *gherkin.Feature
|
||||
scenario *gherkin.Scenario
|
||||
step *gherkin.Step
|
||||
}
|
||||
|
||||
type pending struct {
|
||||
// undefined represents a pending step data structure
|
||||
// with all necessary references
|
||||
type undefined struct {
|
||||
feature *gherkin.Feature
|
||||
scenario *gherkin.Scenario
|
||||
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 true
|
||||
|
@ -89,11 +105,7 @@ func (f *pretty) canPrintStep(step *gherkin.Step) bool {
|
|||
return !f.doneBackground
|
||||
}
|
||||
|
||||
func (f *pretty) comment(text, comment string) string {
|
||||
indent := f.commentPos - len(text) + 1
|
||||
return text + strings.Repeat(" ", indent) + comment
|
||||
}
|
||||
|
||||
// Node takes a gherkin node for formatting
|
||||
func (f *pretty) Node(node interface{}) {
|
||||
switch t := node.(type) {
|
||||
case *gherkin.Feature:
|
||||
|
@ -102,11 +114,11 @@ func (f *pretty) Node(node interface{}) {
|
|||
f.scenario = nil
|
||||
f.background = nil
|
||||
f.features = append(f.features, t)
|
||||
fmt.Println("\n" + bcl("Feature: ", white) + t.Title)
|
||||
fmt.Println(bcl("Feature: ", white) + t.Title + "\n")
|
||||
fmt.Println(t.Description)
|
||||
case *gherkin.Background:
|
||||
f.background = t
|
||||
fmt.Println("\n" + bcl("Background:", white))
|
||||
fmt.Println(bcl("Background:", white) + "\n")
|
||||
case *gherkin.Scenario:
|
||||
f.scenario = t
|
||||
f.commentPos = len(t.Token.Text)
|
||||
|
@ -117,14 +129,64 @@ func (f *pretty) Node(node interface{}) {
|
|||
}
|
||||
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("\n" + text)
|
||||
fmt.Println(text + "\n")
|
||||
}
|
||||
}
|
||||
|
||||
// Summary sumarize the feature formatter output
|
||||
func (f *pretty) Summary() {
|
||||
if len(f.failed) > 0 {
|
||||
fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n")
|
||||
for _, fail := range f.failed {
|
||||
fmt.Println(" " + cl(fmt.Sprintf("%s:%d", fail.feature.Path, fail.scenario.Token.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
|
||||
|
@ -165,41 +227,45 @@ func (f *pretty) printMatchedStep(step *gherkin.Step, match *stepMatchHandler, c
|
|||
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.passes = append(f.passes, &passed{
|
||||
f.passed = append(f.passed, &passed{
|
||||
feature: f.feature,
|
||||
scenario: f.scenario,
|
||||
step: 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.skips = append(f.skips, &skipped{
|
||||
f.skipped = append(f.skipped, &skipped{
|
||||
feature: f.feature,
|
||||
scenario: f.scenario,
|
||||
step: step,
|
||||
})
|
||||
}
|
||||
|
||||
func (f *pretty) Pending(step *gherkin.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.pendings = append(f.pendings, &pending{
|
||||
f.undefined = append(f.undefined, &undefined{
|
||||
feature: f.feature,
|
||||
scenario: f.scenario,
|
||||
step: 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.failures = append(f.failures, &failed{
|
||||
f.failed = append(f.failed, &failed{
|
||||
feature: f.feature,
|
||||
scenario: f.scenario,
|
||||
step: step,
|
||||
|
|
7
suite.go
7
suite.go
|
@ -119,12 +119,10 @@ func (s *suite) Run() {
|
|||
s.features, err = cfg.features()
|
||||
fatal(err)
|
||||
|
||||
fmt.Println("running", cl("godog", cyan)+", num registered steps:", cl(len(s.steps), yellow))
|
||||
fmt.Println("have loaded", cl(len(s.features), yellow), "features from path:", cl(cfg.featuresPath, green))
|
||||
|
||||
for _, f := range s.features {
|
||||
s.runFeature(f)
|
||||
}
|
||||
s.fmt.Summary()
|
||||
}
|
||||
|
||||
func (s *suite) runStep(step *gherkin.Step) (err error) {
|
||||
|
@ -140,7 +138,7 @@ func (s *suite) runStep(step *gherkin.Step) (err error) {
|
|||
}
|
||||
}
|
||||
if match == nil {
|
||||
s.fmt.Pending(step)
|
||||
s.fmt.Undefined(step)
|
||||
return errPending
|
||||
}
|
||||
|
||||
|
@ -184,7 +182,6 @@ func (s *suite) runFeature(f *gherkin.Feature) {
|
|||
var failed bool
|
||||
for _, scenario := range f.Scenarios {
|
||||
// background
|
||||
// @TODO: do not print more than once
|
||||
if f.Background != nil && !failed {
|
||||
s.fmt.Node(f.Background)
|
||||
failed = s.runSteps(f.Background.Steps)
|
||||
|
|
|
@ -10,7 +10,7 @@ type suiteFeature struct {
|
|||
}
|
||||
|
||||
func (s *suiteFeature) featurePath(args ...Arg) error {
|
||||
cfg.featuresPath = args[0].String()
|
||||
cfg.paths = []string{args[0].String()}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче