must be able to customize output for formatters
since it maybe configured by flag values in the future example: - godog -f junit:stdout - godog -f junit:output.xml
Этот коммит содержится в:
родитель
aed77a5b4b
коммит
e71d596404
6 изменённых файлов: 110 добавлений и 80 удалений
33
fmt.go
33
fmt.go
|
@ -3,6 +3,7 @@ package godog
|
|||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -43,13 +44,13 @@ type undefinedSnippet struct {
|
|||
|
||||
type registeredFormatter struct {
|
||||
name string
|
||||
fmt Formatter
|
||||
fmt FormatterFunc
|
||||
description string
|
||||
}
|
||||
|
||||
var formatters []*registeredFormatter
|
||||
|
||||
func findFmt(format string) (f Formatter, err error) {
|
||||
func findFmt(format string) (f FormatterFunc, err error) {
|
||||
var names []string
|
||||
for _, el := range formatters {
|
||||
if el.name == format {
|
||||
|
@ -66,9 +67,10 @@ func findFmt(format string) (f Formatter, err error) {
|
|||
}
|
||||
|
||||
// Format registers a feature suite output
|
||||
// Formatter as the name and descriptiongiven.
|
||||
// Formatter is used to represent suite output
|
||||
func Format(name, description string, f Formatter) {
|
||||
// formatter by given name, description and
|
||||
// FormatterFunc constructor function, to initialize
|
||||
// formatter with the output recorder.
|
||||
func Format(name, description string, f FormatterFunc) {
|
||||
formatters = append(formatters, ®isteredFormatter{
|
||||
name: name,
|
||||
fmt: f,
|
||||
|
@ -95,6 +97,10 @@ type Formatter interface {
|
|||
Summary()
|
||||
}
|
||||
|
||||
// FormatterFunc builds a formatter with given
|
||||
// io.Writer to record output.
|
||||
type FormatterFunc func(io.Writer) Formatter
|
||||
|
||||
type stepType int
|
||||
|
||||
const (
|
||||
|
@ -149,6 +155,7 @@ func (f stepResult) line() string {
|
|||
}
|
||||
|
||||
type basefmt struct {
|
||||
out io.Writer
|
||||
owner interface{}
|
||||
indent int
|
||||
|
||||
|
@ -286,23 +293,23 @@ func (f *basefmt) Summary() {
|
|||
scenarios = append(scenarios, parts...)
|
||||
elapsed := time.Since(f.started)
|
||||
|
||||
fmt.Println("")
|
||||
fmt.Fprintln(f.out, "")
|
||||
if total == 0 {
|
||||
fmt.Println("No scenarios")
|
||||
fmt.Fprintln(f.out, "No scenarios")
|
||||
} else {
|
||||
fmt.Println(fmt.Sprintf("%d scenarios (%s)", total, strings.Join(scenarios, ", ")))
|
||||
fmt.Fprintln(f.out, fmt.Sprintf("%d scenarios (%s)", total, strings.Join(scenarios, ", ")))
|
||||
}
|
||||
|
||||
if nsteps == 0 {
|
||||
fmt.Println("No steps")
|
||||
fmt.Fprintln(f.out, "No steps")
|
||||
} else {
|
||||
fmt.Println(fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
||||
fmt.Fprintln(f.out, fmt.Sprintf("%d steps (%s)", nsteps, strings.Join(steps, ", ")))
|
||||
}
|
||||
fmt.Println(elapsed)
|
||||
fmt.Fprintln(f.out, elapsed)
|
||||
|
||||
if text := f.snippets(); text != "" {
|
||||
fmt.Println(cl("\nYou can implement step definitions for undefined steps with these snippets:", yellow))
|
||||
fmt.Println(cl(text, yellow))
|
||||
fmt.Fprintln(f.out, cl("\nYou can implement step definitions for undefined steps with these snippets:", yellow))
|
||||
fmt.Fprintln(f.out, cl(text, yellow))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,26 +5,20 @@ import (
|
|||
"encoding/hex"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"sync"
|
||||
"io"
|
||||
"time"
|
||||
|
||||
"gopkg.in/cucumber/gherkin-go.v3"
|
||||
)
|
||||
|
||||
const nanoSec = 1000000
|
||||
const spec = "0.1.0"
|
||||
|
||||
func init() {
|
||||
Format("events", "Produces a JSON event stream.", &events{
|
||||
basefmt: basefmt{
|
||||
started: time.Now(),
|
||||
indent: 2,
|
||||
},
|
||||
runID: sha1RunID(),
|
||||
suite: "main",
|
||||
})
|
||||
Format("events", fmt.Sprintf("Produces JSON event stream, based on spec: %s.", spec), eventsFunc)
|
||||
}
|
||||
|
||||
func sha1RunID() string {
|
||||
func eventsFunc(out io.Writer) Formatter {
|
||||
data, err := json.Marshal(&struct {
|
||||
Time int64
|
||||
Runner string
|
||||
|
@ -36,11 +30,33 @@ func sha1RunID() string {
|
|||
|
||||
hasher := sha1.New()
|
||||
hasher.Write(data)
|
||||
return hex.EncodeToString(hasher.Sum(nil))
|
||||
|
||||
formatter := &events{
|
||||
basefmt: basefmt{
|
||||
started: time.Now(),
|
||||
indent: 2,
|
||||
out: out,
|
||||
},
|
||||
runID: hex.EncodeToString(hasher.Sum(nil)),
|
||||
suite: "main",
|
||||
}
|
||||
|
||||
formatter.event(&struct {
|
||||
Event string `json:"event"`
|
||||
RunID string `json:"run_id"`
|
||||
Version string `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}{
|
||||
"TestRunStarted",
|
||||
formatter.runID,
|
||||
spec,
|
||||
time.Now().UnixNano() / nanoSec,
|
||||
})
|
||||
|
||||
return formatter
|
||||
}
|
||||
|
||||
type events struct {
|
||||
sync.Once
|
||||
basefmt
|
||||
|
||||
runID string
|
||||
|
@ -59,26 +75,11 @@ func (f *events) event(ev interface{}) {
|
|||
if err != nil {
|
||||
panic(fmt.Sprintf("failed to marshal stream event: %+v - %v", ev, err))
|
||||
}
|
||||
fmt.Println(string(data))
|
||||
}
|
||||
|
||||
func (f *events) started() {
|
||||
f.event(&struct {
|
||||
Event string `json:"event"`
|
||||
RunID string `json:"run_id"`
|
||||
Version string `json:"version"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
}{
|
||||
"TestRunStarted",
|
||||
f.runID,
|
||||
"0.1.0",
|
||||
time.Now().UnixNano() / nanoSec,
|
||||
})
|
||||
fmt.Fprintln(f.out, string(data))
|
||||
}
|
||||
|
||||
func (f *events) Node(n interface{}) {
|
||||
f.basefmt.Node(n)
|
||||
f.Do(f.started)
|
||||
|
||||
switch t := n.(type) {
|
||||
case *gherkin.Scenario:
|
||||
|
@ -187,8 +188,8 @@ func (f *events) step(res *stepResult) {
|
|||
line = last.TableBody[len(last.TableBody)-1].Location.Line
|
||||
}
|
||||
case *gherkin.Scenario:
|
||||
line = t.Steps[len(t.Steps)-1].Location.Line
|
||||
finished = line == res.step.Location.Line
|
||||
line = t.Location.Line
|
||||
finished = t.Steps[len(t.Steps)-1].Location.Line == res.step.Location.Line
|
||||
}
|
||||
|
||||
if finished {
|
||||
|
@ -213,7 +214,7 @@ func (f *events) step(res *stepResult) {
|
|||
func (f *events) Defined(step *gherkin.Step, def *StepDef) {
|
||||
if def != nil {
|
||||
m := def.Expr.FindStringSubmatchIndex(step.Text)[2:]
|
||||
args := make([][2]int, 0)
|
||||
var args [][2]int
|
||||
for i := 0; i < len(m)/2; i++ {
|
||||
pair := m[i : i*2+2]
|
||||
var idxs [2]int
|
||||
|
@ -222,6 +223,10 @@ func (f *events) Defined(step *gherkin.Step, def *StepDef) {
|
|||
args = append(args, idxs)
|
||||
}
|
||||
|
||||
if len(args) == 0 {
|
||||
args = make([][2]int, 0)
|
||||
}
|
||||
|
||||
f.event(&struct {
|
||||
Event string `json:"event"`
|
||||
RunID string `json:"run_id"`
|
||||
|
|
16
fmt_junit.go
16
fmt_junit.go
|
@ -11,17 +11,23 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
Format("junit", "Prints junit compatible xml to stdout", &junitFormatter{
|
||||
Format("junit", "Prints junit compatible xml to stdout", junitFunc)
|
||||
}
|
||||
|
||||
func junitFunc(out io.Writer) Formatter {
|
||||
return &junitFormatter{
|
||||
suite: &junitPackageSuite{
|
||||
Name: "main", // @TODO: it should extract package name
|
||||
TestSuites: make([]*junitTestSuite, 0),
|
||||
},
|
||||
out: out,
|
||||
started: time.Now(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type junitFormatter struct {
|
||||
suite *junitPackageSuite
|
||||
out io.Writer
|
||||
|
||||
// timing
|
||||
started time.Time
|
||||
|
@ -45,7 +51,7 @@ func (j *junitFormatter) Feature(feature *gherkin.Feature, path string, c []byte
|
|||
j.suite.TestSuites = append(j.suite.TestSuites, testSuite)
|
||||
}
|
||||
|
||||
func (f *junitFormatter) Defined(*gherkin.Step, *StepDef) {
|
||||
func (j *junitFormatter) Defined(*gherkin.Step, *StepDef) {
|
||||
|
||||
}
|
||||
|
||||
|
@ -129,12 +135,12 @@ func (j *junitFormatter) Pending(step *gherkin.Step, match *StepDef) {
|
|||
|
||||
func (j *junitFormatter) Summary() {
|
||||
j.suite.Time = time.Since(j.started).String()
|
||||
io.WriteString(os.Stdout, xml.Header)
|
||||
io.WriteString(j.out, xml.Header)
|
||||
|
||||
enc := xml.NewEncoder(os.Stdout)
|
||||
enc.Indent("", s(2))
|
||||
if err := enc.Encode(j.suite); err != nil {
|
||||
fmt.Println("failed to write junit xml:", err)
|
||||
fmt.Fprintln(os.Stderr, "failed to write junit xml:", err)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package godog
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
@ -12,12 +13,17 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
Format("pretty", "Prints every feature with runtime statuses.", &pretty{
|
||||
Format("pretty", "Prints every feature with runtime statuses.", prettyFunc)
|
||||
}
|
||||
|
||||
func prettyFunc(out io.Writer) Formatter {
|
||||
return &pretty{
|
||||
basefmt: basefmt{
|
||||
started: time.Now(),
|
||||
indent: 2,
|
||||
out: out,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
var outlinePlaceholderRegexp = regexp.MustCompile("<[^>]+>")
|
||||
|
@ -48,13 +54,13 @@ type pretty struct {
|
|||
func (f *pretty) Feature(ft *gherkin.Feature, p string, c []byte) {
|
||||
if len(f.features) != 0 {
|
||||
// not a first feature, add a newline
|
||||
fmt.Println("")
|
||||
fmt.Fprintln(f.out, "")
|
||||
}
|
||||
f.features = append(f.features, &feature{Path: p, Feature: ft})
|
||||
fmt.Println(bcl(ft.Keyword+": ", white) + ft.Name)
|
||||
fmt.Fprintln(f.out, bcl(ft.Keyword+": ", white)+ft.Name)
|
||||
if strings.TrimSpace(ft.Description) != "" {
|
||||
for _, line := range strings.Split(ft.Description, "\n") {
|
||||
fmt.Println(s(f.indent) + strings.TrimSpace(line))
|
||||
fmt.Fprintln(f.out, s(f.indent)+strings.TrimSpace(line))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,7 +110,7 @@ func (f *pretty) Summary() {
|
|||
}
|
||||
}
|
||||
if len(failedScenarios) > 0 {
|
||||
fmt.Println("\n--- " + cl("Failed scenarios:", red) + "\n")
|
||||
fmt.Fprintln(f.out, "\n--- "+cl("Failed scenarios:", red)+"\n")
|
||||
var unique []string
|
||||
for _, fail := range failedScenarios {
|
||||
var found bool
|
||||
|
@ -120,7 +126,7 @@ func (f *pretty) Summary() {
|
|||
}
|
||||
|
||||
for _, fail := range unique {
|
||||
fmt.Println(" " + cl(fail, red))
|
||||
fmt.Fprintln(f.out, " "+cl(fail, red))
|
||||
}
|
||||
}
|
||||
f.basefmt.Summary()
|
||||
|
@ -173,7 +179,7 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
|||
text = cl(ostep.Text, cyan)
|
||||
}
|
||||
// print the step outline
|
||||
fmt.Println(s(f.indent*2) + cl(strings.TrimSpace(ostep.Keyword), cyan) + " " + text)
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+cl(strings.TrimSpace(ostep.Keyword), cyan)+" "+text)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -181,13 +187,13 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
|||
max := longest(example)
|
||||
// an example table header
|
||||
if firstExample {
|
||||
fmt.Println("")
|
||||
fmt.Println(s(f.indent*2) + bcl(example.Keyword+": ", white) + example.Name)
|
||||
fmt.Fprintln(f.out, "")
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+bcl(example.Keyword+": ", white)+example.Name)
|
||||
|
||||
for i, cell := range example.TableHeader.Cells {
|
||||
cells[i] = cl(cell.Value, cyan) + s(max[i]-len(cell.Value))
|
||||
}
|
||||
fmt.Println(s(f.indent*3) + "| " + strings.Join(cells, " | ") + " |")
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |")
|
||||
}
|
||||
|
||||
// an example table row
|
||||
|
@ -195,11 +201,11 @@ func (f *pretty) printOutlineExample(outline *gherkin.ScenarioOutline) {
|
|||
for i, cell := range row.Cells {
|
||||
cells[i] = cl(cell.Value, clr) + s(max[i]-len(cell.Value))
|
||||
}
|
||||
fmt.Println(s(f.indent*3) + "| " + strings.Join(cells, " | ") + " |")
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+"| "+strings.Join(cells, " | ")+" |")
|
||||
|
||||
// if there is an error
|
||||
if msg != "" {
|
||||
fmt.Println(s(f.indent*4) + bcl(msg, red))
|
||||
fmt.Fprintln(f.out, s(f.indent*4)+bcl(msg, red))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -226,7 +232,7 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
|||
text += cl(step.Text, c)
|
||||
}
|
||||
|
||||
fmt.Println(text)
|
||||
fmt.Fprintln(f.out, text)
|
||||
switch t := step.Argument.(type) {
|
||||
case *gherkin.DataTable:
|
||||
f.printTable(t, c)
|
||||
|
@ -235,11 +241,11 @@ func (f *pretty) printStep(step *gherkin.Step, def *StepDef, c color) {
|
|||
if len(t.ContentType) > 0 {
|
||||
ct = " " + cl(t.ContentType, c)
|
||||
}
|
||||
fmt.Println(s(f.indent*3) + cl(t.Delimitter, c) + ct)
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+cl(t.Delimitter, c)+ct)
|
||||
for _, ln := range strings.Split(t.Content, "\n") {
|
||||
fmt.Println(s(f.indent*3) + cl(ln, c))
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+cl(ln, c))
|
||||
}
|
||||
fmt.Println(s(f.indent*3) + cl(t.Delimitter, c))
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+cl(t.Delimitter, c))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -249,7 +255,7 @@ func (f *pretty) printStepKind(res *stepResult) {
|
|||
// first background step
|
||||
case f.bgSteps > 0 && f.bgSteps == len(f.feature.Background.Steps):
|
||||
f.commentPos = f.longestStep(f.feature.Background.Steps, f.length(f.feature.Background))
|
||||
fmt.Println("\n" + s(f.indent) + bcl(f.feature.Background.Keyword+": "+f.feature.Background.Name, white))
|
||||
fmt.Fprintln(f.out, "\n"+s(f.indent)+bcl(f.feature.Background.Keyword+": "+f.feature.Background.Name, white))
|
||||
f.bgSteps--
|
||||
// subsequent background steps
|
||||
case f.bgSteps > 0:
|
||||
|
@ -266,7 +272,7 @@ func (f *pretty) printStepKind(res *stepResult) {
|
|||
}
|
||||
text := s(f.indent) + bcl(f.scenario.Keyword+": ", white) + f.scenario.Name
|
||||
text += s(f.commentPos-f.length(f.scenario)+1) + f.line(f.scenario.Location)
|
||||
fmt.Println("\n" + text)
|
||||
fmt.Fprintln(f.out, "\n"+text)
|
||||
f.scenarioKeyword = true
|
||||
}
|
||||
f.steps--
|
||||
|
@ -285,7 +291,7 @@ func (f *pretty) printStepKind(res *stepResult) {
|
|||
}
|
||||
text := s(f.indent) + bcl(f.outline.Keyword+": ", white) + f.outline.Name
|
||||
text += s(f.commentPos-f.length(f.outline)+1) + f.line(f.outline.Location)
|
||||
fmt.Println("\n" + text)
|
||||
fmt.Fprintln(f.out, "\n"+text)
|
||||
f.scenarioKeyword = true
|
||||
}
|
||||
if len(f.outlineSteps) == len(f.outline.Steps)+f.bgSteps {
|
||||
|
@ -298,10 +304,10 @@ func (f *pretty) printStepKind(res *stepResult) {
|
|||
|
||||
f.printStep(res.step, res.def, res.typ.clr())
|
||||
if res.err != nil {
|
||||
fmt.Println(s(f.indent*2) + bcl(res.err, red))
|
||||
fmt.Fprintln(f.out, s(f.indent*2)+bcl(res.err, red))
|
||||
}
|
||||
if res.typ == pending {
|
||||
fmt.Println(s(f.indent*3) + cl("TODO: write pending definition", yellow))
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+cl("TODO: write pending definition", yellow))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -313,7 +319,7 @@ func (f *pretty) printTable(t *gherkin.DataTable, c color) {
|
|||
for i, cell := range row.Cells {
|
||||
cols[i] = cell.Value + s(l[i]-len(cell.Value))
|
||||
}
|
||||
fmt.Println(s(f.indent*3) + cl("| "+strings.Join(cols, " | ")+" |", c))
|
||||
fmt.Fprintln(f.out, s(f.indent*3)+cl("| "+strings.Join(cols, " | ")+" |", c))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package godog
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
@ -10,13 +11,18 @@ import (
|
|||
)
|
||||
|
||||
func init() {
|
||||
Format("progress", "Prints a character per step.", &progress{
|
||||
Format("progress", "Prints a character per step.", progressFunc)
|
||||
}
|
||||
|
||||
func progressFunc(out io.Writer) Formatter {
|
||||
return &progress{
|
||||
basefmt: basefmt{
|
||||
started: time.Now(),
|
||||
indent: 2,
|
||||
out: out,
|
||||
},
|
||||
stepsPerRow: 70,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type progress struct {
|
||||
|
@ -47,13 +53,13 @@ func (f *progress) Summary() {
|
|||
fmt.Printf(" %d\n", f.steps)
|
||||
}
|
||||
}
|
||||
fmt.Println("")
|
||||
fmt.Fprintln(f.out, "")
|
||||
|
||||
if len(f.failed) > 0 {
|
||||
fmt.Println("\n--- " + cl("Failed steps:", red) + "\n")
|
||||
fmt.Fprintln(f.out, "\n--- "+cl("Failed steps:", red)+"\n")
|
||||
for _, fail := range f.failed {
|
||||
fmt.Println(s(4) + cl(fail.step.Keyword+" "+fail.step.Text, red) + cl(" # "+fail.line(), black))
|
||||
fmt.Println(s(6) + cl("Error: ", red) + bcl(fail.err, red) + "\n")
|
||||
fmt.Fprintln(f.out, s(4)+cl(fail.step.Keyword+" "+fail.step.Text, red)+cl(" # "+fail.line(), black))
|
||||
fmt.Fprintln(f.out, s(6)+cl("Error: ", red)+bcl(fail.err, red)+"\n")
|
||||
}
|
||||
}
|
||||
f.basefmt.Summary()
|
||||
|
@ -74,7 +80,7 @@ func (f *progress) step(res *stepResult) {
|
|||
}
|
||||
f.steps++
|
||||
if math.Mod(float64(f.steps), float64(f.stepsPerRow)) == 0 {
|
||||
fmt.Printf(" %d\n", f.steps)
|
||||
fmt.Fprintf(f.out, " %d\n", f.steps)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
2
run.go
2
run.go
|
@ -93,7 +93,7 @@ func RunWithOptions(contextInitializer func(suite *Suite), opt Options) int {
|
|||
fatal(err)
|
||||
|
||||
r := runner{
|
||||
fmt: formatter,
|
||||
fmt: formatter(os.Stdout),
|
||||
initializer: contextInitializer,
|
||||
features: features,
|
||||
stopOnFailure: opt.StopOnFailure,
|
||||
|
|
Загрузка…
Создание таблицы
Сослаться в новой задаче