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
Этот коммит содержится в:
gedi 2016-05-22 15:07:19 +03:00
родитель aed77a5b4b
коммит e71d596404
6 изменённых файлов: 110 добавлений и 80 удалений

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, &registeredFormatter{
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"`

Просмотреть файл

@ -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
Просмотреть файл

@ -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,