From 79fe5a5c2ef62f17af7aa7ae04d2b2356fd4a1a8 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 11:45:56 +0200 Subject: [PATCH 01/17] Remove cmd pkg and use SimonBaeumer/cmd --- CHANGELOG.md | 1 + go.mod | 7 +- go.sum | 10 + pkg/app/add_command.go | 9 +- pkg/cmd/command.go | 162 --------- pkg/cmd/command_darwin.go | 15 - pkg/cmd/command_darwin_test.go | 34 -- pkg/cmd/command_linux.go | 15 - pkg/cmd/command_linux_test.go | 37 --- pkg/cmd/command_test.go | 133 -------- pkg/cmd/command_windows.go | 15 - pkg/cmd/command_windows_test.go | 37 --- pkg/matcher/matcher.go | 28 +- pkg/matcher/matcher_test.go | 3 +- pkg/runtime/runtime.go | 24 +- pkg/runtime/runtime_test.go | 13 + pkg/runtime/validator_test.go | 1 - vendor/github.com/SimonBaeumer/cmd/.gitignore | 1 + .../github.com/SimonBaeumer/cmd/.travis.yml | 45 +++ vendor/github.com/SimonBaeumer/cmd/LICENSE | 21 ++ vendor/github.com/SimonBaeumer/cmd/Makefile | 35 ++ vendor/github.com/SimonBaeumer/cmd/README.md | 68 ++++ vendor/github.com/SimonBaeumer/cmd/command.go | 194 +++++++++++ .../SimonBaeumer/cmd/command_darwin.go | 10 + .../SimonBaeumer/cmd/command_linux.go | 10 + .../SimonBaeumer/cmd/command_windows.go | 10 + vendor/github.com/SimonBaeumer/cmd/go.mod | 5 + vendor/github.com/SimonBaeumer/cmd/go.sum | 10 + vendor/github.com/davecgh/go-spew/LICENSE | 2 +- .../github.com/davecgh/go-spew/spew/bypass.go | 187 +++++------ .../davecgh/go-spew/spew/bypasssafe.go | 2 +- .../github.com/davecgh/go-spew/spew/common.go | 2 +- .../github.com/davecgh/go-spew/spew/dump.go | 10 +- .../github.com/davecgh/go-spew/spew/format.go | 4 +- .../testify/assert/assertion_format.go | 82 +++++ .../testify/assert/assertion_forward.go | 164 ++++++++++ .../testify/assert/assertion_order.go | 309 ++++++++++++++++++ .../stretchr/testify/assert/assertions.go | 96 +++++- vendor/gopkg.in/yaml.v2/decode.go | 38 +++ vendor/gopkg.in/yaml.v2/resolve.go | 2 +- vendor/gopkg.in/yaml.v2/scannerc.go | 16 + vendor/modules.txt | 8 +- 42 files changed, 1280 insertions(+), 595 deletions(-) delete mode 100644 pkg/cmd/command.go delete mode 100644 pkg/cmd/command_darwin.go delete mode 100644 pkg/cmd/command_darwin_test.go delete mode 100644 pkg/cmd/command_linux.go delete mode 100644 pkg/cmd/command_linux_test.go delete mode 100644 pkg/cmd/command_test.go delete mode 100644 pkg/cmd/command_windows.go delete mode 100644 pkg/cmd/command_windows_test.go create mode 100644 vendor/github.com/SimonBaeumer/cmd/.gitignore create mode 100644 vendor/github.com/SimonBaeumer/cmd/.travis.yml create mode 100644 vendor/github.com/SimonBaeumer/cmd/LICENSE create mode 100644 vendor/github.com/SimonBaeumer/cmd/Makefile create mode 100644 vendor/github.com/SimonBaeumer/cmd/README.md create mode 100644 vendor/github.com/SimonBaeumer/cmd/command.go create mode 100644 vendor/github.com/SimonBaeumer/cmd/command_darwin.go create mode 100644 vendor/github.com/SimonBaeumer/cmd/command_linux.go create mode 100644 vendor/github.com/SimonBaeumer/cmd/command_windows.go create mode 100644 vendor/github.com/SimonBaeumer/cmd/go.mod create mode 100644 vendor/github.com/SimonBaeumer/cmd/go.sum create mode 100644 vendor/github.com/stretchr/testify/assert/assertion_order.go diff --git a/CHANGELOG.md b/CHANGELOG.md index a0219c72..9eb62ca1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - Added `xml` assertion to `stdout` and `stderr` - Added `json` assertin to `stdout` and `stderr` + - Remove `cmd` pkg and use `github.com/SimonBaeumer/cmd@v1.1.0` instead # v1.2.2 diff --git a/go.mod b/go.mod index 399971d0..6d92ab76 100644 --- a/go.mod +++ b/go.mod @@ -1,13 +1,16 @@ module github.com/SimonBaeumer/commander require ( + github.com/SimonBaeumer/cmd v1.1.0 github.com/antchfx/xmlquery v1.1.0 github.com/antchfx/xpath v1.1.0 // indirect github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e github.com/pmezard/go-difflib v1.0.0 - github.com/stretchr/testify v1.3.0 + github.com/stretchr/objx v0.2.0 // indirect + github.com/stretchr/testify v1.4.0 github.com/tidwall/gjson v1.3.2 github.com/urfave/cli v1.20.0 golang.org/x/net v0.0.0-20191014212845-da9a3fd4c582 // indirect - gopkg.in/yaml.v2 v2.2.2 + gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 // indirect + gopkg.in/yaml.v2 v2.2.4 ) diff --git a/go.sum b/go.sum index eebd43f5..fca1964f 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +github.com/SimonBaeumer/cmd v1.1.0 h1:tr5dUMlly/8bLiC5B0J1AcE4ISru8POEfzAirWnUJnY= +github.com/SimonBaeumer/cmd v1.1.0/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= @@ -6,13 +8,18 @@ github.com/antchfx/xpath v1.1.0 h1:mJTvYpiHvxNQRD4Lbfin/FodHVCHh2a5KrOFr4ZxMOI= github.com/antchfx/xpath v1.1.0/go.mod h1:Yee4kTMuNiPYJ7nSNorELQMr1J33uOpXDMByNYhvtNk= github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= github.com/tidwall/gjson v1.3.2 h1:+7p3qQFaH3fOMXAJSrdZwGKcOO/lYdGS0HqGhPqDdTI= github.com/tidwall/gjson v1.3.2/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= github.com/tidwall/match v1.0.1 h1:PnKP62LPNxHKTwvHHZZzdOAOCtsJTjo6dZLCwpKm5xc= @@ -28,5 +35,8 @@ golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5h golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4 h1:/eiJrUcujPVeJ3xlSWaiNi3uSVmDGBK1pDHUHAnao1I= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/app/add_command.go b/pkg/app/add_command.go index 2377c0f9..818638d9 100644 --- a/pkg/app/add_command.go +++ b/pkg/app/add_command.go @@ -1,10 +1,11 @@ package app import ( - "github.com/SimonBaeumer/commander/pkg/cmd" + "github.com/SimonBaeumer/cmd" "github.com/SimonBaeumer/commander/pkg/runtime" "github.com/SimonBaeumer/commander/pkg/suite" "gopkg.in/yaml.v2" + "strings" ) // AddCommand executes the add command @@ -46,10 +47,12 @@ func AddCommand(command string, existed []byte) ([]byte, error) { } } + stdout := strings.TrimSpace(c.Stdout()) + stderr := strings.TrimSpace(c.Stderr()) conf.Tests[command] = suite.YAMLTest{ Title: command, - Stdout: runtime.ExpectedOut{Contains: []string{c.Stdout()}}, - Stderr: runtime.ExpectedOut{Contains: []string{c.Stderr()}}, + Stdout: runtime.ExpectedOut{Contains: []string{stdout}}, + Stderr: runtime.ExpectedOut{Contains: []string{stderr}}, ExitCode: c.ExitCode(), } diff --git a/pkg/cmd/command.go b/pkg/cmd/command.go deleted file mode 100644 index 796d18a0..00000000 --- a/pkg/cmd/command.go +++ /dev/null @@ -1,162 +0,0 @@ -package cmd - -import ( - "bytes" - "fmt" - "os" - "os/exec" - "regexp" - "strings" - "syscall" - "time" -) - -//Command represents a single command which can be executed -type Command struct { - Cmd string - Env []string - Dir string - Timeout time.Duration - executed bool - stderr string - stdout string - exitCode int -} - -//NewCommand creates a new command -func NewCommand(cmd string) *Command { - return &Command{ - Cmd: cmd, - Timeout: 1 * time.Minute, - executed: false, - Env: []string{}, - } -} - -// 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) { - vars := parseEnvVariableFromShell(value) - for _, v := range vars { - value = strings.Replace(value, v, os.Getenv(removeEnvVarSyntax(v)), -1) - } - - c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value)) -} - -// Removes the ${...} characters -func removeEnvVarSyntax(v string) string { - return v[2:(len(v) - 1)] -} - -//Read all environment variables from the given value -//with the syntax ${VAR_NAME} -func parseEnvVariableFromShell(val string) []string { - reg := regexp.MustCompile(`\$\{.*?\}`) - matches := reg.FindAllString(val, -1) - return matches -} - -//SetTimeoutMS sets the timeout in milliseconds -func (c *Command) SetTimeoutMS(ms int) { - if ms == 0 { - c.Timeout = 1 * time.Minute - return - } - c.Timeout = time.Duration(ms) * time.Millisecond -} - -// SetTimeout sets the timeout given a time unit -// Example: SetTimeout("100s") sets the timeout to 100 seconds -func (c *Command) SetTimeout(timeout string) error { - d, err := time.ParseDuration(timeout) - if err != nil { - return err - } - - c.Timeout = d - return nil -} - -//Stdout returns the output to stdout -func (c *Command) Stdout() string { - c.isExecuted("Stdout") - return c.stdout -} - -//Stderr returns the output to stderr -func (c *Command) Stderr() string { - c.isExecuted("Stderr") - return c.stderr -} - -//ExitCode returns the exit code of the command -func (c *Command) ExitCode() int { - c.isExecuted("ExitCode") - return c.exitCode -} - -//Executed returns if the command was already executed -func (c *Command) Executed() bool { - return c.executed -} - -func (c *Command) isExecuted(property string) { - if !c.executed { - panic("Can not read " + property + " if command was not executed.") - } -} - -// Execute executes the command and writes the results into it's own instance -// The results can be received with the Stdout(), Stderr() and ExitCode() methods -func (c *Command) Execute() error { - cmd := createBaseCommand(c) - cmd.Env = c.Env - cmd.Dir = c.Dir - - var ( - outBuff bytes.Buffer - errBuff bytes.Buffer - ) - cmd.Stdout = &outBuff - cmd.Stderr = &errBuff - - err := cmd.Start() - if err != nil { - return err - } - - done := make(chan error) - go func() { - done <- cmd.Wait() - }() - - select { - case err := <-done: - if err != nil { - c.getExitCode(err) - break - } - c.exitCode = 0 - case <-time.After(c.Timeout): - if err := cmd.Process.Kill(); err != nil { - return fmt.Errorf("Timeout occurred and can not kill process with pid %v", cmd.Process.Pid) - } - return fmt.Errorf("Command timed out after %v", c.Timeout) - } - - // Remove leading and trailing whitespaces - c.stderr = c.removeLineBreaks(errBuff.String()) - c.stdout = c.removeLineBreaks(outBuff.String()) - c.executed = true - - return nil -} - -func (c *Command) getExitCode(err error) { - if exitErr, ok := err.(*exec.ExitError); ok { - if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { - c.exitCode = status.ExitStatus() - } - } -} diff --git a/pkg/cmd/command_darwin.go b/pkg/cmd/command_darwin.go deleted file mode 100644 index 7f9dac26..00000000 --- a/pkg/cmd/command_darwin.go +++ /dev/null @@ -1,15 +0,0 @@ -package cmd - -import ( - "os/exec" - "strings" -) - -func createBaseCommand(c *Command) *exec.Cmd { - cmd := exec.Command("/bin/sh", "-c", c.Cmd) - return cmd -} - -func (c *Command) removeLineBreaks(text string) string { - return strings.Trim(text, "\n") -} diff --git a/pkg/cmd/command_darwin_test.go b/pkg/cmd/command_darwin_test.go deleted file mode 100644 index 86d043ac..00000000 --- a/pkg/cmd/command_darwin_test.go +++ /dev/null @@ -1,34 +0,0 @@ -package cmd - -import ( - "github.com/stretchr/testify/assert" - "testing" -) - -func TestCommand_ExecuteStderr(t *testing.T) { - cmd := NewCommand(">&2 echo hello") - - err := cmd.Execute() - - assert.Nil(t, err) - assert.Equal(t, "hello", cmd.Stderr()) -} - -func TestCommand_WithTimeout(t *testing.T) { - cmd := NewCommand("sleep 0.5;") - cmd.SetTimeoutMS(5) - - err := cmd.Execute() - - assert.NotNil(t, err) - assert.Equal(t, "Command timed out after 5ms", err.Error()) -} - -func TestCommand_WithValidTimeout(t *testing.T) { - cmd := NewCommand("sleep 0.01;") - cmd.SetTimeoutMS(500) - - err := cmd.Execute() - - assert.Nil(t, err) -} diff --git a/pkg/cmd/command_linux.go b/pkg/cmd/command_linux.go deleted file mode 100644 index 7f9dac26..00000000 --- a/pkg/cmd/command_linux.go +++ /dev/null @@ -1,15 +0,0 @@ -package cmd - -import ( - "os/exec" - "strings" -) - -func createBaseCommand(c *Command) *exec.Cmd { - cmd := exec.Command("/bin/sh", "-c", c.Cmd) - return cmd -} - -func (c *Command) removeLineBreaks(text string) string { - return strings.Trim(text, "\n") -} diff --git a/pkg/cmd/command_linux_test.go b/pkg/cmd/command_linux_test.go deleted file mode 100644 index 9fddebe7..00000000 --- a/pkg/cmd/command_linux_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package cmd - -import ( - "github.com/stretchr/testify/assert" - "strings" - "testing" -) - -func TestCommand_ExecuteStderr(t *testing.T) { - cmd := NewCommand(">&2 echo hello") - - err := cmd.Execute() - - assert.Nil(t, err) - assert.Equal(t, "hello", cmd.Stderr()) -} - -func TestCommand_WithTimeout(t *testing.T) { - cmd := NewCommand("sleep 0.01;") - cmd.SetTimeoutMS(1) - - err := cmd.Execute() - - assert.NotNil(t, err) - // Sadly a process can not be killed every time :( - containsMsg := strings.Contains(err.Error(), "Timeout occurred and can not kill process with pid") || strings.Contains(err.Error(), "Command timed out after 1ms") - assert.True(t, containsMsg) -} - -func TestCommand_WithValidTimeout(t *testing.T) { - cmd := NewCommand("sleep 0.01;") - cmd.SetTimeoutMS(500) - - err := cmd.Execute() - - assert.Nil(t, err) -} diff --git a/pkg/cmd/command_test.go b/pkg/cmd/command_test.go deleted file mode 100644 index 89b56561..00000000 --- a/pkg/cmd/command_test.go +++ /dev/null @@ -1,133 +0,0 @@ -package cmd - -import ( - "fmt" - "github.com/stretchr/testify/assert" - "os" - "runtime" - "testing" - "time" -) - -func TestCommand_NewCommand(t *testing.T) { - cmd := NewCommand("") - assert.False(t, cmd.Executed()) -} - -func TestCommand_Execute(t *testing.T) { - cmd := NewCommand("echo hello") - - err := cmd.Execute() - - assert.Nil(t, err) - assert.True(t, cmd.Executed()) - assert.Equal(t, cmd.Stdout(), "hello") -} - -func TestCommand_ExitCode(t *testing.T) { - cmd := NewCommand("exit 120") - - err := cmd.Execute() - - assert.Nil(t, err) - assert.Equal(t, 120, cmd.ExitCode()) -} - -func TestCommand_WithEnvVariables(t *testing.T) { - envVar := "$TEST" - if runtime.GOOS == "windows" { - envVar = "%TEST%" - } - cmd := NewCommand(fmt.Sprintf("echo %s", envVar)) - cmd.Env = []string{"TEST=hey"} - - _ = cmd.Execute() - - assert.Equal(t, cmd.Stdout(), "hey") -} - -func TestCommand_Executed(t *testing.T) { - defer func() { - r := recover() - if r != nil { - assert.Contains(t, r, "Can not read Stdout if command was not executed") - } - assert.NotNil(t, r) - }() - - c := NewCommand("echo will not be executed") - _ = c.Stdout() -} - -func TestCommand_AddEnv(t *testing.T) { - c := NewCommand("echo test") - c.AddEnv("key", "value") - assert.Equal(t, []string{"key=value"}, c.Env) -} - -func TestCommand_AddEnvWithShellVariable(t *testing.T) { - const TestEnvKey = "COMMANDER_TEST_SOME_KEY" - os.Setenv(TestEnvKey, "test from shell") - defer os.Unsetenv(TestEnvKey) - - c := NewCommand(getCommand()) - c.AddEnv("SOME_KEY", fmt.Sprintf("${%s}", TestEnvKey)) - - err := c.Execute() - - assert.Nil(t, err) - assert.Equal(t, "test from shell", c.Stdout()) -} - -func TestCommand_AddMultipleEnvWithShellVariable(t *testing.T) { - const TestEnvKeyPlanet = "COMMANDER_TEST_PLANET" - const TestEnvKeyName = "COMMANDER_TEST_NAME" - os.Setenv(TestEnvKeyPlanet, "world") - os.Setenv(TestEnvKeyName, "Simon") - defer func() { - os.Unsetenv(TestEnvKeyPlanet) - os.Unsetenv(TestEnvKeyName) - }() - - c := NewCommand(getCommand()) - envValue := fmt.Sprintf("Hello ${%s}, I am ${%s}", TestEnvKeyPlanet, TestEnvKeyName) - c.AddEnv("SOME_KEY", envValue) - - err := c.Execute() - - assert.Nil(t, err) - assert.Equal(t, "Hello world, I am Simon", c.Stdout()) -} - -func getCommand() string { - command := "echo $SOME_KEY" - if runtime.GOOS == "windows" { - command = "echo %SOME_KEY%" - } - return command -} - -func TestCommand_SetTimeoutMS_DefaultTimeout(t *testing.T) { - c := NewCommand("echo test") - c.SetTimeoutMS(0) - assert.Equal(t, (1 * time.Minute), c.Timeout) -} - -func TestCommand_SetTimeoutMS(t *testing.T) { - c := NewCommand("echo test") - c.SetTimeoutMS(100) - assert.Equal(t, 100*time.Millisecond, c.Timeout) -} - -func TestCommand_SetTimeout(t *testing.T) { - c := NewCommand("echo test") - _ = c.SetTimeout("100s") - duration, _ := time.ParseDuration("100s") - assert.Equal(t, duration, c.Timeout) -} - -func TestCommand_SetInvalidTimeout(t *testing.T) { - c := NewCommand("echo test") - err := c.SetTimeout("1") - assert.Equal(t, "time: missing unit in duration 1", err.Error()) -} diff --git a/pkg/cmd/command_windows.go b/pkg/cmd/command_windows.go deleted file mode 100644 index 103dabed..00000000 --- a/pkg/cmd/command_windows.go +++ /dev/null @@ -1,15 +0,0 @@ -package cmd - -import ( - "os/exec" - "strings" -) - -func createBaseCommand(c *Command) *exec.Cmd { - cmd := exec.Command(`c:\windows\system32\cmd.exe`, "/C", c.Cmd) - return cmd -} - -func (c *Command) removeLineBreaks(text string) string { - return strings.Trim(text, "\r\n") -} diff --git a/pkg/cmd/command_windows_test.go b/pkg/cmd/command_windows_test.go deleted file mode 100644 index f6f41dba..00000000 --- a/pkg/cmd/command_windows_test.go +++ /dev/null @@ -1,37 +0,0 @@ -package cmd - -import ( - "github.com/stretchr/testify/assert" - "strings" - "testing" -) - -func TestCommand_ExecuteStderr(t *testing.T) { - cmd := NewCommand("echo hello 1>&2") - - err := cmd.Execute() - - assert.Nil(t, err) - assert.Equal(t, "hello ", cmd.Stderr()) -} - -func TestCommand_WithTimeout(t *testing.T) { - cmd := NewCommand("timeout 0.005;") - cmd.SetTimeoutMS(5) - - err := cmd.Execute() - - assert.NotNil(t, err) - // This is needed because windows sometimes can not kill the process :( - containsMsg := strings.Contains(err.Error(), "Timeout occurred and can not kill process with pid") || strings.Contains(err.Error(), "Command timed out after 5ms") - assert.True(t, containsMsg) -} - -func TestCommand_WithValidTimeout(t *testing.T) { - cmd := NewCommand("timeout 0.01;") - cmd.SetTimeoutMS(1000) - - err := cmd.Execute() - - assert.Nil(t, err) -} diff --git a/pkg/matcher/matcher.go b/pkg/matcher/matcher.go index 250d70c9..d01c4147 100644 --- a/pkg/matcher/matcher.go +++ b/pkg/matcher/matcher.go @@ -213,30 +213,30 @@ func (m XMLMatcher) Match(got interface{}, expected interface{}) MatcherResult { node, err := xmlquery.Query(doc, q) if err != nil { - return MatcherResult{ - Success: false, - Diff: fmt.Sprintf("Error occured: %s", err), - } + return MatcherResult{ + Success: false, + Diff: fmt.Sprintf("Error occured: %s", err), + } } if node == nil { - return MatcherResult{ - Success: false, - Diff: fmt.Sprintf(`Query "%s" did not match a path`, q), - } - } + return MatcherResult{ + Success: false, + Diff: fmt.Sprintf(`Query "%s" did not match a path`, q), + } + } - if node.InnerText() != e { - result.Success = false - result.Diff = fmt.Sprintf(`Expected xml path "%s" with result + if node.InnerText() != e { + result.Success = false + result.Diff = fmt.Sprintf(`Expected xml path "%s" with result %s to be equal to %s`, q, e, node.InnerText()) - } - } + } + } return result } diff --git a/pkg/matcher/matcher_test.go b/pkg/matcher/matcher_test.go index a8dd1588..7e8a2cca 100644 --- a/pkg/matcher/matcher_test.go +++ b/pkg/matcher/matcher_test.go @@ -168,7 +168,6 @@ func TestJSONMatcher_MatchArray(t *testing.T) { assert.Equal(t, "", r.Diff) } - func TestJSONMatcher_DoesNotMatch(t *testing.T) { m := NewMatcher(JSON) r := m.Match(`{"book": "another"}`, map[string]string{"book": "test"}) @@ -183,4 +182,4 @@ another` assert.False(t, r.Success) assert.Equal(t, diff, r.Diff) -} \ No newline at end of file +} diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index b11cf440..ad33c271 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -2,7 +2,7 @@ package runtime import ( "fmt" - "github.com/SimonBaeumer/commander/pkg/cmd" + "github.com/SimonBaeumer/cmd" "log" "runtime" "strings" @@ -157,10 +157,20 @@ func executeRetryInterval(t TestCase) { // runTest executes the current test case func runTest(test TestCase) TestResult { + timeoutOpt := cmd.WithoutTimeout + if test.Command.Timeout != "" { + d, err := time.ParseDuration(test.Command.Timeout) + if err != nil { + test.Result = CommandResult{Error: err} + return TestResult{ + TestCase: test, + } + } + timeoutOpt = cmd.WithTimeout(d) + } + // cut = command under test - cut := cmd.NewCommand(test.Command.Cmd) - cut.SetTimeout(test.Command.Timeout) - cut.Dir = test.Command.Dir + cut := cmd.NewCommand(test.Command.Cmd, timeoutOpt, cmd.WithWorkingDir(test.Command.Dir)) for k, v := range test.Command.Env { cut.AddEnv(k, v) } @@ -176,15 +186,15 @@ func runTest(test TestCase) TestResult { } } - log.Println("title: '"+test.Title+"'", " Command: ", cut.Cmd) + log.Println("title: '"+test.Title+"'", " Command: ", test.Command.Cmd) log.Println("title: '"+test.Title+"'", " Directory: ", cut.Dir) log.Println("title: '"+test.Title+"'", " Env: ", cut.Env) // Write test result test.Result = CommandResult{ ExitCode: cut.ExitCode(), - Stdout: strings.Replace(cut.Stdout(), "\r\n", "\n", -1), - Stderr: strings.Replace(cut.Stderr(), "\r\n", "\n", -1), + Stdout: strings.TrimSpace(cut.Stdout()), + Stderr: strings.TrimSpace(cut.Stderr()), } log.Println("title: '"+test.Title+"'", " ExitCode: ", test.Result.ExitCode) diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 1f69c002..332c5474 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -105,6 +105,19 @@ func Test_runTestShouldReturnError(t *testing.T) { } } +func TestRuntime_WithInvalidDuration(t *testing.T) { + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo test", + Timeout: "600lightyears", + }, + } + + got := runTest(test) + + assert.Equal(t, "time: unknown unit lightyears in duration 600lightyears", got.TestCase.Result.Error.Error()) +} + func getExampleTestSuite() []TestCase { tests := []TestCase{ { diff --git a/pkg/runtime/validator_test.go b/pkg/runtime/validator_test.go index 61756889..d81d0a43 100644 --- a/pkg/runtime/validator_test.go +++ b/pkg/runtime/validator_test.go @@ -109,7 +109,6 @@ func Test_ValidateExpectedOut_ValidateXML(t *testing.T) { assert.True(t, r.Success) assert.Equal(t, "", r.Diff) - diff := `Expected xml path "/book//author" with result Joanne K. Rowling diff --git a/vendor/github.com/SimonBaeumer/cmd/.gitignore b/vendor/github.com/SimonBaeumer/cmd/.gitignore new file mode 100644 index 00000000..723ef36f --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/.gitignore @@ -0,0 +1 @@ +.idea \ No newline at end of file diff --git a/vendor/github.com/SimonBaeumer/cmd/.travis.yml b/vendor/github.com/SimonBaeumer/cmd/.travis.yml new file mode 100644 index 00000000..027d5d4c --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/.travis.yml @@ -0,0 +1,45 @@ +language: go + +env: + global: + - GO111MODULE=on + - CC_TEST_REPORTER_ID=1fee6b47ad638c3cb28b932d28413d643a06c90b277bc5839e306f40e932422e + +stages: + - test + +go: + - 1.13.x + +sudo: required +dist: trusty + +before_install: + - go get -u golang.org/x/lint/golint + - make deps + - curl -L https://github.com/SimonBaeumer/commander/releases/download/v0.3.0/commander-linux-amd64 -o ~/bin/commander + - chmod +x ~/bin/commander + +jobs: + include: + - name: macOS Unit + os: osx + script: + - make test + + - name: windows Unit + os: windows + before_install: + - choco install make + script: + - make test-windows + + - name: Unit tests + before_script: + - curl https://s3.amazonaws.com/codeclimate/test-reporter/test-reporter-0.6.3-linux-amd64 --output test-reporter + - chmod +x test-reporter + - ./test-reporter before-build + script: + - make test-coverage + after_script: + - ./test-reporter after-build -t gocov --exit-code $TRAVIS_TEST_RESULT diff --git a/vendor/github.com/SimonBaeumer/cmd/LICENSE b/vendor/github.com/SimonBaeumer/cmd/LICENSE new file mode 100644 index 00000000..130be1dc --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2016 Svett Ralchev + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. \ No newline at end of file diff --git a/vendor/github.com/SimonBaeumer/cmd/Makefile b/vendor/github.com/SimonBaeumer/cmd/Makefile new file mode 100644 index 00000000..c09c86d6 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/Makefile @@ -0,0 +1,35 @@ +exe = cmd/operator/* +cmd = operator +TRAVIS_TAG ?= "0.0.0" + +.PHONY: deps lint test integration integration-windows git-hooks init + +init: git-hooks + +git-hooks: + $(info INFO: Starting build $@) + ln -sf ../../.githooks/pre-commit .git/hooks/pre-commit + +deps: + $(info INFO: Starting build $@) + go mod vendor + +build: + $(info INFO: Starting build $@) + go build $(exe) + +lint: + $(info INFO: Starting build $@) + golint pkg/ cmd/ + +test: + $(info INFO: Starting build $@) + go test `go list ./... | grep -v examples` + +test-windows: + $(info INFO: Starting build $@) + go test . + +test-coverage: + $(info INFO: Starting build $@) + go test -coverprofile c.out `go list ./... | grep -v examples` diff --git a/vendor/github.com/SimonBaeumer/cmd/README.md b/vendor/github.com/SimonBaeumer/cmd/README.md new file mode 100644 index 00000000..c819dec8 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/README.md @@ -0,0 +1,68 @@ +[![Build Status](https://travis-ci.org/SimonBaeumer/cmd.svg?branch=master)](https://travis-ci.org/SimonBaeumer/cmd) +[![GoDoc](https://godoc.org/github.com/SimonBaeumer/cmd?status.svg)](https://godoc.org/github.com/SimonBaeumer/cmd) +[![Test Coverage](https://api.codeclimate.com/v1/badges/af3487439a313d580619/test_coverage)](https://codeclimate.com/github/SimonBaeumer/cmd/test_coverage) +[![Maintainability](https://api.codeclimate.com/v1/badges/af3487439a313d580619/maintainability)](https://codeclimate.com/github/SimonBaeumer/cmd/maintainability) +[![Go Report Card](https://goreportcard.com/badge/github.com/SimonBaeumer/cmd)](https://goreportcard.com/report/github.com/SimonBaeumer/cmd) + +# cmd package + +A simple package to execute shell commands on linux, darwin and windows. + +## Installation + +`$ go get -u github.com/SimonBaeumer/cmd@v1.0.0` + +## Usage + +```go +c := cmd.NewCommand("echo hello") + +err := c.Execute() +if err != nil { + panic(err.Error()) +} + +fmt.Println(c.Stdout()) +fmt.Println(c.Stderr()) +``` + +### Configure the command + +To configure the command a option function will be passed which receives the command object as an argument passed by reference. + +Default option functions: + + - `cmd.WithStandardStreams` + - `cmd.WithTimeout(time.Duration)` + - `cmd.WithoutTimeout` + - `cmd.WithWorkingDir(string)` + +#### Example + +```go +c := cmd.NewCommand("echo hello", cmd.WithStandardStreams) +c.Execute() +``` + +#### Set custom options + +```go +setWorkingDir := func (c *Command) { + c.WorkingDir = "/tmp/test" +} + +c := cmd.NewCommand("pwd", setWorkingDir) +c.Execute() +``` + +## Development + +### Running tests + +``` +make test +``` + +### ToDo + + - os.Stdout and os.Stderr output access after execution via `c.Stdout()` and `c.Stderr()` diff --git a/vendor/github.com/SimonBaeumer/cmd/command.go b/vendor/github.com/SimonBaeumer/cmd/command.go new file mode 100644 index 00000000..c97b898c --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/command.go @@ -0,0 +1,194 @@ +package cmd + +import ( + "bytes" + "fmt" + "io" + "os" + "os/exec" + "syscall" + "time" +) + +//Command represents a single command which can be executed +type Command struct { + Command string + Env []string + Dir string + Timeout time.Duration + StderrWriter io.Writer + StdoutWriter io.Writer + WorkingDir string + executed bool + exitCode int + // stderr and stdout retrieve the output after the command was executed + stderr bytes.Buffer + stdout bytes.Buffer +} + +// NewCommand creates a new command +// You can add option with variadic option argument +// Default timeout is set to 30 minutes +// +// Example: +// c := cmd.NewCommand("echo hello", function (c *Command) { +// c.WorkingDir = "/tmp" +// }) +// c.Execute() +// +// or you can use existing options functions +// +// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams) +// c.Execute() +// +func NewCommand(cmd string, options ...func(*Command)) *Command { + c := &Command{ + Command: cmd, + Timeout: 30 * time.Minute, + executed: false, + Env: []string{}, + } + + c.StdoutWriter = &c.stdout + c.StderrWriter = &c.stderr + + for _, o := range options { + o(c) + } + + return c +} + +// WithStandardStreams is used as an option by the NewCommand constructor function and writes the output streams +// to stderr and stdout of the operating system +// +// Example: +// +// c := cmd.NewCommand("echo hello", cmd.WithStandardStreams) +// c.Execute() +// +func WithStandardStreams(c *Command) { + c.StdoutWriter = os.Stdout + c.StderrWriter = os.Stderr +} + +// WithTimeout sets the timeout of the command +// +// Example: +// cmd.NewCommand("sleep 10;", cmd.WithTimeout(500)) +// +func WithTimeout(t time.Duration) func(c *Command) { + return func(c *Command) { + c.Timeout = t + } +} + +// WithoutTimeout disables the timeout for the command +func WithoutTimeout(c *Command) { + c.Timeout = 0 +} + +// WithWorkingDir sets the current working directory +func WithWorkingDir(dir string) func(c *Command) { + return func(c *Command) { + c.WorkingDir = dir + } +} + +// 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) { + value = os.ExpandEnv(value) + c.Env = append(c.Env, fmt.Sprintf("%s=%s", key, value)) +} + +//Stdout returns the output to stdout +func (c *Command) Stdout() string { + c.isExecuted("Stdout") + return c.stdout.String() +} + +//Stderr returns the output to stderr +func (c *Command) Stderr() string { + c.isExecuted("Stderr") + return c.stderr.String() +} + +//ExitCode returns the exit code of the command +func (c *Command) ExitCode() int { + c.isExecuted("ExitCode") + return c.exitCode +} + +//Executed returns if the command was already executed +func (c *Command) Executed() bool { + return c.executed +} + +func (c *Command) isExecuted(property string) { + if !c.executed { + panic("Can not read " + property + " if command was not executed.") + } +} + +// Execute executes the command and writes the results into it's own instance +// The results can be received with the Stdout(), Stderr() and ExitCode() methods +func (c *Command) Execute() error { + cmd := createBaseCommand(c) + cmd.Env = c.Env + cmd.Dir = c.Dir + cmd.Stdout = c.StdoutWriter + cmd.Stderr = c.StderrWriter + cmd.Dir = c.WorkingDir + + // Create timer only if timeout was set > 0 + var timeoutChan = make(<-chan time.Time, 1) + if c.Timeout != 0 { + timeoutChan = time.After(c.Timeout) + } + + err := cmd.Start() + if err != nil { + return err + } + + done := make(chan error, 1) + quit := make(chan bool, 1) + defer close(quit) + + go func() { + select { + case <-quit: + return + case done <- cmd.Wait(): + return + } + }() + + select { + case err := <-done: + if err != nil { + c.getExitCode(err) + break + } + c.exitCode = 0 + case <-timeoutChan: + quit <- true + if err := cmd.Process.Kill(); err != nil { + return fmt.Errorf("Timeout occurred and can not kill process with pid %v", cmd.Process.Pid) + } + return fmt.Errorf("Command timed out after %v", c.Timeout) + } + + c.executed = true + + return nil +} + +func (c *Command) getExitCode(err error) { + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(syscall.WaitStatus); ok { + c.exitCode = status.ExitStatus() + } + } +} diff --git a/vendor/github.com/SimonBaeumer/cmd/command_darwin.go b/vendor/github.com/SimonBaeumer/cmd/command_darwin.go new file mode 100644 index 00000000..eb108967 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/command_darwin.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "os/exec" +) + +func createBaseCommand(c *Command) *exec.Cmd { + cmd := exec.Command("/bin/sh", "-c", c.Command) + return cmd +} diff --git a/vendor/github.com/SimonBaeumer/cmd/command_linux.go b/vendor/github.com/SimonBaeumer/cmd/command_linux.go new file mode 100644 index 00000000..eb108967 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/command_linux.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "os/exec" +) + +func createBaseCommand(c *Command) *exec.Cmd { + cmd := exec.Command("/bin/sh", "-c", c.Command) + return cmd +} diff --git a/vendor/github.com/SimonBaeumer/cmd/command_windows.go b/vendor/github.com/SimonBaeumer/cmd/command_windows.go new file mode 100644 index 00000000..45d5cece --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/command_windows.go @@ -0,0 +1,10 @@ +package cmd + +import ( + "os/exec" +) + +func createBaseCommand(c *Command) *exec.Cmd { + cmd := exec.Command(`C:\windows\system32\cmd.exe`, "/C", c.Command) + return cmd +} diff --git a/vendor/github.com/SimonBaeumer/cmd/go.mod b/vendor/github.com/SimonBaeumer/cmd/go.mod new file mode 100644 index 00000000..b552e181 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/go.mod @@ -0,0 +1,5 @@ +module github.com/SimonBaeumer/cmd + +go 1.12 + +require github.com/stretchr/testify v1.4.0 diff --git a/vendor/github.com/SimonBaeumer/cmd/go.sum b/vendor/github.com/SimonBaeumer/cmd/go.sum new file mode 100644 index 00000000..e863f517 --- /dev/null +++ b/vendor/github.com/SimonBaeumer/cmd/go.sum @@ -0,0 +1,10 @@ +github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/vendor/github.com/davecgh/go-spew/LICENSE b/vendor/github.com/davecgh/go-spew/LICENSE index c8364161..bc52e96f 100644 --- a/vendor/github.com/davecgh/go-spew/LICENSE +++ b/vendor/github.com/davecgh/go-spew/LICENSE @@ -2,7 +2,7 @@ ISC License Copyright (c) 2012-2016 Dave Collins -Permission to use, copy, modify, and distribute this software for any +Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. diff --git a/vendor/github.com/davecgh/go-spew/spew/bypass.go b/vendor/github.com/davecgh/go-spew/spew/bypass.go index 8a4a6589..79299478 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypass.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypass.go @@ -16,7 +16,9 @@ // when the code is not running on Google App Engine, compiled by GopherJS, and // "-tags safe" is not added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. -// +build !js,!appengine,!safe,!disableunsafe +// Go versions prior to 1.4 are disabled because they use a different layout +// for interfaces which make the implementation of unsafeReflectValue more complex. +// +build !js,!appengine,!safe,!disableunsafe,go1.4 package spew @@ -34,80 +36,49 @@ const ( ptrSize = unsafe.Sizeof((*byte)(nil)) ) +type flag uintptr + var ( - // offsetPtr, offsetScalar, and offsetFlag are the offsets for the - // internal reflect.Value fields. These values are valid before golang - // commit ecccf07e7f9d which changed the format. The are also valid - // after commit 82f48826c6c7 which changed the format again to mirror - // the original format. Code in the init function updates these offsets - // as necessary. - offsetPtr = uintptr(ptrSize) - offsetScalar = uintptr(0) - offsetFlag = uintptr(ptrSize * 2) - - // flagKindWidth and flagKindShift indicate various bits that the - // reflect package uses internally to track kind information. - // - // flagRO indicates whether or not the value field of a reflect.Value is - // read-only. - // - // flagIndir indicates whether the value field of a reflect.Value is - // the actual data or a pointer to the data. - // - // These values are valid before golang commit 90a7c3c86944 which - // changed their positions. Code in the init function updates these - // flags as necessary. - flagKindWidth = uintptr(5) - flagKindShift = uintptr(flagKindWidth - 1) - flagRO = uintptr(1 << 0) - flagIndir = uintptr(1 << 1) + // flagRO indicates whether the value field of a reflect.Value + // is read-only. + flagRO flag + + // flagAddr indicates whether the address of the reflect.Value's + // value may be taken. + flagAddr flag ) -func init() { - // Older versions of reflect.Value stored small integers directly in the - // ptr field (which is named val in the older versions). Versions - // between commits ecccf07e7f9d and 82f48826c6c7 added a new field named - // scalar for this purpose which unfortunately came before the flag - // field, so the offset of the flag field is different for those - // versions. - // - // This code constructs a new reflect.Value from a known small integer - // and checks if the size of the reflect.Value struct indicates it has - // the scalar field. When it does, the offsets are updated accordingly. - vv := reflect.ValueOf(0xf00) - if unsafe.Sizeof(vv) == (ptrSize * 4) { - offsetScalar = ptrSize * 2 - offsetFlag = ptrSize * 3 - } +// flagKindMask holds the bits that make up the kind +// part of the flags field. In all the supported versions, +// it is in the lower 5 bits. +const flagKindMask = flag(0x1f) - // Commit 90a7c3c86944 changed the flag positions such that the low - // order bits are the kind. This code extracts the kind from the flags - // field and ensures it's the correct type. When it's not, the flag - // order has been changed to the newer format, so the flags are updated - // accordingly. - upf := unsafe.Pointer(uintptr(unsafe.Pointer(&vv)) + offsetFlag) - upfv := *(*uintptr)(upf) - flagKindMask := uintptr((1<>flagKindShift != uintptr(reflect.Int) { - flagKindShift = 0 - flagRO = 1 << 5 - flagIndir = 1 << 6 - - // Commit adf9b30e5594 modified the flags to separate the - // flagRO flag into two bits which specifies whether or not the - // field is embedded. This causes flagIndir to move over a bit - // and means that flagRO is the combination of either of the - // original flagRO bit and the new bit. - // - // This code detects the change by extracting what used to be - // the indirect bit to ensure it's set. When it's not, the flag - // order has been changed to the newer format, so the flags are - // updated accordingly. - if upfv&flagIndir == 0 { - flagRO = 3 << 5 - flagIndir = 1 << 7 - } +// Different versions of Go have used different +// bit layouts for the flags type. This table +// records the known combinations. +var okFlags = []struct { + ro, addr flag +}{{ + // From Go 1.4 to 1.5 + ro: 1 << 5, + addr: 1 << 7, +}, { + // Up to Go tip. + ro: 1<<5 | 1<<6, + addr: 1 << 8, +}} + +var flagValOffset = func() uintptr { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") } + return field.Offset +}() + +// flagField returns a pointer to the flag field of a reflect.Value. +func flagField(v *reflect.Value) *flag { + return (*flag)(unsafe.Pointer(uintptr(unsafe.Pointer(v)) + flagValOffset)) } // unsafeReflectValue converts the passed reflect.Value into a one that bypasses @@ -119,34 +90,56 @@ func init() { // This allows us to check for implementations of the Stringer and error // interfaces to be used for pretty printing ordinarily unaddressable and // inaccessible values such as unexported struct fields. -func unsafeReflectValue(v reflect.Value) (rv reflect.Value) { - indirects := 1 - vt := v.Type() - upv := unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetPtr) - rvf := *(*uintptr)(unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + offsetFlag)) - if rvf&flagIndir != 0 { - vt = reflect.PtrTo(v.Type()) - indirects++ - } else if offsetScalar != 0 { - // The value is in the scalar field when it's not one of the - // reference types. - switch vt.Kind() { - case reflect.Uintptr: - case reflect.Chan: - case reflect.Func: - case reflect.Map: - case reflect.Ptr: - case reflect.UnsafePointer: - default: - upv = unsafe.Pointer(uintptr(unsafe.Pointer(&v)) + - offsetScalar) - } +func unsafeReflectValue(v reflect.Value) reflect.Value { + if !v.IsValid() || (v.CanInterface() && v.CanAddr()) { + return v } + flagFieldPtr := flagField(&v) + *flagFieldPtr &^= flagRO + *flagFieldPtr |= flagAddr + return v +} - pv := reflect.NewAt(vt, upv) - rv = pv - for i := 0; i < indirects; i++ { - rv = rv.Elem() +// Sanity checks against future reflect package changes +// to the type or semantics of the Value.flag field. +func init() { + field, ok := reflect.TypeOf(reflect.Value{}).FieldByName("flag") + if !ok { + panic("reflect.Value has no flag field") + } + if field.Type.Kind() != reflect.TypeOf(flag(0)).Kind() { + panic("reflect.Value flag field has changed kind") + } + type t0 int + var t struct { + A t0 + // t0 will have flagEmbedRO set. + t0 + // a will have flagStickyRO set + a t0 + } + vA := reflect.ValueOf(t).FieldByName("A") + va := reflect.ValueOf(t).FieldByName("a") + vt0 := reflect.ValueOf(t).FieldByName("t0") + + // Infer flagRO from the difference between the flags + // for the (otherwise identical) fields in t. + flagPublic := *flagField(&vA) + flagWithRO := *flagField(&va) | *flagField(&vt0) + flagRO = flagPublic ^ flagWithRO + + // Infer flagAddr from the difference between a value + // taken from a pointer and not. + vPtrA := reflect.ValueOf(&t).Elem().FieldByName("A") + flagNoPtr := *flagField(&vA) + flagPtr := *flagField(&vPtrA) + flagAddr = flagNoPtr ^ flagPtr + + // Check that the inferred flags tally with one of the known versions. + for _, f := range okFlags { + if flagRO == f.ro && flagAddr == f.addr { + return + } } - return rv + panic("reflect.Value read-only flag has changed semantics") } diff --git a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go index 1fe3cf3d..205c28d6 100644 --- a/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go +++ b/vendor/github.com/davecgh/go-spew/spew/bypasssafe.go @@ -16,7 +16,7 @@ // when the code is running on Google App Engine, compiled by GopherJS, or // "-tags safe" is added to the go build command line. The "disableunsafe" // tag is deprecated and thus should not be used. -// +build js appengine safe disableunsafe +// +build js appengine safe disableunsafe !go1.4 package spew diff --git a/vendor/github.com/davecgh/go-spew/spew/common.go b/vendor/github.com/davecgh/go-spew/spew/common.go index 7c519ff4..1be8ce94 100644 --- a/vendor/github.com/davecgh/go-spew/spew/common.go +++ b/vendor/github.com/davecgh/go-spew/spew/common.go @@ -180,7 +180,7 @@ func printComplex(w io.Writer, c complex128, floatPrecision int) { w.Write(closeParenBytes) } -// printHexPtr outputs a uintptr formatted as hexidecimal with a leading '0x' +// printHexPtr outputs a uintptr formatted as hexadecimal with a leading '0x' // prefix to Writer w. func printHexPtr(w io.Writer, p uintptr) { // Null pointer. diff --git a/vendor/github.com/davecgh/go-spew/spew/dump.go b/vendor/github.com/davecgh/go-spew/spew/dump.go index df1d582a..f78d89fc 100644 --- a/vendor/github.com/davecgh/go-spew/spew/dump.go +++ b/vendor/github.com/davecgh/go-spew/spew/dump.go @@ -35,16 +35,16 @@ var ( // cCharRE is a regular expression that matches a cgo char. // It is used to detect character arrays to hexdump them. - cCharRE = regexp.MustCompile("^.*\\._Ctype_char$") + cCharRE = regexp.MustCompile(`^.*\._Ctype_char$`) // cUnsignedCharRE is a regular expression that matches a cgo unsigned // char. It is used to detect unsigned character arrays to hexdump // them. - cUnsignedCharRE = regexp.MustCompile("^.*\\._Ctype_unsignedchar$") + cUnsignedCharRE = regexp.MustCompile(`^.*\._Ctype_unsignedchar$`) // cUint8tCharRE is a regular expression that matches a cgo uint8_t. // It is used to detect uint8_t arrays to hexdump them. - cUint8tCharRE = regexp.MustCompile("^.*\\._Ctype_uint8_t$") + cUint8tCharRE = regexp.MustCompile(`^.*\._Ctype_uint8_t$`) ) // dumpState contains information about the state of a dump operation. @@ -143,10 +143,10 @@ func (d *dumpState) dumpPtr(v reflect.Value) { // Display dereferenced value. d.w.Write(openParenBytes) switch { - case nilFound == true: + case nilFound: d.w.Write(nilAngleBytes) - case cycleFound == true: + case cycleFound: d.w.Write(circularBytes) default: diff --git a/vendor/github.com/davecgh/go-spew/spew/format.go b/vendor/github.com/davecgh/go-spew/spew/format.go index c49875ba..b04edb7d 100644 --- a/vendor/github.com/davecgh/go-spew/spew/format.go +++ b/vendor/github.com/davecgh/go-spew/spew/format.go @@ -182,10 +182,10 @@ func (f *formatState) formatPtr(v reflect.Value) { // Display dereferenced value. switch { - case nilFound == true: + case nilFound: f.fs.Write(nilAngleBytes) - case cycleFound == true: + case cycleFound: f.fs.Write(circularShortBytes) default: diff --git a/vendor/github.com/stretchr/testify/assert/assertion_format.go b/vendor/github.com/stretchr/testify/assert/assertion_format.go index aa1c2b95..e0364e9e 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_format.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_format.go @@ -113,6 +113,17 @@ func Errorf(t TestingT, err error, msg string, args ...interface{}) bool { return Error(t, err, append([]interface{}{msg}, args...)...) } +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventuallyf(t, func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func Eventuallyf(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Eventually(t, condition, waitFor, tick, append([]interface{}{msg}, args...)...) +} + // Exactlyf asserts that two objects are equal in value and type. // // assert.Exactlyf(t, int32(123, "error message %s", "formatted"), int64(123)) @@ -157,6 +168,31 @@ func FileExistsf(t TestingT, path string, msg string, args ...interface{}) bool return FileExists(t, path, append([]interface{}{msg}, args...)...) } +// Greaterf asserts that the first element is greater than the second +// +// assert.Greaterf(t, 2, 1, "error message %s", "formatted") +// assert.Greaterf(t, float64(2, "error message %s", "formatted"), float64(1)) +// assert.Greaterf(t, "b", "a", "error message %s", "formatted") +func Greaterf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Greater(t, e1, e2, append([]interface{}{msg}, args...)...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqualf(t, 2, 1, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "a", "error message %s", "formatted") +// assert.GreaterOrEqualf(t, "b", "b", "error message %s", "formatted") +func GreaterOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return GreaterOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) +} + // HTTPBodyContainsf asserts that a specified handler returns a // body that contains a string. // @@ -289,6 +325,14 @@ func JSONEqf(t TestingT, expected string, actual string, msg string, args ...int return JSONEq(t, expected, actual, append([]interface{}{msg}, args...)...) } +// YAMLEqf asserts that two YAML strings are equivalent. +func YAMLEqf(t TestingT, expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return YAMLEq(t, expected, actual, append([]interface{}{msg}, args...)...) +} + // Lenf asserts that the specified object has specific length. // Lenf also fails if the object has a type that len() not accept. // @@ -300,6 +344,31 @@ func Lenf(t TestingT, object interface{}, length int, msg string, args ...interf return Len(t, object, length, append([]interface{}{msg}, args...)...) } +// Lessf asserts that the first element is less than the second +// +// assert.Lessf(t, 1, 2, "error message %s", "formatted") +// assert.Lessf(t, float64(1, "error message %s", "formatted"), float64(2)) +// assert.Lessf(t, "a", "b", "error message %s", "formatted") +func Lessf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Less(t, e1, e2, append([]interface{}{msg}, args...)...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// assert.LessOrEqualf(t, 1, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, 2, 2, "error message %s", "formatted") +// assert.LessOrEqualf(t, "a", "b", "error message %s", "formatted") +// assert.LessOrEqualf(t, "b", "b", "error message %s", "formatted") +func LessOrEqualf(t TestingT, e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return LessOrEqual(t, e1, e2, append([]interface{}{msg}, args...)...) +} + // Nilf asserts that the specified object is nil. // // assert.Nilf(t, err, "error message %s", "formatted") @@ -444,6 +513,19 @@ func Regexpf(t TestingT, rx interface{}, str interface{}, msg string, args ...in return Regexp(t, rx, str, append([]interface{}{msg}, args...)...) } +// Samef asserts that two pointers reference the same object. +// +// assert.Samef(t, ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Samef(t TestingT, expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + return Same(t, expected, actual, append([]interface{}{msg}, args...)...) +} + // Subsetf asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // diff --git a/vendor/github.com/stretchr/testify/assert/assertion_forward.go b/vendor/github.com/stretchr/testify/assert/assertion_forward.go index de39f794..26830403 100644 --- a/vendor/github.com/stretchr/testify/assert/assertion_forward.go +++ b/vendor/github.com/stretchr/testify/assert/assertion_forward.go @@ -215,6 +215,28 @@ func (a *Assertions) Errorf(err error, msg string, args ...interface{}) bool { return Errorf(a.t, err, msg, args...) } +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventually(func() bool { return true; }, time.Second, 10*time.Millisecond) +func (a *Assertions) Eventually(condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Eventually(a.t, condition, waitFor, tick, msgAndArgs...) +} + +// Eventuallyf asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// a.Eventuallyf(func() bool { return true; }, time.Second, 10*time.Millisecond, "error message %s", "formatted") +func (a *Assertions) Eventuallyf(condition func() bool, waitFor time.Duration, tick time.Duration, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Eventuallyf(a.t, condition, waitFor, tick, msg, args...) +} + // Exactly asserts that two objects are equal in value and type. // // a.Exactly(int32(123), int64(123)) @@ -303,6 +325,56 @@ func (a *Assertions) FileExistsf(path string, msg string, args ...interface{}) b return FileExistsf(a.t, path, msg, args...) } +// Greater asserts that the first element is greater than the second +// +// a.Greater(2, 1) +// a.Greater(float64(2), float64(1)) +// a.Greater("b", "a") +func (a *Assertions) Greater(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Greater(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqual(2, 1) +// a.GreaterOrEqual(2, 2) +// a.GreaterOrEqual("b", "a") +// a.GreaterOrEqual("b", "b") +func (a *Assertions) GreaterOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return GreaterOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// GreaterOrEqualf asserts that the first element is greater than or equal to the second +// +// a.GreaterOrEqualf(2, 1, "error message %s", "formatted") +// a.GreaterOrEqualf(2, 2, "error message %s", "formatted") +// a.GreaterOrEqualf("b", "a", "error message %s", "formatted") +// a.GreaterOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) GreaterOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return GreaterOrEqualf(a.t, e1, e2, msg, args...) +} + +// Greaterf asserts that the first element is greater than the second +// +// a.Greaterf(2, 1, "error message %s", "formatted") +// a.Greaterf(float64(2, "error message %s", "formatted"), float64(1)) +// a.Greaterf("b", "a", "error message %s", "formatted") +func (a *Assertions) Greaterf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Greaterf(a.t, e1, e2, msg, args...) +} + // HTTPBodyContains asserts that a specified handler returns a // body that contains a string. // @@ -567,6 +639,22 @@ func (a *Assertions) JSONEqf(expected string, actual string, msg string, args .. return JSONEqf(a.t, expected, actual, msg, args...) } +// YAMLEq asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEq(expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return YAMLEq(a.t, expected, actual, msgAndArgs...) +} + +// YAMLEqf asserts that two YAML strings are equivalent. +func (a *Assertions) YAMLEqf(expected string, actual string, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return YAMLEqf(a.t, expected, actual, msg, args...) +} + // Len asserts that the specified object has specific length. // Len also fails if the object has a type that len() not accept. // @@ -589,6 +677,56 @@ func (a *Assertions) Lenf(object interface{}, length int, msg string, args ...in return Lenf(a.t, object, length, msg, args...) } +// Less asserts that the first element is less than the second +// +// a.Less(1, 2) +// a.Less(float64(1), float64(2)) +// a.Less("a", "b") +func (a *Assertions) Less(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Less(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// a.LessOrEqual(1, 2) +// a.LessOrEqual(2, 2) +// a.LessOrEqual("a", "b") +// a.LessOrEqual("b", "b") +func (a *Assertions) LessOrEqual(e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return LessOrEqual(a.t, e1, e2, msgAndArgs...) +} + +// LessOrEqualf asserts that the first element is less than or equal to the second +// +// a.LessOrEqualf(1, 2, "error message %s", "formatted") +// a.LessOrEqualf(2, 2, "error message %s", "formatted") +// a.LessOrEqualf("a", "b", "error message %s", "formatted") +// a.LessOrEqualf("b", "b", "error message %s", "formatted") +func (a *Assertions) LessOrEqualf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return LessOrEqualf(a.t, e1, e2, msg, args...) +} + +// Lessf asserts that the first element is less than the second +// +// a.Lessf(1, 2, "error message %s", "formatted") +// a.Lessf(float64(1, "error message %s", "formatted"), float64(2)) +// a.Lessf("a", "b", "error message %s", "formatted") +func (a *Assertions) Lessf(e1 interface{}, e2 interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Lessf(a.t, e1, e2, msg, args...) +} + // Nil asserts that the specified object is nil. // // a.Nil(err) @@ -877,6 +1015,32 @@ func (a *Assertions) Regexpf(rx interface{}, str interface{}, msg string, args . return Regexpf(a.t, rx, str, msg, args...) } +// Same asserts that two pointers reference the same object. +// +// a.Same(ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Same(expected interface{}, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Same(a.t, expected, actual, msgAndArgs...) +} + +// Samef asserts that two pointers reference the same object. +// +// a.Samef(ptr1, ptr2, "error message %s", "formatted") +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func (a *Assertions) Samef(expected interface{}, actual interface{}, msg string, args ...interface{}) bool { + if h, ok := a.t.(tHelper); ok { + h.Helper() + } + return Samef(a.t, expected, actual, msg, args...) +} + // Subset asserts that the specified list(array, slice...) contains all // elements given in the specified subset(array, slice...). // diff --git a/vendor/github.com/stretchr/testify/assert/assertion_order.go b/vendor/github.com/stretchr/testify/assert/assertion_order.go new file mode 100644 index 00000000..15a486ca --- /dev/null +++ b/vendor/github.com/stretchr/testify/assert/assertion_order.go @@ -0,0 +1,309 @@ +package assert + +import ( + "fmt" + "reflect" +) + +func compare(obj1, obj2 interface{}, kind reflect.Kind) (int, bool) { + switch kind { + case reflect.Int: + { + intobj1 := obj1.(int) + intobj2 := obj2.(int) + if intobj1 > intobj2 { + return -1, true + } + if intobj1 == intobj2 { + return 0, true + } + if intobj1 < intobj2 { + return 1, true + } + } + case reflect.Int8: + { + int8obj1 := obj1.(int8) + int8obj2 := obj2.(int8) + if int8obj1 > int8obj2 { + return -1, true + } + if int8obj1 == int8obj2 { + return 0, true + } + if int8obj1 < int8obj2 { + return 1, true + } + } + case reflect.Int16: + { + int16obj1 := obj1.(int16) + int16obj2 := obj2.(int16) + if int16obj1 > int16obj2 { + return -1, true + } + if int16obj1 == int16obj2 { + return 0, true + } + if int16obj1 < int16obj2 { + return 1, true + } + } + case reflect.Int32: + { + int32obj1 := obj1.(int32) + int32obj2 := obj2.(int32) + if int32obj1 > int32obj2 { + return -1, true + } + if int32obj1 == int32obj2 { + return 0, true + } + if int32obj1 < int32obj2 { + return 1, true + } + } + case reflect.Int64: + { + int64obj1 := obj1.(int64) + int64obj2 := obj2.(int64) + if int64obj1 > int64obj2 { + return -1, true + } + if int64obj1 == int64obj2 { + return 0, true + } + if int64obj1 < int64obj2 { + return 1, true + } + } + case reflect.Uint: + { + uintobj1 := obj1.(uint) + uintobj2 := obj2.(uint) + if uintobj1 > uintobj2 { + return -1, true + } + if uintobj1 == uintobj2 { + return 0, true + } + if uintobj1 < uintobj2 { + return 1, true + } + } + case reflect.Uint8: + { + uint8obj1 := obj1.(uint8) + uint8obj2 := obj2.(uint8) + if uint8obj1 > uint8obj2 { + return -1, true + } + if uint8obj1 == uint8obj2 { + return 0, true + } + if uint8obj1 < uint8obj2 { + return 1, true + } + } + case reflect.Uint16: + { + uint16obj1 := obj1.(uint16) + uint16obj2 := obj2.(uint16) + if uint16obj1 > uint16obj2 { + return -1, true + } + if uint16obj1 == uint16obj2 { + return 0, true + } + if uint16obj1 < uint16obj2 { + return 1, true + } + } + case reflect.Uint32: + { + uint32obj1 := obj1.(uint32) + uint32obj2 := obj2.(uint32) + if uint32obj1 > uint32obj2 { + return -1, true + } + if uint32obj1 == uint32obj2 { + return 0, true + } + if uint32obj1 < uint32obj2 { + return 1, true + } + } + case reflect.Uint64: + { + uint64obj1 := obj1.(uint64) + uint64obj2 := obj2.(uint64) + if uint64obj1 > uint64obj2 { + return -1, true + } + if uint64obj1 == uint64obj2 { + return 0, true + } + if uint64obj1 < uint64obj2 { + return 1, true + } + } + case reflect.Float32: + { + float32obj1 := obj1.(float32) + float32obj2 := obj2.(float32) + if float32obj1 > float32obj2 { + return -1, true + } + if float32obj1 == float32obj2 { + return 0, true + } + if float32obj1 < float32obj2 { + return 1, true + } + } + case reflect.Float64: + { + float64obj1 := obj1.(float64) + float64obj2 := obj2.(float64) + if float64obj1 > float64obj2 { + return -1, true + } + if float64obj1 == float64obj2 { + return 0, true + } + if float64obj1 < float64obj2 { + return 1, true + } + } + case reflect.String: + { + stringobj1 := obj1.(string) + stringobj2 := obj2.(string) + if stringobj1 > stringobj2 { + return -1, true + } + if stringobj1 == stringobj2 { + return 0, true + } + if stringobj1 < stringobj2 { + return 1, true + } + } + } + + return 0, false +} + +// Greater asserts that the first element is greater than the second +// +// assert.Greater(t, 2, 1) +// assert.Greater(t, float64(2), float64(1)) +// assert.Greater(t, "b", "a") +func Greater(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != -1 { + return Fail(t, fmt.Sprintf("\"%v\" is not greater than \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} + +// GreaterOrEqual asserts that the first element is greater than or equal to the second +// +// assert.GreaterOrEqual(t, 2, 1) +// assert.GreaterOrEqual(t, 2, 2) +// assert.GreaterOrEqual(t, "b", "a") +// assert.GreaterOrEqual(t, "b", "b") +func GreaterOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != -1 && res != 0 { + return Fail(t, fmt.Sprintf("\"%v\" is not greater than or equal to \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} + +// Less asserts that the first element is less than the second +// +// assert.Less(t, 1, 2) +// assert.Less(t, float64(1), float64(2)) +// assert.Less(t, "a", "b") +func Less(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != 1 { + return Fail(t, fmt.Sprintf("\"%v\" is not less than \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} + +// LessOrEqual asserts that the first element is less than or equal to the second +// +// assert.LessOrEqual(t, 1, 2) +// assert.LessOrEqual(t, 2, 2) +// assert.LessOrEqual(t, "a", "b") +// assert.LessOrEqual(t, "b", "b") +func LessOrEqual(t TestingT, e1 interface{}, e2 interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + e1Kind := reflect.ValueOf(e1).Kind() + e2Kind := reflect.ValueOf(e2).Kind() + if e1Kind != e2Kind { + return Fail(t, "Elements should be the same type", msgAndArgs...) + } + + res, isComparable := compare(e1, e2, e1Kind) + if !isComparable { + return Fail(t, fmt.Sprintf("Can not compare type \"%s\"", reflect.TypeOf(e1)), msgAndArgs...) + } + + if res != 1 && res != 0 { + return Fail(t, fmt.Sprintf("\"%v\" is not less than or equal to \"%v\"", e1, e2), msgAndArgs...) + } + + return true +} diff --git a/vendor/github.com/stretchr/testify/assert/assertions.go b/vendor/github.com/stretchr/testify/assert/assertions.go index 9bd4a80e..044da8b0 100644 --- a/vendor/github.com/stretchr/testify/assert/assertions.go +++ b/vendor/github.com/stretchr/testify/assert/assertions.go @@ -18,6 +18,7 @@ import ( "github.com/davecgh/go-spew/spew" "github.com/pmezard/go-difflib/difflib" + yaml "gopkg.in/yaml.v2" ) //go:generate go run ../_codegen/main.go -output-package=assert -template=assertion_format.go.tmpl @@ -350,6 +351,37 @@ func Equal(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) } +// Same asserts that two pointers reference the same object. +// +// assert.Same(t, ptr1, ptr2) +// +// Both arguments must be pointer variables. Pointer variable sameness is +// determined based on the equality of both type and value. +func Same(t TestingT, expected, actual interface{}, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + expectedPtr, actualPtr := reflect.ValueOf(expected), reflect.ValueOf(actual) + if expectedPtr.Kind() != reflect.Ptr || actualPtr.Kind() != reflect.Ptr { + return Fail(t, "Invalid operation: both arguments must be pointers", msgAndArgs...) + } + + expectedType, actualType := reflect.TypeOf(expected), reflect.TypeOf(actual) + if expectedType != actualType { + return Fail(t, fmt.Sprintf("Pointer expected to be of type %v, but was %v", + expectedType, actualType), msgAndArgs...) + } + + if expected != actual { + return Fail(t, fmt.Sprintf("Not same: \n"+ + "expected: %p %#v\n"+ + "actual : %p %#v", expected, expected, actual, actual), msgAndArgs...) + } + + return true +} + // formatUnequalValues takes two values of arbitrary types and returns string // representations appropriate to be presented to the user. // @@ -479,14 +511,14 @@ func isEmpty(object interface{}) bool { // collection types are empty when they have no element case reflect.Array, reflect.Chan, reflect.Map, reflect.Slice: return objValue.Len() == 0 - // pointers are empty if nil or if the value they point to is empty + // pointers are empty if nil or if the value they point to is empty case reflect.Ptr: if objValue.IsNil() { return true } deref := objValue.Elem().Interface() return isEmpty(deref) - // for all other types, compare against the zero value + // for all other types, compare against the zero value default: zero := reflect.Zero(objValue.Type()) return reflect.DeepEqual(object, zero.Interface()) @@ -629,7 +661,7 @@ func NotEqual(t TestingT, expected, actual interface{}, msgAndArgs ...interface{ func includeElement(list interface{}, element interface{}) (ok, found bool) { listValue := reflect.ValueOf(list) - elementValue := reflect.ValueOf(element) + listKind := reflect.TypeOf(list).Kind() defer func() { if e := recover(); e != nil { ok = false @@ -637,11 +669,12 @@ func includeElement(list interface{}, element interface{}) (ok, found bool) { } }() - if reflect.TypeOf(list).Kind() == reflect.String { + if listKind == reflect.String { + elementValue := reflect.ValueOf(element) return true, strings.Contains(listValue.String(), elementValue.String()) } - if reflect.TypeOf(list).Kind() == reflect.Map { + if listKind == reflect.Map { mapKeys := listValue.MapKeys() for i := 0; i < len(mapKeys); i++ { if ObjectsAreEqual(mapKeys[i].Interface(), element) { @@ -1337,6 +1370,24 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{ return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...) } +// YAMLEq asserts that two YAML strings are equivalent. +func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + var expectedYAMLAsInterface, actualYAMLAsInterface interface{} + + if err := yaml.Unmarshal([]byte(expected), &expectedYAMLAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid yaml.\nYAML parsing error: '%s'", expected, err.Error()), msgAndArgs...) + } + + if err := yaml.Unmarshal([]byte(actual), &actualYAMLAsInterface); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid yaml.\nYAML error: '%s'", actual, err.Error()), msgAndArgs...) + } + + return Equal(t, expectedYAMLAsInterface, actualYAMLAsInterface, msgAndArgs...) +} + func typeAndKind(v interface{}) (reflect.Type, reflect.Kind) { t := reflect.TypeOf(v) k := t.Kind() @@ -1371,8 +1422,8 @@ func diff(expected interface{}, actual interface{}) string { e = spewConfig.Sdump(expected) a = spewConfig.Sdump(actual) } else { - e = expected.(string) - a = actual.(string) + e = reflect.ValueOf(expected).String() + a = reflect.ValueOf(actual).String() } diff, _ := difflib.GetUnifiedDiffString(difflib.UnifiedDiff{ @@ -1414,3 +1465,34 @@ var spewConfig = spew.ConfigState{ type tHelper interface { Helper() } + +// Eventually asserts that given condition will be met in waitFor time, +// periodically checking target function each tick. +// +// assert.Eventually(t, func() bool { return true; }, time.Second, 10*time.Millisecond) +func Eventually(t TestingT, condition func() bool, waitFor time.Duration, tick time.Duration, msgAndArgs ...interface{}) bool { + if h, ok := t.(tHelper); ok { + h.Helper() + } + + timer := time.NewTimer(waitFor) + ticker := time.NewTicker(tick) + checkPassed := make(chan bool) + defer timer.Stop() + defer ticker.Stop() + defer close(checkPassed) + for { + select { + case <-timer.C: + return Fail(t, "Condition never satisfied", msgAndArgs...) + case result := <-checkPassed: + if result { + return true + } + case <-ticker.C: + go func() { + checkPassed <- condition() + }() + } + } +} diff --git a/vendor/gopkg.in/yaml.v2/decode.go b/vendor/gopkg.in/yaml.v2/decode.go index e4e56e28..53108765 100644 --- a/vendor/gopkg.in/yaml.v2/decode.go +++ b/vendor/gopkg.in/yaml.v2/decode.go @@ -229,6 +229,10 @@ type decoder struct { mapType reflect.Type terrors []string strict bool + + decodeCount int + aliasCount int + aliasDepth int } var ( @@ -314,7 +318,39 @@ func (d *decoder) prepare(n *node, out reflect.Value) (newout reflect.Value, unm return out, false, false } +const ( + // 400,000 decode operations is ~500kb of dense object declarations, or ~5kb of dense object declarations with 10000% alias expansion + alias_ratio_range_low = 400000 + // 4,000,000 decode operations is ~5MB of dense object declarations, or ~4.5MB of dense object declarations with 10% alias expansion + alias_ratio_range_high = 4000000 + // alias_ratio_range is the range over which we scale allowed alias ratios + alias_ratio_range = float64(alias_ratio_range_high - alias_ratio_range_low) +) + +func allowedAliasRatio(decodeCount int) float64 { + switch { + case decodeCount <= alias_ratio_range_low: + // allow 99% to come from alias expansion for small-to-medium documents + return 0.99 + case decodeCount >= alias_ratio_range_high: + // allow 10% to come from alias expansion for very large documents + return 0.10 + default: + // scale smoothly from 99% down to 10% over the range. + // this maps to 396,000 - 400,000 allowed alias-driven decodes over the range. + // 400,000 decode operations is ~100MB of allocations in worst-case scenarios (single-item maps). + return 0.99 - 0.89*(float64(decodeCount-alias_ratio_range_low)/alias_ratio_range) + } +} + func (d *decoder) unmarshal(n *node, out reflect.Value) (good bool) { + d.decodeCount++ + if d.aliasDepth > 0 { + d.aliasCount++ + } + if d.aliasCount > 100 && d.decodeCount > 1000 && float64(d.aliasCount)/float64(d.decodeCount) > allowedAliasRatio(d.decodeCount) { + failf("document contains excessive aliasing") + } switch n.kind { case documentNode: return d.document(n, out) @@ -353,7 +389,9 @@ func (d *decoder) alias(n *node, out reflect.Value) (good bool) { failf("anchor '%s' value contains itself", n.value) } d.aliases[n] = true + d.aliasDepth++ good = d.unmarshal(n.alias, out) + d.aliasDepth-- delete(d.aliases, n) return good } diff --git a/vendor/gopkg.in/yaml.v2/resolve.go b/vendor/gopkg.in/yaml.v2/resolve.go index 6c151db6..4120e0c9 100644 --- a/vendor/gopkg.in/yaml.v2/resolve.go +++ b/vendor/gopkg.in/yaml.v2/resolve.go @@ -81,7 +81,7 @@ func resolvableTag(tag string) bool { return false } -var yamlStyleFloat = regexp.MustCompile(`^[-+]?[0-9]*\.?[0-9]+([eE][-+][0-9]+)?$`) +var yamlStyleFloat = regexp.MustCompile(`^[-+]?(\.[0-9]+|[0-9]+(\.[0-9]*)?)([eE][-+]?[0-9]+)?$`) func resolve(tag string, in string) (rtag string, out interface{}) { if !resolvableTag(tag) { diff --git a/vendor/gopkg.in/yaml.v2/scannerc.go b/vendor/gopkg.in/yaml.v2/scannerc.go index 077fd1dd..570b8ecd 100644 --- a/vendor/gopkg.in/yaml.v2/scannerc.go +++ b/vendor/gopkg.in/yaml.v2/scannerc.go @@ -906,6 +906,9 @@ func yaml_parser_remove_simple_key(parser *yaml_parser_t) bool { return true } +// max_flow_level limits the flow_level +const max_flow_level = 10000 + // Increase the flow level and resize the simple key list if needed. func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { // Reset the simple key on the next level. @@ -913,6 +916,11 @@ func yaml_parser_increase_flow_level(parser *yaml_parser_t) bool { // Increase the flow level. parser.flow_level++ + if parser.flow_level > max_flow_level { + return yaml_parser_set_scanner_error(parser, + "while increasing flow level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_flow_level)) + } return true } @@ -925,6 +933,9 @@ func yaml_parser_decrease_flow_level(parser *yaml_parser_t) bool { return true } +// max_indents limits the indents stack size +const max_indents = 10000 + // Push the current indentation level to the stack and set the new level // the current column is greater than the indentation level. In this case, // append or insert the specified token into the token queue. @@ -939,6 +950,11 @@ func yaml_parser_roll_indent(parser *yaml_parser_t, column, number int, typ yaml // indentation level. parser.indents = append(parser.indents, parser.indent) parser.indent = column + if len(parser.indents) > max_indents { + return yaml_parser_set_scanner_error(parser, + "while increasing indent level", parser.simple_keys[len(parser.simple_keys)-1].mark, + fmt.Sprintf("exceeded max depth of %d", max_indents)) + } // Create a token and insert it into the queue. token := yaml_token_t{ diff --git a/vendor/modules.txt b/vendor/modules.txt index 7e18dcf7..75f855b0 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1,14 +1,16 @@ +# github.com/SimonBaeumer/cmd v1.1.0 +github.com/SimonBaeumer/cmd # github.com/antchfx/xmlquery v1.1.0 github.com/antchfx/xmlquery # github.com/antchfx/xpath v1.1.0 github.com/antchfx/xpath -# github.com/davecgh/go-spew v1.1.0 +# github.com/davecgh/go-spew v1.1.1 github.com/davecgh/go-spew/spew # github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e github.com/logrusorgru/aurora # github.com/pmezard/go-difflib v1.0.0 github.com/pmezard/go-difflib/difflib -# github.com/stretchr/testify v1.3.0 +# github.com/stretchr/testify v1.4.0 github.com/stretchr/testify/assert # github.com/tidwall/gjson v1.3.2 github.com/tidwall/gjson @@ -38,5 +40,5 @@ golang.org/x/text/language golang.org/x/text/internal/utf8internal golang.org/x/text/runes golang.org/x/text/internal/tag -# gopkg.in/yaml.v2 v2.2.2 +# gopkg.in/yaml.v2 v2.2.4 gopkg.in/yaml.v2 From e4bba174220ed7ec735dc5cbe720ab84a43a6e33 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 12:08:06 +0200 Subject: [PATCH 02/17] Add inheritance logic --- integration/unix/commander_test.yaml | 9 +++- pkg/runtime/runtime.go | 80 +++++++++++++++++++--------- pkg/runtime/runtime_test.go | 38 +++++++++++++ pkg/suite/yaml_suite.go | 22 ++++---- 4 files changed, 114 insertions(+), 35 deletions(-) diff --git a/integration/unix/commander_test.yaml b/integration/unix/commander_test.yaml index 6c7424cb..529381fe 100644 --- a/integration/unix/commander_test.yaml +++ b/integration/unix/commander_test.yaml @@ -57,4 +57,11 @@ tests: stdout: xml: /books/0/author: J. R. R. Tokien - /books/1/author: Joanne K. Rowling \ No newline at end of file + /books/1/author: Joanne K. Rowling + + it should inhereit from parent env: + config: + inherit-env: true + command: echo $USER + stdout: simon + exit-code: 0 \ No newline at end of file diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index ad33c271..50af30ca 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -4,6 +4,7 @@ import ( "fmt" "github.com/SimonBaeumer/cmd" "log" + "os" "runtime" "strings" "sync" @@ -40,11 +41,12 @@ type TestCase struct { //TestConfig represents the configuration for a test type TestConfig struct { - Env map[string]string - Dir string - Timeout string - Retries int - Interval string + Env map[string]string + Dir string + Timeout string + Retries int + Interval string + InheritEnv bool } // ResultStatus represents the status code of a test result @@ -81,12 +83,13 @@ type ExpectedOut struct { // CommandUnderTest represents the command under test type CommandUnderTest struct { - Cmd string - Env map[string]string - Dir string - Timeout string - Retries int - Interval string + Cmd string + InheritEnv bool + Env map[string]string + Dir string + Timeout string + Retries int + Interval string } // TestResult represents the TestCase and the ValidationResult @@ -157,23 +160,22 @@ func executeRetryInterval(t TestCase) { // runTest executes the current test case func runTest(test TestCase) TestResult { - timeoutOpt := cmd.WithoutTimeout - if test.Command.Timeout != "" { - d, err := time.ParseDuration(test.Command.Timeout) - if err != nil { - test.Result = CommandResult{Error: err} - return TestResult{ - TestCase: test, - } + timeoutOpt, err := createTimeoutOption(test.Command.Timeout) + if err != nil { + test.Result = CommandResult{Error: err} + return TestResult{ + TestCase: test, } - timeoutOpt = cmd.WithTimeout(d) } + envOpt := createEnvVarsOption(test) + // cut = command under test - cut := cmd.NewCommand(test.Command.Cmd, timeoutOpt, cmd.WithWorkingDir(test.Command.Dir)) - for k, v := range test.Command.Env { - cut.AddEnv(k, v) - } + cut := cmd.NewCommand( + test.Command.Cmd, + cmd.WithWorkingDir(test.Command.Dir), + timeoutOpt, + envOpt) if err := cut.Execute(); err != nil { log.Println(test.Title, " failed ", err.Error()) @@ -204,6 +206,36 @@ func runTest(test TestCase) TestResult { return Validate(test) } +func createEnvVarsOption(test TestCase) func(c *cmd.Command) { + return func(c *cmd.Command) { + // Add all env variables from parent process + if test.Command.InheritEnv { + for _, v := range os.Environ() { + split := strings.Split(v, "=") + c.AddEnv(split[0], split[1]) + } + } + + // Add custom env variables + for k, v := range test.Command.Env { + c.AddEnv(k, v) + } + } +} + +func createTimeoutOption(timeout string) (func(c *cmd.Command), error) { + timeoutOpt := cmd.WithoutTimeout + if timeout != "" { + d, err := time.ParseDuration(timeout) + if err != nil { + return func(c *cmd.Command) {}, err + } + timeoutOpt = cmd.WithTimeout(d) + } + + return timeoutOpt, nil +} + // GetRetries returns the retries of the command func (c *CommandUnderTest) GetRetries() int { if c.Retries == 0 { diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 332c5474..7e6899b3 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -3,6 +3,7 @@ package runtime import ( "fmt" "github.com/stretchr/testify/assert" + "os" "runtime" "testing" "time" @@ -118,6 +119,43 @@ func TestRuntime_WithInvalidDuration(t *testing.T) { assert.Equal(t, "time: unknown unit lightyears in duration 600lightyears", got.TestCase.Result.Error.Error()) } +func TestRuntime_WithInheritFromShell(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER", + InheritEnv: true, + }, + } + + got := runTest(test) + + assert.Equal(t, "test", got.TestCase.Result.Stdout) +} + +func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) +} + func getExampleTestSuite() []TestCase { tests := []TestCase{ { diff --git a/pkg/suite/yaml_suite.go b/pkg/suite/yaml_suite.go index 76361f96..fe5ef713 100644 --- a/pkg/suite/yaml_suite.go +++ b/pkg/suite/yaml_suite.go @@ -16,11 +16,12 @@ type YAMLConfig struct { // YAMLTestConfig is a struct to represent the test config type YAMLTestConfig struct { - Env map[string]string `yaml:"env,omitempty"` - Dir string `yaml:"dir,omitempty"` - Timeout string `yaml:"timeout,omitempty"` - Retries int `yaml:"retries,omitempty"` - Interval string `yaml:"interval,omitempty"` + InheritEnv bool `yaml:"inherit-env,omitempty"` + Env map[string]string `yaml:"env,omitempty"` + Dir string `yaml:"dir,omitempty"` + Timeout string `yaml:"timeout,omitempty"` + Retries int `yaml:"retries,omitempty"` + Interval string `yaml:"interval,omitempty"` } // YAMLTest represents a test in the yaml test suite @@ -71,11 +72,12 @@ func ParseYAML(content []byte) Suite { return YAMLSuite{ TestCases: convertYAMLConfToTestCases(yamlConfig), Config: runtime.TestConfig{ - Env: yamlConfig.Config.Env, - Dir: yamlConfig.Config.Dir, - Timeout: yamlConfig.Config.Timeout, - Retries: yamlConfig.Config.Retries, - Interval: yamlConfig.Config.Interval, + InheritEnv: yamlConfig.Config.InheritEnv, + Env: yamlConfig.Config.Env, + Dir: yamlConfig.Config.Dir, + Timeout: yamlConfig.Config.Timeout, + Retries: yamlConfig.Config.Retries, + Interval: yamlConfig.Config.Interval, }, } } From f24a62375348b110adef0633f97070894ce8d589 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 19:21:22 +0200 Subject: [PATCH 03/17] Add inherit-env to yaml parsing --- pkg/suite/yaml_suite.go | 28 +++++++++++++++++----------- pkg/suite/yaml_suite_test.go | 3 +++ 2 files changed, 20 insertions(+), 11 deletions(-) diff --git a/pkg/suite/yaml_suite.go b/pkg/suite/yaml_suite.go index fe5ef713..480f8b38 100644 --- a/pkg/suite/yaml_suite.go +++ b/pkg/suite/yaml_suite.go @@ -89,12 +89,13 @@ func convertYAMLConfToTestCases(conf YAMLConfig) []runtime.TestCase { tests = append(tests, runtime.TestCase{ Title: t.Title, Command: runtime.CommandUnderTest{ - Cmd: t.Command, - Env: t.Config.Env, - Dir: t.Config.Dir, - Timeout: t.Config.Timeout, - Retries: t.Config.Retries, - Interval: t.Config.Interval, + Cmd: t.Command, + InheritEnv: t.Config.InheritEnv, + Env: t.Config.Env, + Dir: t.Config.Dir, + Timeout: t.Config.Timeout, + Retries: t.Config.Retries, + Interval: t.Config.Interval, }, Expected: runtime.Expected{ ExitCode: t.ExitCode, @@ -146,11 +147,12 @@ func (y *YAMLConfig) UnmarshalYAML(unmarshal func(interface{}) error) error { //Parse global configuration y.Config = YAMLTestConfig{ - Env: params.Config.Env, - Dir: params.Config.Dir, - Timeout: params.Config.Timeout, - Retries: params.Config.Retries, - Interval: params.Config.Interval, + InheritEnv: params.Config.InheritEnv, + Env: params.Config.Env, + Dir: params.Config.Dir, + Timeout: params.Config.Timeout, + Retries: params.Config.Retries, + Interval: params.Config.Interval, } return nil @@ -261,6 +263,10 @@ func (y *YAMLConfig) mergeConfigs(local YAMLTestConfig, global YAMLTestConfig) Y conf.Interval = local.Interval } + if local.InheritEnv { + conf.InheritEnv = local.InheritEnv + } + return conf } diff --git a/pkg/suite/yaml_suite_test.go b/pkg/suite/yaml_suite_test.go index 1dd1f72a..18f6ad35 100644 --- a/pkg/suite/yaml_suite_test.go +++ b/pkg/suite/yaml_suite_test.go @@ -188,6 +188,7 @@ config: timeout: 10ms retries: 2 interval: 500ms + inherit-env: true tests: echo hello: @@ -207,12 +208,14 @@ tests: assert.Equal(t, "10ms", got.GetGlobalConfig().Timeout) assert.Equal(t, 2, got.GetGlobalConfig().Retries) assert.Equal(t, "500ms", got.GetGlobalConfig().Interval) + assert.True(t, got.GetGlobalConfig().InheritEnv) assert.Equal(t, map[string]string{"KEY": "local", "ANOTHER_KEY": "another_global"}, got.GetTests()[0].Command.Env) assert.Equal(t, "/home/test", got.GetTests()[0].Command.Dir) assert.Equal(t, "1s", got.GetTests()[0].Command.Timeout) assert.Equal(t, 10, got.GetTests()[0].Command.Retries) assert.Equal(t, "5s", got.GetTests()[0].Command.Interval) + assert.True(t, got.GetTests()[0].Command.InheritEnv) } func TestYAMLSuite_ShouldThrowAnErrorIfFieldIsNotRegistered(t *testing.T) { From f78d0042c59f34646770a1c23d66c1655c28cf4a Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 19:21:32 +0200 Subject: [PATCH 04/17] Add documentation --- README.md | 438 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 431 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index a76f219d..1cd01a1a 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,6 @@ # Commander - Define language independent tests for your command line scripts and programs in simple `yaml` files. - It runs on `windows`, `osx` and `linux` @@ -16,6 +15,42 @@ Define language independent tests for your command line scripts and programs in For more information take a look at the [manual](docs/manual.md), the [examples](examples) or the [integration tests](integration). +## Table of contents + +* [Installation](#installation) + + [Any system with Go installed](#any-system-with-go-installed) + + [Linux & osx](#linux---osx) + + [Windows](#windows) +* [Quick start](#quick-start) + + [Complete YAML file](#complete-yaml-file) + + [Executing](#executing) + + [Adding tests](#adding-tests) +* [Documentation](#documentation) + + [Usage](#usage) + + [Config](#config) + - [dir](#dir) + - [env](#env) + - [inherit-env](#inherit-env) + - [interval](#interval) + - [retries](#retries) + - [timeout](#timeout) + + [Tests](#tests) + * [test case](#test-case) + - [command](#command) + - [config](#config) + - [exit-code](#exit-code) + - [stdout](#stdout) + + [contains](#contains) + + [exactly](#exactly) + + [json](#json) + + [lines](#lines) + + [line-count](#line-count) + + [not-contains](#not-contains) + + [xml](#xml) + - [stderr](#stderr) + + [Development](#development) +* [Misc](#misc) + ## Installation ### Any system with Go installed @@ -69,7 +104,7 @@ Duration: 0.002s Count: 1, Failed: 0 ``` -## Complete YAML file +### Complete YAML file Here you can see an example with all features. @@ -111,6 +146,7 @@ tests: exactly: hello line-count: 1 config: + inherit-env: true # You can inherit the parent shells env variables dir: /home/user # Overwrite working dir env: KEY: local # Overwrite env variable @@ -120,7 +156,7 @@ tests: exit-code: 0 ``` -## Executing +### Executing ```bash # Execute file commander.yaml in current directory @@ -133,7 +169,7 @@ $ ./commander test /tmp/test.yaml $ ./commander test /tmp/test.yaml "my test" ``` -## Adding tests +### Adding tests You can use the `add` argument if you want to `commander` to create your tests. @@ -163,7 +199,9 @@ tests: stdout: hello ``` -## Usage +## Documentation + +### Usage ``` NAME: @@ -182,8 +220,394 @@ GLOBAL OPTIONS: --version, -v print the version ``` +### Config + +You can add configs to which apply to all tests or just for a specific test case like: + +```yaml +config: + dir: /home/root # Set working directory +tests: + echo hello: + config: # Define test specific configs which overwrite global configs + timeout: 5s + exit-code: 0 +``` + +#### dir + +`dir` is a `string` which sets the current working directory for the command under test. +The test will fail if the given directory does not exist. + + - name: `dir` + - type: `string` + - default: `current working dir` + +```yaml +dir: /home/root +``` + +#### env + +`env` is a `hash-map` which is used to set custom env variables. The `key` represents the variable name and the `value` setting the value of the env variable. + + - name: `env` + - type: `hash-map` + - default: `{}` + - notes: + - read env variables with `${PATH}` + - overwrites inherited variables, see [#inherit-env](#inherit-env) + +```yaml +env: + VAR_NAME: my value # Set custom env var + CURRENT_USER: ${USER} # Set env var and read from current env +``` + +#### inherit-env + +`inherit-env` is a `boolean` type which allows you to inherit all environment variables from your active shell. + + - name: `inherit-env` + - type: `bool` + - default: `false` + - notes: If this config is set to `true` in the global configuration it will be applied for all tests and ignores local test configs. + +```yaml +inherit-env: true +``` + +#### interval + +`interval` is a `string` type and sets the `interval` between [retries](#retries). + + - name: `interval` + - type: `string` + - default: `0ns` + - notes: + - valid time units: ns, us, µs, ms, s, m, h + - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration) + +```yaml +interval: 5s # Waits 5 seconds until the next try after a failed test is started +``` + +#### retries + +`retries` is an `int` type and configures how often a test is allowed to fail until it will be marked as failed for the whole test run. + + - name: `retries` + - type: `int` + - default: `0` + - notes: [interval](#interval) can be defined between retry executions + +```yaml +retries: 3 # Test will be executed 3 times or until it succeeds +``` + +#### timeout + +`timeout` is a `string` type and sets the time a test is allowed to run. +The time is parsed from a duration string like `300ms`. +If a tests exceeds the given `timeout` the test will fail. + + - name: `timeout` + - type: `string` + - default: `no limit` + - notes: + - valid time units: ns, us, µs, ms, s, m, h + - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration) + +```yaml +timeout: 600s +``` + +### Tests + +Tests define the commands to be executed. After the execution the defined assertions will be checked. +If the result fo the command matches with the expected values a test passes. + +All `tests` are defined in a `map` as a `root` element of the `yaml` file. + +```yaml +tests: + echo test: + stdout: test + exit-code: 0 +``` + +A test case is `map` type which configures a test. +The `key` of the test can either be the `command` itself or the `title` of the test. + +If the same `command` is tested multiple times it is useful to set the `title` of the test manually and use the `command` property. +Further the `title` can be useful to describe tests better. See [the commander test suite](commander_unix.yaml) as an example. + + - name: `title or command under test` + - type: `map` + - default: `{}` + +Examples: + +```yaml +tests: + echo test: # command and title will be the same + stdout: test + exit-code: 0 + + my title: # custom title + command: exit 1 # set command manually + exit-code: 1 +``` + +#### command + +`command` is a `string` containing the `command` to be tested. Further the `command` property is automatically parsed from +the `key` if no `command` property was given. + + - name: `command` + - type: `string` + - default: `can't be empty` + - notes: Will be parsed as the `key` if no `command` property was provided + + +```yaml +echo test: # use command as key and title + exit-code: 0 + +it should print hello world: # use a more descriptive title... + command: echo hello world # ... and set the command in the property manually + stdout: hello world + exit-code: 0 +``` + +#### config + +`config` sets configuration for the test. `config` can overwrite global configurations. + + - name: `config` + - type: `map` + - default: `{}` + - notes: + - for more information look at [config](#Config) + +```yaml +echo test: + config: + timeout: 5s +``` + +#### exit-code + +`exit-code` is an `int` type and compares the given code to the `exit-code` of the given command. + + - name: `exit-code` + - type: `int` + - default: `0` + +```yaml +exit 1: # will pass + exit-code: 1 +exit 0: # will fail + exit-code: 1 +``` + +#### stdout + +`stdout` and `stderr` allow to make assertions on the output of the command. +The type can either be a `string` or a `map` of different assertions. + +If only a `string` is provided it will check if the given string is [contained](#contains) in the output. + + - name: `stdout` + - type: `string` or `map` + - default: ` ` + - notes: [stderr](#stderr) works the same way + +```yaml +echo test: + stdout: test # make a contains assertion + +echo hello world: + stdout: + line-count: 1 # assert the amount of lines and use stdout as a map +``` + +###### contains + +`contains` is an `array` or `string`. It checks if a `string` is contained in the output. +It is the default if a `string` is directly assigned to `stdout` or `stderr`. + + - name: `contains` + - type: `string` or `array` + - default: `[]` + - notes: default assertion if directly assigned to `stdout` or `stderr` + +```yaml +echo hello world: + stdout: hello # Default is a contains assertion + +echo more output: + stdout: + contains: + - more + - output +``` + +###### exactly + +`exactly` is a `string` type which matches the exact output. + + - name: `exactly` + - type: `string` + - default: ` ` + +```yaml +echo test: + stdout: + exactly: test +``` + +###### json + +`json` is a `map` type and allows to parse `json` documents with a given `GJSON syntax` to query for specific data. +The `key` represents the query, the `value` the expected value. + + - name: `json` + - type: `map` + - default: `{}` + - notes: Syntax taken from [GJSON](https://github.com/tidwall/gjson#path-syntax) + +```yaml +cat some.json: # print json file to stdout + name.last: Anderson # assert on name.last, see document below +``` + +`some.json` file: + +```json +{ + "name": {"first": "Tom", "last": "Anderson"}, + "age":37, + "children": ["Sara","Alex","Jack"], + "fav.movie": "Deer Hunter", + "friends": [ + {"first": "Dale", "last": "Murphy", "age": 44, "nets": ["ig", "fb", "tw"]}, + {"first": "Roger", "last": "Craig", "age": 68, "nets": ["fb", "tw"]}, + {"first": "Jane", "last": "Murphy", "age": 47, "nets": ["ig", "tw"]} + ] +} +``` + +More examples queries: + +``` +"name.last" >> "Anderson" +"age" >> 37 +"children" >> ["Sara","Alex","Jack"] +"children.#" >> 3 +"children.1" >> "Alex" +"child*.2" >> "Jack" +"c?ildren.0" >> "Sara" +"fav\.movie" >> "Deer Hunter" +"friends.#.first" >> ["Dale","Roger","Jane"] +"friends.1.last" >> "Craig" +``` + +###### lines + +`lines` is a `map` which is make exact assertions on a given line by line number. + + - name: `lines` + - type: `map` + - default: `{}` + - note: starts counting at `1` ;-) + +```yaml +echo test\nline 2: + stdout: + lines: + 2: line 2 # asserts only the second line +``` + +###### line-count + +`line-count` asserts the amount of lines printed to the output. If set to `0` this property is ignored. + + - name: `line-count` + - type: `int` + - default: `0` + +```yaml +echo test\nline 2: + stdout: + line-count: 2 +``` + +###### not-contains + +`not-contains` is a `array` of elements which are not allowed to be contained in the output. +It is the inversion of [contains](#contains). + + - name: `not-contains` + - type: `array` + - default: `[]` + +```yaml +echo hello: + stdout: + not-contains: bonjour # test passes because bonjour does not occur in the output + +echo bonjour: + stdout: + not-contains: bonjour # test fails because bonjour occurs in the output +``` + +###### xml + +`xml` is a `map` which allows to query `xml` documents viá `xpath` queries. +Like the [json][#json] assertion this uses the `key` of the map as the query parameter to, the `value` is the expected value. + + - name: `xml` + - type: `map` + - default: `{}` + - notes: Used library [xmlquery](https://github.com/antchfx/xmlquery) + +```yaml +cat some.xml: + stdout: + xml: + //book//author: J. R. R. Tolkien +``` + +`some.xml` file: + +```xml + + J. R. R. Tolkien + +``` + +##### stderr + +See [stdout](#stdout) for more information. + + - name: `stderr` + - type: `string` or `map` + - default: ` ` + - notes: is identical to [stdout](#stdout) + +```yaml +# >&2 echos directly to stderr +">&2 echo error": + stderr: error + exit-code: 0 + +">&2 echo more errors": + stderr: + line-count: 1 +``` -## Development +### Development ``` # Initialise dev environment @@ -205,7 +629,7 @@ $ make integration $ make deps ``` -# Misc +## Misc Heavily inspired by [goss](https://github.com/aelsabbahy/goss). From 4c4d00e65ecef32a7bfc9b9fa7c09895715f9110 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 22:18:34 +0200 Subject: [PATCH 05/17] Fix inhereit-env integration test The test "it should inherit from parent env" could not find the $USER env variable because commander resettet them in the parent test suite. --- commander_unix.yaml | 5 ++++- integration/unix/commander_test.yaml | 4 ++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/commander_unix.yaml b/commander_unix.yaml index 836075ad..0144f31e 100644 --- a/commander_unix.yaml +++ b/commander_unix.yaml @@ -8,11 +8,14 @@ tests: exit-code: 0 it should execute tests: + config: + env: + USER: from_parent command: ./commander test ./integration/unix/commander_test.yaml stdout: contains: - ✓ it should exit with error code - line-count: 15 + line-count: 16 exit-code: 0 it should assert that commander will fail: diff --git a/integration/unix/commander_test.yaml b/integration/unix/commander_test.yaml index 529381fe..d3170971 100644 --- a/integration/unix/commander_test.yaml +++ b/integration/unix/commander_test.yaml @@ -59,9 +59,9 @@ tests: /books/0/author: J. R. R. Tokien /books/1/author: Joanne K. Rowling - it should inhereit from parent env: + it should inherit from parent env: config: inherit-env: true command: echo $USER - stdout: simon + stdout: from_parent exit-code: 0 \ No newline at end of file From 488baca0073f87caf468fed842975655758c928b Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 22:21:47 +0200 Subject: [PATCH 06/17] Add CHANGELOG entry for inherit-env --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9eb62ca1..d43fd5ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - Added `xml` assertion to `stdout` and `stderr` - Added `json` assertin to `stdout` and `stderr` - Remove `cmd` pkg and use `github.com/SimonBaeumer/cmd@v1.1.0` instead + - Add `inherit-env` to `config`, it enables inheriting the parent's env variables. # v1.2.2 From 3585627fbd9249413558959e364df556759ef4a8 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 22:38:45 +0200 Subject: [PATCH 07/17] Update documentation --- README.md | 276 ++++++++++++++++++------------------- cmd/commander/commander.go | 2 +- docs/manual.md | 74 ---------- 3 files changed, 139 insertions(+), 213 deletions(-) delete mode 100644 docs/manual.md diff --git a/README.md b/README.md index 1cd01a1a..b33155d7 100644 --- a/README.md +++ b/README.md @@ -27,6 +27,19 @@ For more information take a look at the [manual](docs/manual.md), the [examples] + [Adding tests](#adding-tests) * [Documentation](#documentation) + [Usage](#usage) + + [Tests](#tests) + - [command](#command) + - [config](#config) + - [exit-code](#exit-code) + - [stdout](#stdout) + * [contains](#contains) + * [exactly](#exactly) + * [json](#json) + * [lines](#lines) + * [line-count](#line-count) + * [not-contains](#not-contains) + * [xml](#xml) + - [stderr](#stderr) + [Config](#config) - [dir](#dir) - [env](#env) @@ -34,20 +47,6 @@ For more information take a look at the [manual](docs/manual.md), the [examples] - [interval](#interval) - [retries](#retries) - [timeout](#timeout) - + [Tests](#tests) - * [test case](#test-case) - - [command](#command) - - [config](#config) - - [exit-code](#exit-code) - - [stdout](#stdout) - + [contains](#contains) - + [exactly](#exactly) - + [json](#json) - + [lines](#lines) - + [line-count](#line-count) - + [not-contains](#not-contains) - + [xml](#xml) - - [stderr](#stderr) + [Development](#development) * [Misc](#misc) @@ -68,7 +67,7 @@ This works on any OS, as long as go is installed. If go is not installed on your Visit the [release](https://github.com/SimonBaeumer/commander/releases) page to get the binary for you system. ```bash -curl -L https://github.com/SimonBaeumer/commander/releases/download/v1.0.0/commander-linux-amd64 -o commander +curl -L https://github.com/SimonBaeumer/commander/releases/download/v1.2.2/commander-linux-amd64 -o commander chmod +x commander ``` @@ -81,12 +80,12 @@ chmod +x commander ## Quick start -`Commander` will always search for a default `commander.yaml` in the current working directory and execute all defined tests in it. - +A `commander` test suite consists of a `config` and `tests` root element. To start quickly you can use +the following examples. ```bash # You can even let commander add tests for you! -$ ./commander test examples/commander.yaml +$ ./commander add --stdout --file=/tmp/commander.yaml echo hello tests: echo hello: exit-code: 0 @@ -95,8 +94,8 @@ tests: written to /tmp/commander.yaml # ... and execute! -$ ./commander test -Starting test file commander.yaml... +$ ./commander test /tmp/commander.yaml +Starting test file /tmp/commander.yaml... ✓ echo hello @@ -106,7 +105,7 @@ Count: 1, Failed: 0 ### Complete YAML file -Here you can see an example with all features. +Here you can see an example with all features for a quick reference ```yaml config: # Config for all executed tests @@ -220,124 +219,22 @@ GLOBAL OPTIONS: --version, -v print the version ``` -### Config - -You can add configs to which apply to all tests or just for a specific test case like: - -```yaml -config: - dir: /home/root # Set working directory -tests: - echo hello: - config: # Define test specific configs which overwrite global configs - timeout: 5s - exit-code: 0 -``` - -#### dir - -`dir` is a `string` which sets the current working directory for the command under test. -The test will fail if the given directory does not exist. - - - name: `dir` - - type: `string` - - default: `current working dir` - -```yaml -dir: /home/root -``` - -#### env - -`env` is a `hash-map` which is used to set custom env variables. The `key` represents the variable name and the `value` setting the value of the env variable. - - - name: `env` - - type: `hash-map` - - default: `{}` - - notes: - - read env variables with `${PATH}` - - overwrites inherited variables, see [#inherit-env](#inherit-env) - -```yaml -env: - VAR_NAME: my value # Set custom env var - CURRENT_USER: ${USER} # Set env var and read from current env -``` - -#### inherit-env - -`inherit-env` is a `boolean` type which allows you to inherit all environment variables from your active shell. - - - name: `inherit-env` - - type: `bool` - - default: `false` - - notes: If this config is set to `true` in the global configuration it will be applied for all tests and ignores local test configs. - -```yaml -inherit-env: true -``` - -#### interval - -`interval` is a `string` type and sets the `interval` between [retries](#retries). - - - name: `interval` - - type: `string` - - default: `0ns` - - notes: - - valid time units: ns, us, µs, ms, s, m, h - - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration) - -```yaml -interval: 5s # Waits 5 seconds until the next try after a failed test is started -``` - -#### retries - -`retries` is an `int` type and configures how often a test is allowed to fail until it will be marked as failed for the whole test run. - - - name: `retries` - - type: `int` - - default: `0` - - notes: [interval](#interval) can be defined between retry executions - -```yaml -retries: 3 # Test will be executed 3 times or until it succeeds -``` - -#### timeout - -`timeout` is a `string` type and sets the time a test is allowed to run. -The time is parsed from a duration string like `300ms`. -If a tests exceeds the given `timeout` the test will fail. - - - name: `timeout` - - type: `string` - - default: `no limit` - - notes: - - valid time units: ns, us, µs, ms, s, m, h - - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration) - -```yaml -timeout: 600s -``` ### Tests -Tests define the commands to be executed. After the execution the defined assertions will be checked. -If the result fo the command matches with the expected values a test passes. +Tests are defined in the `tests` root element. Every test consists of a [command](#command) and an expected result, +i.e. an [exit-code](#exit-code). -All `tests` are defined in a `map` as a `root` element of the `yaml` file. ```yaml -tests: - echo test: +tests: # root element + echo test: # test case - can either be the command or a given title stdout: test exit-code: 0 ``` -A test case is `map` type which configures a test. -The `key` of the test can either be the `command` itself or the `title` of the test. +A test is a `map` which configures the test. +The `key` (`echo test` in the example above) of the test can either be the `command` itself or the `title` of the test which will be displayed in the test execution. If the same `command` is tested multiple times it is useful to set the `title` of the test manually and use the `command` property. Further the `title` can be useful to describe tests better. See [the commander test suite](commander_unix.yaml) as an example. @@ -367,7 +264,7 @@ the `key` if no `command` property was given. - name: `command` - type: `string` - default: `can't be empty` - - notes: Will be parsed as the `key` if no `command` property was provided + - notes: Will be parsed from the `key` if no `command` property was provided and used as the title too ```yaml @@ -432,7 +329,7 @@ echo hello world: line-count: 1 # assert the amount of lines and use stdout as a map ``` -###### contains +##### contains `contains` is an `array` or `string`. It checks if a `string` is contained in the output. It is the default if a `string` is directly assigned to `stdout` or `stderr`. @@ -453,7 +350,7 @@ echo more output: - output ``` -###### exactly +##### exactly `exactly` is a `string` type which matches the exact output. @@ -467,7 +364,7 @@ echo test: exactly: test ``` -###### json +##### json `json` is a `map` type and allows to parse `json` documents with a given `GJSON syntax` to query for specific data. The `key` represents the query, the `value` the expected value. @@ -513,7 +410,7 @@ More examples queries: "friends.1.last" >> "Craig" ``` -###### lines +##### lines `lines` is a `map` which is make exact assertions on a given line by line number. @@ -529,7 +426,7 @@ echo test\nline 2: 2: line 2 # asserts only the second line ``` -###### line-count +##### line-count `line-count` asserts the amount of lines printed to the output. If set to `0` this property is ignored. @@ -543,7 +440,7 @@ echo test\nline 2: line-count: 2 ``` -###### not-contains +##### not-contains `not-contains` is a `array` of elements which are not allowed to be contained in the output. It is the inversion of [contains](#contains). @@ -562,7 +459,7 @@ echo bonjour: not-contains: bonjour # test fails because bonjour occurs in the output ``` -###### xml +##### xml `xml` is a `map` which allows to query `xml` documents viá `xpath` queries. Like the [json][#json] assertion this uses the `key` of the map as the query parameter to, the `value` is the expected value. @@ -587,7 +484,7 @@ cat some.xml: ``` -##### stderr +#### stderr See [stdout](#stdout) for more information. @@ -607,6 +504,109 @@ See [stdout](#stdout) for more information. line-count: 1 ``` +### Config + +You can add configs which will be applied globally to all tests or just for a specific test case, i.e.: + +```yaml +config: + dir: /home/root # Set working directory for all tests + +tests: + echo hello: + config: # Define test specific configs which overwrite global configs + timeout: 5s + exit-code: 0 +``` + +#### dir + +`dir` is a `string` which sets the current working directory for the command under test. +The test will fail if the given directory does not exist. + + - name: `dir` + - type: `string` + - default: `current working dir` + +```yaml +dir: /home/root +``` + +#### env + +`env` is a `hash-map` which is used to set custom env variables. The `key` represents the variable name and the `value` setting the value of the env variable. + + - name: `env` + - type: `hash-map` + - default: `{}` + - notes: + - read env variables with `${PATH}` + - overwrites inherited variables, see [#inherit-env](#inherit-env) + +```yaml +env: + VAR_NAME: my value # Set custom env var + CURRENT_USER: ${USER} # Set env var and read from current env +``` + +#### inherit-env + +`inherit-env` is a `boolean` type which allows you to inherit all environment variables from your active shell. + + - name: `inherit-env` + - type: `bool` + - default: `false` + - notes: If this config is set to `true` in the global configuration it will be applied for all tests and ignores local test configs. + +```yaml +inherit-env: true +``` + +#### interval + +`interval` is a `string` type and sets the `interval` between [retries](#retries). + + - name: `interval` + - type: `string` + - default: `0ns` + - notes: + - valid time units: ns, us, µs, ms, s, m, h + - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration) + +```yaml +interval: 5s # Waits 5 seconds until the next try after a failed test is started +``` + +#### retries + +`retries` is an `int` type and configures how often a test is allowed to fail until it will be marked as failed for the whole test run. + + - name: `retries` + - type: `int` + - default: `0` + - notes: [interval](#interval) can be defined between retry executions + +```yaml +retries: 3 # Test will be executed 3 times or until it succeeds +``` + +#### timeout + +`timeout` is a `string` type and sets the time a test is allowed to run. +The time is parsed from a duration string like `300ms`. +If a tests exceeds the given `timeout` the test will fail. + + - name: `timeout` + - type: `string` + - default: `no limit` + - notes: + - valid time units: ns, us, µs, ms, s, m, h + - time string will be evaluated by golang's `time` package, further reading [time/#ParseDuration](https://golang.org/pkg/time/#ParseDuration) + +```yaml +timeout: 600s +``` + ### Development ``` diff --git a/cmd/commander/commander.go b/cmd/commander/commander.go index a0858441..c153fd8f 100644 --- a/cmd/commander/commander.go +++ b/cmd/commander/commander.go @@ -45,7 +45,7 @@ func createCliApp() *cli.App { func createTestCommand() cli.Command { return cli.Command{ Name: "test", - Usage: "Execute the test suite", + Usage: "Execute the test suite, by default it will use the commander.yaml from your current directory", ArgsUsage: "[file] [title]", Flags: []cli.Flag{ cli.IntFlag{ diff --git a/docs/manual.md b/docs/manual.md deleted file mode 100644 index 724d2f92..00000000 --- a/docs/manual.md +++ /dev/null @@ -1,74 +0,0 @@ -# Manual - -`commander` will automatically search for a `commander.yaml` in the current working directory. - -## Commands - -## add - -The `add` command allows you to automatically create tests. It will automatically create a `comannder.yaml`. - -**Example** - -```bash -./commander add --no-file --stdout echo hello -tests: - echo hello: - exit-code: 0 - stdout: hello -``` - -**Options** - -``` ---stdout Output test file to stdout ---no-file Don't create a commander.yaml ---file value Write to another file, default is commander.yaml -``` - -## test - -```yaml -config: # Config for all tests - dir: /tmp #Set working directory - env: # Environment variables - KEY: global - PATH_FROM_SHELL: ${PATH} # Read an env variable from the current shell - timeout: 5000 # Timeout in ms - retries: 2 # Define retries for each test - -tests: - echo hello: # Define command as title - stdout: hello # Default is to check if it contains the given characters - exit-code: 0 # Assert exit-code - - it should fail: - command: invalid - stderr: - contains: - - invalid # Assert only contain work - not-contains: - - does not contains # validate that a string does not occur in stdout - exactly: "/bin/sh: 1: invalid: not found" - line-count: 1 # Assert amount of lines - lines: # Assert specific lines - 1: "/bin/sh: 1: invalid: not found" - exit-code: 127 - - it has configs: - command: echo hello - stdout: - contains: - - hello #See test "it should fail" - exactly: hello - line-count: 1 - config: - dir: /home/user # Overwrite working dir - env: - KEY: local # Overwrite env variable - ANOTHER: yeah # Add another env variable - timeout: 1000 # Overwrite timeout - retries: 5 - interval: 30ms - exit-code: 0 -``` \ No newline at end of file From 4f0c51e689dfdc1aff55ca6a4a45e4673b6d01f8 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 22:42:41 +0200 Subject: [PATCH 08/17] Test fails on windows due to timeout issues --- pkg/runtime/runtime_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 7e6899b3..c6d872d4 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -73,7 +73,7 @@ func TestRuntime_WithEnvVariables(t *testing.T) { s := TestCase{ Command: CommandUnderTest{ Cmd: fmt.Sprintf("echo %s", envVar), - Timeout: "50ms", + Timeout: "2s", Env: map[string]string{"KEY": "value"}, }, Expected: Expected{ From 1562f835542a211d0f18b4b6a8a66a76617b8018 Mon Sep 17 00:00:00 2001 From: Simon Date: Sat, 19 Oct 2019 22:48:12 +0200 Subject: [PATCH 09/17] Fix readme anchor due to duplicated headings --- README.md | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b33155d7..80ac9270 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ Define language independent tests for your command line scripts and programs in - It is a self-contained binary - no need to install a heavy lib or language - It is easy and fast to write -For more information take a look at the [manual](docs/manual.md), the [examples](examples) or the [integration tests](integration). +For more information take a look at the [quick start](#quick-start), the [examples](examples) or the [integration tests](integration). ## Table of contents @@ -29,7 +29,7 @@ For more information take a look at the [manual](docs/manual.md), the [examples] + [Usage](#usage) + [Tests](#tests) - [command](#command) - - [config](#config) + - [config](#user-content-config-test) - [exit-code](#exit-code) - [stdout](#stdout) * [contains](#contains) @@ -40,7 +40,7 @@ For more information take a look at the [manual](docs/manual.md), the [examples] * [not-contains](#not-contains) * [xml](#xml) - [stderr](#stderr) - + [Config](#config) + + [Config](#user-content-config-config) - [dir](#dir) - [env](#env) - [inherit-env](#inherit-env) @@ -277,7 +277,7 @@ it should print hello world: # use a more descriptive title... exit-code: 0 ``` -#### config +#### config `config` sets configuration for the test. `config` can overwrite global configurations. @@ -285,7 +285,7 @@ it should print hello world: # use a more descriptive title... - type: `map` - default: `{}` - notes: - - for more information look at [config](#Config) + - for more information look at [config](#user-content-config-config) ```yaml echo test: @@ -412,7 +412,7 @@ More examples queries: ##### lines -`lines` is a `map` which is make exact assertions on a given line by line number. +`lines` is a `map` which makes exact assertions on a given line by line number. - name: `lines` - type: `map` @@ -504,7 +504,7 @@ See [stdout](#stdout) for more information. line-count: 1 ``` -### Config +### Config You can add configs which will be applied globally to all tests or just for a specific test case, i.e.: From 75539ff2e8f512a03f45b15d439ec08d0412cffc Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 21 Oct 2019 11:09:31 +0200 Subject: [PATCH 10/17] Add inherit tests for windows, linux and darwin This is neccessary because windows uses %VAR% for env variable expansions, on the other side linux and darwin both are using the unix standard which uses $VAR. --- pkg/runtime/runtime_darwin_test.go | 44 +++++++++++++++++++++++++++++ pkg/runtime/runtime_linux_test.go | 44 +++++++++++++++++++++++++++++ pkg/runtime/runtime_test.go | 38 ------------------------- pkg/runtime/runtime_windows_test.go | 44 +++++++++++++++++++++++++++++ 4 files changed, 132 insertions(+), 38 deletions(-) create mode 100644 pkg/runtime/runtime_darwin_test.go create mode 100644 pkg/runtime/runtime_linux_test.go create mode 100644 pkg/runtime/runtime_windows_test.go diff --git a/pkg/runtime/runtime_darwin_test.go b/pkg/runtime/runtime_darwin_test.go new file mode 100644 index 00000000..3868b71e --- /dev/null +++ b/pkg/runtime/runtime_darwin_test.go @@ -0,0 +1,44 @@ +package runtime + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestRuntime_WithInheritFromShell(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER", + InheritEnv: true, + }, + } + + got := runTest(test) + + assert.Equal(t, "test", got.TestCase.Result.Stdout) +} + +func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) +} \ No newline at end of file diff --git a/pkg/runtime/runtime_linux_test.go b/pkg/runtime/runtime_linux_test.go new file mode 100644 index 00000000..3868b71e --- /dev/null +++ b/pkg/runtime/runtime_linux_test.go @@ -0,0 +1,44 @@ +package runtime + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestRuntime_WithInheritFromShell(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER", + InheritEnv: true, + }, + } + + got := runTest(test) + + assert.Equal(t, "test", got.TestCase.Result.Stdout) +} + +func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) +} \ No newline at end of file diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index c6d872d4..b42d95dd 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -3,7 +3,6 @@ package runtime import ( "fmt" "github.com/stretchr/testify/assert" - "os" "runtime" "testing" "time" @@ -119,43 +118,6 @@ func TestRuntime_WithInvalidDuration(t *testing.T) { assert.Equal(t, "time: unknown unit lightyears in duration 600lightyears", got.TestCase.Result.Error.Error()) } -func TestRuntime_WithInheritFromShell(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - defer os.Unsetenv("TEST_COMMANDER") - - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo $TEST_COMMANDER", - InheritEnv: true, - }, - } - - got := runTest(test) - - assert.Equal(t, "test", got.TestCase.Result.Stdout) -} - -func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - os.Setenv("ANOTHER_ENV", "from-parent") - defer func() { - os.Unsetenv("TEST_COMMANDER") - os.Unsetenv("ANOTHER_ENV") - }() - - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", - InheritEnv: true, - Env: map[string]string{"TEST_COMMANDER": "overwrite"}, - }, - } - - got := runTest(test) - - assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) -} - func getExampleTestSuite() []TestCase { tests := []TestCase{ { diff --git a/pkg/runtime/runtime_windows_test.go b/pkg/runtime/runtime_windows_test.go new file mode 100644 index 00000000..fb35c8f5 --- /dev/null +++ b/pkg/runtime/runtime_windows_test.go @@ -0,0 +1,44 @@ +package runtime + +import ( + "github.com/stretchr/testify/assert" + "os" + "testing" +) + +func TestRuntime_WithInheritFromShell(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo %TEST_COMMANDER%", + InheritEnv: true, + }, + } + + got := runTest(test) + + assert.Equal(t, "test", got.TestCase.Result.Stdout) +} + +func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo %TEST_COMMANDER% %ANOTHER_ENV%", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) +} From 6cf2d5fd06240111e61741a02b09ab4c8b3311f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Simon=20B=C3=A4umer?= Date: Mon, 21 Oct 2019 14:54:30 +0200 Subject: [PATCH 11/17] Fix windows tests --- pkg/app/test_command_test.go | 4 ++-- pkg/runtime/runtime.go | 19 ++++++++++++++----- pkg/runtime/runtime_test.go | 2 +- 3 files changed, 17 insertions(+), 8 deletions(-) diff --git a/pkg/app/test_command_test.go b/pkg/app/test_command_test.go index 91642806..4d6c1d45 100644 --- a/pkg/app/test_command_test.go +++ b/pkg/app/test_command_test.go @@ -10,7 +10,7 @@ func Test_TestCommand(t *testing.T) { err := TestCommand("commander.yaml", "", AddCommandContext{}) if runtime.GOOS == "windows" { - assert.Equal(t, "Error open commander.yaml: The system cannot find the file specified.", err.Error()) + assert.Contains(t, err.Error(), "Error open commander.yaml:") } else { assert.Equal(t, "Error open commander.yaml: no such file or directory", err.Error()) } @@ -20,7 +20,7 @@ func Test_TestCommand_ShouldUseCustomFile(t *testing.T) { err := TestCommand("my-test.yaml", "", AddCommandContext{}) if runtime.GOOS == "windows" { - assert.Equal(t, "Error open my-test.yaml: The system cannot find the file specified.", err.Error()) + assert.Contains(t, err.Error(), "Error open my-test.yaml: ") } else { assert.Equal(t, "Error open my-test.yaml: no such file or directory", err.Error()) } diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index 50af30ca..c9920c0e 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -195,17 +195,26 @@ func runTest(test TestCase) TestResult { // Write test result test.Result = CommandResult{ ExitCode: cut.ExitCode(), - Stdout: strings.TrimSpace(cut.Stdout()), - Stderr: strings.TrimSpace(cut.Stderr()), + Stdout: strings.TrimSpace(strings.Replace(cut.Stdout(), "\r\n", "\n", -1)), + Stderr: strings.TrimSpace(strings.Replace(cut.Stderr(), "\r\n", "\n", -1)), } - log.Println("title: '"+test.Title+"'", " ExitCode: ", test.Result.ExitCode) - log.Println("title: '"+test.Title+"'", " Stdout: ", test.Result.Stdout) - log.Println("title: '"+test.Title+"'", " Stderr: ", test.Result.Stderr) + log.Println("title: '" + test.Title + "'", " ExitCode: ", test.Result.ExitCode) + log.Println("title: '" + test.Title + "'", " Stdout: ", test.Result.Stdout) + log.Println("title: '" + test.Title + "'", " Stderr: ", test.Result.Stderr) return Validate(test) } +// trimSpace implementation to trim CLRF off +func trimSpace(s string) string { + result := strings.TrimSpace(s) + if runtime.GOOS == "windows" { + return strings.Trim(s, "\r\n") + } + return result +} + func createEnvVarsOption(test TestCase) func(c *cmd.Command) { return func(c *cmd.Command) { // Add all env variables from parent process diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index b42d95dd..35b15c7e 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -99,7 +99,7 @@ func Test_runTestShouldReturnError(t *testing.T) { got := runTest(test) if runtime.GOOS == "windows" { - assert.Contains(t, got.TestCase.Result.Error.Error(), "chdir /home/invalid: The system cannot find the path specified.") + assert.Contains(t, got.TestCase.Result.Error.Error(), "chdir /home/invalid") } else { assert.Equal(t, "chdir /home/invalid: no such file or directory", got.TestCase.Result.Error.Error()) } From 26ec2b5c5792867e841feae1167d846a9e8056d7 Mon Sep 17 00:00:00 2001 From: Simon Date: Mon, 21 Oct 2019 16:22:50 +0200 Subject: [PATCH 12/17] Add runtine and validation tests --- pkg/app/app.go | 1 - pkg/app/app_test.go | 24 ++++++++ pkg/app/test_command_test.go | 49 +++++++++++++++ pkg/runtime/runtime.go | 15 +---- pkg/runtime/runtime_darwin_test.go | 64 +++++++++---------- pkg/runtime/runtime_linux_test.go | 64 +++++++++---------- pkg/runtime/runtime_test.go | 2 +- pkg/runtime/runtime_windows_test.go | 62 +++++++++---------- pkg/runtime/validator_test.go | 95 +++++++++++++++++++++++++++++ 9 files changed, 267 insertions(+), 109 deletions(-) create mode 100644 pkg/app/app_test.go diff --git a/pkg/app/app.go b/pkg/app/app.go index 063558f2..57c66f57 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -13,7 +13,6 @@ const ( type AddCommandContext struct { Verbose bool NoColor bool - Debug bool Concurrent int } diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go new file mode 100644 index 00000000..5bf2c589 --- /dev/null +++ b/pkg/app/app_test.go @@ -0,0 +1,24 @@ +package app + +import ( + "flag" + "github.com/stretchr/testify/assert" + "github.com/urfave/cli" + "testing" +) + +func TestNewAddCommandContextFromCli(t *testing.T) { + set := flag.NewFlagSet("verbose", 0) + set.Bool("verbose", true, "") + set.Bool("no-color", true, "") + set.Int("concurrent", 5, "") + + context := &cli.Context{} + ctx := cli.NewContext(nil, set, context) + + r := NewAddContextFromCli(ctx) + + assert.True(t, r.Verbose) + assert.True(t, r.NoColor) + assert.Equal(t, 5, r.Concurrent) +} diff --git a/pkg/app/test_command_test.go b/pkg/app/test_command_test.go index 4d6c1d45..0df66c4b 100644 --- a/pkg/app/test_command_test.go +++ b/pkg/app/test_command_test.go @@ -1,11 +1,30 @@ package app import ( + "bytes" "github.com/stretchr/testify/assert" + "io" + "log" + "os" "runtime" + "sync" "testing" ) +func Test_TestCommand_Verbose(t *testing.T) { + out := captureOutput(func() { + TestCommand("commander.yaml", "", AddCommandContext{Verbose: true}) + log.Println("test test test") + }) + + assert.Contains(t, out, "test test test") +} + +func Test_TestCommand_DefaultFile(t *testing.T) { + err := TestCommand("", "", AddCommandContext{Verbose: true}) + assert.Contains(t, err.Error(), "commander.yaml") +} + func Test_TestCommand(t *testing.T) { err := TestCommand("commander.yaml", "", AddCommandContext{}) @@ -25,3 +44,33 @@ func Test_TestCommand_ShouldUseCustomFile(t *testing.T) { assert.Equal(t, "Error open my-test.yaml: no such file or directory", err.Error()) } } + +func captureOutput(f func()) string { + 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() + f() + writer.Close() + return <-out +} diff --git a/pkg/runtime/runtime.go b/pkg/runtime/runtime.go index c9920c0e..fd389dda 100644 --- a/pkg/runtime/runtime.go +++ b/pkg/runtime/runtime.go @@ -199,22 +199,13 @@ func runTest(test TestCase) TestResult { Stderr: strings.TrimSpace(strings.Replace(cut.Stderr(), "\r\n", "\n", -1)), } - log.Println("title: '" + test.Title + "'", " ExitCode: ", test.Result.ExitCode) - log.Println("title: '" + test.Title + "'", " Stdout: ", test.Result.Stdout) - log.Println("title: '" + test.Title + "'", " Stderr: ", test.Result.Stderr) + log.Println("title: '"+test.Title+"'", " ExitCode: ", test.Result.ExitCode) + log.Println("title: '"+test.Title+"'", " Stdout: ", test.Result.Stdout) + log.Println("title: '"+test.Title+"'", " Stderr: ", test.Result.Stderr) return Validate(test) } -// trimSpace implementation to trim CLRF off -func trimSpace(s string) string { - result := strings.TrimSpace(s) - if runtime.GOOS == "windows" { - return strings.Trim(s, "\r\n") - } - return result -} - func createEnvVarsOption(test TestCase) func(c *cmd.Command) { return func(c *cmd.Command) { // Add all env variables from parent process diff --git a/pkg/runtime/runtime_darwin_test.go b/pkg/runtime/runtime_darwin_test.go index 3868b71e..7e4b2cdd 100644 --- a/pkg/runtime/runtime_darwin_test.go +++ b/pkg/runtime/runtime_darwin_test.go @@ -1,44 +1,44 @@ package runtime import ( - "github.com/stretchr/testify/assert" - "os" - "testing" + "github.com/stretchr/testify/assert" + "os" + "testing" ) func TestRuntime_WithInheritFromShell(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - defer os.Unsetenv("TEST_COMMANDER") + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo $TEST_COMMANDER", - InheritEnv: true, - }, - } + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER", + InheritEnv: true, + }, + } - got := runTest(test) + got := runTest(test) - assert.Equal(t, "test", got.TestCase.Result.Stdout) + assert.Equal(t, "test", got.TestCase.Result.Stdout) } func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - os.Setenv("ANOTHER_ENV", "from-parent") - defer func() { - os.Unsetenv("TEST_COMMANDER") - os.Unsetenv("ANOTHER_ENV") - }() - - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", - InheritEnv: true, - Env: map[string]string{"TEST_COMMANDER": "overwrite"}, - }, - } - - got := runTest(test) - - assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) -} \ No newline at end of file + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) +} diff --git a/pkg/runtime/runtime_linux_test.go b/pkg/runtime/runtime_linux_test.go index 3868b71e..7e4b2cdd 100644 --- a/pkg/runtime/runtime_linux_test.go +++ b/pkg/runtime/runtime_linux_test.go @@ -1,44 +1,44 @@ package runtime import ( - "github.com/stretchr/testify/assert" - "os" - "testing" + "github.com/stretchr/testify/assert" + "os" + "testing" ) func TestRuntime_WithInheritFromShell(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - defer os.Unsetenv("TEST_COMMANDER") + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo $TEST_COMMANDER", - InheritEnv: true, - }, - } + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER", + InheritEnv: true, + }, + } - got := runTest(test) + got := runTest(test) - assert.Equal(t, "test", got.TestCase.Result.Stdout) + assert.Equal(t, "test", got.TestCase.Result.Stdout) } func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - os.Setenv("ANOTHER_ENV", "from-parent") - defer func() { - os.Unsetenv("TEST_COMMANDER") - os.Unsetenv("ANOTHER_ENV") - }() - - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", - InheritEnv: true, - Env: map[string]string{"TEST_COMMANDER": "overwrite"}, - }, - } - - got := runTest(test) - - assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) -} \ No newline at end of file + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo $TEST_COMMANDER $ANOTHER_ENV", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) +} diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index 35b15c7e..ba909ad9 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -49,7 +49,7 @@ func TestRuntime_WithRetriesAndInterval(t *testing.T) { s[0].Command.Interval = "50ms" start := time.Now() - got := Start(s, 1) + got := Start(s, 0) var counter = 0 for r := range got { diff --git a/pkg/runtime/runtime_windows_test.go b/pkg/runtime/runtime_windows_test.go index fb35c8f5..0293e549 100644 --- a/pkg/runtime/runtime_windows_test.go +++ b/pkg/runtime/runtime_windows_test.go @@ -1,44 +1,44 @@ package runtime import ( - "github.com/stretchr/testify/assert" - "os" - "testing" + "github.com/stretchr/testify/assert" + "os" + "testing" ) func TestRuntime_WithInheritFromShell(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - defer os.Unsetenv("TEST_COMMANDER") + os.Setenv("TEST_COMMANDER", "test") + defer os.Unsetenv("TEST_COMMANDER") - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo %TEST_COMMANDER%", - InheritEnv: true, - }, - } + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo %TEST_COMMANDER%", + InheritEnv: true, + }, + } - got := runTest(test) + got := runTest(test) - assert.Equal(t, "test", got.TestCase.Result.Stdout) + assert.Equal(t, "test", got.TestCase.Result.Stdout) } func TestRuntime_WithInheritFromShell_Overwrite(t *testing.T) { - os.Setenv("TEST_COMMANDER", "test") - os.Setenv("ANOTHER_ENV", "from-parent") - defer func() { - os.Unsetenv("TEST_COMMANDER") - os.Unsetenv("ANOTHER_ENV") - }() - - test := TestCase{ - Command: CommandUnderTest{ - Cmd: "echo %TEST_COMMANDER% %ANOTHER_ENV%", - InheritEnv: true, - Env: map[string]string{"TEST_COMMANDER": "overwrite"}, - }, - } - - got := runTest(test) - - assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) + os.Setenv("TEST_COMMANDER", "test") + os.Setenv("ANOTHER_ENV", "from-parent") + defer func() { + os.Unsetenv("TEST_COMMANDER") + os.Unsetenv("ANOTHER_ENV") + }() + + test := TestCase{ + Command: CommandUnderTest{ + Cmd: "echo %TEST_COMMANDER% %ANOTHER_ENV%", + InheritEnv: true, + Env: map[string]string{"TEST_COMMANDER": "overwrite"}, + }, + } + + got := runTest(test) + + assert.Equal(t, "overwrite from-parent", got.TestCase.Result.Stdout) } diff --git a/pkg/runtime/validator_test.go b/pkg/runtime/validator_test.go index d81d0a43..bb55cead 100644 --- a/pkg/runtime/validator_test.go +++ b/pkg/runtime/validator_test.go @@ -35,6 +35,53 @@ func Test_ValidateStdoutShouldFail(t *testing.T) { assert.Equal(t, "Stdout", got.FailedProperty) } +func Test_ValidateStderrShouldFail(t *testing.T) { + test := getExampleTest() + test.Expected.Stdout = ExpectedOut{} + test.Result = CommandResult{ + Stderr: "is not in message", + ExitCode: 0, + } + + got := Validate(test) + + assert.False(t, got.ValidationResult.Success) + assert.Equal(t, "Stderr", got.FailedProperty) +} + +func Test_ValidateExitCodeShouldFail(t *testing.T) { + test := getExampleTest() + test.Expected.Stdout = ExpectedOut{} + test.Expected.Stderr = ExpectedOut{} + test.Result = CommandResult{ + ExitCode: 1, + } + + got := Validate(test) + + assert.False(t, got.ValidationResult.Success) + assert.Equal(t, "ExitCode", got.FailedProperty) +} + +func Test_ValidateExpectedOut_Contains_Fails(t *testing.T) { + value := `test` + + got := validateExpectedOut(value, ExpectedOut{Contains: []string{"not-exists"}}) + + diff := ` +Expected + +test + +to contain + +not-exists +` + + assert.False(t, got.Success) + assert.Equal(t, diff, got.Diff) +} + func Test_ValidateExpectedOut_MatchLines(t *testing.T) { value := `my multi @@ -47,6 +94,54 @@ output` assert.Empty(t, got.Diff) } +func Test_ValidateExpectedOut_MatchLines_Fails(t *testing.T) { + value := `` + + got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{1: "my", 3: "line"}}) + + assert.False(t, got.Success) + diff := `--- Got ++++ Expected +@@ -1 +1 @@ +- ++my +` + assert.Equal(t, diff, got.Diff) +} + +func Test_ValidateExpectedOut_LineCount_Fails(t *testing.T) { + value := `` + + got := validateExpectedOut(value, ExpectedOut{LineCount: 2}) + + assert.False(t, got.Success) + diff := `--- Got ++++ Expected +@@ -1 +1 @@ +-0 ++2 +` + assert.Equal(t, diff, got.Diff) +} + +func Test_ValidateExpectedOut_NotContains_Fails(t *testing.T) { + value := `my string contains` + + got := validateExpectedOut(value, ExpectedOut{NotContains: []string{"contains"}}) + + diff := ` +Expected + +my string contains + +to not contain + +contains +` + assert.False(t, got.Success) + assert.Equal(t, diff, got.Diff) +} + func Test_ValidateExpectedOut_PanicIfLineDoesNotExist_TooHigh(t *testing.T) { defer func() { r := recover() From e7ee9aaf624c3429b55d9bd5170e652d9d075030 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 22 Oct 2019 10:50:58 +0200 Subject: [PATCH 13/17] Line matcher should return an error instead of panic --- pkg/runtime/validator.go | 21 +++++++++++++++---- pkg/runtime/validator_test.go | 39 ++++++++++++++++------------------- 2 files changed, 35 insertions(+), 25 deletions(-) diff --git a/pkg/runtime/validator.go b/pkg/runtime/validator.go index e7d14bcb..d51b2413 100644 --- a/pkg/runtime/validator.go +++ b/pkg/runtime/validator.go @@ -137,12 +137,25 @@ func validateExpectedLines(got string, expected ExpectedOut) matcher.MatcherResu actualLines := strings.Split(got, getLineBreak()) result := matcher.MatcherResult{Success: true} - for k, expL := range expected.Lines { - if (k-1 > len(actualLines)) || (k-1 < 0) { - panic(fmt.Sprintf("Invalid line number given %d", k)) + for key, expectedLine := range expected.Lines { + // line number 0 or below 0 + if key <= 0 { + panic(fmt.Sprintf("Invalid line number given %d", key)) } - if result = m.Match(actualLines[k-1], expL); !result.Success { + // line number exceeds result set + if key > len(actualLines) { + return matcher.MatcherResult{ + Success: false, + Diff: fmt.Sprintf( + "Line number %d does not exists in result: \n\n%s", + key-1, + strings.Join(actualLines, "\n"), + ), + } + } + + if result = m.Match(actualLines[key-1], expectedLine); !result.Success { return result } } diff --git a/pkg/runtime/validator_test.go b/pkg/runtime/validator_test.go index bb55cead..841c81a9 100644 --- a/pkg/runtime/validator_test.go +++ b/pkg/runtime/validator_test.go @@ -94,17 +94,31 @@ output` assert.Empty(t, got.Diff) } +func Test_ValidateExpectedOut_MatchLines_ExpectedLineDoesNotExists(t *testing.T) { + value := `test` + + got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{2: "my", 3: "line"}}) + + assert.False(t, got.Success) + diff := `Line number 1 does not exists in result: + +test` + assert.Equal(t, diff, got.Diff) +} + func Test_ValidateExpectedOut_MatchLines_Fails(t *testing.T) { - value := `` + value := `test +line 2 +line 3` - got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{1: "my", 3: "line"}}) + got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{2: "line 3"}}) assert.False(t, got.Success) diff := `--- Got +++ Expected @@ -1 +1 @@ -- -+my +-line 2 ++line 3 ` assert.Equal(t, diff, got.Diff) } @@ -142,23 +156,6 @@ contains assert.Equal(t, diff, got.Diff) } -func Test_ValidateExpectedOut_PanicIfLineDoesNotExist_TooHigh(t *testing.T) { - defer func() { - r := recover() - if r != nil { - assert.Equal(t, "Invalid line number given 99", r) - } - assert.NotNil(t, r) - }() - - value := `my -multi -line -output` - - _ = validateExpectedOut(value, ExpectedOut{Lines: map[int]string{99: "my"}}) -} - func Test_ValidateExpectedOut_PanicIfLineDoesNotExist(t *testing.T) { defer func() { r := recover() From 5659c888136771bd6053fa2463b3696b9591e605 Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 22 Oct 2019 11:00:07 +0200 Subject: [PATCH 14/17] Remove interval integration test because of instability --- commander_unix.yaml | 1 - integration/unix/retries.yaml | 1 - 2 files changed, 2 deletions(-) diff --git a/commander_unix.yaml b/commander_unix.yaml index 0144f31e..d70c188a 100644 --- a/commander_unix.yaml +++ b/commander_unix.yaml @@ -64,5 +64,4 @@ tests: - ✗ echo hello, retries 3 - ✓ it should retry failed commands, retries 2 - ✗ it should retry failed commands with an interval, retries 2 - - "Duration: 0.1" # Assertion that the interval is working exit-code: 1 \ No newline at end of file diff --git a/integration/unix/retries.yaml b/integration/unix/retries.yaml index e3ecb75c..6baa5cc5 100644 --- a/integration/unix/retries.yaml +++ b/integration/unix/retries.yaml @@ -15,5 +15,4 @@ tests: stdout: fail exit-code: 0 config: - interval: 50ms retries: 2 \ No newline at end of file From e1c7088c26b3adbbf8943b2b17aac8bc5c182fbc Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 22 Oct 2019 11:08:50 +0200 Subject: [PATCH 15/17] Add assertions to windows test --- commander_windows.yaml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/commander_windows.yaml b/commander_windows.yaml index 7ff8910c..c0683190 100644 --- a/commander_windows.yaml +++ b/commander_windows.yaml @@ -41,7 +41,10 @@ tests: COMMANDER_FROM_SHELL: from_shell stdout: contains: - - should print global + - ✓ should print global + - ✓ should print local + - ✓ should execute in given dir + - ✓ should work with timeout exit-code: 0 test add command: From bec4e887ca50271cf5be9f844c59c403ecd9147f Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 22 Oct 2019 11:41:17 +0200 Subject: [PATCH 16/17] Fix validator test --- pkg/runtime/validator.go | 2 +- pkg/runtime/validator_test.go | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pkg/runtime/validator.go b/pkg/runtime/validator.go index d51b2413..0683f5a2 100644 --- a/pkg/runtime/validator.go +++ b/pkg/runtime/validator.go @@ -149,7 +149,7 @@ func validateExpectedLines(got string, expected ExpectedOut) matcher.MatcherResu Success: false, Diff: fmt.Sprintf( "Line number %d does not exists in result: \n\n%s", - key-1, + key, strings.Join(actualLines, "\n"), ), } diff --git a/pkg/runtime/validator_test.go b/pkg/runtime/validator_test.go index 841c81a9..958cc866 100644 --- a/pkg/runtime/validator_test.go +++ b/pkg/runtime/validator_test.go @@ -100,7 +100,7 @@ func Test_ValidateExpectedOut_MatchLines_ExpectedLineDoesNotExists(t *testing.T) got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{2: "my", 3: "line"}}) assert.False(t, got.Success) - diff := `Line number 1 does not exists in result: + diff := `Line number 2 does not exists in result: test` assert.Equal(t, diff, got.Diff) From 70b81d5cd2c6da240fdbe13e042f5ac7591d28fc Mon Sep 17 00:00:00 2001 From: Simon Date: Tue, 22 Oct 2019 13:04:13 +0200 Subject: [PATCH 17/17] Increase timeout for instable test on windows --- integration/windows/config_test.yaml | 2 +- pkg/runtime/runtime_test.go | 2 +- pkg/runtime/validator_test.go | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/integration/windows/config_test.yaml b/integration/windows/config_test.yaml index 041b675f..2eb56c42 100644 --- a/integration/windows/config_test.yaml +++ b/integration/windows/config_test.yaml @@ -31,5 +31,5 @@ tests: should work with timeout: command: echo hello config: - timeout: 100ms + timeout: 5m exit-code: 0 \ No newline at end of file diff --git a/pkg/runtime/runtime_test.go b/pkg/runtime/runtime_test.go index ba909ad9..34b27af6 100644 --- a/pkg/runtime/runtime_test.go +++ b/pkg/runtime/runtime_test.go @@ -123,7 +123,7 @@ func getExampleTestSuite() []TestCase { { Command: CommandUnderTest{ Cmd: "echo hello", - Timeout: "50ms", + Timeout: "5s", }, Expected: Expected{ Stdout: ExpectedOut{ diff --git a/pkg/runtime/validator_test.go b/pkg/runtime/validator_test.go index 958cc866..fc7f6926 100644 --- a/pkg/runtime/validator_test.go +++ b/pkg/runtime/validator_test.go @@ -97,7 +97,7 @@ output` func Test_ValidateExpectedOut_MatchLines_ExpectedLineDoesNotExists(t *testing.T) { value := `test` - got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{2: "my", 3: "line"}}) + got := validateExpectedOut(value, ExpectedOut{Lines: map[int]string{2: "my"}}) assert.False(t, got.Success) diff := `Line number 2 does not exists in result: