diff --git a/.gitignore b/.gitignore index 97df1329..d844ef45 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,7 @@ commander !cmd/commander release +dev.sh tmp.yml *.out diff --git a/go.mod b/go.mod index 6d92ab76..ce31ffc9 100644 --- a/go.mod +++ b/go.mod @@ -1,7 +1,7 @@ module github.com/SimonBaeumer/commander require ( - github.com/SimonBaeumer/cmd v1.1.0 + github.com/SimonBaeumer/cmd v1.2.1 github.com/antchfx/xmlquery v1.1.0 github.com/antchfx/xpath v1.1.0 // indirect github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e diff --git a/go.sum b/go.sum index fca1964f..3f5df4d1 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ github.com/SimonBaeumer/cmd v1.1.0 h1:tr5dUMlly/8bLiC5B0J1AcE4ISru8POEfzAirWnUJnY= github.com/SimonBaeumer/cmd v1.1.0/go.mod h1:4mc/LDXDWNbkeooqHP83yx3JXtInPHjJkF8zhzqqmZE= +github.com/SimonBaeumer/cmd v1.2.1 h1:NAhJp2ME68xZZJE4CVxOrNXStHdL77z1WF38OZFAQhM= +github.com/SimonBaeumer/cmd v1.2.1/go.mod h1:4mc/LDXDWNbkeooqHP83yx3JXtInPHjJkF8zhzqqmZE= github.com/antchfx/jsonquery v1.0.0 h1:1Yhk496SrCoY6fJkFZqpXEqbwOw5sFtLns9la4NoK3I= github.com/antchfx/jsonquery v1.0.0/go.mod h1:h7950pvPrUZzJIflNqsELgDQovTpPNa0rAHf8NwjegY= github.com/antchfx/xmlquery v1.1.0 h1:vj0kZ1y3Q6my4AV+a9xbWrMYzubw+84zuiKgvfV8vb8= diff --git a/pkg/output/cli.go b/pkg/output/cli.go index 1cbf7592..a2c51e4b 100644 --- a/pkg/output/cli.go +++ b/pkg/output/cli.go @@ -50,6 +50,7 @@ func (w *OutputWriter) Start(results <-chan runtime.TestResult) bool { failed++ s := w.addTries("✗ "+r.TestCase.Title, r) w.fprintf(au.Red(s)) + w.fprintf(r.TestCase.Result.Combined) } } diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index fd389dda..30e76e6e 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -57,6 +57,7 @@ type CommandResult struct { Status ResultStatus Stdout string Stderr string + Combined string ExitCode int FailureProperties []string Error error @@ -197,6 +198,7 @@ func runTest(test TestCase) TestResult { ExitCode: cut.ExitCode(), Stdout: strings.TrimSpace(strings.Replace(cut.Stdout(), "\r\n", "\n", -1)), Stderr: strings.TrimSpace(strings.Replace(cut.Stderr(), "\r\n", "\n", -1)), + Combined: strings.TrimSpace(strings.Replace(cut.Combined(), "\r\n", "\n", -1)), } log.Println("title: '"+test.Title+"'", " ExitCode: ", test.Result.ExitCode) diff --git a/vendor/github.com/SimonBaeumer/cmd/README.md b/vendor/github.com/SimonBaeumer/cmd/README.md index c819dec8..6706e4bc 100644 --- a/vendor/github.com/SimonBaeumer/cmd/README.md +++ b/vendor/github.com/SimonBaeumer/cmd/README.md @@ -33,9 +33,13 @@ To configure the command a option function will be passed which receives the com Default option functions: - `cmd.WithStandardStreams` + - `cmd.WithCustomStdout(...io.Writers)` + - `cmd.WithCustomStderr(...io.Writers)` - `cmd.WithTimeout(time.Duration)` - `cmd.WithoutTimeout` - `cmd.WithWorkingDir(string)` + - `cmd.WithEnvironmentVariables(cmd.EnvVars)` + - `cmd.WithInheritedEnvironment(cmd.EnvVars)` #### Example @@ -55,6 +59,23 @@ c := cmd.NewCommand("pwd", setWorkingDir) c.Execute() ``` +### Testing + +You can catch output streams to `stdout` and `stderr` with `cmd.CaptureStandardOut`. + +```golang +// caputred is the captured output from all executed source code +// fnResult contains the result of the executed function +captured, fnResult := cmd.CaptureStandardOut(func() interface{} { + c := NewCommand("echo hello", cmd.WithStandardStream) + err := c.Execute() + return err +}) + +// prints "hello" +fmt.Println(captured) +``` + ## Development ### Running tests diff --git a/vendor/github.com/SimonBaeumer/cmd/command.go b/vendor/github.com/SimonBaeumer/cmd/command.go index c97b898c..451b7b9e 100644 --- a/vendor/github.com/SimonBaeumer/cmd/command.go +++ b/vendor/github.com/SimonBaeumer/cmd/command.go @@ -10,7 +10,7 @@ import ( "time" ) -//Command represents a single command which can be executed +// Command represents a single command which can be executed type Command struct { Command string Env []string @@ -22,10 +22,20 @@ type Command struct { executed bool exitCode int // stderr and stdout retrieve the output after the command was executed - stderr bytes.Buffer - stdout bytes.Buffer + stderr bytes.Buffer + stdout bytes.Buffer + combined bytes.Buffer } +// EnvVars represents a map where the key is the name of the env variable +// and the value is the value of the variable +// +// Example: +// +// env := map[string]string{"ENV": "VALUE"} +// +type EnvVars map[string]string + // NewCommand creates a new command // You can add option with variadic option argument // Default timeout is set to 30 minutes @@ -49,8 +59,8 @@ func NewCommand(cmd string, options ...func(*Command)) *Command { Env: []string{}, } - c.StdoutWriter = &c.stdout - c.StderrWriter = &c.stderr + c.StdoutWriter = NewMultiplexedWriter(&c.stdout, &c.combined) + c.StderrWriter = NewMultiplexedWriter(&c.stderr, &c.combined) for _, o := range options { o(c) @@ -68,8 +78,28 @@ func NewCommand(cmd string, options ...func(*Command)) *Command { // c.Execute() // func WithStandardStreams(c *Command) { - c.StdoutWriter = os.Stdout - c.StderrWriter = os.Stderr + c.StdoutWriter = NewMultiplexedWriter(os.Stdout, &c.stdout, &c.combined) + c.StderrWriter = NewMultiplexedWriter(os.Stderr, &c.stdout, &c.combined) +} + +// WithCustomStdout allows to add custom writers to stdout +func WithCustomStdout(writers ...io.Writer) func(c *Command) { + return func(c *Command) { + writers = append(writers, &c.stdout, &c.combined) + c.StdoutWriter = NewMultiplexedWriter(writers...) + + c.StderrWriter = NewMultiplexedWriter(&c.stderr, &c.combined) + } +} + +// WithCustomStderr allows to add custom writers to stderr +func WithCustomStderr(writers ...io.Writer) func(c *Command) { + return func(c *Command) { + writers = append(writers, &c.stderr, &c.combined) + c.StderrWriter = NewMultiplexedWriter(writers...) + + c.StdoutWriter = NewMultiplexedWriter(&c.stdout, &c.combined) + } } // WithTimeout sets the timeout of the command @@ -95,6 +125,27 @@ func WithWorkingDir(dir string) func(c *Command) { } } +// WithInheritedEnvironment uses the env from the current process and +// allow to add more variables. +func WithInheritedEnvironment(env EnvVars) func(c *Command) { + return func(c *Command) { + c.Env = os.Environ() + + // Set custom variables + fn := WithEnvironmentVariables(env) + fn(c) + } +} + +// WithEnvironmentVariables sets environment variables for the executed command +func WithEnvironmentVariables(env EnvVars) func(c *Command) { + return func(c *Command) { + for key, value := range env { + c.AddEnv(key, value) + } + } +} + // AddEnv adds an environment variable to the command // If a variable gets passed like ${VAR_NAME} the env variable will be read out by the current shell func (c *Command) AddEnv(key string, value string) { @@ -102,18 +153,24 @@ func (c *Command) AddEnv(key string, value string) { c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value)) } -//Stdout returns the output to stdout +// Stdout returns the output to stdout func (c *Command) Stdout() string { c.isExecuted("Stdout") return c.stdout.String() } -//Stderr returns the output to stderr +// Stderr returns the output to stderr func (c *Command) Stderr() string { c.isExecuted("Stderr") return c.stderr.String() } +// Combined returns the combined output of stderr and stdout according to their timeline +func (c *Command) Combined() string { + c.isExecuted("Combined") + return c.combined.String() +} + //ExitCode returns the exit code of the command func (c *Command) ExitCode() int { c.isExecuted("ExitCode") diff --git a/vendor/github.com/SimonBaeumer/cmd/multiplexed_writer.go b/vendor/github.com/SimonBaeumer/cmd/multiplexed_writer.go new file mode 100644 index 00000000..a354aa79 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/multiplexed_writer.go @@ -0,0 +1,29 @@ +package cmd + +import ( + "fmt" + "io" +) + +// NewMultiplexedWriter returns a new multiplexer +func NewMultiplexedWriter(outputs ...io.Writer) *MultiplexedWriter { + return &MultiplexedWriter{Outputs: outputs} +} + +// MultiplexedWriter writes to multiple writers at once +type MultiplexedWriter struct { + Outputs []io.Writer +} + +// Write writes the given bytes. If one write fails it returns the error +// and bytes of the failed write operation +func (w MultiplexedWriter) Write(p []byte) (n int, err error) { + for _, o := range w.Outputs { + n, err = o.Write(p) + if err != nil { + return 0, fmt.Errorf("Error in writer: %s", err.Error()) + } + } + + return n, nil +} diff --git a/vendor/github.com/SimonBaeumer/cmd/testing_utils.go b/vendor/github.com/SimonBaeumer/cmd/testing_utils.go new file mode 100644 index 00000000..006954c5 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/testing_utils.go @@ -0,0 +1,55 @@ +package cmd + +import ( + "bytes" + "github.com/stretchr/testify/assert" + "io" + "log" + "os" + "runtime" + "sync" + "testing" +) + +// CaptureStandardOutput allows to capture the output which will be written +// to os.Stdout and os.Stderr. +// It returns the captured output and the return value of the called function +func CaptureStandardOutput(f func() interface{}) (string, interface{}) { + reader, writer, err := os.Pipe() + if err != nil { + panic(err) + } + stdout := os.Stdout + stderr := os.Stderr + defer func() { + os.Stdout = stdout + os.Stderr = stderr + log.SetOutput(os.Stderr) + }() + os.Stdout = writer + os.Stderr = writer + log.SetOutput(writer) + out := make(chan string) + wg := new(sync.WaitGroup) + wg.Add(1) + go func() { + var buf bytes.Buffer + wg.Done() + io.Copy(&buf, reader) + out <- buf.String() + }() + wg.Wait() + result := f() + writer.Close() + return <-out, result +} + +func assertEqualWithLineBreak(t *testing.T, expected string, actual string) { + if runtime.GOOS == "windows" { + expected = expected + "\r\n" + } else { + expected = expected + "\n" + } + + assert.Equal(t, expected, actual) +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 75f855b0..f577049b 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,4 +1,4 @@ -# github.com/SimonBaeumer/cmd v1.1.0 +# github.com/SimonBaeumer/cmd v1.2.1 github.com/SimonBaeumer/cmd # github.com/antchfx/xmlquery v1.1.0 github.com/antchfx/xmlquery