diff --git a/cmd/commander/commander.go b/cmd/commander/commander.go index 1cc12b45..1d84c821 100644 --- a/cmd/commander/commander.go +++ b/cmd/commander/commander.go @@ -2,6 +2,7 @@ package main import ( "fmt" + "github.com/commander-cli/commander/pkg/output" "io/ioutil" "log" "os" @@ -75,8 +76,8 @@ commander test commander.yaml --filter="^filter1$" Flags: []cli.Flag{ cli.BoolFlag{ Name: "no-color", - EnvVar: "COMMANDER_NO_COLOR", Usage: "Activate or deactivate colored output", + EnvVar: "COMMANDER_NO_COLOR", }, cli.BoolFlag{ Name: "verbose", @@ -95,6 +96,11 @@ commander test commander.yaml --filter="^filter1$" Name: "filter", Usage: `Filter tests by a given regex pattern. Tests are filtered by its title.`, }, + cli.StringFlag{ + Name: "format", + Usage: `Use a different test output format. Available are: cli, tap`, + Value: output.CLI, + }, }, Action: func(c *cli.Context) error { return app.TestCommand(c.Args().First(), app.NewTestContextFromCli(c)) diff --git a/pkg/app/app.go b/pkg/app/app.go index 8ec97bbc..eb9191dd 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -19,6 +19,7 @@ type TestCommandContext struct { Workdir string Concurrent int Filters []string + Format string } //NewTestContextFromCli is a constructor which creates the context @@ -30,5 +31,6 @@ func NewTestContextFromCli(c *cli.Context) TestCommandContext { Workdir: c.String("workdir"), Concurrent: c.Int("concurrent"), Filters: c.StringSlice("filter"), + Format: c.String("format"), } } diff --git a/pkg/app/test_command.go b/pkg/app/test_command.go index a4b04364..15cbdfbd 100644 --- a/pkg/app/test_command.go +++ b/pkg/app/test_command.go @@ -15,7 +15,7 @@ import ( "github.com/commander-cli/commander/pkg/suite" ) -var out output.OutputWriter +var out output.Output // TestCommand executes the test argument // testPath is the path to the test suite config, it can be a dir or file @@ -33,14 +33,17 @@ func TestCommand(testPath string, ctx TestCommandContext) error { log.SetOutput(os.Stdout) } - out = output.NewCliOutput(!ctx.NoColor) + var err error + out, err = output.NewOutput(ctx.Format, !ctx.NoColor) + if err != nil { + return err + } if testPath == "" { testPath = CommanderFile } var result runtime.Result - var err error switch { case ctx.Dir: fmt.Println("Starting test against directory: " + testPath + "...") @@ -64,7 +67,8 @@ func TestCommand(testPath string, ctx TestCommandContext) error { return fmt.Errorf(err.Error()) } - if !out.PrintSummary(result) && !ctx.Verbose { + out.PrintSummary(result) + if result.Failed != 0 && !ctx.Verbose { return fmt.Errorf("Test suite failed, use --verbose for more detailed output") } diff --git a/pkg/output/cli.go b/pkg/output/cli.go index 36add127..0c1fe599 100644 --- a/pkg/output/cli.go +++ b/pkg/output/cli.go @@ -11,15 +11,17 @@ import ( "github.com/logrusorgru/aurora" ) -// OutputWriter represents the output -type OutputWriter struct { +var _ Output = (*CLIOutputWriter)(nil) + +// CLIOutputWriter represents the output +type CLIOutputWriter struct { out io.Writer au aurora.Aurora template cliTemplate } -// NewCliOutput creates a new OutputWriter with a stdout writer -func NewCliOutput(color bool) OutputWriter { +// NewCliOutput creates a new C̄LIOutputWriter with a stdout writer +func NewCliOutput(color bool) Output { au := aurora.NewAurora(color) if run.GOOS == "windows" { au = aurora.NewAurora(false) @@ -27,7 +29,7 @@ func NewCliOutput(color bool) OutputWriter { t := newCliTemplate() - return OutputWriter{ + return CLIOutputWriter{ out: os.Stdout, au: au, template: t, @@ -48,7 +50,7 @@ type TestResult struct { } // GetEventHandler create a new runtime.EventHandler -func (w *OutputWriter) GetEventHandler() *runtime.EventHandler { +func (w CLIOutputWriter) GetEventHandler() *runtime.EventHandler { handler := runtime.EventHandler{} handler.TestFinished = func(testResult runtime.TestResult) { tr := convertTestResult(testResult) @@ -64,7 +66,7 @@ func (w *OutputWriter) GetEventHandler() *runtime.EventHandler { } // PrintSummary prints summary -func (w *OutputWriter) PrintSummary(result runtime.Result) bool { +func (w CLIOutputWriter) PrintSummary(result runtime.Result) { if result.Failed > 0 { w.printFailures(result.TestResults) } @@ -77,12 +79,10 @@ func (w *OutputWriter) PrintSummary(result runtime.Result) bool { } else { w.fprintf(w.au.Green(summary)) } - - return result.Failed == 0 } // printResult prints the simple output form of a TestReault -func (w *OutputWriter) printResult(r TestResult) { +func (w CLIOutputWriter) printResult(r TestResult) { if !r.Success { w.fprintf(w.au.Red(w.template.testResult(r))) return @@ -90,11 +90,11 @@ func (w *OutputWriter) printResult(r TestResult) { w.fprintf(w.template.testResult(r)) } -func (w *OutputWriter) printSkip(r TestResult) { +func (w CLIOutputWriter) printSkip(r TestResult) { w.fprintf(fmt.Sprintf("- [%s] %s, was skipped", r.Node, r.Title)) } -func (w *OutputWriter) printFailures(results []runtime.TestResult) { +func (w CLIOutputWriter) printFailures(results []runtime.TestResult) { w.fprintf("") w.fprintf(w.au.Bold("Results")) w.fprintf(w.au.Bold("")) @@ -118,7 +118,7 @@ func (w *OutputWriter) printFailures(results []runtime.TestResult) { } } -func (w *OutputWriter) fprintf(a ...interface{}) { +func (w CLIOutputWriter) fprintf(a ...interface{}) { if _, err := fmt.Fprintln(w.out, a...); err != nil { log.Fatal(err) } diff --git a/pkg/output/cli_test.go b/pkg/output/cli_test.go index 239b2a97..654d5d5a 100644 --- a/pkg/output/cli_test.go +++ b/pkg/output/cli_test.go @@ -11,7 +11,7 @@ import ( func Test_NewCliOutput(t *testing.T) { got := NewCliOutput(true) - assert.IsType(t, OutputWriter{}, got) + assert.IsType(t, CLIOutputWriter{}, got) } func Test_GetEventHandler(t *testing.T) { @@ -22,8 +22,9 @@ func Test_GetEventHandler(t *testing.T) { func Test_EventHandlerTestFinished(t *testing.T) { var buf bytes.Buffer - writer := NewCliOutput(true) - writer.out = &buf + writer := CLIOutputWriter{ + out: &buf, + } eh := writer.GetEventHandler() testResults := createFakeTestResults() @@ -39,8 +40,9 @@ func Test_EventHandlerTestFinished(t *testing.T) { func Test_EventHandlerTestSkipped(t *testing.T) { var buf bytes.Buffer - writer := NewCliOutput(true) - writer.out = &buf + writer := CLIOutputWriter{ + out: &buf, + } eh := writer.GetEventHandler() testResults := createFakeTestResults() @@ -62,11 +64,11 @@ func Test_PrintSummary(t *testing.T) { } var buf bytes.Buffer - writer := NewCliOutput(true) - writer.out = &buf + writer := CLIOutputWriter{ + out: &buf, + } - outResult := writer.PrintSummary(r) - assert.False(t, outResult) + writer.PrintSummary(r) output := buf.String() assert.Contains(t, output, "✗ [192.168.0.1] 'Failed test', on property 'Stdout'") diff --git a/pkg/output/output.go b/pkg/output/output.go new file mode 100644 index 00000000..24142fc1 --- /dev/null +++ b/pkg/output/output.go @@ -0,0 +1,28 @@ +package output + +import ( + "errors" + "fmt" + "github.com/commander-cli/commander/pkg/runtime" +) + +const ( + TAP = "tap" + CLI = "cli" +) + +type Output interface { + GetEventHandler() *runtime.EventHandler + PrintSummary(result runtime.Result) +} + +// NewOutput creates a new output +func NewOutput(format string, color bool) (Output, error) { + switch format { + case TAP: + return NewTAPOutputWriter(), nil + case CLI: + return NewCliOutput(color), nil + } + return nil, errors.New(fmt.Sprintf("Invalid format type %s", format)) +} diff --git a/pkg/output/output_test.go b/pkg/output/output_test.go new file mode 100644 index 00000000..884cfdda --- /dev/null +++ b/pkg/output/output_test.go @@ -0,0 +1,13 @@ +package output + +import ( + "github.com/stretchr/testify/assert" + "testing" +) + +func TestNewOutput(t *testing.T) { + var o Output + o, err := NewOutput(CLI, false) + assert.NoError(t, err) + assert.Implements(t, (*Output)(nil), o) +} diff --git a/pkg/output/tap.go b/pkg/output/tap.go new file mode 100644 index 00000000..7f22be54 --- /dev/null +++ b/pkg/output/tap.go @@ -0,0 +1,52 @@ +package output + +import ( + "fmt" + "github.com/commander-cli/commander/pkg/runtime" + "io" + "log" + "os" +) + +var _ Output = (*CLIOutputWriter)(nil) + +// TAPOutputWriter writes TAP results +type TAPOutputWriter struct { + out io.Writer +} + +// NewTAPOutputWriter represents the output, defaults to stdout +func NewTAPOutputWriter() Output { + return TAPOutputWriter{ + out: os.Stdout, + } +} + +func (w TAPOutputWriter) GetEventHandler() *runtime.EventHandler { + return runtime.NewEmptyEventHandler() +} + +func (w TAPOutputWriter) PrintSummary(result runtime.Result) { + counter := 0 + for _, r := range result.TestResults { + if r.Skipped { + // skipped tests are not specified in the TAP specification + continue + } + + counter++ + if r.FailedProperty != "" { + w.fprintf("%d ok - %s", counter+1, r.TestCase.Title) + } else { + w.fprintf("%d not ok - %s", counter+1, r.TestCase.Title) + } + } + + w.fprintf("1..%d", counter) +} + +func (w TAPOutputWriter) fprintf(msg string , a ...interface{}) { + if _, err := fmt.Fprintln(w.out, fmt.Sprintf(msg, a...)); err != nil { + log.Fatal(err) + } +} diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 5e933211..0601a76e 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -123,7 +123,7 @@ type TestResult struct { Skipped bool } -// Result respresents the aggregation of all TestResults/summary of a runtime +// Result represents the aggregation of all TestResults/summary of a runtime type Result struct { TestResults []TestResult Duration time.Duration @@ -166,3 +166,11 @@ func (r *Runtime) Start(tests []TestCase) Result { return result } + +// NewEmptyEventHandler returns an event handler with empty implementations +func NewEmptyEventHandler() *EventHandler { + return &EventHandler{ + TestFinished: func(result TestResult) {}, + TestSkipped: func(result TestResult) {}, + } +}