From e71d5964042ade73113722bd784e21a3fa31c4a2 Mon Sep 17 00:00:00 2001 From: gedi Date: Sun, 22 May 2016 15:07:19 +0300 Subject: [PATCH] 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 --- fmt.go | 33 ++++++++++++++---------- fmt_events.go | 67 ++++++++++++++++++++++++++----------------------- fmt_junit.go | 16 ++++++++---- fmt_pretty.go | 52 +++++++++++++++++++++----------------- fmt_progress.go | 20 +++++++++------ run.go | 2 +- 6 files changed, 110 insertions(+), 80 deletions(-) diff --git a/fmt.go b/fmt.go index f1082d9..2696c75 100644 --- a/fmt.go +++ b/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)) } } diff --git a/fmt_events.go b/fmt_events.go index 43fd944..41e7d88 100644 --- a/fmt_events.go +++ b/fmt_events.go @@ -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"` diff --git a/fmt_junit.go b/fmt_junit.go index cfaa430..d56fafe 100644 --- a/fmt_junit.go +++ b/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) } } diff --git a/fmt_pretty.go b/fmt_pretty.go index c8e45d2..c27ce1e 100644 --- a/fmt_pretty.go +++ b/fmt_pretty.go @@ -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)) } } diff --git a/fmt_progress.go b/fmt_progress.go index 0a23e0e..daa5de2 100644 --- a/fmt_progress.go +++ b/fmt_progress.go @@ -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) } } diff --git a/run.go b/run.go index f2919ea..3a1a9e5 100644 --- a/run.go +++ b/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,