1
0
mirror of https://github.com/SAP/jenkins-library.git synced 2024-12-14 11:03:09 +02:00
sap-jenkins-library/pkg/command/command_test.go
Oliver Nocon dc296b0727
Allow retrieving exit code from command execution (#1728)
* Allow retrieving exit code from command execution

This will be helpful to derive error categories in case
an executable provides context-specific error codes.

* make sure that we always have a non 0 exit code for errors
2020-06-30 12:57:27 +02:00

286 lines
7.5 KiB
Go

package command
import (
"bytes"
"fmt"
"io/ioutil"
"os"
"os/exec"
"strings"
"testing"
"github.com/SAP/jenkins-library/pkg/log"
"github.com/stretchr/testify/assert"
)
//based on https://golang.org/src/os/exec/exec_test.go
func helperCommand(command string, s ...string) (cmd *exec.Cmd) {
cs := []string{"-test.run=TestHelperProcess", "--", command}
cs = append(cs, s...)
cmd = exec.Command(os.Args[0], cs...)
cmd.Env = []string{"GO_WANT_HELPER_PROCESS=1"}
return cmd
}
func TestShellRun(t *testing.T) {
t.Run("test shell", func(t *testing.T) {
ExecCommand = helperCommand
defer func() { ExecCommand = exec.Command }()
o := new(bytes.Buffer)
e := new(bytes.Buffer)
s := Command{stdout: o, stderr: e}
s.RunShell("/bin/bash", "myScript")
t.Run("success case", func(t *testing.T) {
t.Run("stdin-stdout", func(t *testing.T) {
expectedOut := "Stdout: command /bin/bash - Stdin: myScript\n"
if oStr := o.String(); oStr != expectedOut {
t.Errorf("expected: %v got: %v", expectedOut, oStr)
}
})
t.Run("stderr", func(t *testing.T) {
expectedErr := "Stderr: command /bin/bash\n"
if eStr := e.String(); eStr != expectedErr {
t.Errorf("expected: %v got: %v", expectedErr, eStr)
}
})
})
})
}
func TestExecutableRun(t *testing.T) {
t.Run("test executable", func(t *testing.T) {
ExecCommand = helperCommand
defer func() { ExecCommand = exec.Command }()
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
t.Run("success case", func(t *testing.T) {
ex := Command{stdout: stdout, stderr: stderr}
ex.RunExecutable("echo", []string{"foo bar", "baz"}...)
assert.Equal(t, 0, ex.GetExitCode())
t.Run("stdin", func(t *testing.T) {
expectedOut := "foo bar baz\n"
if oStr := stdout.String(); oStr != expectedOut {
t.Errorf("expected: %v got: %v", expectedOut, oStr)
}
})
t.Run("stderr", func(t *testing.T) {
expectedErr := "Stderr: command echo\n"
if eStr := stderr.String(); eStr != expectedErr {
t.Errorf("expected: %v got: %v", expectedErr, eStr)
}
})
})
t.Run("success case - log parsing", func(t *testing.T) {
log.SetErrorCategory(log.ErrorUndefined)
ex := Command{stdout: stdout, stderr: stderr, ErrorCategoryMapping: map[string][]string{"config": {"command echo"}}}
ex.RunExecutable("echo", []string{"foo bar", "baz"}...)
assert.Equal(t, log.ErrorConfiguration, log.GetErrorCategory())
})
t.Run("success case - log parsing long line", func(t *testing.T) {
log.SetErrorCategory(log.ErrorUndefined)
ex := Command{stdout: stdout, stderr: stderr, ErrorCategoryMapping: map[string][]string{"config": {"aaaa"}}}
ex.RunExecutable("long", []string{"foo bar", "baz"}...)
assert.Equal(t, log.ErrorUndefined, log.GetErrorCategory())
})
log.SetErrorCategory(log.ErrorUndefined)
})
}
func TestEnvironmentVariables(t *testing.T) {
ExecCommand = helperCommand
defer func() { ExecCommand = exec.Command }()
stdout := new(bytes.Buffer)
stderr := new(bytes.Buffer)
ex := Command{stdout: stdout, stderr: stderr}
// helperCommand function replaces the full environment with one single entry
// (GO_WANT_HELPER_PROCESS), hence there is no need for checking if the DEBUG
// environment variable already exists in the set of environment variables for the
// current process.
ex.SetEnv([]string{"DEBUG=true"})
ex.RunExecutable("env")
oStr := stdout.String()
if !strings.Contains(oStr, "DEBUG=true") {
t.Errorf("expected Environment variable not found")
}
}
func TestPrepareOut(t *testing.T) {
t.Run("os", func(t *testing.T) {
s := Command{}
s.prepareOut()
if s.stdout != os.Stdout {
t.Errorf("expected out to be os.Stdout")
}
if s.stderr != os.Stderr {
t.Errorf("expected err to be os.Stderr")
}
})
t.Run("custom", func(t *testing.T) {
o := bytes.NewBufferString("")
e := bytes.NewBufferString("")
s := Command{stdout: o, stderr: e}
s.prepareOut()
expectOut := "Test out"
expectErr := "Test err"
s.stdout.Write([]byte(expectOut))
s.stderr.Write([]byte(expectErr))
t.Run("out", func(t *testing.T) {
if o.String() != expectOut {
t.Errorf("expected: %v got: %v", expectOut, o.String())
}
})
t.Run("err", func(t *testing.T) {
if e.String() != expectErr {
t.Errorf("expected: %v got: %v", expectErr, e.String())
}
})
})
}
func TestParseConsoleErrors(t *testing.T) {
cmd := Command{
ErrorCategoryMapping: map[string][]string{
"config": {"configuration error 1", "configuration error 2"},
"build": {"build failed"},
},
}
tt := []struct {
consoleLine string
expectedCategory log.ErrorCategory
}{
{consoleLine: "this is an error", expectedCategory: log.ErrorUndefined},
{consoleLine: "this is configuration error 2", expectedCategory: log.ErrorConfiguration},
{consoleLine: "the build failed", expectedCategory: log.ErrorBuild},
}
for _, test := range tt {
log.SetErrorCategory(log.ErrorUndefined)
cmd.parseConsoleErrors(test.consoleLine)
assert.Equal(t, test.expectedCategory, log.GetErrorCategory(), test.consoleLine)
}
log.SetErrorCategory(log.ErrorUndefined)
}
func TestMatchPattern(t *testing.T) {
tt := []struct {
text string
pattern string
expected bool
}{
{text: "", pattern: "", expected: true},
{text: "simple test", pattern: "", expected: false},
{text: "simple test", pattern: "no", expected: false},
{text: "simple test", pattern: "simple", expected: true},
{text: "simple test", pattern: "test", expected: true},
{text: "advanced pattern test", pattern: "advanced * test", expected: true},
{text: "advanced pattern failed", pattern: "advanced * test", expected: false},
{text: "advanced pattern with multiple placeholders", pattern: "advanced * with * placeholders", expected: true},
{text: "advanced pattern lacking multiple placeholders", pattern: "advanced * with * placeholders", expected: false},
}
for _, test := range tt {
assert.Equalf(t, test.expected, matchPattern(test.text, test.pattern), test.text)
}
}
func TestCmdPipes(t *testing.T) {
cmd := helperCommand("echo", "foo bar", "baz")
defer func() { ExecCommand = exec.Command }()
t.Run("success case", func(t *testing.T) {
o, e, err := cmdPipes(cmd)
t.Run("no error", func(t *testing.T) {
if err != nil {
t.Errorf("error occured but no error expected")
}
})
t.Run("out pipe", func(t *testing.T) {
if o == nil {
t.Errorf("no pipe received")
}
})
t.Run("err pipe", func(t *testing.T) {
if e == nil {
t.Errorf("no pipe received")
}
})
})
}
//based on https://golang.org/src/os/exec/exec_test.go
//this is not directly executed
func TestHelperProcess(*testing.T) {
if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" {
return
}
defer os.Exit(0)
args := os.Args
for len(args) > 0 {
if args[0] == "--" {
args = args[1:]
break
}
args = args[1:]
}
if len(args) == 0 {
fmt.Fprintf(os.Stderr, "No command\n")
os.Exit(2)
}
cmd, args := args[0], args[1:]
switch cmd {
case "/bin/bash":
o, _ := ioutil.ReadAll(os.Stdin)
fmt.Fprintf(os.Stdout, "Stdout: command %v - Stdin: %v\n", cmd, string(o))
fmt.Fprintf(os.Stderr, "Stderr: command %v\n", cmd)
case "echo":
iargs := []interface{}{}
for _, s := range args {
iargs = append(iargs, s)
}
fmt.Println(iargs...)
fmt.Fprintf(os.Stderr, "Stderr: command %v\n", cmd)
case "env":
for _, e := range os.Environ() {
fmt.Println(e)
}
case "long":
b := []byte("a")
size := 64000
b = bytes.Repeat(b, size)
fmt.Fprint(os.Stderr, b)
default:
fmt.Fprintf(os.Stderr, "Unknown command %q\n", cmd)
os.Exit(2)
}
}