diff --git a/.travis.yml b/.travis.yml index ca5cf1b..db1cad3 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,8 +6,9 @@ go: - tip script: - # pull all dependencies - - go get -t ./... + # pull all dependencies manually to see how many we have + # remove them at all if possible + - go get golang.org/x/tools/imports # run standard go tests - go test -v ./... diff --git a/config.go b/config.go index 806b032..1b2d69b 100644 --- a/config.go +++ b/config.go @@ -32,10 +32,6 @@ func RegisterFormatter(name, description string, f Formatter) { var cfg *config -func s(n int) string { - return strings.Repeat(" ", n) -} - func init() { cfg = &config{} @@ -180,10 +176,3 @@ func (c *config) formatter() (f Formatter) { } panic("formatter name had to be validated") } - -func fatal(err error) { - if err != nil { - fmt.Println(err) - os.Exit(1) - } -} diff --git a/formatter.go b/formatter.go index 132d705..65b4988 100644 --- a/formatter.go +++ b/formatter.go @@ -2,21 +2,10 @@ 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 { @@ -28,22 +17,6 @@ type Formatter interface { 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 { @@ -81,194 +54,3 @@ type skipped struct { 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 true - } - - if step.Background == nil { - f.doneBackground = true - return true - } - - return !f.doneBackground -} - -// Node takes a gherkin node for formatting -func (f *pretty) Node(node interface{}) { - switch t := node.(type) { - case *gherkin.Feature: - if f.feature != nil { - // not a first feature, add a newline - fmt.Println("") - } - f.feature = t - f.doneBackground = false - f.background = nil - f.features = append(f.features, t) - fmt.Println(bcl("Feature: ", white) + t.Title) - fmt.Println(t.Description) - case *gherkin.Background: - // determine comment position based on step length - f.commentPos = len(t.Token.Text) - for _, step := range t.Steps { - if len(step.Token.Text) > f.commentPos { - f.commentPos = len(step.Token.Text) - } - } - // do not repeat background - if !f.doneBackground { - f.background = t - fmt.Println("\n" + strings.Repeat(" ", t.Token.Indent) + bcl("Background:", white)) - } - case *gherkin.Scenario: - // determine comment position based on step length - 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("\n" + text) - } -} - -// 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) { - 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) { - if f.canPrintStep(step) { - 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) { - if f.canPrintStep(step) { - f.printMatchedStep(step, match, red) - fmt.Println(strings.Repeat(" ", step.Token.Indent) + bcl(err, red)) - } - f.failed = append(f.failed, &failed{step, err}) -} diff --git a/formatter_pretty.go b/formatter_pretty.go new file mode 100644 index 0000000..4b65fc7 --- /dev/null +++ b/formatter_pretty.go @@ -0,0 +1,225 @@ +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(), + }) +} + +// a built in default pretty formatter +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 +} + +// 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 + } + + if step.Background == nil { + f.doneBackground = true + return true + } + + return !f.doneBackground +} + +// Node takes a gherkin node for formatting +func (f *pretty) Node(node interface{}) { + switch t := node.(type) { + case *gherkin.Feature: + if f.feature != nil { + // not a first feature, add a newline + fmt.Println("") + } + f.feature = t + f.doneBackground = false + f.background = nil + f.features = append(f.features, t) + fmt.Println(bcl("Feature: ", white) + t.Title) + fmt.Println(t.Description) + case *gherkin.Background: + // determine comment position based on step length + f.commentPos = len(t.Token.Text) + for _, step := range t.Steps { + if len(step.Token.Text) > f.commentPos { + f.commentPos = len(step.Token.Text) + } + } + // do not repeat background + if !f.doneBackground { + f.background = t + fmt.Println("\n" + s(t.Token.Indent) + bcl("Background:", white)) + } + case *gherkin.Scenario: + // determine comment position based on step length + 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 := s(t.Token.Indent) + bcl("Scenario: ", white) + t.Title + text += s(f.commentPos-len(t.Token.Text)+1) + f.line(t.Token) + fmt.Println("\n" + text) + } +} + +// 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) { + 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 = s(step.Token.Indent) + text + text += s(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) { + if f.canPrintStep(step) { + 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) { + if f.canPrintStep(step) { + f.printMatchedStep(step, match, red) + fmt.Println(s(step.Token.Indent) + bcl(err, red)) + } + f.failed = append(f.failed, &failed{step, err}) +} diff --git a/formatter_progress.go b/formatter_progress.go new file mode 100644 index 0000000..01cee24 --- /dev/null +++ b/formatter_progress.go @@ -0,0 +1,3 @@ +package godog + +// @TODO: implement progress formatter diff --git a/colors.go b/utils.go similarity index 52% rename from colors.go rename to utils.go index a6634a1..04f2f60 100644 --- a/colors.go +++ b/utils.go @@ -1,6 +1,10 @@ package godog -import "fmt" +import ( + "fmt" + "os" + "strings" +) type color int @@ -17,10 +21,25 @@ const ( white ) +// colorizes foreground s with color c func cl(s interface{}, c color) string { return fmt.Sprintf("%s[%dm%v%s[0m", ansiEscape, c, s, ansiEscape) } +// colorizes foreground s with bold color c func bcl(s interface{}, c color) string { return fmt.Sprintf("%s[1;%dm%v%s[0m", ansiEscape, c, s, ansiEscape) } + +// repeats a space n times +func s(n int) string { + return strings.Repeat(" ", n) +} + +// checks the error and exits with error status code +func fatal(err error) { + if err != nil { + fmt.Println(err) + os.Exit(1) + } +}