From bfc78fdf809bf8c1755dec1d5befb2937926c89d Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sat, 1 Feb 2020 21:16:06 +0200 Subject: [PATCH 01/21] some initial tests --- exec_test.go | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++++ go.mod | 1 + go.sum | 11 +++++++++ 3 files changed, 77 insertions(+) create mode 100644 exec_test.go diff --git a/exec_test.go b/exec_test.go new file mode 100644 index 0000000..10872bb --- /dev/null +++ b/exec_test.go @@ -0,0 +1,65 @@ +package exec_test + +import ( + "github.com/go-exec/exec" + "github.com/stretchr/testify/require" + "testing" +) + +func TestNewArgument(t *testing.T) { + arg := &exec.Argument{ + Name: "name", + Type: 0, + Default: nil, + Multiple: false, + Description: "description", + Value: nil, + } + require.Equal(t, exec.NewArgument(arg.Name, arg.Description), arg) +} + +func TestAddArgument(t *testing.T) { + arg := &exec.Argument{ + Name: "test", + Type: 0, + Default: nil, + Multiple: false, + Description: "", + Value: nil, + } + exec.AddArgument(arg) + + require.Equal(t, arg, exec.Arguments[arg.Name]) +} + +func TestGetArgument(t *testing.T) { + type testCase struct { + test string + name string + arg *exec.Argument + } + + testCases := []testCase{ + { + test: "valid argument", + name: "valid", + arg: &exec.Argument{ + Name: "valid", + }, + }, + { + test: "invalid argument", + name: "invalid", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.arg != nil { + exec.AddArgument(testCase.arg) + } + + require.Equal(t, exec.GetArgument(testCase.name), testCase.arg) + }) + } +} diff --git a/go.mod b/go.mod index 799d4c7..ac55d3e 100644 --- a/go.mod +++ b/go.mod @@ -7,6 +7,7 @@ require ( github.com/mattn/go-isatty v0.0.4 // indirect github.com/pkg/errors v0.8.1 github.com/satori/go.uuid v1.2.0 + github.com/stretchr/testify v1.4.0 golang.org/x/crypto v0.0.0-20170118185426-b8a2a83acfe6 golang.org/x/sys v0.0.0-20161214190518-d75a52659825 // indirect gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 // indirect diff --git a/go.sum b/go.sum index cff569e..8d78f2a 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +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/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= @@ -11,11 +13,20 @@ github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +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/satori/go.uuid v1.2.0 h1:0uYX9dsZ2yD7q2RtLRtPSdGDWzjeM3TbMJP9utgA0ww= github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= +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= golang.org/x/crypto v0.0.0-20170118185426-b8a2a83acfe6 h1:cwnjxMgUhW6Oz2++KLc+loQIC0/qUZL1PHXWLuiyCmc= golang.org/x/crypto v0.0.0-20170118185426-b8a2a83acfe6/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= golang.org/x/sys v0.0.0-20161214190518-d75a52659825 h1:4d9VvrP9mESHxCpAwE1G5e1D8Ybj9v7pX19HkGQV0lk= golang.org/x/sys v0.0.0-20161214190518-d75a52659825/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/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= From c024365acb2363981331a4b16fefc8393c00c9c8 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sat, 1 Feb 2020 21:18:52 +0200 Subject: [PATCH 02/21] added github actions and makefile --- .github/workflows/tests.yml | 20 ++++++++++++++++++++ Makefile | 18 ++++++++++++++++++ 2 files changed, 38 insertions(+) create mode 100644 .github/workflows/tests.yml create mode 100644 Makefile diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..acb7976 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,20 @@ +on: [push] +name: tests +jobs: + test: + strategy: + matrix: + go-version: [1.13] + platform: [ubuntu-latest] + runs-on: ${{ matrix.platform }} + steps: + - name: Install Go + uses: actions/setup-go@v1 + with: + go-version: ${{ matrix.go-version }} + - name: Checkout code + uses: actions/checkout@v1 + - name: Lint + run: make lint + - name: Tests + run: make tests diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..926b7bd --- /dev/null +++ b/Makefile @@ -0,0 +1,18 @@ +all: + +lint-local: + @echo "Running linters" + golangci-lint run -v ./... + +lint: + @echo "Running linters" + docker run --rm -v $(PWD):/app -w /app golangci/golangci-lint:v1.21.0 golangci-lint run -v ./... + +tests: + @echo "Running tests" + @mkdir -p artifacts + go test -race -cover -coverprofile=artifacts/coverage.out -v ./... + +coverage: tests + @echo "Running tests & coverage" + go tool cover -html=artifacts/coverage.out -o artifacts/coverage.html From c75e99d348b3c33452ba7264edb387fe07f1f730 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sat, 1 Feb 2020 22:19:02 +0200 Subject: [PATCH 03/21] lint updates --- output.go | 12 ++---------- recipes/deploy/defaults.go | 2 +- ssh.go | 7 +++---- task.go | 2 +- utils.go | 4 +--- 5 files changed, 8 insertions(+), 19 deletions(-) diff --git a/output.go b/output.go index e66ba46..e367f4d 100644 --- a/output.go +++ b/output.go @@ -11,11 +11,7 @@ type output struct { } func (o output) HasError() bool { - if o.err != nil { - return true - } - - return false + return o.err != nil } func (o output) String() string { @@ -31,11 +27,7 @@ func (o output) Int() int { } func (o output) Bool() bool { - if "true" == o.text { - return true - } - - return false + return "true" == o.text } func (o output) Slice(sep string) []string { diff --git a/recipes/deploy/defaults.go b/recipes/deploy/defaults.go index 66b1728..90714f1 100644 --- a/recipes/deploy/defaults.go +++ b/recipes/deploy/defaults.go @@ -101,7 +101,7 @@ func init() { }).Once().Private() exec.Task("onEnd", func() { - exec.Println(fmt.Sprintf("Finished in %s!", time.Now().Sub(exec.Get("startTime").Time()).String())) + exec.Println(fmt.Sprintf("Finished in %s!", time.Since(exec.Get("startTime").Time()).String())) exec.Println("End") }).Once().Private() } diff --git a/ssh.go b/ssh.go index 71b09f4..36668be 100644 --- a/ssh.go +++ b/ssh.go @@ -27,7 +27,6 @@ type sshClient struct { sessOpened bool running bool env string //export FOO="bar"; export BAR="baz"; - color string keys []string authMethod ssh.AuthMethod initAuthMethodOnce sync.Once @@ -66,12 +65,12 @@ func (c *sshClient) parseHost(host string) error { c.user = u.Username } - if strings.Index(c.host, "/") != -1 { + if strings.Contains(c.host, "/") { return errConnect{c.user, c.host, "unexpected slash in the host URL"} } // Add default port, if not set - if strings.Index(c.host, ":") == -1 { + if !strings.Contains(c.host, ":") { c.host += ":22" } @@ -281,7 +280,7 @@ func (c *sshClient) Signal(sig os.Signal) error { // which sounds like something that should be fixed/resolved // upstream in the golang.org/x/crypto/ssh pkg. // https://github.com/golang/go/issues/4115#issuecomment-66070418 - c.remoteStdin.Write([]byte("\x03")) + _, _ = c.remoteStdin.Write([]byte("\x03")) return c.sess.Signal(ssh.SIGINT) default: return fmt.Errorf("%v not supported", sig) diff --git a/task.go b/task.go index 183d2f4..700b537 100644 --- a/task.go +++ b/task.go @@ -121,7 +121,7 @@ func (t *task) getOrderedArguments() sortArguments { // printhelp prints the return value of help to the standard output. func (t *task) printhelp(taskName string) { - fmt.Printf(t.help(taskName)) + fmt.Print(t.help(taskName)) } // usageString returns a short string containing the syntax of this command. diff --git a/utils.go b/utils.go index 2767f9d..6cf0fb9 100644 --- a/utils.go +++ b/utils.go @@ -34,7 +34,7 @@ func Parse(text string) string { return text } return re.ReplaceAllStringFunc(text, func(str string) string { - name := strings.TrimRight(strings.TrimLeft(str, "{{"), "}}") + name := strings.TrimSuffix(strings.TrimPrefix(str, "{{"), "}}") if Has(name) { return Parse(Get(name).String()) } @@ -359,10 +359,8 @@ func commandToString(run interface{}) string { switch rt.Kind() { case reflect.Slice: runS = strings.Join(run.([]string), " ; ") - break case reflect.String: runS = run.(string) - break } return runS } From da6dfae4d251c29245d50c6ae393fb4d8d340cb5 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sat, 1 Feb 2020 22:26:50 +0200 Subject: [PATCH 04/21] lint updates --- recipes/deploy/release.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/recipes/deploy/release.go b/recipes/deploy/release.go index ca02d98..e2be271 100644 --- a/recipes/deploy/release.go +++ b/recipes/deploy/release.go @@ -96,7 +96,7 @@ func init() { // Metainfo. // Save metainfo about release. - exec.Remote("echo `date +\"%Y%m%d%H%M%S\"`,{{release_name}} >> .dep/releases") + exec.Remote("echo `date +'%Y%m%d%H%M%S'`,{{release_name}} >> .dep/releases") // Make new release. exec.Remote(fmt.Sprintf("mkdir %s", releasePath)) From 15933906d28139d044680131fb17bb97999e615f Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 09:01:55 +0200 Subject: [PATCH 05/21] lint updates --- Makefile | 1 + recipes/deploy/release.go | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Makefile b/Makefile index 926b7bd..412ac21 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ all: lint-local: @echo "Running linters" + golangci-lint cache clean golangci-lint run -v ./... lint: diff --git a/recipes/deploy/release.go b/recipes/deploy/release.go index e2be271..80e507f 100644 --- a/recipes/deploy/release.go +++ b/recipes/deploy/release.go @@ -96,7 +96,7 @@ func init() { // Metainfo. // Save metainfo about release. - exec.Remote("echo `date +'%Y%m%d%H%M%S'`,{{release_name}} >> .dep/releases") + exec.Remote("echo `%s`,{{release_name}} >> .dep/releases", "date +\"%Y%m%d%H%M%S\"") // Make new release. exec.Remote(fmt.Sprintf("mkdir %s", releasePath)) From 952494779dfd27f97830fa4f277761bbcdfc212e Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 09:26:00 +0200 Subject: [PATCH 06/21] more tests --- exec_test.go | 192 ++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 181 insertions(+), 11 deletions(-) diff --git a/exec_test.go b/exec_test.go index 10872bb..576d56a 100644 --- a/exec_test.go +++ b/exec_test.go @@ -1,13 +1,12 @@ -package exec_test +package exec import ( - "github.com/go-exec/exec" "github.com/stretchr/testify/require" "testing" ) func TestNewArgument(t *testing.T) { - arg := &exec.Argument{ + arg := &Argument{ Name: "name", Type: 0, Default: nil, @@ -15,11 +14,11 @@ func TestNewArgument(t *testing.T) { Description: "description", Value: nil, } - require.Equal(t, exec.NewArgument(arg.Name, arg.Description), arg) + require.Equal(t, NewArgument(arg.Name, arg.Description), arg) } func TestAddArgument(t *testing.T) { - arg := &exec.Argument{ + arg := &Argument{ Name: "test", Type: 0, Default: nil, @@ -27,23 +26,23 @@ func TestAddArgument(t *testing.T) { Description: "", Value: nil, } - exec.AddArgument(arg) + AddArgument(arg) - require.Equal(t, arg, exec.Arguments[arg.Name]) + require.Equal(t, arg, Arguments[arg.Name]) } func TestGetArgument(t *testing.T) { type testCase struct { test string name string - arg *exec.Argument + arg *Argument } testCases := []testCase{ { test: "valid argument", name: "valid", - arg: &exec.Argument{ + arg: &Argument{ Name: "valid", }, }, @@ -56,10 +55,181 @@ func TestGetArgument(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { if testCase.arg != nil { - exec.AddArgument(testCase.arg) + AddArgument(testCase.arg) } - require.Equal(t, exec.GetArgument(testCase.name), testCase.arg) + require.Equal(t, GetArgument(testCase.name), testCase.arg) + }) + } +} + +func TestNewOption(t *testing.T) { + opt := &Option{ + Name: "name", + Type: 0, + Default: nil, + Description: "description", + Value: nil, + } + require.Equal(t, NewOption(opt.Name, opt.Description), opt) +} + +func TestAddOption(t *testing.T) { + opt := &Option{ + Name: "name", + Type: 0, + Default: nil, + Description: "description", + Value: nil, + } + AddOption(opt) + + require.Equal(t, opt, Arguments[opt.Name]) +} + +func TestGetOption(t *testing.T) { + type testCase struct { + test string + name string + opt *Option + } + + testCases := []testCase{ + { + test: "valid option", + name: "valid", + opt: &Option{ + Name: "valid", + }, + }, + { + test: "invalid option", + name: "invalid", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.opt != nil { + AddOption(testCase.opt) + } + + require.Equal(t, GetOption(testCase.name), testCase.opt) + }) + } +} + +func TestSet(t *testing.T) { + cfg := &config{ + Name: "cfg", + value: "val", + } + Set(cfg.Name, cfg.value) + + require.Equal(t, cfg, Configs[cfg.Name]) +} + +func TestGet(t *testing.T) { + type testCase struct { + test string + name string + cfg *config + serverCtx *server + } + + testCases := []testCase{ + { + test: "valid cfg", + name: "valid", + cfg: &config{ + Name: "valid", + }, + }, + { + test: "invalid cfg", + name: "invalid", + }, + { + test: "valid cfg in server ctx", + name: "valid", + cfg: &config{ + Name: "valid", + }, + serverCtx: &server{}, + }, + { + test: "invalid cfg in server ctx", + name: "invalid", + serverCtx: &server{}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.cfg != nil { + Set(testCase.cfg.Name, testCase.cfg.value) + } + + if testCase.serverCtx != nil { + ServerContext = testCase.serverCtx + } + + require.Equal(t, Get(testCase.name), testCase.cfg) + }) + } +} + +func TestHas(t *testing.T) { + type testCase struct { + test string + name string + cfg *config + serverCtx *server + expectedResult bool + } + + testCases := []testCase{ + { + test: "valid cfg", + name: "valid", + cfg: &config{ + Name: "valid", + }, + expectedResult: true, + }, + { + test: "invalid cfg", + name: "invalid", + expectedResult: false, + }, + { + test: "valid cfg in server ctx", + name: "valid", + cfg: &config{ + Name: "valid", + }, + serverCtx: &server{}, + expectedResult: true, + }, + { + test: "invalid cfg in server ctx", + name: "invalid", + serverCtx: &server{}, + expectedResult: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.cfg != nil { + Set(testCase.cfg.Name, testCase.cfg.value) + } + + if testCase.serverCtx != nil { + ServerContext = testCase.serverCtx + } + + require.Equal(t, Has(testCase.name), testCase.expectedResult) }) } } From 010b3e5318344480cd4f9c3daa57b498c1b0b4d7 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 09:27:55 +0200 Subject: [PATCH 07/21] test fix --- artifacts/coverage.out | 487 +++++++++++++++++++++++++++++++++++++++++ exec_test.go | 2 +- 2 files changed, 488 insertions(+), 1 deletion(-) create mode 100644 artifacts/coverage.out diff --git a/artifacts/coverage.out b/artifacts/coverage.out new file mode 100644 index 0000000..27e35ef --- /dev/null +++ b/artifacts/coverage.out @@ -0,0 +1,487 @@ +mode: atomic +github.com/go-exec/exec/options.go:29.36,31.23 2 0 +github.com/go-exec/exec/options.go:34.2,34.26 1 0 +github.com/go-exec/exec/options.go:31.23,33.3 1 0 +github.com/go-exec/exec/options.go:38.35,40.2 1 0 +github.com/go-exec/exec/options.go:43.31,45.2 1 0 +github.com/go-exec/exec/options.go:48.29,50.2 1 0 +github.com/go-exec/exec/options.go:66.38,68.18 2 0 +github.com/go-exec/exec/options.go:71.2,71.38 1 0 +github.com/go-exec/exec/options.go:68.18,70.3 1 0 +github.com/go-exec/exec/options.go:75.37,77.2 1 0 +github.com/go-exec/exec/options.go:80.33,82.2 1 0 +github.com/go-exec/exec/options.go:85.31,87.2 1 0 +github.com/go-exec/exec/options.go:91.38,93.2 1 0 +github.com/go-exec/exec/options.go:95.48,97.2 1 0 +github.com/go-exec/exec/options.go:99.43,101.2 1 0 +github.com/go-exec/exec/output.go:13.33,15.2 1 0 +github.com/go-exec/exec/output.go:17.33,19.2 1 0 +github.com/go-exec/exec/output.go:21.27,23.16 2 0 +github.com/go-exec/exec/output.go:26.2,26.10 1 0 +github.com/go-exec/exec/output.go:23.16,25.3 1 0 +github.com/go-exec/exec/output.go:29.29,31.2 1 0 +github.com/go-exec/exec/output.go:33.44,35.2 1 0 +github.com/go-exec/exec/server.go:15.47,18.2 2 0 +github.com/go-exec/exec/server.go:20.44,21.28 1 0 +github.com/go-exec/exec/server.go:26.2,26.14 1 0 +github.com/go-exec/exec/server.go:21.28,22.16 1 0 +github.com/go-exec/exec/server.go:22.16,24.4 1 0 +github.com/go-exec/exec/server.go:29.62,32.2 2 0 +github.com/go-exec/exec/server.go:34.43,38.2 3 0 +github.com/go-exec/exec/server.go:40.35,42.2 1 0 +github.com/go-exec/exec/server.go:44.35,45.35 1 0 +github.com/go-exec/exec/server.go:48.2,48.44 1 0 +github.com/go-exec/exec/server.go:45.35,47.3 1 0 +github.com/go-exec/exec/task.go:39.59,42.2 2 0 +github.com/go-exec/exec/task.go:44.54,47.2 2 0 +github.com/go-exec/exec/task.go:49.48,52.2 2 0 +github.com/go-exec/exec/task.go:54.51,57.2 2 0 +github.com/go-exec/exec/task.go:59.49,62.2 2 0 +github.com/go-exec/exec/task.go:64.53,67.2 2 0 +github.com/go-exec/exec/task.go:69.46,72.2 2 0 +github.com/go-exec/exec/task.go:74.44,77.2 2 0 +github.com/go-exec/exec/task.go:79.57,80.25 1 0 +github.com/go-exec/exec/task.go:83.2,83.12 1 0 +github.com/go-exec/exec/task.go:80.25,82.3 1 0 +github.com/go-exec/exec/task.go:86.53,87.23 1 0 +github.com/go-exec/exec/task.go:90.2,90.12 1 0 +github.com/go-exec/exec/task.go:87.23,89.3 1 0 +github.com/go-exec/exec/task.go:93.29,96.2 2 0 +github.com/go-exec/exec/task.go:98.32,101.2 2 0 +github.com/go-exec/exec/task.go:103.54,106.2 2 0 +github.com/go-exec/exec/task.go:108.51,111.2 2 0 +github.com/go-exec/exec/task.go:113.52,115.39 2 0 +github.com/go-exec/exec/task.go:118.2,119.13 2 0 +github.com/go-exec/exec/task.go:115.39,117.3 1 0 +github.com/go-exec/exec/task.go:123.43,125.2 1 0 +github.com/go-exec/exec/task.go:129.52,131.25 2 0 +github.com/go-exec/exec/task.go:134.2,135.46 2 0 +github.com/go-exec/exec/task.go:138.2,138.11 1 0 +github.com/go-exec/exec/task.go:131.25,133.3 1 0 +github.com/go-exec/exec/task.go:135.46,137.3 1 0 +github.com/go-exec/exec/task.go:145.45,151.24 4 0 +github.com/go-exec/exec/task.go:158.2,158.26 1 0 +github.com/go-exec/exec/task.go:165.2,165.25 1 0 +github.com/go-exec/exec/task.go:177.2,177.28 1 0 +github.com/go-exec/exec/task.go:185.2,185.11 1 0 +github.com/go-exec/exec/task.go:151.24,153.33 2 0 +github.com/go-exec/exec/task.go:153.33,155.4 1 0 +github.com/go-exec/exec/task.go:158.26,160.47 2 0 +github.com/go-exec/exec/task.go:160.47,162.4 1 0 +github.com/go-exec/exec/task.go:165.25,168.39 3 0 +github.com/go-exec/exec/task.go:171.3,172.44 2 0 +github.com/go-exec/exec/task.go:168.39,170.4 1 0 +github.com/go-exec/exec/task.go:172.44,174.4 1 0 +github.com/go-exec/exec/task.go:177.28,180.50 3 0 +github.com/go-exec/exec/task.go:180.50,182.4 1 0 +github.com/go-exec/exec/task.go:194.65,196.16 2 0 +github.com/go-exec/exec/task.go:202.2,202.54 1 0 +github.com/go-exec/exec/task.go:208.2,208.18 1 0 +github.com/go-exec/exec/task.go:214.2,214.30 1 0 +github.com/go-exec/exec/task.go:219.2,221.23 2 0 +github.com/go-exec/exec/task.go:228.2,230.22 2 0 +github.com/go-exec/exec/task.go:237.2,240.31 2 0 +github.com/go-exec/exec/task.go:244.2,244.12 1 0 +github.com/go-exec/exec/task.go:196.16,199.3 2 0 +github.com/go-exec/exec/task.go:202.54,205.3 2 0 +github.com/go-exec/exec/task.go:208.18,211.3 2 0 +github.com/go-exec/exec/task.go:214.30,216.3 1 0 +github.com/go-exec/exec/task.go:221.23,222.31 1 0 +github.com/go-exec/exec/task.go:222.31,224.4 1 0 +github.com/go-exec/exec/task.go:230.22,231.30 1 0 +github.com/go-exec/exec/task.go:231.30,233.4 1 0 +github.com/go-exec/exec/task.go:240.31,242.3 1 0 +github.com/go-exec/exec/task.go:247.47,249.25 2 0 +github.com/go-exec/exec/task.go:251.2,251.35 1 0 +github.com/go-exec/exec/task.go:271.2,272.14 2 0 +github.com/go-exec/exec/task.go:276.2,276.51 1 0 +github.com/go-exec/exec/task.go:306.2,306.12 1 0 +github.com/go-exec/exec/task.go:249.26,249.27 0 0 +github.com/go-exec/exec/task.go:251.35,252.22 1 0 +github.com/go-exec/exec/task.go:253.15,254.29 1 0 +github.com/go-exec/exec/task.go:257.4,257.75 1 0 +github.com/go-exec/exec/task.go:258.13,259.29 1 0 +github.com/go-exec/exec/task.go:262.4,262.71 1 0 +github.com/go-exec/exec/task.go:263.12,264.29 1 0 +github.com/go-exec/exec/task.go:267.4,267.69 1 0 +github.com/go-exec/exec/task.go:254.29,256.5 1 0 +github.com/go-exec/exec/task.go:259.29,261.5 1 0 +github.com/go-exec/exec/task.go:264.29,266.5 1 0 +github.com/go-exec/exec/task.go:272.14,274.3 1 0 +github.com/go-exec/exec/task.go:276.51,277.24 1 0 +github.com/go-exec/exec/task.go:278.15,279.55 1 0 +github.com/go-exec/exec/task.go:286.13,288.100 2 0 +github.com/go-exec/exec/task.go:295.12,297.100 2 0 +github.com/go-exec/exec/task.go:279.55,281.5 1 0 +github.com/go-exec/exec/task.go:281.10,281.35 1 0 +github.com/go-exec/exec/task.go:281.35,283.5 1 0 +github.com/go-exec/exec/task.go:283.10,285.5 1 0 +github.com/go-exec/exec/task.go:288.100,290.5 1 0 +github.com/go-exec/exec/task.go:290.10,290.49 1 0 +github.com/go-exec/exec/task.go:290.49,292.5 1 0 +github.com/go-exec/exec/task.go:292.10,294.5 1 0 +github.com/go-exec/exec/task.go:297.100,299.5 1 0 +github.com/go-exec/exec/task.go:299.10,299.49 1 0 +github.com/go-exec/exec/task.go:299.49,301.5 1 0 +github.com/go-exec/exec/task.go:301.10,303.5 1 0 +github.com/go-exec/exec/taskGroup.go:9.62,12.2 2 0 +github.com/go-exec/exec/taskGroup.go:14.64,17.2 2 0 +github.com/go-exec/exec/taskGroup.go:19.58,22.2 2 0 +github.com/go-exec/exec/taskGroup.go:24.61,27.2 2 0 +github.com/go-exec/exec/exec.go:33.37,33.51 1 0 +github.com/go-exec/exec/exec.go:39.13,42.32 2 0 +github.com/go-exec/exec/exec.go:51.2,51.31 1 0 +github.com/go-exec/exec/exec.go:58.2,58.29 1 0 +github.com/go-exec/exec/exec.go:75.2,82.39 4 0 +github.com/go-exec/exec/exec.go:42.32,46.20 3 0 +github.com/go-exec/exec/exec.go:46.20,48.4 1 0 +github.com/go-exec/exec/exec.go:51.31,56.3 4 0 +github.com/go-exec/exec/exec.go:58.29,59.31 1 0 +github.com/go-exec/exec/exec.go:66.3,66.30 1 0 +github.com/go-exec/exec/exec.go:59.31,60.41 1 0 +github.com/go-exec/exec/exec.go:60.41,61.25 1 0 +github.com/go-exec/exec/exec.go:61.25,63.6 1 0 +github.com/go-exec/exec/exec.go:66.30,67.40 1 0 +github.com/go-exec/exec/exec.go:67.40,68.25 1 0 +github.com/go-exec/exec/exec.go:68.25,70.6 1 0 +github.com/go-exec/exec/exec.go:82.39,84.3 1 0 +github.com/go-exec/exec/exec.go:84.8,85.29 1 0 +github.com/go-exec/exec/exec.go:85.29,87.4 1 0 +github.com/go-exec/exec/exec.go:92.61,98.2 2 1 +github.com/go-exec/exec/exec.go:101.38,102.44 1 2 +github.com/go-exec/exec/exec.go:102.44,106.3 3 2 +github.com/go-exec/exec/exec.go:110.41,111.36 1 2 +github.com/go-exec/exec/exec.go:115.2,115.12 1 1 +github.com/go-exec/exec/exec.go:111.36,113.3 1 1 +github.com/go-exec/exec/exec.go:119.57,125.2 2 1 +github.com/go-exec/exec/exec.go:128.32,129.40 1 2 +github.com/go-exec/exec/exec.go:129.40,131.3 1 2 +github.com/go-exec/exec/exec.go:135.37,136.34 1 2 +github.com/go-exec/exec/exec.go:140.2,140.12 1 1 +github.com/go-exec/exec/exec.go:136.34,138.3 1 1 +github.com/go-exec/exec/exec.go:144.42,146.2 1 5 +github.com/go-exec/exec/exec.go:149.31,150.26 1 4 +github.com/go-exec/exec/exec.go:155.2,155.32 1 4 +github.com/go-exec/exec/exec.go:158.2,158.12 1 2 +github.com/go-exec/exec/exec.go:150.26,151.47 1 2 +github.com/go-exec/exec/exec.go:151.47,153.4 1 0 +github.com/go-exec/exec/exec.go:155.32,157.3 1 2 +github.com/go-exec/exec/exec.go:162.28,163.26 1 4 +github.com/go-exec/exec/exec.go:168.2,169.11 2 4 +github.com/go-exec/exec/exec.go:163.26,164.47 1 4 +github.com/go-exec/exec/exec.go:164.47,166.4 1 0 +github.com/go-exec/exec/exec.go:174.46,182.2 2 0 +github.com/go-exec/exec/exec.go:186.40,193.35 1 0 +github.com/go-exec/exec/exec.go:197.2,197.27 1 0 +github.com/go-exec/exec/exec.go:234.2,234.20 1 0 +github.com/go-exec/exec/exec.go:193.35,195.4 1 0 +github.com/go-exec/exec/exec.go:197.27,204.32 3 0 +github.com/go-exec/exec/exec.go:232.3,232.20 1 0 +github.com/go-exec/exec/exec.go:204.32,205.35 1 0 +github.com/go-exec/exec/exec.go:205.35,206.40 1 0 +github.com/go-exec/exec/exec.go:206.40,207.91 1 0 +github.com/go-exec/exec/exec.go:207.91,218.7 4 0 +github.com/go-exec/exec/exec.go:222.9,222.40 1 0 +github.com/go-exec/exec/exec.go:222.40,227.4 2 0 +github.com/go-exec/exec/exec.go:227.9,229.4 1 0 +github.com/go-exec/exec/exec.go:239.57,246.16 1 0 +github.com/go-exec/exec/exec.go:272.2,273.25 2 0 +github.com/go-exec/exec/exec.go:246.16,248.32 2 0 +github.com/go-exec/exec/exec.go:248.32,249.28 1 0 +github.com/go-exec/exec/exec.go:253.6,253.54 1 0 +github.com/go-exec/exec/exec.go:258.6,262.55 3 0 +github.com/go-exec/exec/exec.go:267.6,267.23 1 0 +github.com/go-exec/exec/exec.go:249.28,250.15 1 0 +github.com/go-exec/exec/exec.go:253.54,254.15 1 0 +github.com/go-exec/exec/exec.go:262.55,264.7 1 0 +github.com/go-exec/exec/exec.go:277.60,285.16 5 0 +github.com/go-exec/exec/exec.go:290.2,291.16 2 0 +github.com/go-exec/exec/exec.go:297.2,298.16 2 0 +github.com/go-exec/exec/exec.go:304.2,309.16 4 0 +github.com/go-exec/exec/exec.go:318.2,318.17 1 0 +github.com/go-exec/exec/exec.go:329.2,331.22 2 0 +github.com/go-exec/exec/exec.go:337.2,338.16 2 0 +github.com/go-exec/exec/exec.go:342.2,342.10 1 0 +github.com/go-exec/exec/exec.go:285.16,289.3 3 0 +github.com/go-exec/exec/exec.go:291.16,295.3 3 0 +github.com/go-exec/exec/exec.go:298.16,302.3 3 0 +github.com/go-exec/exec/exec.go:309.16,311.3 1 0 +github.com/go-exec/exec/exec.go:311.8,313.29 2 0 +github.com/go-exec/exec/exec.go:316.3,316.27 1 0 +github.com/go-exec/exec/exec.go:313.29,315.4 1 0 +github.com/go-exec/exec/exec.go:318.17,321.29 3 0 +github.com/go-exec/exec/exec.go:324.3,324.34 1 0 +github.com/go-exec/exec/exec.go:321.29,323.4 1 0 +github.com/go-exec/exec/exec.go:324.34,326.4 1 0 +github.com/go-exec/exec/exec.go:331.22,335.3 3 0 +github.com/go-exec/exec/exec.go:338.16,340.3 1 0 +github.com/go-exec/exec/exec.go:346.27,348.2 1 0 +github.com/go-exec/exec/exec.go:351.35,353.2 1 0 +github.com/go-exec/exec/exec.go:356.59,362.34 4 0 +github.com/go-exec/exec/exec.go:370.2,370.33 1 0 +github.com/go-exec/exec/exec.go:416.2,416.10 1 0 +github.com/go-exec/exec/exec.go:362.34,364.17 2 0 +github.com/go-exec/exec/exec.go:364.17,367.4 2 0 +github.com/go-exec/exec/exec.go:370.33,372.17 2 0 +github.com/go-exec/exec/exec.go:377.3,382.17 4 0 +github.com/go-exec/exec/exec.go:391.3,391.18 1 0 +github.com/go-exec/exec/exec.go:402.3,404.23 2 0 +github.com/go-exec/exec/exec.go:410.3,411.17 2 0 +github.com/go-exec/exec/exec.go:372.17,375.4 2 0 +github.com/go-exec/exec/exec.go:382.17,384.4 1 0 +github.com/go-exec/exec/exec.go:384.9,386.30 2 0 +github.com/go-exec/exec/exec.go:389.4,389.28 1 0 +github.com/go-exec/exec/exec.go:386.30,388.5 1 0 +github.com/go-exec/exec/exec.go:391.18,394.30 3 0 +github.com/go-exec/exec/exec.go:397.4,397.35 1 0 +github.com/go-exec/exec/exec.go:394.30,396.5 1 0 +github.com/go-exec/exec/exec.go:397.35,399.5 1 0 +github.com/go-exec/exec/exec.go:404.23,408.4 3 0 +github.com/go-exec/exec/exec.go:411.17,413.4 1 0 +github.com/go-exec/exec/exec.go:420.61,423.10 2 0 +github.com/go-exec/exec/exec.go:428.2,428.26 1 0 +github.com/go-exec/exec/exec.go:432.2,432.10 1 0 +github.com/go-exec/exec/exec.go:423.10,426.3 2 0 +github.com/go-exec/exec/exec.go:428.26,430.3 1 0 +github.com/go-exec/exec/exec.go:436.35,439.10 2 0 +github.com/go-exec/exec/exec.go:443.2,444.30 2 0 +github.com/go-exec/exec/exec.go:447.2,449.32 2 0 +github.com/go-exec/exec/exec.go:439.10,441.3 1 0 +github.com/go-exec/exec/exec.go:444.30,446.3 1 0 +github.com/go-exec/exec/exec.go:453.37,456.10 2 0 +github.com/go-exec/exec/exec.go:460.2,461.30 2 0 +github.com/go-exec/exec/exec.go:464.2,466.32 2 0 +github.com/go-exec/exec/exec.go:456.10,458.3 1 0 +github.com/go-exec/exec/exec.go:461.30,463.3 1 0 +github.com/go-exec/exec/exec.go:470.49,471.33 1 0 +github.com/go-exec/exec/exec.go:471.33,472.34 1 0 +github.com/go-exec/exec/exec.go:472.34,474.4 1 0 +github.com/go-exec/exec/exec.go:479.47,480.32 1 0 +github.com/go-exec/exec/exec.go:480.32,481.33 1 0 +github.com/go-exec/exec/exec.go:481.33,483.4 1 0 +github.com/go-exec/exec/exec.go:487.50,491.39 2 0 +github.com/go-exec/exec/exec.go:496.2,496.24 1 0 +github.com/go-exec/exec/exec.go:521.2,521.23 1 0 +github.com/go-exec/exec/exec.go:491.39,493.3 1 0 +github.com/go-exec/exec/exec.go:496.24,498.52 1 0 +github.com/go-exec/exec/exec.go:503.3,503.41 1 0 +github.com/go-exec/exec/exec.go:516.3,516.51 1 0 +github.com/go-exec/exec/exec.go:498.52,500.4 1 0 +github.com/go-exec/exec/exec.go:503.41,505.33 2 0 +github.com/go-exec/exec/exec.go:513.4,513.41 1 0 +github.com/go-exec/exec/exec.go:505.33,506.51 1 0 +github.com/go-exec/exec/exec.go:506.51,508.19 1 0 +github.com/go-exec/exec/exec.go:508.19,510.7 1 0 +github.com/go-exec/exec/exec.go:516.51,518.4 1 0 +github.com/go-exec/exec/exec.go:524.70,526.2 1 0 +github.com/go-exec/exec/exec.go:528.64,530.2 1 0 +github.com/go-exec/exec/exec.go:533.16,534.38 1 0 +github.com/go-exec/exec/exec.go:534.38,536.3 1 0 +github.com/go-exec/exec/exec.go:540.14,541.36 1 0 +github.com/go-exec/exec/exec.go:541.36,543.3 1 0 +github.com/go-exec/exec/console.go:20.32,24.2 3 0 +github.com/go-exec/exec/console.go:32.64,36.2 3 0 +github.com/go-exec/exec/console.go:38.60,39.50 1 0 +github.com/go-exec/exec/console.go:44.2,44.19 1 0 +github.com/go-exec/exec/console.go:39.50,40.46 1 0 +github.com/go-exec/exec/console.go:40.46,42.4 1 0 +github.com/go-exec/exec/console.go:47.72,50.2 2 0 +github.com/go-exec/exec/ssh.go:41.36,43.2 1 0 +github.com/go-exec/exec/ssh.go:46.50,50.47 2 0 +github.com/go-exec/exec/ssh.go:54.2,54.48 1 0 +github.com/go-exec/exec/ssh.go:60.2,60.18 1 0 +github.com/go-exec/exec/ssh.go:68.2,68.35 1 0 +github.com/go-exec/exec/ssh.go:73.2,73.36 1 0 +github.com/go-exec/exec/ssh.go:77.2,77.12 1 0 +github.com/go-exec/exec/ssh.go:50.47,52.3 1 0 +github.com/go-exec/exec/ssh.go:54.48,57.3 2 0 +github.com/go-exec/exec/ssh.go:60.18,62.17 2 0 +github.com/go-exec/exec/ssh.go:65.3,65.22 1 0 +github.com/go-exec/exec/ssh.go:62.17,64.4 1 0 +github.com/go-exec/exec/ssh.go:68.35,70.3 1 0 +github.com/go-exec/exec/ssh.go:73.36,75.3 1 0 +github.com/go-exec/exec/ssh.go:81.38,86.16 3 0 +github.com/go-exec/exec/ssh.go:91.2,97.30 2 0 +github.com/go-exec/exec/ssh.go:109.2,109.43 1 0 +github.com/go-exec/exec/ssh.go:86.16,89.3 2 0 +github.com/go-exec/exec/ssh.go:97.30,99.17 2 0 +github.com/go-exec/exec/ssh.go:102.3,103.17 2 0 +github.com/go-exec/exec/ssh.go:106.3,106.36 1 0 +github.com/go-exec/exec/ssh.go:99.17,100.12 1 0 +github.com/go-exec/exec/ssh.go:103.17,104.12 1 0 +github.com/go-exec/exec/ssh.go:117.48,119.2 1 0 +github.com/go-exec/exec/ssh.go:123.72,124.18 1 0 +github.com/go-exec/exec/ssh.go:128.2,131.16 3 0 +github.com/go-exec/exec/ssh.go:135.2,140.84 1 0 +github.com/go-exec/exec/ssh.go:145.2,146.16 2 0 +github.com/go-exec/exec/ssh.go:149.2,151.12 2 0 +github.com/go-exec/exec/ssh.go:124.18,126.3 1 0 +github.com/go-exec/exec/ssh.go:131.16,133.3 1 0 +github.com/go-exec/exec/ssh.go:140.84,142.4 1 0 +github.com/go-exec/exec/ssh.go:146.16,148.3 1 0 +github.com/go-exec/exec/ssh.go:155.43,156.15 1 0 +github.com/go-exec/exec/ssh.go:159.2,159.18 1 0 +github.com/go-exec/exec/ssh.go:163.2,164.16 2 0 +github.com/go-exec/exec/ssh.go:168.2,169.16 2 0 +github.com/go-exec/exec/ssh.go:173.2,174.16 2 0 +github.com/go-exec/exec/ssh.go:178.2,179.16 2 0 +github.com/go-exec/exec/ssh.go:184.2,190.64 2 0 +github.com/go-exec/exec/ssh.go:195.2,195.48 1 0 +github.com/go-exec/exec/ssh.go:199.2,202.12 4 0 +github.com/go-exec/exec/ssh.go:156.15,158.3 1 0 +github.com/go-exec/exec/ssh.go:159.18,161.3 1 0 +github.com/go-exec/exec/ssh.go:164.16,166.3 1 0 +github.com/go-exec/exec/ssh.go:169.16,171.3 1 0 +github.com/go-exec/exec/ssh.go:174.16,176.3 1 0 +github.com/go-exec/exec/ssh.go:179.16,181.3 1 0 +github.com/go-exec/exec/ssh.go:190.64,192.3 1 0 +github.com/go-exec/exec/ssh.go:195.48,197.3 1 0 +github.com/go-exec/exec/ssh.go:207.34,208.16 1 0 +github.com/go-exec/exec/ssh.go:212.2,217.12 5 0 +github.com/go-exec/exec/ssh.go:208.16,210.3 1 0 +github.com/go-exec/exec/ssh.go:221.98,223.16 2 0 +github.com/go-exec/exec/ssh.go:226.2,227.16 2 0 +github.com/go-exec/exec/ssh.go:230.2,230.44 1 0 +github.com/go-exec/exec/ssh.go:223.16,225.3 1 0 +github.com/go-exec/exec/ssh.go:227.16,229.3 1 0 +github.com/go-exec/exec/ssh.go:235.35,236.18 1 0 +github.com/go-exec/exec/ssh.go:240.2,240.19 1 0 +github.com/go-exec/exec/ssh.go:244.2,248.12 4 0 +github.com/go-exec/exec/ssh.go:236.18,239.3 2 0 +github.com/go-exec/exec/ssh.go:240.19,242.3 1 0 +github.com/go-exec/exec/ssh.go:251.44,253.2 1 0 +github.com/go-exec/exec/ssh.go:255.40,257.2 1 0 +github.com/go-exec/exec/ssh.go:259.40,261.2 1 0 +github.com/go-exec/exec/ssh.go:263.56,265.2 1 0 +github.com/go-exec/exec/ssh.go:267.40,269.2 1 0 +github.com/go-exec/exec/ssh.go:271.49,272.19 1 0 +github.com/go-exec/exec/ssh.go:276.2,276.13 1 0 +github.com/go-exec/exec/ssh.go:272.19,274.3 1 0 +github.com/go-exec/exec/ssh.go:277.20,284.35 2 0 +github.com/go-exec/exec/ssh.go:285.10,286.45 1 0 +github.com/go-exec/exec/utils.go:19.22,23.2 3 0 +github.com/go-exec/exec/utils.go:26.40,28.2 1 0 +github.com/go-exec/exec/utils.go:31.32,33.27 2 0 +github.com/go-exec/exec/utils.go:36.2,36.63 1 0 +github.com/go-exec/exec/utils.go:33.27,35.3 1 0 +github.com/go-exec/exec/utils.go:36.63,38.16 2 0 +github.com/go-exec/exec/utils.go:41.3,41.13 1 0 +github.com/go-exec/exec/utils.go:38.16,40.4 1 0 +github.com/go-exec/exec/utils.go:47.67,49.2 1 0 +github.com/go-exec/exec/utils.go:52.53,53.38 1 0 +github.com/go-exec/exec/utils.go:53.38,55.3 1 0 +github.com/go-exec/exec/utils.go:60.62,62.2 1 0 +github.com/go-exec/exec/utils.go:65.44,66.41 1 0 +github.com/go-exec/exec/utils.go:66.41,68.3 1 0 +github.com/go-exec/exec/utils.go:72.49,76.2 3 0 +github.com/go-exec/exec/utils.go:79.78,83.16 3 0 +github.com/go-exec/exec/utils.go:86.2,87.49 2 0 +github.com/go-exec/exec/utils.go:91.2,91.83 1 0 +github.com/go-exec/exec/utils.go:83.16,85.3 1 0 +github.com/go-exec/exec/utils.go:87.49,89.3 1 0 +github.com/go-exec/exec/utils.go:91.83,93.3 1 0 +github.com/go-exec/exec/utils.go:93.8,97.3 3 0 +github.com/go-exec/exec/utils.go:101.60,103.87 2 0 +github.com/go-exec/exec/utils.go:103.87,105.3 1 0 +github.com/go-exec/exec/utils.go:105.8,109.3 3 0 +github.com/go-exec/exec/utils.go:113.73,117.16 3 0 +github.com/go-exec/exec/utils.go:120.2,121.49 2 0 +github.com/go-exec/exec/utils.go:125.2,125.83 1 0 +github.com/go-exec/exec/utils.go:117.16,119.3 1 0 +github.com/go-exec/exec/utils.go:121.49,123.3 1 0 +github.com/go-exec/exec/utils.go:125.83,127.3 1 0 +github.com/go-exec/exec/utils.go:127.8,129.3 1 0 +github.com/go-exec/exec/utils.go:133.74,135.16 2 0 +github.com/go-exec/exec/utils.go:138.2,139.49 2 0 +github.com/go-exec/exec/utils.go:142.2,142.21 1 0 +github.com/go-exec/exec/utils.go:135.16,137.3 1 0 +github.com/go-exec/exec/utils.go:139.49,141.3 1 0 +github.com/go-exec/exec/utils.go:146.76,148.16 2 0 +github.com/go-exec/exec/utils.go:151.2,152.49 2 0 +github.com/go-exec/exec/utils.go:155.2,155.21 1 0 +github.com/go-exec/exec/utils.go:148.16,150.3 1 0 +github.com/go-exec/exec/utils.go:152.49,154.3 1 0 +github.com/go-exec/exec/utils.go:159.56,163.67 4 0 +github.com/go-exec/exec/utils.go:163.67,165.3 1 0 +github.com/go-exec/exec/utils.go:165.8,167.96 2 0 +github.com/go-exec/exec/utils.go:167.96,169.4 1 0 +github.com/go-exec/exec/utils.go:169.9,173.4 3 0 +github.com/go-exec/exec/utils.go:178.41,182.67 4 0 +github.com/go-exec/exec/utils.go:182.67,184.3 1 0 +github.com/go-exec/exec/utils.go:184.8,186.96 2 0 +github.com/go-exec/exec/utils.go:186.96,188.4 1 0 +github.com/go-exec/exec/utils.go:188.9,192.4 3 0 +github.com/go-exec/exec/utils.go:197.46,201.67 4 0 +github.com/go-exec/exec/utils.go:201.67,203.3 1 0 +github.com/go-exec/exec/utils.go:203.8,205.96 2 0 +github.com/go-exec/exec/utils.go:205.96,207.4 1 0 +github.com/go-exec/exec/utils.go:207.9,211.4 3 0 +github.com/go-exec/exec/utils.go:216.45,219.2 2 0 +github.com/go-exec/exec/utils.go:223.56,228.26 3 0 +github.com/go-exec/exec/utils.go:233.2,237.20 4 0 +github.com/go-exec/exec/utils.go:241.2,241.17 1 0 +github.com/go-exec/exec/utils.go:228.26,231.3 2 0 +github.com/go-exec/exec/utils.go:237.20,239.3 1 0 +github.com/go-exec/exec/utils.go:246.68,257.26 4 0 +github.com/go-exec/exec/utils.go:266.2,270.59 4 0 +github.com/go-exec/exec/utils.go:274.2,274.24 1 0 +github.com/go-exec/exec/utils.go:257.26,259.22 2 0 +github.com/go-exec/exec/utils.go:259.22,261.4 1 0 +github.com/go-exec/exec/utils.go:261.9,263.4 1 0 +github.com/go-exec/exec/utils.go:270.59,272.3 1 0 +github.com/go-exec/exec/utils.go:293.97,307.25 3 0 +github.com/go-exec/exec/utils.go:332.2,334.11 2 0 +github.com/go-exec/exec/utils.go:349.2,349.48 1 0 +github.com/go-exec/exec/utils.go:353.2,353.18 1 0 +github.com/go-exec/exec/utils.go:307.25,313.26 4 0 +github.com/go-exec/exec/utils.go:318.3,318.19 1 0 +github.com/go-exec/exec/utils.go:323.3,323.26 1 0 +github.com/go-exec/exec/utils.go:327.3,327.19 1 0 +github.com/go-exec/exec/utils.go:313.26,316.4 2 0 +github.com/go-exec/exec/utils.go:318.19,321.4 2 0 +github.com/go-exec/exec/utils.go:323.26,325.4 1 0 +github.com/go-exec/exec/utils.go:327.19,329.4 1 0 +github.com/go-exec/exec/utils.go:334.11,338.21 3 0 +github.com/go-exec/exec/utils.go:342.3,342.19 1 0 +github.com/go-exec/exec/utils.go:338.21,340.4 1 0 +github.com/go-exec/exec/utils.go:342.19,343.41 1 0 +github.com/go-exec/exec/utils.go:343.41,345.5 1 0 +github.com/go-exec/exec/utils.go:349.48,351.3 1 0 +github.com/go-exec/exec/utils.go:356.46,359.19 3 0 +github.com/go-exec/exec/utils.go:365.2,365.13 1 0 +github.com/go-exec/exec/utils.go:360.21,361.45 1 0 +github.com/go-exec/exec/utils.go:362.22,363.22 1 0 +github.com/go-exec/exec/utils.go:368.110,370.15 2 0 +github.com/go-exec/exec/utils.go:373.2,373.15 1 0 +github.com/go-exec/exec/utils.go:376.2,377.25 2 0 +github.com/go-exec/exec/utils.go:384.2,384.15 1 0 +github.com/go-exec/exec/utils.go:370.15,372.3 1 0 +github.com/go-exec/exec/utils.go:373.15,375.3 1 0 +github.com/go-exec/exec/utils.go:377.25,378.23 1 0 +github.com/go-exec/exec/utils.go:378.23,379.34 1 0 +github.com/go-exec/exec/utils.go:379.34,381.5 1 0 +github.com/go-exec/exec/utils.go:387.104,389.15 2 0 +github.com/go-exec/exec/utils.go:392.2,392.15 1 0 +github.com/go-exec/exec/utils.go:395.2,396.25 2 0 +github.com/go-exec/exec/utils.go:403.2,403.15 1 0 +github.com/go-exec/exec/utils.go:389.15,391.3 1 0 +github.com/go-exec/exec/utils.go:392.15,394.3 1 0 +github.com/go-exec/exec/utils.go:396.25,397.23 1 0 +github.com/go-exec/exec/utils.go:397.23,398.34 1 0 +github.com/go-exec/exec/utils.go:398.34,400.5 1 0 +github.com/go-exec/exec/utils.go:406.49,408.26 2 0 +github.com/go-exec/exec/utils.go:412.2,413.11 2 0 +github.com/go-exec/exec/utils.go:408.26,410.3 1 0 +github.com/go-exec/exec/config.go:15.38,18.30 2 0 +github.com/go-exec/exec/config.go:31.2,31.16 1 0 +github.com/go-exec/exec/config.go:18.30,21.40 2 0 +github.com/go-exec/exec/config.go:25.3,25.46 1 0 +github.com/go-exec/exec/config.go:29.3,29.39 1 0 +github.com/go-exec/exec/config.go:21.40,22.82 1 0 +github.com/go-exec/exec/config.go:25.46,26.57 1 0 +github.com/go-exec/exec/config.go:34.34,36.2 1 0 +github.com/go-exec/exec/config.go:38.28,40.2 1 0 +github.com/go-exec/exec/config.go:42.32,44.2 1 0 +github.com/go-exec/exec/config.go:46.30,48.2 1 0 +github.com/go-exec/exec/config.go:50.35,52.2 1 0 +github.com/go-exec/exec/config.go:54.35,56.2 1 0 diff --git a/exec_test.go b/exec_test.go index 576d56a..fe1a016 100644 --- a/exec_test.go +++ b/exec_test.go @@ -84,7 +84,7 @@ func TestAddOption(t *testing.T) { } AddOption(opt) - require.Equal(t, opt, Arguments[opt.Name]) + require.Equal(t, opt, Options[opt.Name]) } func TestGetOption(t *testing.T) { From 2af1d31a23e0864b8475759537fc0135130a8883 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 12:29:22 +0200 Subject: [PATCH 08/21] refactoring and test updates --- .gitignore | 1 + Makefile | 2 +- artifacts/coverage.out | 487 ---------------------------------------- examples/simple/main.go | 164 +++++++------- exec.go | 283 ++++++++++++----------- exec_test.go | 114 +++++++--- output.go | 12 +- task.go | 5 +- utils.go | 116 +++++----- 9 files changed, 386 insertions(+), 798 deletions(-) delete mode 100644 artifacts/coverage.out diff --git a/.gitignore b/.gitignore index 7b28844..d9b46d3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ .idea examples/*/* !examples/*/*.go +artifacts diff --git a/Makefile b/Makefile index 412ac21..403924c 100644 --- a/Makefile +++ b/Makefile @@ -12,7 +12,7 @@ lint: tests: @echo "Running tests" @mkdir -p artifacts - go test -race -cover -coverprofile=artifacts/coverage.out -v ./... + go test -race -count=1 -cover -coverprofile=artifacts/coverage.out -v ./... coverage: tests @echo "Running tests & coverage" diff --git a/artifacts/coverage.out b/artifacts/coverage.out deleted file mode 100644 index 27e35ef..0000000 --- a/artifacts/coverage.out +++ /dev/null @@ -1,487 +0,0 @@ -mode: atomic -github.com/go-exec/exec/options.go:29.36,31.23 2 0 -github.com/go-exec/exec/options.go:34.2,34.26 1 0 -github.com/go-exec/exec/options.go:31.23,33.3 1 0 -github.com/go-exec/exec/options.go:38.35,40.2 1 0 -github.com/go-exec/exec/options.go:43.31,45.2 1 0 -github.com/go-exec/exec/options.go:48.29,50.2 1 0 -github.com/go-exec/exec/options.go:66.38,68.18 2 0 -github.com/go-exec/exec/options.go:71.2,71.38 1 0 -github.com/go-exec/exec/options.go:68.18,70.3 1 0 -github.com/go-exec/exec/options.go:75.37,77.2 1 0 -github.com/go-exec/exec/options.go:80.33,82.2 1 0 -github.com/go-exec/exec/options.go:85.31,87.2 1 0 -github.com/go-exec/exec/options.go:91.38,93.2 1 0 -github.com/go-exec/exec/options.go:95.48,97.2 1 0 -github.com/go-exec/exec/options.go:99.43,101.2 1 0 -github.com/go-exec/exec/output.go:13.33,15.2 1 0 -github.com/go-exec/exec/output.go:17.33,19.2 1 0 -github.com/go-exec/exec/output.go:21.27,23.16 2 0 -github.com/go-exec/exec/output.go:26.2,26.10 1 0 -github.com/go-exec/exec/output.go:23.16,25.3 1 0 -github.com/go-exec/exec/output.go:29.29,31.2 1 0 -github.com/go-exec/exec/output.go:33.44,35.2 1 0 -github.com/go-exec/exec/server.go:15.47,18.2 2 0 -github.com/go-exec/exec/server.go:20.44,21.28 1 0 -github.com/go-exec/exec/server.go:26.2,26.14 1 0 -github.com/go-exec/exec/server.go:21.28,22.16 1 0 -github.com/go-exec/exec/server.go:22.16,24.4 1 0 -github.com/go-exec/exec/server.go:29.62,32.2 2 0 -github.com/go-exec/exec/server.go:34.43,38.2 3 0 -github.com/go-exec/exec/server.go:40.35,42.2 1 0 -github.com/go-exec/exec/server.go:44.35,45.35 1 0 -github.com/go-exec/exec/server.go:48.2,48.44 1 0 -github.com/go-exec/exec/server.go:45.35,47.3 1 0 -github.com/go-exec/exec/task.go:39.59,42.2 2 0 -github.com/go-exec/exec/task.go:44.54,47.2 2 0 -github.com/go-exec/exec/task.go:49.48,52.2 2 0 -github.com/go-exec/exec/task.go:54.51,57.2 2 0 -github.com/go-exec/exec/task.go:59.49,62.2 2 0 -github.com/go-exec/exec/task.go:64.53,67.2 2 0 -github.com/go-exec/exec/task.go:69.46,72.2 2 0 -github.com/go-exec/exec/task.go:74.44,77.2 2 0 -github.com/go-exec/exec/task.go:79.57,80.25 1 0 -github.com/go-exec/exec/task.go:83.2,83.12 1 0 -github.com/go-exec/exec/task.go:80.25,82.3 1 0 -github.com/go-exec/exec/task.go:86.53,87.23 1 0 -github.com/go-exec/exec/task.go:90.2,90.12 1 0 -github.com/go-exec/exec/task.go:87.23,89.3 1 0 -github.com/go-exec/exec/task.go:93.29,96.2 2 0 -github.com/go-exec/exec/task.go:98.32,101.2 2 0 -github.com/go-exec/exec/task.go:103.54,106.2 2 0 -github.com/go-exec/exec/task.go:108.51,111.2 2 0 -github.com/go-exec/exec/task.go:113.52,115.39 2 0 -github.com/go-exec/exec/task.go:118.2,119.13 2 0 -github.com/go-exec/exec/task.go:115.39,117.3 1 0 -github.com/go-exec/exec/task.go:123.43,125.2 1 0 -github.com/go-exec/exec/task.go:129.52,131.25 2 0 -github.com/go-exec/exec/task.go:134.2,135.46 2 0 -github.com/go-exec/exec/task.go:138.2,138.11 1 0 -github.com/go-exec/exec/task.go:131.25,133.3 1 0 -github.com/go-exec/exec/task.go:135.46,137.3 1 0 -github.com/go-exec/exec/task.go:145.45,151.24 4 0 -github.com/go-exec/exec/task.go:158.2,158.26 1 0 -github.com/go-exec/exec/task.go:165.2,165.25 1 0 -github.com/go-exec/exec/task.go:177.2,177.28 1 0 -github.com/go-exec/exec/task.go:185.2,185.11 1 0 -github.com/go-exec/exec/task.go:151.24,153.33 2 0 -github.com/go-exec/exec/task.go:153.33,155.4 1 0 -github.com/go-exec/exec/task.go:158.26,160.47 2 0 -github.com/go-exec/exec/task.go:160.47,162.4 1 0 -github.com/go-exec/exec/task.go:165.25,168.39 3 0 -github.com/go-exec/exec/task.go:171.3,172.44 2 0 -github.com/go-exec/exec/task.go:168.39,170.4 1 0 -github.com/go-exec/exec/task.go:172.44,174.4 1 0 -github.com/go-exec/exec/task.go:177.28,180.50 3 0 -github.com/go-exec/exec/task.go:180.50,182.4 1 0 -github.com/go-exec/exec/task.go:194.65,196.16 2 0 -github.com/go-exec/exec/task.go:202.2,202.54 1 0 -github.com/go-exec/exec/task.go:208.2,208.18 1 0 -github.com/go-exec/exec/task.go:214.2,214.30 1 0 -github.com/go-exec/exec/task.go:219.2,221.23 2 0 -github.com/go-exec/exec/task.go:228.2,230.22 2 0 -github.com/go-exec/exec/task.go:237.2,240.31 2 0 -github.com/go-exec/exec/task.go:244.2,244.12 1 0 -github.com/go-exec/exec/task.go:196.16,199.3 2 0 -github.com/go-exec/exec/task.go:202.54,205.3 2 0 -github.com/go-exec/exec/task.go:208.18,211.3 2 0 -github.com/go-exec/exec/task.go:214.30,216.3 1 0 -github.com/go-exec/exec/task.go:221.23,222.31 1 0 -github.com/go-exec/exec/task.go:222.31,224.4 1 0 -github.com/go-exec/exec/task.go:230.22,231.30 1 0 -github.com/go-exec/exec/task.go:231.30,233.4 1 0 -github.com/go-exec/exec/task.go:240.31,242.3 1 0 -github.com/go-exec/exec/task.go:247.47,249.25 2 0 -github.com/go-exec/exec/task.go:251.2,251.35 1 0 -github.com/go-exec/exec/task.go:271.2,272.14 2 0 -github.com/go-exec/exec/task.go:276.2,276.51 1 0 -github.com/go-exec/exec/task.go:306.2,306.12 1 0 -github.com/go-exec/exec/task.go:249.26,249.27 0 0 -github.com/go-exec/exec/task.go:251.35,252.22 1 0 -github.com/go-exec/exec/task.go:253.15,254.29 1 0 -github.com/go-exec/exec/task.go:257.4,257.75 1 0 -github.com/go-exec/exec/task.go:258.13,259.29 1 0 -github.com/go-exec/exec/task.go:262.4,262.71 1 0 -github.com/go-exec/exec/task.go:263.12,264.29 1 0 -github.com/go-exec/exec/task.go:267.4,267.69 1 0 -github.com/go-exec/exec/task.go:254.29,256.5 1 0 -github.com/go-exec/exec/task.go:259.29,261.5 1 0 -github.com/go-exec/exec/task.go:264.29,266.5 1 0 -github.com/go-exec/exec/task.go:272.14,274.3 1 0 -github.com/go-exec/exec/task.go:276.51,277.24 1 0 -github.com/go-exec/exec/task.go:278.15,279.55 1 0 -github.com/go-exec/exec/task.go:286.13,288.100 2 0 -github.com/go-exec/exec/task.go:295.12,297.100 2 0 -github.com/go-exec/exec/task.go:279.55,281.5 1 0 -github.com/go-exec/exec/task.go:281.10,281.35 1 0 -github.com/go-exec/exec/task.go:281.35,283.5 1 0 -github.com/go-exec/exec/task.go:283.10,285.5 1 0 -github.com/go-exec/exec/task.go:288.100,290.5 1 0 -github.com/go-exec/exec/task.go:290.10,290.49 1 0 -github.com/go-exec/exec/task.go:290.49,292.5 1 0 -github.com/go-exec/exec/task.go:292.10,294.5 1 0 -github.com/go-exec/exec/task.go:297.100,299.5 1 0 -github.com/go-exec/exec/task.go:299.10,299.49 1 0 -github.com/go-exec/exec/task.go:299.49,301.5 1 0 -github.com/go-exec/exec/task.go:301.10,303.5 1 0 -github.com/go-exec/exec/taskGroup.go:9.62,12.2 2 0 -github.com/go-exec/exec/taskGroup.go:14.64,17.2 2 0 -github.com/go-exec/exec/taskGroup.go:19.58,22.2 2 0 -github.com/go-exec/exec/taskGroup.go:24.61,27.2 2 0 -github.com/go-exec/exec/exec.go:33.37,33.51 1 0 -github.com/go-exec/exec/exec.go:39.13,42.32 2 0 -github.com/go-exec/exec/exec.go:51.2,51.31 1 0 -github.com/go-exec/exec/exec.go:58.2,58.29 1 0 -github.com/go-exec/exec/exec.go:75.2,82.39 4 0 -github.com/go-exec/exec/exec.go:42.32,46.20 3 0 -github.com/go-exec/exec/exec.go:46.20,48.4 1 0 -github.com/go-exec/exec/exec.go:51.31,56.3 4 0 -github.com/go-exec/exec/exec.go:58.29,59.31 1 0 -github.com/go-exec/exec/exec.go:66.3,66.30 1 0 -github.com/go-exec/exec/exec.go:59.31,60.41 1 0 -github.com/go-exec/exec/exec.go:60.41,61.25 1 0 -github.com/go-exec/exec/exec.go:61.25,63.6 1 0 -github.com/go-exec/exec/exec.go:66.30,67.40 1 0 -github.com/go-exec/exec/exec.go:67.40,68.25 1 0 -github.com/go-exec/exec/exec.go:68.25,70.6 1 0 -github.com/go-exec/exec/exec.go:82.39,84.3 1 0 -github.com/go-exec/exec/exec.go:84.8,85.29 1 0 -github.com/go-exec/exec/exec.go:85.29,87.4 1 0 -github.com/go-exec/exec/exec.go:92.61,98.2 2 1 -github.com/go-exec/exec/exec.go:101.38,102.44 1 2 -github.com/go-exec/exec/exec.go:102.44,106.3 3 2 -github.com/go-exec/exec/exec.go:110.41,111.36 1 2 -github.com/go-exec/exec/exec.go:115.2,115.12 1 1 -github.com/go-exec/exec/exec.go:111.36,113.3 1 1 -github.com/go-exec/exec/exec.go:119.57,125.2 2 1 -github.com/go-exec/exec/exec.go:128.32,129.40 1 2 -github.com/go-exec/exec/exec.go:129.40,131.3 1 2 -github.com/go-exec/exec/exec.go:135.37,136.34 1 2 -github.com/go-exec/exec/exec.go:140.2,140.12 1 1 -github.com/go-exec/exec/exec.go:136.34,138.3 1 1 -github.com/go-exec/exec/exec.go:144.42,146.2 1 5 -github.com/go-exec/exec/exec.go:149.31,150.26 1 4 -github.com/go-exec/exec/exec.go:155.2,155.32 1 4 -github.com/go-exec/exec/exec.go:158.2,158.12 1 2 -github.com/go-exec/exec/exec.go:150.26,151.47 1 2 -github.com/go-exec/exec/exec.go:151.47,153.4 1 0 -github.com/go-exec/exec/exec.go:155.32,157.3 1 2 -github.com/go-exec/exec/exec.go:162.28,163.26 1 4 -github.com/go-exec/exec/exec.go:168.2,169.11 2 4 -github.com/go-exec/exec/exec.go:163.26,164.47 1 4 -github.com/go-exec/exec/exec.go:164.47,166.4 1 0 -github.com/go-exec/exec/exec.go:174.46,182.2 2 0 -github.com/go-exec/exec/exec.go:186.40,193.35 1 0 -github.com/go-exec/exec/exec.go:197.2,197.27 1 0 -github.com/go-exec/exec/exec.go:234.2,234.20 1 0 -github.com/go-exec/exec/exec.go:193.35,195.4 1 0 -github.com/go-exec/exec/exec.go:197.27,204.32 3 0 -github.com/go-exec/exec/exec.go:232.3,232.20 1 0 -github.com/go-exec/exec/exec.go:204.32,205.35 1 0 -github.com/go-exec/exec/exec.go:205.35,206.40 1 0 -github.com/go-exec/exec/exec.go:206.40,207.91 1 0 -github.com/go-exec/exec/exec.go:207.91,218.7 4 0 -github.com/go-exec/exec/exec.go:222.9,222.40 1 0 -github.com/go-exec/exec/exec.go:222.40,227.4 2 0 -github.com/go-exec/exec/exec.go:227.9,229.4 1 0 -github.com/go-exec/exec/exec.go:239.57,246.16 1 0 -github.com/go-exec/exec/exec.go:272.2,273.25 2 0 -github.com/go-exec/exec/exec.go:246.16,248.32 2 0 -github.com/go-exec/exec/exec.go:248.32,249.28 1 0 -github.com/go-exec/exec/exec.go:253.6,253.54 1 0 -github.com/go-exec/exec/exec.go:258.6,262.55 3 0 -github.com/go-exec/exec/exec.go:267.6,267.23 1 0 -github.com/go-exec/exec/exec.go:249.28,250.15 1 0 -github.com/go-exec/exec/exec.go:253.54,254.15 1 0 -github.com/go-exec/exec/exec.go:262.55,264.7 1 0 -github.com/go-exec/exec/exec.go:277.60,285.16 5 0 -github.com/go-exec/exec/exec.go:290.2,291.16 2 0 -github.com/go-exec/exec/exec.go:297.2,298.16 2 0 -github.com/go-exec/exec/exec.go:304.2,309.16 4 0 -github.com/go-exec/exec/exec.go:318.2,318.17 1 0 -github.com/go-exec/exec/exec.go:329.2,331.22 2 0 -github.com/go-exec/exec/exec.go:337.2,338.16 2 0 -github.com/go-exec/exec/exec.go:342.2,342.10 1 0 -github.com/go-exec/exec/exec.go:285.16,289.3 3 0 -github.com/go-exec/exec/exec.go:291.16,295.3 3 0 -github.com/go-exec/exec/exec.go:298.16,302.3 3 0 -github.com/go-exec/exec/exec.go:309.16,311.3 1 0 -github.com/go-exec/exec/exec.go:311.8,313.29 2 0 -github.com/go-exec/exec/exec.go:316.3,316.27 1 0 -github.com/go-exec/exec/exec.go:313.29,315.4 1 0 -github.com/go-exec/exec/exec.go:318.17,321.29 3 0 -github.com/go-exec/exec/exec.go:324.3,324.34 1 0 -github.com/go-exec/exec/exec.go:321.29,323.4 1 0 -github.com/go-exec/exec/exec.go:324.34,326.4 1 0 -github.com/go-exec/exec/exec.go:331.22,335.3 3 0 -github.com/go-exec/exec/exec.go:338.16,340.3 1 0 -github.com/go-exec/exec/exec.go:346.27,348.2 1 0 -github.com/go-exec/exec/exec.go:351.35,353.2 1 0 -github.com/go-exec/exec/exec.go:356.59,362.34 4 0 -github.com/go-exec/exec/exec.go:370.2,370.33 1 0 -github.com/go-exec/exec/exec.go:416.2,416.10 1 0 -github.com/go-exec/exec/exec.go:362.34,364.17 2 0 -github.com/go-exec/exec/exec.go:364.17,367.4 2 0 -github.com/go-exec/exec/exec.go:370.33,372.17 2 0 -github.com/go-exec/exec/exec.go:377.3,382.17 4 0 -github.com/go-exec/exec/exec.go:391.3,391.18 1 0 -github.com/go-exec/exec/exec.go:402.3,404.23 2 0 -github.com/go-exec/exec/exec.go:410.3,411.17 2 0 -github.com/go-exec/exec/exec.go:372.17,375.4 2 0 -github.com/go-exec/exec/exec.go:382.17,384.4 1 0 -github.com/go-exec/exec/exec.go:384.9,386.30 2 0 -github.com/go-exec/exec/exec.go:389.4,389.28 1 0 -github.com/go-exec/exec/exec.go:386.30,388.5 1 0 -github.com/go-exec/exec/exec.go:391.18,394.30 3 0 -github.com/go-exec/exec/exec.go:397.4,397.35 1 0 -github.com/go-exec/exec/exec.go:394.30,396.5 1 0 -github.com/go-exec/exec/exec.go:397.35,399.5 1 0 -github.com/go-exec/exec/exec.go:404.23,408.4 3 0 -github.com/go-exec/exec/exec.go:411.17,413.4 1 0 -github.com/go-exec/exec/exec.go:420.61,423.10 2 0 -github.com/go-exec/exec/exec.go:428.2,428.26 1 0 -github.com/go-exec/exec/exec.go:432.2,432.10 1 0 -github.com/go-exec/exec/exec.go:423.10,426.3 2 0 -github.com/go-exec/exec/exec.go:428.26,430.3 1 0 -github.com/go-exec/exec/exec.go:436.35,439.10 2 0 -github.com/go-exec/exec/exec.go:443.2,444.30 2 0 -github.com/go-exec/exec/exec.go:447.2,449.32 2 0 -github.com/go-exec/exec/exec.go:439.10,441.3 1 0 -github.com/go-exec/exec/exec.go:444.30,446.3 1 0 -github.com/go-exec/exec/exec.go:453.37,456.10 2 0 -github.com/go-exec/exec/exec.go:460.2,461.30 2 0 -github.com/go-exec/exec/exec.go:464.2,466.32 2 0 -github.com/go-exec/exec/exec.go:456.10,458.3 1 0 -github.com/go-exec/exec/exec.go:461.30,463.3 1 0 -github.com/go-exec/exec/exec.go:470.49,471.33 1 0 -github.com/go-exec/exec/exec.go:471.33,472.34 1 0 -github.com/go-exec/exec/exec.go:472.34,474.4 1 0 -github.com/go-exec/exec/exec.go:479.47,480.32 1 0 -github.com/go-exec/exec/exec.go:480.32,481.33 1 0 -github.com/go-exec/exec/exec.go:481.33,483.4 1 0 -github.com/go-exec/exec/exec.go:487.50,491.39 2 0 -github.com/go-exec/exec/exec.go:496.2,496.24 1 0 -github.com/go-exec/exec/exec.go:521.2,521.23 1 0 -github.com/go-exec/exec/exec.go:491.39,493.3 1 0 -github.com/go-exec/exec/exec.go:496.24,498.52 1 0 -github.com/go-exec/exec/exec.go:503.3,503.41 1 0 -github.com/go-exec/exec/exec.go:516.3,516.51 1 0 -github.com/go-exec/exec/exec.go:498.52,500.4 1 0 -github.com/go-exec/exec/exec.go:503.41,505.33 2 0 -github.com/go-exec/exec/exec.go:513.4,513.41 1 0 -github.com/go-exec/exec/exec.go:505.33,506.51 1 0 -github.com/go-exec/exec/exec.go:506.51,508.19 1 0 -github.com/go-exec/exec/exec.go:508.19,510.7 1 0 -github.com/go-exec/exec/exec.go:516.51,518.4 1 0 -github.com/go-exec/exec/exec.go:524.70,526.2 1 0 -github.com/go-exec/exec/exec.go:528.64,530.2 1 0 -github.com/go-exec/exec/exec.go:533.16,534.38 1 0 -github.com/go-exec/exec/exec.go:534.38,536.3 1 0 -github.com/go-exec/exec/exec.go:540.14,541.36 1 0 -github.com/go-exec/exec/exec.go:541.36,543.3 1 0 -github.com/go-exec/exec/console.go:20.32,24.2 3 0 -github.com/go-exec/exec/console.go:32.64,36.2 3 0 -github.com/go-exec/exec/console.go:38.60,39.50 1 0 -github.com/go-exec/exec/console.go:44.2,44.19 1 0 -github.com/go-exec/exec/console.go:39.50,40.46 1 0 -github.com/go-exec/exec/console.go:40.46,42.4 1 0 -github.com/go-exec/exec/console.go:47.72,50.2 2 0 -github.com/go-exec/exec/ssh.go:41.36,43.2 1 0 -github.com/go-exec/exec/ssh.go:46.50,50.47 2 0 -github.com/go-exec/exec/ssh.go:54.2,54.48 1 0 -github.com/go-exec/exec/ssh.go:60.2,60.18 1 0 -github.com/go-exec/exec/ssh.go:68.2,68.35 1 0 -github.com/go-exec/exec/ssh.go:73.2,73.36 1 0 -github.com/go-exec/exec/ssh.go:77.2,77.12 1 0 -github.com/go-exec/exec/ssh.go:50.47,52.3 1 0 -github.com/go-exec/exec/ssh.go:54.48,57.3 2 0 -github.com/go-exec/exec/ssh.go:60.18,62.17 2 0 -github.com/go-exec/exec/ssh.go:65.3,65.22 1 0 -github.com/go-exec/exec/ssh.go:62.17,64.4 1 0 -github.com/go-exec/exec/ssh.go:68.35,70.3 1 0 -github.com/go-exec/exec/ssh.go:73.36,75.3 1 0 -github.com/go-exec/exec/ssh.go:81.38,86.16 3 0 -github.com/go-exec/exec/ssh.go:91.2,97.30 2 0 -github.com/go-exec/exec/ssh.go:109.2,109.43 1 0 -github.com/go-exec/exec/ssh.go:86.16,89.3 2 0 -github.com/go-exec/exec/ssh.go:97.30,99.17 2 0 -github.com/go-exec/exec/ssh.go:102.3,103.17 2 0 -github.com/go-exec/exec/ssh.go:106.3,106.36 1 0 -github.com/go-exec/exec/ssh.go:99.17,100.12 1 0 -github.com/go-exec/exec/ssh.go:103.17,104.12 1 0 -github.com/go-exec/exec/ssh.go:117.48,119.2 1 0 -github.com/go-exec/exec/ssh.go:123.72,124.18 1 0 -github.com/go-exec/exec/ssh.go:128.2,131.16 3 0 -github.com/go-exec/exec/ssh.go:135.2,140.84 1 0 -github.com/go-exec/exec/ssh.go:145.2,146.16 2 0 -github.com/go-exec/exec/ssh.go:149.2,151.12 2 0 -github.com/go-exec/exec/ssh.go:124.18,126.3 1 0 -github.com/go-exec/exec/ssh.go:131.16,133.3 1 0 -github.com/go-exec/exec/ssh.go:140.84,142.4 1 0 -github.com/go-exec/exec/ssh.go:146.16,148.3 1 0 -github.com/go-exec/exec/ssh.go:155.43,156.15 1 0 -github.com/go-exec/exec/ssh.go:159.2,159.18 1 0 -github.com/go-exec/exec/ssh.go:163.2,164.16 2 0 -github.com/go-exec/exec/ssh.go:168.2,169.16 2 0 -github.com/go-exec/exec/ssh.go:173.2,174.16 2 0 -github.com/go-exec/exec/ssh.go:178.2,179.16 2 0 -github.com/go-exec/exec/ssh.go:184.2,190.64 2 0 -github.com/go-exec/exec/ssh.go:195.2,195.48 1 0 -github.com/go-exec/exec/ssh.go:199.2,202.12 4 0 -github.com/go-exec/exec/ssh.go:156.15,158.3 1 0 -github.com/go-exec/exec/ssh.go:159.18,161.3 1 0 -github.com/go-exec/exec/ssh.go:164.16,166.3 1 0 -github.com/go-exec/exec/ssh.go:169.16,171.3 1 0 -github.com/go-exec/exec/ssh.go:174.16,176.3 1 0 -github.com/go-exec/exec/ssh.go:179.16,181.3 1 0 -github.com/go-exec/exec/ssh.go:190.64,192.3 1 0 -github.com/go-exec/exec/ssh.go:195.48,197.3 1 0 -github.com/go-exec/exec/ssh.go:207.34,208.16 1 0 -github.com/go-exec/exec/ssh.go:212.2,217.12 5 0 -github.com/go-exec/exec/ssh.go:208.16,210.3 1 0 -github.com/go-exec/exec/ssh.go:221.98,223.16 2 0 -github.com/go-exec/exec/ssh.go:226.2,227.16 2 0 -github.com/go-exec/exec/ssh.go:230.2,230.44 1 0 -github.com/go-exec/exec/ssh.go:223.16,225.3 1 0 -github.com/go-exec/exec/ssh.go:227.16,229.3 1 0 -github.com/go-exec/exec/ssh.go:235.35,236.18 1 0 -github.com/go-exec/exec/ssh.go:240.2,240.19 1 0 -github.com/go-exec/exec/ssh.go:244.2,248.12 4 0 -github.com/go-exec/exec/ssh.go:236.18,239.3 2 0 -github.com/go-exec/exec/ssh.go:240.19,242.3 1 0 -github.com/go-exec/exec/ssh.go:251.44,253.2 1 0 -github.com/go-exec/exec/ssh.go:255.40,257.2 1 0 -github.com/go-exec/exec/ssh.go:259.40,261.2 1 0 -github.com/go-exec/exec/ssh.go:263.56,265.2 1 0 -github.com/go-exec/exec/ssh.go:267.40,269.2 1 0 -github.com/go-exec/exec/ssh.go:271.49,272.19 1 0 -github.com/go-exec/exec/ssh.go:276.2,276.13 1 0 -github.com/go-exec/exec/ssh.go:272.19,274.3 1 0 -github.com/go-exec/exec/ssh.go:277.20,284.35 2 0 -github.com/go-exec/exec/ssh.go:285.10,286.45 1 0 -github.com/go-exec/exec/utils.go:19.22,23.2 3 0 -github.com/go-exec/exec/utils.go:26.40,28.2 1 0 -github.com/go-exec/exec/utils.go:31.32,33.27 2 0 -github.com/go-exec/exec/utils.go:36.2,36.63 1 0 -github.com/go-exec/exec/utils.go:33.27,35.3 1 0 -github.com/go-exec/exec/utils.go:36.63,38.16 2 0 -github.com/go-exec/exec/utils.go:41.3,41.13 1 0 -github.com/go-exec/exec/utils.go:38.16,40.4 1 0 -github.com/go-exec/exec/utils.go:47.67,49.2 1 0 -github.com/go-exec/exec/utils.go:52.53,53.38 1 0 -github.com/go-exec/exec/utils.go:53.38,55.3 1 0 -github.com/go-exec/exec/utils.go:60.62,62.2 1 0 -github.com/go-exec/exec/utils.go:65.44,66.41 1 0 -github.com/go-exec/exec/utils.go:66.41,68.3 1 0 -github.com/go-exec/exec/utils.go:72.49,76.2 3 0 -github.com/go-exec/exec/utils.go:79.78,83.16 3 0 -github.com/go-exec/exec/utils.go:86.2,87.49 2 0 -github.com/go-exec/exec/utils.go:91.2,91.83 1 0 -github.com/go-exec/exec/utils.go:83.16,85.3 1 0 -github.com/go-exec/exec/utils.go:87.49,89.3 1 0 -github.com/go-exec/exec/utils.go:91.83,93.3 1 0 -github.com/go-exec/exec/utils.go:93.8,97.3 3 0 -github.com/go-exec/exec/utils.go:101.60,103.87 2 0 -github.com/go-exec/exec/utils.go:103.87,105.3 1 0 -github.com/go-exec/exec/utils.go:105.8,109.3 3 0 -github.com/go-exec/exec/utils.go:113.73,117.16 3 0 -github.com/go-exec/exec/utils.go:120.2,121.49 2 0 -github.com/go-exec/exec/utils.go:125.2,125.83 1 0 -github.com/go-exec/exec/utils.go:117.16,119.3 1 0 -github.com/go-exec/exec/utils.go:121.49,123.3 1 0 -github.com/go-exec/exec/utils.go:125.83,127.3 1 0 -github.com/go-exec/exec/utils.go:127.8,129.3 1 0 -github.com/go-exec/exec/utils.go:133.74,135.16 2 0 -github.com/go-exec/exec/utils.go:138.2,139.49 2 0 -github.com/go-exec/exec/utils.go:142.2,142.21 1 0 -github.com/go-exec/exec/utils.go:135.16,137.3 1 0 -github.com/go-exec/exec/utils.go:139.49,141.3 1 0 -github.com/go-exec/exec/utils.go:146.76,148.16 2 0 -github.com/go-exec/exec/utils.go:151.2,152.49 2 0 -github.com/go-exec/exec/utils.go:155.2,155.21 1 0 -github.com/go-exec/exec/utils.go:148.16,150.3 1 0 -github.com/go-exec/exec/utils.go:152.49,154.3 1 0 -github.com/go-exec/exec/utils.go:159.56,163.67 4 0 -github.com/go-exec/exec/utils.go:163.67,165.3 1 0 -github.com/go-exec/exec/utils.go:165.8,167.96 2 0 -github.com/go-exec/exec/utils.go:167.96,169.4 1 0 -github.com/go-exec/exec/utils.go:169.9,173.4 3 0 -github.com/go-exec/exec/utils.go:178.41,182.67 4 0 -github.com/go-exec/exec/utils.go:182.67,184.3 1 0 -github.com/go-exec/exec/utils.go:184.8,186.96 2 0 -github.com/go-exec/exec/utils.go:186.96,188.4 1 0 -github.com/go-exec/exec/utils.go:188.9,192.4 3 0 -github.com/go-exec/exec/utils.go:197.46,201.67 4 0 -github.com/go-exec/exec/utils.go:201.67,203.3 1 0 -github.com/go-exec/exec/utils.go:203.8,205.96 2 0 -github.com/go-exec/exec/utils.go:205.96,207.4 1 0 -github.com/go-exec/exec/utils.go:207.9,211.4 3 0 -github.com/go-exec/exec/utils.go:216.45,219.2 2 0 -github.com/go-exec/exec/utils.go:223.56,228.26 3 0 -github.com/go-exec/exec/utils.go:233.2,237.20 4 0 -github.com/go-exec/exec/utils.go:241.2,241.17 1 0 -github.com/go-exec/exec/utils.go:228.26,231.3 2 0 -github.com/go-exec/exec/utils.go:237.20,239.3 1 0 -github.com/go-exec/exec/utils.go:246.68,257.26 4 0 -github.com/go-exec/exec/utils.go:266.2,270.59 4 0 -github.com/go-exec/exec/utils.go:274.2,274.24 1 0 -github.com/go-exec/exec/utils.go:257.26,259.22 2 0 -github.com/go-exec/exec/utils.go:259.22,261.4 1 0 -github.com/go-exec/exec/utils.go:261.9,263.4 1 0 -github.com/go-exec/exec/utils.go:270.59,272.3 1 0 -github.com/go-exec/exec/utils.go:293.97,307.25 3 0 -github.com/go-exec/exec/utils.go:332.2,334.11 2 0 -github.com/go-exec/exec/utils.go:349.2,349.48 1 0 -github.com/go-exec/exec/utils.go:353.2,353.18 1 0 -github.com/go-exec/exec/utils.go:307.25,313.26 4 0 -github.com/go-exec/exec/utils.go:318.3,318.19 1 0 -github.com/go-exec/exec/utils.go:323.3,323.26 1 0 -github.com/go-exec/exec/utils.go:327.3,327.19 1 0 -github.com/go-exec/exec/utils.go:313.26,316.4 2 0 -github.com/go-exec/exec/utils.go:318.19,321.4 2 0 -github.com/go-exec/exec/utils.go:323.26,325.4 1 0 -github.com/go-exec/exec/utils.go:327.19,329.4 1 0 -github.com/go-exec/exec/utils.go:334.11,338.21 3 0 -github.com/go-exec/exec/utils.go:342.3,342.19 1 0 -github.com/go-exec/exec/utils.go:338.21,340.4 1 0 -github.com/go-exec/exec/utils.go:342.19,343.41 1 0 -github.com/go-exec/exec/utils.go:343.41,345.5 1 0 -github.com/go-exec/exec/utils.go:349.48,351.3 1 0 -github.com/go-exec/exec/utils.go:356.46,359.19 3 0 -github.com/go-exec/exec/utils.go:365.2,365.13 1 0 -github.com/go-exec/exec/utils.go:360.21,361.45 1 0 -github.com/go-exec/exec/utils.go:362.22,363.22 1 0 -github.com/go-exec/exec/utils.go:368.110,370.15 2 0 -github.com/go-exec/exec/utils.go:373.2,373.15 1 0 -github.com/go-exec/exec/utils.go:376.2,377.25 2 0 -github.com/go-exec/exec/utils.go:384.2,384.15 1 0 -github.com/go-exec/exec/utils.go:370.15,372.3 1 0 -github.com/go-exec/exec/utils.go:373.15,375.3 1 0 -github.com/go-exec/exec/utils.go:377.25,378.23 1 0 -github.com/go-exec/exec/utils.go:378.23,379.34 1 0 -github.com/go-exec/exec/utils.go:379.34,381.5 1 0 -github.com/go-exec/exec/utils.go:387.104,389.15 2 0 -github.com/go-exec/exec/utils.go:392.2,392.15 1 0 -github.com/go-exec/exec/utils.go:395.2,396.25 2 0 -github.com/go-exec/exec/utils.go:403.2,403.15 1 0 -github.com/go-exec/exec/utils.go:389.15,391.3 1 0 -github.com/go-exec/exec/utils.go:392.15,394.3 1 0 -github.com/go-exec/exec/utils.go:396.25,397.23 1 0 -github.com/go-exec/exec/utils.go:397.23,398.34 1 0 -github.com/go-exec/exec/utils.go:398.34,400.5 1 0 -github.com/go-exec/exec/utils.go:406.49,408.26 2 0 -github.com/go-exec/exec/utils.go:412.2,413.11 2 0 -github.com/go-exec/exec/utils.go:408.26,410.3 1 0 -github.com/go-exec/exec/config.go:15.38,18.30 2 0 -github.com/go-exec/exec/config.go:31.2,31.16 1 0 -github.com/go-exec/exec/config.go:18.30,21.40 2 0 -github.com/go-exec/exec/config.go:25.3,25.46 1 0 -github.com/go-exec/exec/config.go:29.3,29.39 1 0 -github.com/go-exec/exec/config.go:21.40,22.82 1 0 -github.com/go-exec/exec/config.go:25.46,26.57 1 0 -github.com/go-exec/exec/config.go:34.34,36.2 1 0 -github.com/go-exec/exec/config.go:38.28,40.2 1 0 -github.com/go-exec/exec/config.go:42.32,44.2 1 0 -github.com/go-exec/exec/config.go:46.30,48.2 1 0 -github.com/go-exec/exec/config.go:50.35,52.2 1 0 -github.com/go-exec/exec/config.go:54.35,56.2 1 0 diff --git a/examples/simple/main.go b/examples/simple/main.go index 9bc6186..973542b 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -11,168 +11,170 @@ import ( Example with general setup of tasks */ func main() { - exec.Task("onStart", func() { - exec.Set("startTime", time.Now()) + executor := exec.New() + + executor.Task("onStart", func() { + executor.Set("startTime", time.Now()) }).Private() - exec.Task("onEnd", func() { - exec.Println(fmt.Sprintf("Finished in %s!`", time.Now().Sub(exec.Get("startTime").Time()).String())) + executor.Task("onEnd", func() { + executor.Println(fmt.Sprintf("Finished in %s!`", time.Now().Sub(executor.Get("startTime").Time()).String())) }).Private() type F struct { F func() interface{} } - stage := exec.NewArgument("stage", "Provide the running stage") + stage := executor.NewArgument("stage", "Provide the running stage") stage.Default = "qa" stage.Type = exec.String - exec.AddArgument(stage) + executor.AddArgument(stage) //run always on the server set by stage dynamically - //exec.OnServers(func() []string { - // return []string{exec.GetArgument("stage").String()} + //executor.OnServers(func() []string { + // return []string{executor.GetArgument("stage").String()} //}) - arg2 := exec.NewArgument("arg2", "Provide the arg2") + arg2 := executor.NewArgument("arg2", "Provide the arg2") arg2.Default = "test" - exec.AddArgument(arg2) + executor.AddArgument(arg2) - exec.Set("env", "prod") + executor.Set("env", "prod") - exec.Set("bin/mysql", "mysql default") + executor.Set("bin/mysql", "mysql default") - exec.Set("test", func() interface{} { return "text" }) + executor.Set("test", func() interface{} { return "text" }) - exec.Set("functest", F{F: func() interface{} { + executor.Set("functest", F{F: func() interface{} { return "date" }}) - exec.Set("localUser", exec.Local("git config --get %s", "user.name")) + executor.Set("localUser", executor.Local("git config --get %s", "user.name")) - exec. + executor. Server("prod1", "root@domain.com"). AddRole("prod"). Set("bin/mysql", "mysql prod") - exec. + executor. Server("prod2", "root@domain.com"). AddRole("prod"). Set("bin/mysql", "mysql prod") - exec. + executor. Server("qa", "root@domain.com"). Key("~/.ssh/id_rsa"). AddRole("qa"). Set("bin/mysql", "mysql qa") - exec. + executor. Server("stage", "root@domain.com"). AddRole("stage") - opt1 := exec.NewOption("opt1", "test") - opt2 := exec.NewOption("opt2", "test") + opt1 := executor.NewOption("opt1", "test") + opt2 := executor.NewOption("opt2", "test") - exec. + executor. Task("upload", func() { - exec.Remote("ls -la /") - exec.Upload("test.txt", "~/test.txt") + executor.Remote("ls -la /") + executor.Upload("test.txt", "~/test.txt") }) - exec. + executor. Task("download", func() { - exec.Remote("ls -la /") - exec.Download("~/test.txt", "test.txt") + executor.Remote("ls -la /") + executor.Download("~/test.txt", "test.txt") }) - exec. + executor. Task("test1", func() { - //fmt.Println(exec.TaskContext.GetOption("opt1").ToString()) - exec.Remote("echo Git user is: " + exec.Get("localUser").String()) - exec.Remote("ls -la /") - fmt.Println(exec.TaskContext.GetArgument("stage")) - fmt.Println(exec.TaskContext.GetArgument("arg2")) + //fmt.Println(executor.TaskContext.GetOption("opt1").ToString()) + executor.Remote("echo Git user is: " + executor.Get("localUser").String()) + executor.Remote("ls -la /") + fmt.Println(executor.TaskContext.GetArgument("stage")) + fmt.Println(executor.TaskContext.GetArgument("arg2")) }). ShortDescription("Running test1 task"). AddOption(opt1) - exec. + executor. Task("test2", func() { - exec.Remote("ls -la ~") + executor.Remote("ls -la ~") }). ShortDescription("Running test2 task"). AddOption(opt2) - exec. + executor. Task("test3", func() { - exec.Remote("ls -la ~/.ssh") + executor.Remote("ls -la ~/.ssh") }). ShortDescription("Running test3 task") //should avoid using RunLocal in a task that will run in a stage with multiple servers associated! - exec. + executor. Task("local", func() { - exec.Local("ls -la ~/Public; ls -la /Users/") - exec.Local("docker") + executor.Local("ls -la ~/Public; ls -la /Users/") + executor.Local("docker") }). Once(). ShortDescription("Running local task") - exec. + executor. Task("yarn", func() { - exec.Local("yarn") + executor.Local("yarn") }) - exec. + executor. Task("docker", func() { - exec.Local("docker stats") + executor.Local("docker stats") }) - exec. + executor. Task("docker-remote", func() { - exec.Remote("docker") + executor.Remote("docker") }). OnServers(func() []string { return []string{"prod1"} }) - exec. + executor. Task("get", func() { - exec.Remote(fmt.Sprintf("%s", exec.Get("bin/mysql").String())) - fmt.Println(exec.TaskContext.GetArgument("stage")) - fmt.Println(exec.TaskContext.GetArgument("arg2")) + executor.Remote(fmt.Sprintf("%s", executor.Get("bin/mysql").String())) + fmt.Println(executor.TaskContext.GetArgument("stage")) + fmt.Println(executor.TaskContext.GetArgument("arg2")) }). ShortDescription("Testing get in different servers contexts") - exec. + executor. Task("get2", func() { - exec.Remote(fmt.Sprintf("%s", exec.Get("functest").Value().(F).F())) + executor.Remote(fmt.Sprintf("%s", executor.Get("functest").Value().(F).F())) }). ShortDescription("Testing get2 in different servers contexts") - exec. + executor. Task("get3", func() { - exec.Println(exec.Get("test").String()) + executor.Println(executor.Get("test").String()) }). ShortDescription("Testing get3 in different servers contexts"). RemoveArgument("stage") - exec. + executor. TaskGroup("deploy1", "test1", "test2"). ShortDescription("Deploy code 1") - exec. + executor. TaskGroup("deploy2", "local", "test3"). ShortDescription("Deploy code 2") - exec. + executor. TaskGroup("deploy3", "get"). ShortDescription("Deploy code 3") - exec. + executor. Task("onservers:a", func() { - exec.RunIfNoBinary("docker", []string{ + executor.RunIfNoBinary("docker", []string{ "echo 'a'", "echo 'b'", }) @@ -181,9 +183,9 @@ func main() { return []string{"prod1", "prod2"} }) - exec. + executor. Task("onservers:b", func() { - exec.RunIfNoBinary("wget", []string{ + executor.RunIfNoBinary("wget", []string{ "echo 'a'", "echo 'b'", }) @@ -192,34 +194,34 @@ func main() { return []string{"prod1", "prod2"} }) - exec. + executor. Task("onservers:c", func() { - exec.RunIfNoBinary("docker", []string{ + executor.RunIfNoBinary("docker", []string{ "echo 'a'", "echo 'b'", }) }). OnlyOnServers([]string{"prod1"}) - exec. + executor. Task("servercontext:host", func() { - fmt.Println(exec.ServerContext.Name, exec.ServerContext.GetHost()) + fmt.Println(executor.ServerContext.Name, executor.ServerContext.GetHost()) }). OnServers(func() []string { return []string{"prod1", "prod2"} }) - exec. + executor. Task("onservers:read", func() { - fmt.Printf("`%s`\n", exec.Remote("git config --get %s", "user.name").String()) + fmt.Printf("`%s`\n", executor.Remote("git config --get %s", "user.name").String()) }). OnServers(func() []string { return []string{"prod1"} }) - exec. + executor. Task("ask", func() { - response := exec.Ask("How are you?", "better") + response := executor.Ask("How are you?", "better") color.Yellow("Your response is `%s`", response) }). @@ -227,13 +229,13 @@ func main() { return []string{"prod1"} }) - exec. + executor. Task("ask-confirmation", func() { - response := exec.AskWithConfirmation("Would you like to give it a shot?", true) + response := executor.AskWithConfirmation("Would you like to give it a shot?", true) color.Yellow("Your response is `%t`", response) - response = exec.AskWithConfirmation("Would you like to give it a shot again?", false) + response = executor.AskWithConfirmation("Would you like to give it a shot again?", false) color.Yellow("Your response is `%t`", response) }). @@ -241,9 +243,9 @@ func main() { return []string{"prod1"} }) - exec. + executor. Task("ask-choices", func() { - response := exec.AskWithChoices("What are your choices?", map[string]interface{}{ + response := executor.AskWithChoices("What are your choices?", map[string]interface{}{ "default": []string{ "agent", }, @@ -260,12 +262,12 @@ func main() { return []string{"prod1"} }) - exec.Before("test3", "local") - exec.Before("get3", "test3") - exec.Before("get3", "local") - exec.After("local", "onservers:a") - exec.After("local", "get3") - exec.After("onservers:a", "local") + executor.Before("test3", "local") + executor.Before("get3", "test3") + executor.Before("get3", "local") + executor.After("local", "onservers:a") + executor.After("local", "get3") + executor.After("onservers:a", "local") - exec.Init() + executor.Init() } diff --git a/exec.go b/exec.go index f367a35..855afba 100644 --- a/exec.go +++ b/exec.go @@ -10,63 +10,77 @@ import ( "strings" ) -var ( +type Exec struct { // Configs contains all exec context vars used by Get and Set - Configs = make(map[string]*config) + Configs map[string]*config // Tasks contains all exec tasks - Tasks = make(map[string]*task) + Tasks map[string]*task // Servers contains all exec servers - Servers = make(map[string]*server) + Servers map[string]*server // TaskGroups contains all exec task groups - TaskGroups = make(map[string]*taskGroup) + TaskGroups map[string]*taskGroup // Arguments contains all exec arguments - Arguments = make(map[string]*Argument) + Arguments map[string]*Argument // Options contains all exec options - Options = make(map[string]*Option) + Options map[string]*Option // ServerContext is the current active server ServerContext *server // TaskContext is the current executed task TaskContext *task - before = make(map[string][]string) - after = make(map[string][]string) - serverContextF = func() []string { return nil } //must return one server name + before map[string][]string + after map[string][]string + serverContextF func() []string //must return one server name argumentSequence int -) +} + +func New() *Exec { + return &Exec{ + Configs: make(map[string]*config), + Tasks: make(map[string]*task), + Servers: make(map[string]*server), + TaskGroups: make(map[string]*taskGroup), + Arguments: make(map[string]*Argument), + Options: make(map[string]*Option), + before: make(map[string][]string), + after: make(map[string][]string), + serverContextF: func() []string { return nil }, + } +} // Init initializes the exec and executes the current command // should be added to the end of all exec declarations -func Init() { +func (e *Exec) Init() { subtasks := make(map[string]*task) - for name, task := range Tasks { - task.Arguments = mergeArguments(task.removeArguments, Arguments, task.Arguments) - task.Options = mergeOptions(task.removeOptions, Options, task.Options) + for name, task := range e.Tasks { + task.Arguments = mergeArguments(task.removeArguments, e.Arguments, task.Arguments) + task.Options = mergeOptions(task.removeOptions, e.Options, task.Options) if !task.private { subtasks[name] = task } } - for name := range TaskGroups { - TaskGroups[name].task.Arguments = mergeArguments(TaskGroups[name].task.removeArguments, Arguments, TaskGroups[name].task.Arguments) - TaskGroups[name].task.Options = mergeOptions(TaskGroups[name].task.removeOptions, Options, TaskGroups[name].task.Options) - Tasks[name] = TaskGroups[name].task - subtasks[name] = TaskGroups[name].task + for name := range e.TaskGroups { + e.TaskGroups[name].task.Arguments = mergeArguments(e.TaskGroups[name].task.removeArguments, e.Arguments, e.TaskGroups[name].task.Arguments) + e.TaskGroups[name].task.Options = mergeOptions(e.TaskGroups[name].task.removeOptions, e.Options, e.TaskGroups[name].task.Options) + e.Tasks[name] = e.TaskGroups[name].task + subtasks[name] = e.TaskGroups[name].task } - for _, task := range Tasks { - if before[task.Name] != nil { - for _, bt := range before[task.Name] { - if Tasks[bt] != nil { - task.before = append(task.before, Tasks[bt]) + for _, task := range e.Tasks { + if e.before[task.Name] != nil { + for _, bt := range e.before[task.Name] { + if e.Tasks[bt] != nil { + task.before = append(task.before, e.Tasks[bt]) } } } - if after[task.Name] != nil { - for _, at := range after[task.Name] { - if Tasks[at] != nil { - task.after = append(task.after, Tasks[at]) + if e.after[task.Name] != nil { + for _, at := range e.after[task.Name] { + if e.Tasks[at] != nil { + task.after = append(task.after, e.Tasks[at]) } } } @@ -76,20 +90,20 @@ func Init() { subtasks: subtasks, } - rootTask.Arguments = Arguments - rootTask.Options = mergeOptions(map[string]string{}, Options, rootTask.Options) + rootTask.Arguments = e.Arguments + rootTask.Options = mergeOptions(map[string]string{}, e.Options, rootTask.Options) if err := run(&rootTask); err != nil { - fmt.Fprintln(os.Stderr, err) + _, _ = fmt.Fprintln(os.Stderr, err) } else { - for _, s := range Servers { - s.sshClient.Close() + for _, s := range e.Servers { + _ = s.sshClient.Close() } } } // NewArgument returns a new Argument -func NewArgument(name string, description string) *Argument { +func (e *Exec) NewArgument(name string, description string) *Argument { var arg = &Argument{ Name: name, Description: description, @@ -98,17 +112,17 @@ func NewArgument(name string, description string) *Argument { } // AddArgument adds an Argument to exec -func AddArgument(argument *Argument) { - if _, ok := Arguments[argument.Name]; !ok { - argument.sequence = argumentSequence - argumentSequence++ - Arguments[argument.Name] = argument +func (e *Exec) AddArgument(argument *Argument) { + if _, ok := e.Arguments[argument.Name]; !ok { + argument.sequence = e.argumentSequence + e.argumentSequence++ + e.Arguments[argument.Name] = argument } } // GetArgument return an Argument pointer -func GetArgument(name string) *Argument { - if arg, ok := Arguments[name]; ok { +func (e *Exec) GetArgument(name string) *Argument { + if arg, ok := e.Arguments[name]; ok { return arg } @@ -116,7 +130,7 @@ func GetArgument(name string) *Argument { } // NewOption returns a new Option -func NewOption(name string, description string) *Option { +func (e *Exec) NewOption(name string, description string) *Option { var opt = &Option{ Name: name, Description: description, @@ -125,15 +139,15 @@ func NewOption(name string, description string) *Option { } // AddOption adds an Option to exec -func AddOption(option *Option) { - if _, ok := Options[option.Name]; !ok { - Options[option.Name] = option +func (e *Exec) AddOption(option *Option) { + if _, ok := e.Options[option.Name]; !ok { + e.Options[option.Name] = option } } // GetOption return an Option pointer -func GetOption(name string) *Option { - if opt, ok := Options[name]; ok { +func (e *Exec) GetOption(name string) *Option { + if opt, ok := e.Options[name]; ok { return opt } @@ -141,72 +155,73 @@ func GetOption(name string) *Option { } // Set sets a exec Config -func Set(name string, value interface{}) { - Configs[name] = &config{Name: name, value: value} +func (e *Exec) Set(name string, value interface{}) { + e.Configs[name] = &config{Name: name, value: value} } // Get gets a Config value either set in a Server or directly in exec -func Get(name string) *config { - if ServerContext != nil { - if c, ok := ServerContext.Configs[name]; ok { +func (e *Exec) Get(name string) *config { + if e.ServerContext != nil { + if c, ok := e.ServerContext.Configs[name]; ok { return c } } - if c, ok := Configs[name]; ok { + if c, ok := e.Configs[name]; ok { return c } return nil } // Has checks if a Config is available -func Has(name string) bool { - if ServerContext != nil { - if _, ok := ServerContext.Configs[name]; ok { +func (e *Exec) Has(name string) bool { + if e.ServerContext != nil { + if _, ok := e.ServerContext.Configs[name]; ok { return true } } - _, ok := Configs[name] + _, ok := e.Configs[name] return ok } // Server adds a new Server to exec // dsn should be user@host:port -func Server(name string, dsn string) *server { - Servers[name] = &server{ +func (e *Exec) Server(name string, dsn string) *server { + e.Servers[name] = &server{ Name: name, Dsn: dsn, Configs: make(map[string]*config), sshClient: &sshClient{}, } - return Servers[name] + return e.Servers[name] } // Task inherits the exec Arguments and can override and/or have new Options // it accepts a name and a func; the func content is executed on each command execution -func Task(name string, f func()) *task { - Tasks[name] = &task{ +func (e *Exec) Task(name string, f func()) *task { + e.Tasks[name] = &task{ Name: name, Arguments: make(map[string]*Argument), Options: make(map[string]*Option), + exec: e, removeArguments: make(map[string]string), removeOptions: make(map[string]string), serverContextF: func() []string { return nil }, } - Tasks[name].run = func() { + e.Tasks[name].run = func() { // set task context - TaskContext = Tasks[name] + e.TaskContext = e.Tasks[name] - run, onServers := shouldIRun() + run, onServers := e.shouldIRun() //skip tasks's server checking if requested if run && len(onServers) > 0 { - for _, server := range Servers { + for _, server := range e.Servers { for _, onServer := range onServers { - if (server.Name == onServer || server.HasRole(onServer)) && Servers[onServer] != nil { + if (server.Name == onServer || server.HasRole(onServer)) && e.Servers[onServer] != nil { // set server context - ServerContext = server + e.ServerContext = server color.White("➤ Executing task %s on server %s", color.YellowString(name), color.GreenString(fmt.Sprintf("[%s]", server.Name))) @@ -214,7 +229,7 @@ func Task(name string, f func()) *task { f() //reset server context - ServerContext = nil + e.ServerContext = nil } } } @@ -225,19 +240,19 @@ func Task(name string, f func()) *task { //execute task's func f() } else { - taskNotAllowedToRunPrint(onServers, name) + e.taskNotAllowedToRunPrint(onServers, name) } //reset task context - TaskContext = nil + e.TaskContext = nil } - return Tasks[name] + return e.Tasks[name] } // TaskGroup inherits the exec Arguments and can override and/or have new Options // and it will run all associated tasks -func TaskGroup(name string, tasks ...string) *taskGroup { - TaskGroups[name] = &taskGroup{ +func (e *Exec) TaskGroup(name string, tasks ...string) *taskGroup { + e.TaskGroups[name] = &taskGroup{ Name: name, task: &task{ Name: name, @@ -246,36 +261,36 @@ func TaskGroup(name string, tasks ...string) *taskGroup { run: func() { color.White("➤ Executing task group %s", color.YellowString(name)) for _, task := range tasks { - if Tasks[task] == nil { + if e.Tasks[task] == nil { continue } - if Tasks[task].once && Tasks[task].executedOnce { + if e.Tasks[task].once && e.Tasks[task].executedOnce { continue } //set task context - TaskContext = Tasks[task] + e.TaskContext = e.Tasks[task] - Tasks[task].run() + e.Tasks[task].run() - if Tasks[task].once && !Tasks[task].executedOnce { - Tasks[task].executedOnce = true + if e.Tasks[task].once && !e.Tasks[task].executedOnce { + e.Tasks[task].executedOnce = true } //reset task context - TaskContext = nil + e.TaskContext = nil } }, }, } - TaskGroups[name].tasks = append(TaskGroups[name].tasks, tasks...) - return TaskGroups[name] + e.TaskGroups[name].tasks = append(e.TaskGroups[name].tasks, tasks...) + return e.TaskGroups[name] } // Local runs a local command and displays/returns the output for further usage, for example in a Task func -func Local(command string, args ...interface{}) (o output) { - command = Parse(fmt.Sprintf(command, args...)) +func (e *Exec) Local(command string, args ...interface{}) (o Output) { + command = e.Parse(fmt.Sprintf(command, args...)) color.Green("[%s] %s %s", "local", ">", color.WhiteString("`%s`", command)) @@ -326,7 +341,7 @@ func Local(command string, args ...interface{}) (o output) { } } - o.text = strings.TrimSpace(string(output)) + o.text = strings.TrimSpace(output) if len(o.text) == 0 { color.Red("[%s] %s\n", "local", "<") @@ -343,19 +358,19 @@ func Local(command string, args ...interface{}) (o output) { } // Println parses a text template, if founds a {{ var }}, it automatically runs the Get(var) on it -func Println(text string) { - fmt.Println(Parse(text)) +func (e *Exec) Println(text string) { + fmt.Println(e.Parse(text)) } // OnServers sets the server context dynamically -func OnServers(f func() []string) { - serverContextF = f +func (e *Exec) OnServers(f func() []string) { + e.serverContextF = f } -// RemoteRun executes a command on a specific server -func RemoteRun(command string, server *server) (o output) { - ServerContext = server - command = Parse(command) +// remoteRun executes a command on a specific server +func (e *Exec) remoteRun(command string, server *server) (o Output) { + e.ServerContext = server + command = e.Parse(command) color.Green("[%s] %s %s", server.Name, ">", color.WhiteString("`%s`", command)) @@ -417,103 +432,103 @@ func RemoteRun(command string, server *server) (o output) { } // Remote runs a command with args, in the ServerContext -func Remote(command string, args ...interface{}) (o output) { - run, onServers := shouldIRun() +func (e *Exec) Remote(command string, args ...interface{}) (o Output) { + run, onServers := e.shouldIRun() if !run { - commandNotAllowedToRunPrint(onServers, fmt.Sprintf(command, args...)) + e.commandNotAllowedToRunPrint(onServers, fmt.Sprintf(command, args...)) return o } - if ServerContext != nil { - return RemoteRun(fmt.Sprintf(command, args...), ServerContext) + if e.ServerContext != nil { + return e.remoteRun(fmt.Sprintf(command, args...), e.ServerContext) } return o } // Upload uploads a file or directory from local to remote, using native scp binary -func Upload(local, remote string) { - run, onServers := shouldIRun() +func (e *Exec) Upload(local, remote string) { + run, onServers := e.shouldIRun() if !run { - commandNotAllowedToRunPrint(onServers, fmt.Sprintf("scp (local)%s > (remote)%s", local, remote)) + e.commandNotAllowedToRunPrint(onServers, fmt.Sprintf("scp (local)%s > (remote)%s", local, remote)) } var args = []string{"scp", "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -r"} - if ServerContext.key != nil { - args = append(args, "-i "+*ServerContext.key) + if e.ServerContext.key != nil { + args = append(args, "-i "+*e.ServerContext.key) } - args = append(args, local, ServerContext.Dsn+":"+remote) + args = append(args, local, e.ServerContext.Dsn+":"+remote) - Local(strings.Join(args, " ")) + e.Local(strings.Join(args, " ")) } // Download downloads a file or directory from remote to local, using native scp binary -func Download(remote, local string) { - run, onServers := shouldIRun() +func (e *Exec) Download(remote, local string) { + run, onServers := e.shouldIRun() if !run { - commandNotAllowedToRunPrint(onServers, fmt.Sprintf("scp (remote)%s > (local)%s", local, remote)) + e.commandNotAllowedToRunPrint(onServers, fmt.Sprintf("scp (remote)%s > (local)%s", local, remote)) } var args = []string{"scp", "-o StrictHostKeyChecking=no -o UserKnownHostsFile=/dev/null -r"} - if ServerContext.key != nil { - args = append(args, "-i "+*ServerContext.key) + if e.ServerContext.key != nil { + args = append(args, "-i "+*e.ServerContext.key) } - args = append(args, ServerContext.Dsn+":"+remote, local) + args = append(args, e.ServerContext.Dsn+":"+remote, local) - Local(strings.Join(args, " ")) + e.Local(strings.Join(args, " ")) } // Before sets tasks to run before task -func Before(task string, tasksBefore ...string) { +func (e *Exec) Before(task string, tasksBefore ...string) { for _, tb := range tasksBefore { - if !contains(before[task], tb) { - before[task] = append(before[task], tb) + if !contains(e.before[task], tb) { + e.before[task] = append(e.before[task], tb) } } } // After sets tasks to run after task -func After(task string, tasksAfter ...string) { +func (e *Exec) After(task string, tasksAfter ...string) { for _, ta := range tasksAfter { - if !contains(after[task], ta) { - after[task] = append(after[task], ta) + if !contains(e.after[task], ta) { + e.after[task] = append(e.after[task], ta) } } } -func shouldIRun() (run bool, onServers []string) { +func (e *Exec) shouldIRun() (run bool, onServers []string) { run = true //default values if serverContextF is set - if s := serverContextF(); len(s) > 0 { + if s := e.serverContextF(); len(s) > 0 { onServers = s } //inside a task - if TaskContext != nil { + if e.TaskContext != nil { //task has a serverContextF - if s := TaskContext.serverContextF(); len(s) > 0 { + if s := e.TaskContext.serverContextF(); len(s) > 0 { onServers = s } //task needs to run only on some servers - if len(TaskContext.onlyOnServers) > 0 { + if len(e.TaskContext.onlyOnServers) > 0 { run = false for _, oS := range onServers { - for _, oOS := range TaskContext.onlyOnServers { + for _, oOS := range e.TaskContext.onlyOnServers { //task on server matches only on servers if oS == oOS { run = true } } } - onServers = TaskContext.onlyOnServers + onServers = e.TaskContext.onlyOnServers } - if TaskContext.once && TaskContext.executedOnce { + if e.TaskContext.once && e.TaskContext.executedOnce { run = false } } @@ -521,24 +536,24 @@ func shouldIRun() (run bool, onServers []string) { return run, onServers } -func commandNotAllowedToRunPrint(onServers []string, command string) { +func (e *Exec) commandNotAllowedToRunPrint(onServers []string, command string) { fmt.Printf("%s%s%s\n", color.CyanString("[local] > Command `"), color.WhiteString(command), color.CyanString("` can run only on %s", onServers)) } -func taskNotAllowedToRunPrint(onServers []string, task string) { +func (e *Exec) taskNotAllowedToRunPrint(onServers []string, task string) { fmt.Printf("%s%s%s\n", color.CyanString("[local] > Task `"), color.WhiteString(task), color.CyanString("` can run only on %s", onServers)) } // onStart task setup -func onStart() { - if task, ok := Tasks["onStart"]; ok { +func (e *Exec) onStart() { + if task, ok := e.Tasks["onStart"]; ok { task.run() } } // onEnd task setup -func onEnd() { - if task, ok := Tasks["onEnd"]; ok { +func (e *Exec) onEnd() { + if task, ok := e.Tasks["onEnd"]; ok { task.run() } } diff --git a/exec_test.go b/exec_test.go index fe1a016..8619642 100644 --- a/exec_test.go +++ b/exec_test.go @@ -5,7 +5,19 @@ import ( "testing" ) +var e *Exec + +func setupTestCase(t *testing.T) func(t *testing.T) { + e = New() + return func(t *testing.T) { + t.Log("teardown test case") + } +} + func TestNewArgument(t *testing.T) { + teardown := setupTestCase(t) + defer teardown(t) + arg := &Argument{ Name: "name", Type: 0, @@ -14,10 +26,14 @@ func TestNewArgument(t *testing.T) { Description: "description", Value: nil, } - require.Equal(t, NewArgument(arg.Name, arg.Description), arg) + + require.Equal(t, e.NewArgument(arg.Name, arg.Description), arg, "err") } func TestAddArgument(t *testing.T) { + teardown := setupTestCase(t) + defer teardown(t) + arg := &Argument{ Name: "test", Type: 0, @@ -26,9 +42,9 @@ func TestAddArgument(t *testing.T) { Description: "", Value: nil, } - AddArgument(arg) + e.AddArgument(arg) - require.Equal(t, arg, Arguments[arg.Name]) + require.Equal(t, arg, e.Arguments[arg.Name]) } func TestGetArgument(t *testing.T) { @@ -54,16 +70,23 @@ func TestGetArgument(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { + teardown := setupTestCase(t) + if testCase.arg != nil { - AddArgument(testCase.arg) + e.AddArgument(testCase.arg) } - require.Equal(t, GetArgument(testCase.name), testCase.arg) + require.Equal(t, e.GetArgument(testCase.name), testCase.arg) + + teardown(t) }) } } func TestNewOption(t *testing.T) { + teardown := setupTestCase(t) + defer teardown(t) + opt := &Option{ Name: "name", Type: 0, @@ -71,10 +94,13 @@ func TestNewOption(t *testing.T) { Description: "description", Value: nil, } - require.Equal(t, NewOption(opt.Name, opt.Description), opt) + require.Equal(t, e.NewOption(opt.Name, opt.Description), opt) } func TestAddOption(t *testing.T) { + teardown := setupTestCase(t) + defer teardown(t) + opt := &Option{ Name: "name", Type: 0, @@ -82,9 +108,9 @@ func TestAddOption(t *testing.T) { Description: "description", Value: nil, } - AddOption(opt) + e.AddOption(opt) - require.Equal(t, opt, Options[opt.Name]) + require.Equal(t, opt, e.Options[opt.Name]) } func TestGetOption(t *testing.T) { @@ -110,23 +136,30 @@ func TestGetOption(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { + teardown := setupTestCase(t) + if testCase.opt != nil { - AddOption(testCase.opt) + e.AddOption(testCase.opt) } - require.Equal(t, GetOption(testCase.name), testCase.opt) + require.Equal(t, e.GetOption(testCase.name), testCase.opt) + + teardown(t) }) } } func TestSet(t *testing.T) { + teardown := setupTestCase(t) + defer teardown(t) + cfg := &config{ Name: "cfg", value: "val", } - Set(cfg.Name, cfg.value) + e.Set(cfg.Name, cfg.value) - require.Equal(t, cfg, Configs[cfg.Name]) + require.Equal(t, cfg, e.Configs[cfg.Name]) } func TestGet(t *testing.T) { @@ -166,26 +199,30 @@ func TestGet(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { + teardown := setupTestCase(t) + if testCase.cfg != nil { - Set(testCase.cfg.Name, testCase.cfg.value) + e.Set(testCase.cfg.Name, testCase.cfg.value) } if testCase.serverCtx != nil { - ServerContext = testCase.serverCtx + e.ServerContext = testCase.serverCtx } - require.Equal(t, Get(testCase.name), testCase.cfg) + require.Equal(t, e.Get(testCase.name), testCase.cfg) + + teardown(t) }) } } func TestHas(t *testing.T) { type testCase struct { - test string - name string - cfg *config - serverCtx *server - expectedResult bool + test string + name string + cfg *config + serverCtx *server + expectedResult bool } testCases := []testCase{ @@ -198,8 +235,8 @@ func TestHas(t *testing.T) { expectedResult: true, }, { - test: "invalid cfg", - name: "invalid", + test: "invalid cfg", + name: "invalid", expectedResult: false, }, { @@ -208,28 +245,47 @@ func TestHas(t *testing.T) { cfg: &config{ Name: "valid", }, - serverCtx: &server{}, + serverCtx: &server{}, expectedResult: true, }, { - test: "invalid cfg in server ctx", - name: "invalid", - serverCtx: &server{}, + test: "invalid cfg in server ctx", + name: "invalid", + serverCtx: &server{}, expectedResult: false, }, } for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { + teardown := setupTestCase(t) + if testCase.cfg != nil { - Set(testCase.cfg.Name, testCase.cfg.value) + e.Set(testCase.cfg.Name, testCase.cfg.value) } if testCase.serverCtx != nil { - ServerContext = testCase.serverCtx + e.ServerContext = testCase.serverCtx } - require.Equal(t, Has(testCase.name), testCase.expectedResult) + require.Equal(t, e.Has(testCase.name), testCase.expectedResult) + + teardown(t) }) } } + +func TestServer(t *testing.T) { + teardown := setupTestCase(t) + defer teardown(t) + + cfg := &server{ + Name: "server", + Dsn: "user@host:port", + Configs: make(map[string]*config), + sshClient: &sshClient{}, + } + e.Server(cfg.Name, cfg.Dsn) + + require.Equal(t, cfg, e.Servers[cfg.Name]) +} diff --git a/output.go b/output.go index e367f4d..1765c37 100644 --- a/output.go +++ b/output.go @@ -5,20 +5,20 @@ import ( "strings" ) -type output struct { +type Output struct { text string err error } -func (o output) HasError() bool { +func (o Output) HasError() bool { return o.err != nil } -func (o output) String() string { +func (o Output) String() string { return o.text } -func (o output) Int() int { +func (o Output) Int() int { i, err := strconv.Atoi(o.text) if err == nil { return i @@ -26,10 +26,10 @@ func (o output) Int() int { return 0 } -func (o output) Bool() bool { +func (o Output) Bool() bool { return "true" == o.text } -func (o output) Slice(sep string) []string { +func (o Output) Slice(sep string) []string { return strings.Split(o.text, sep) } diff --git a/task.go b/task.go index 700b537..a85ab22 100644 --- a/task.go +++ b/task.go @@ -19,6 +19,7 @@ type task struct { Options map[string]*Option Arguments map[string]*Argument + exec *Exec run taskFunction subtasks map[string]*task shortDescription string @@ -216,7 +217,7 @@ func (t *task) execute(taskName string, cmdArgs []string) error { } // Executing the onStart task - onStart() + t.exec.onStart() if len(t.before) > 0 { for _, tb := range t.before { @@ -234,7 +235,7 @@ func (t *task) execute(taskName string, cmdArgs []string) error { } // Executing the onEnd task - onEnd() + t.exec.onEnd() // Execute it only once if requested if t.once && !t.executedOnce { diff --git a/utils.go b/utils.go index 6cf0fb9..c616357 100644 --- a/utils.go +++ b/utils.go @@ -16,27 +16,27 @@ import ( ) // Cd is a remote helper function that runs a `cd` before a command -func Cd(path string) { - command := "cd " + Parse(path) - color.Green("[%s] %s %s", ServerContext.Name, color.GreenString(">"), command) - ServerContext.sshClient.env = command + "; " +func (e *Exec) Cd(path string) { + command := "cd " + e.Parse(path) + color.Green("[%s] %s %s", e.ServerContext.Name, color.GreenString(">"), command) + e.ServerContext.sshClient.env = command + "; " } // CommandExist checks if a remote command exists on server -func CommandExist(command string) bool { - return Remote("if hash %s 2>/dev/null; then echo 'true'; fi", command).Bool() +func (e *Exec) CommandExist(command string) bool { + return e.Remote("if hash %s 2>/dev/null; then echo 'true'; fi", command).Bool() } // Parse parses {{var}} with Get(var) -func Parse(text string) string { +func (e *Exec) Parse(text string) string { re := regexp.MustCompile(`\{\{\s*([\w\.\/]+)\s*\}\}`) if !re.MatchString(text) { return text } return re.ReplaceAllStringFunc(text, func(str string) string { name := strings.TrimSuffix(strings.TrimPrefix(str, "{{"), "}}") - if Has(name) { - return Parse(Get(name).String()) + if e.Has(name) { + return e.Parse(e.Get(name).String()) } return str }) @@ -44,39 +44,39 @@ func Parse(text string) string { // RunIfNoBinary runs a remote command if a binary is not found // command can be an array of string commands or one a string command -func RunIfNoBinary(binary string, command interface{}) (o output) { - return Remote("if [ ! -e \"`which %s`\" ]; then %s; fi", binary, commandToString(command)) +func (e *Exec) RunIfNoBinary(binary string, command interface{}) (o Output) { + return e.Remote("if [ ! -e \"`which %s`\" ]; then %s; fi", binary, commandToString(command)) } // RunIfNoBinaries runs multiple RunIfNoBinary -func RunIfNoBinaries(config map[string]interface{}) { +func (e *Exec) RunIfNoBinaries(config map[string]interface{}) { for binary, command := range config { - RunIfNoBinary(binary, command) + e.RunIfNoBinary(binary, command) } } // RunIf runs a remote command if condition is true // command can be an array of string commands or one a string command -func RunIf(condition string, command interface{}) (o output) { - return Remote("if %s; then %s; fi", condition, commandToString(command)) +func (e *Exec) RunIf(condition string, command interface{}) (o Output) { + return e.Remote("if %s; then %s; fi", condition, commandToString(command)) } // RunIfs runs multiple RunIf -func RunIfs(config map[string]interface{}) { +func (e *Exec) RunIfs(config map[string]interface{}) { for condition, command := range config { - RunIf(condition, command) + e.RunIf(condition, command) } } // UploadFileSudo uploads a local file to a remote file with sudo -func UploadFileSudo(source, destination string) { +func (e *Exec) UploadFileSudo(source, destination string) { tempFile := "/tmp/" + uuid.NewV4().String() - Upload(source, tempFile) - Remote("sudo mv %s %s", tempFile, destination) + e.Upload(source, tempFile) + e.Remote("sudo mv %s %s", tempFile, destination) } // UploadTemplateFileSudo parses a local template file with context, and uploads it to a remote file with sudo -func UploadTemplateFileSudo(source, destination string, context interface{}) { +func (e *Exec) UploadTemplateFileSudo(source, destination string, context interface{}) { tempFile := "/tmp/" + uuid.NewV4().String() t, err := template.New(path.Base(source)).ParseFiles(source) @@ -91,26 +91,26 @@ func UploadTemplateFileSudo(source, destination string, context interface{}) { if err := ioutil.WriteFile(tempFile, tpl.Bytes(), os.FileMode(0644)); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - Upload(tempFile, tempFile) - Local("rm %s", tempFile) - Remote("sudo mv %s %s", tempFile, destination) + e.Upload(tempFile, tempFile) + e.Local("rm %s", tempFile) + e.Remote("sudo mv %s %s", tempFile, destination) } } // UploadTemplateStringSudo uploads a string content to a remote file with sudo -func UploadTemplateStringSudo(content, destination string) { +func (e *Exec) UploadTemplateStringSudo(content, destination string) { tempFile := "/tmp/" + uuid.NewV4().String() if err := ioutil.WriteFile(tempFile, []byte(content), os.FileMode(0644)); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - Upload(tempFile, tempFile) - Local("rm %s", tempFile) - Remote("sudo mv %s %s", tempFile, destination) + e.Upload(tempFile, tempFile) + e.Local("rm %s", tempFile) + e.Remote("sudo mv %s %s", tempFile, destination) } } // LocalTemplateFile parses a local template file with context, and moves it to a destination -func LocalTemplateFile(source, destination string, context interface{}) { +func (e *Exec) LocalTemplateFile(source, destination string, context interface{}) { tempFile := "/tmp/" + uuid.NewV4().String() t, err := template.New(path.Base(source)).ParseFiles(source) @@ -125,12 +125,12 @@ func LocalTemplateFile(source, destination string, context interface{}) { if err := ioutil.WriteFile(tempFile, tpl.Bytes(), os.FileMode(0644)); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - Local("mv %s %s", tempFile, destination) + e.Local("mv %s %s", tempFile, destination) } } // CompileLocalTemplateFile parses a local source file template with context and returns it -func CompileLocalTemplateFile(source string, context interface{}) string { +func (e *Exec) CompileLocalTemplateFile(source string, context interface{}) string { t, err := template.New(path.Base(source)).ParseFiles(source) if err != nil { color.Red("[%s] %s %s", "local", "<", err) @@ -143,7 +143,7 @@ func CompileLocalTemplateFile(source string, context interface{}) string { } // CompileLocalTemplateString parses a local source string template with context and returns it -func CompileLocalTemplateString(source string, context interface{}) string { +func (e *Exec) CompileLocalTemplateString(source string, context interface{}) string { t, err := template.New(uuid.NewV4().String()).Parse(source) if err != nil { color.Red("[%s] %s %s", "local", "<", err) @@ -156,71 +156,71 @@ func CompileLocalTemplateString(source string, context interface{}) string { } // ReplaceInRemoteFile replaces a search string with a replace string, in a remote file -func ReplaceInRemoteFile(file, search, replace string) { +func (e *Exec) ReplaceInRemoteFile(file, search, replace string) { tempFile := "/tmp/" + uuid.NewV4().String() - Remote("sudo cp %s %s ; sudo chown %s %s", file, tempFile, ServerContext.GetUser(), tempFile) - Download(tempFile, tempFile) + e.Remote("sudo cp %s %s ; sudo chown %s %s", file, tempFile, e.ServerContext.GetUser(), tempFile) + e.Download(tempFile, tempFile) if tempFileContent, err := ioutil.ReadFile(tempFile); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - tempFileContent := strings.Replace(string(tempFileContent), search, Parse(replace), -1) + tempFileContent := strings.Replace(string(tempFileContent), search, e.Parse(replace), -1) if err := ioutil.WriteFile(tempFile, []byte(tempFileContent), os.FileMode(0644)); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - UploadFileSudo(tempFile, file) - Remote("sudo rm -rf %s", tempFile) - Local("rm -rf %s", tempFile) + e.UploadFileSudo(tempFile, file) + e.Remote("sudo rm -rf %s", tempFile) + e.Local("rm -rf %s", tempFile) } } } // AddInRemoteFile appends a text string to a remote file -func AddInRemoteFile(text, file string) { +func (e *Exec) AddInRemoteFile(text, file string) { tempFile := "/tmp/" + uuid.NewV4().String() - Remote("sudo cp %s %s ; sudo chown %s %s", file, tempFile, ServerContext.GetUser(), tempFile) - Download(tempFile, tempFile) + e.Remote("sudo cp %s %s ; sudo chown %s %s", file, tempFile, e.ServerContext.GetUser(), tempFile) + e.Download(tempFile, tempFile) if tempFileContent, err := ioutil.ReadFile(tempFile); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - tempFileContent := string(tempFileContent) + Parse(text) + tempFileContent := string(tempFileContent) + e.Parse(text) if err := ioutil.WriteFile(tempFile, []byte(tempFileContent), os.FileMode(0644)); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - UploadFileSudo(tempFile, file) - Remote("sudo rm -rf %s", tempFile) - Local("rm -rf %s", tempFile) + e.UploadFileSudo(tempFile, file) + e.Remote("sudo rm -rf %s", tempFile) + e.Local("rm -rf %s", tempFile) } } } // RemoveFromRemoteFile cuts out a text string from remote file -func RemoveFromRemoteFile(text, file string) { +func (e *Exec) RemoveFromRemoteFile(text, file string) { tempFile := "/tmp/" + uuid.NewV4().String() - Remote("sudo cp %s %s ; sudo chown %s %s", file, tempFile, ServerContext.GetUser(), tempFile) - Download(tempFile, tempFile) + e.Remote("sudo cp %s %s ; sudo chown %s %s", file, tempFile, e.ServerContext.GetUser(), tempFile) + e.Download(tempFile, tempFile) if tempFileContent, err := ioutil.ReadFile(tempFile); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - tempFileContent := strings.Replace(string(tempFileContent), Parse(text), "", -1) + tempFileContent := strings.Replace(string(tempFileContent), e.Parse(text), "", -1) if err := ioutil.WriteFile(tempFile, []byte(tempFileContent), os.FileMode(0644)); err != nil { color.Red("[%s] %s %s", "local", "<", err) } else { - UploadFileSudo(tempFile, file) - Remote("sudo rm -rf %s", tempFile) - Local("rm -rf %s", tempFile) + e.UploadFileSudo(tempFile, file) + e.Remote("sudo rm -rf %s", tempFile) + e.Local("rm -rf %s", tempFile) } } } // IsInRemoteFile return true if text is found in a remote file -func IsInRemoteFile(text, file string) bool { +func (e *Exec) IsInRemoteFile(text, file string) bool { text = strings.Trim(text, " ") - return Remote("if [ \"`sudo cat %s | grep '%s'`\" ]; then echo 'true'; fi", file, text).Bool() + return e.Remote("if [ \"`sudo cat %s | grep '%s'`\" ]; then echo 'true'; fi", file, text).Bool() } // Ask asks a question and waits for an answer // first item from attributes is set as default value, which is optional -func Ask(question string, attributes ...string) string { +func (e *Exec) Ask(question string, attributes ...string) string { scanner := bufio.NewScanner(os.Stdin) var defaultResponse string @@ -243,7 +243,7 @@ func Ask(question string, attributes ...string) string { // AskWithConfirmation asks a confirmation question and waits for an y/n answer // first item from attributes is set as default value, which is optional -func AskWithConfirmation(question string, attributes ...bool) bool { +func (e *Exec) AskWithConfirmation(question string, attributes ...bool) bool { scanner := bufio.NewScanner(os.Stdin) var defaultResponse bool @@ -290,7 +290,7 @@ First item from attributes must be a map with default and choices keys and strin } ``` */ -func AskWithChoices(question string, attributes ...map[string]interface{}) (responses []string) { +func (e *Exec) AskWithChoices(question string, attributes ...map[string]interface{}) (responses []string) { scanner := bufio.NewScanner(os.Stdin) var ( From 83183210347bd90cff2299a440598830fcd4bd12 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 13:20:54 +0200 Subject: [PATCH 09/21] more refactoring --- examples/simple/main.go | 170 +++++++++++++++++----------------- examples/symfony/main.go | 1 + exec.go | 133 +++++++++++++------------- exec_test.go | 31 +++---- recipes/deploy/cleanup.go | 1 + recipes/deploy/clear_paths.go | 1 + recipes/deploy/copy_dirs.go | 1 + recipes/deploy/defaults.go | 9 +- recipes/deploy/lock.go | 1 + recipes/deploy/prepare.go | 1 + recipes/deploy/release.go | 1 + recipes/deploy/rollback.go | 1 + recipes/deploy/shared.go | 1 + recipes/deploy/stage.go | 1 + recipes/deploy/symlink.go | 1 + recipes/deploy/update_code.go | 1 + recipes/deploy/writable.go | 1 + recipes/php/defaults.go | 1 + recipes/php/vendors.go | 1 + recipes/symfony/recipe.go | 1 + 20 files changed, 188 insertions(+), 171 deletions(-) diff --git a/examples/simple/main.go b/examples/simple/main.go index 973542b..c76fd13 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -3,7 +3,7 @@ package main import ( "fmt" "github.com/fatih/color" - "github.com/go-exec/exec" + e "github.com/go-exec/exec" "time" ) @@ -11,170 +11,170 @@ import ( Example with general setup of tasks */ func main() { - executor := exec.New() - - executor.Task("onStart", func() { - executor.Set("startTime", time.Now()) + exec := e.Instance + + exec.Task("onStart", func() { + exec.Set("startTime", time.Now()) }).Private() - executor.Task("onEnd", func() { - executor.Println(fmt.Sprintf("Finished in %s!`", time.Now().Sub(executor.Get("startTime").Time()).String())) + exec.Task("onEnd", func() { + exec.Println(fmt.Sprintf("Finished in %s!`", time.Now().Sub(exec.Get("startTime").Time()).String())) }).Private() type F struct { F func() interface{} } - stage := executor.NewArgument("stage", "Provide the running stage") + stage := exec.NewArgument("stage", "Provide the running stage") stage.Default = "qa" - stage.Type = exec.String + stage.Type = e.String - executor.AddArgument(stage) + exec.AddArgument(stage) //run always on the server set by stage dynamically - //executor.OnServers(func() []string { - // return []string{executor.GetArgument("stage").String()} + //exec.OnServers(func() []string { + // return []string{exec.GetArgument("stage").String()} //}) - arg2 := executor.NewArgument("arg2", "Provide the arg2") + arg2 := exec.NewArgument("arg2", "Provide the arg2") arg2.Default = "test" - executor.AddArgument(arg2) + exec.AddArgument(arg2) - executor.Set("env", "prod") + exec.Set("env", "prod") - executor.Set("bin/mysql", "mysql default") + exec.Set("bin/mysql", "mysql default") - executor.Set("test", func() interface{} { return "text" }) + exec.Set("test", func() interface{} { return "text" }) - executor.Set("functest", F{F: func() interface{} { + exec.Set("functest", F{F: func() interface{} { return "date" }}) - executor.Set("localUser", executor.Local("git config --get %s", "user.name")) + exec.Set("localUser", exec.Local("git config --get %s", "user.name")) - executor. + exec. Server("prod1", "root@domain.com"). AddRole("prod"). Set("bin/mysql", "mysql prod") - executor. + exec. Server("prod2", "root@domain.com"). AddRole("prod"). Set("bin/mysql", "mysql prod") - executor. + exec. Server("qa", "root@domain.com"). Key("~/.ssh/id_rsa"). AddRole("qa"). Set("bin/mysql", "mysql qa") - executor. + exec. Server("stage", "root@domain.com"). AddRole("stage") - opt1 := executor.NewOption("opt1", "test") - opt2 := executor.NewOption("opt2", "test") + opt1 := exec.NewOption("opt1", "test") + opt2 := exec.NewOption("opt2", "test") - executor. + exec. Task("upload", func() { - executor.Remote("ls -la /") - executor.Upload("test.txt", "~/test.txt") + exec.Remote("ls -la /") + exec.Upload("test.txt", "~/test.txt") }) - executor. + exec. Task("download", func() { - executor.Remote("ls -la /") - executor.Download("~/test.txt", "test.txt") + exec.Remote("ls -la /") + exec.Download("~/test.txt", "test.txt") }) - executor. + exec. Task("test1", func() { - //fmt.Println(executor.TaskContext.GetOption("opt1").ToString()) - executor.Remote("echo Git user is: " + executor.Get("localUser").String()) - executor.Remote("ls -la /") - fmt.Println(executor.TaskContext.GetArgument("stage")) - fmt.Println(executor.TaskContext.GetArgument("arg2")) + //fmt.Println(exec.TaskContext.GetOption("opt1").ToString()) + exec.Remote("echo Git user is: " + exec.Get("localUser").String()) + exec.Remote("ls -la /") + fmt.Println(exec.TaskContext.GetArgument("stage")) + fmt.Println(exec.TaskContext.GetArgument("arg2")) }). ShortDescription("Running test1 task"). AddOption(opt1) - executor. + exec. Task("test2", func() { - executor.Remote("ls -la ~") + exec.Remote("ls -la ~") }). ShortDescription("Running test2 task"). AddOption(opt2) - executor. + exec. Task("test3", func() { - executor.Remote("ls -la ~/.ssh") + exec.Remote("ls -la ~/.ssh") }). ShortDescription("Running test3 task") //should avoid using RunLocal in a task that will run in a stage with multiple servers associated! - executor. + exec. Task("local", func() { - executor.Local("ls -la ~/Public; ls -la /Users/") - executor.Local("docker") + exec.Local("ls -la ~/Public; ls -la /Users/") + exec.Local("docker") }). Once(). ShortDescription("Running local task") - executor. + exec. Task("yarn", func() { - executor.Local("yarn") + exec.Local("yarn") }) - executor. + exec. Task("docker", func() { - executor.Local("docker stats") + exec.Local("docker stats") }) - executor. + exec. Task("docker-remote", func() { - executor.Remote("docker") + exec.Remote("docker") }). OnServers(func() []string { return []string{"prod1"} }) - executor. + exec. Task("get", func() { - executor.Remote(fmt.Sprintf("%s", executor.Get("bin/mysql").String())) - fmt.Println(executor.TaskContext.GetArgument("stage")) - fmt.Println(executor.TaskContext.GetArgument("arg2")) + exec.Remote(fmt.Sprintf("%s", exec.Get("bin/mysql").String())) + fmt.Println(exec.TaskContext.GetArgument("stage")) + fmt.Println(exec.TaskContext.GetArgument("arg2")) }). ShortDescription("Testing get in different servers contexts") - executor. + exec. Task("get2", func() { - executor.Remote(fmt.Sprintf("%s", executor.Get("functest").Value().(F).F())) + exec.Remote(fmt.Sprintf("%s", exec.Get("functest").Value().(F).F())) }). ShortDescription("Testing get2 in different servers contexts") - executor. + exec. Task("get3", func() { - executor.Println(executor.Get("test").String()) + exec.Println(exec.Get("test").String()) }). ShortDescription("Testing get3 in different servers contexts"). RemoveArgument("stage") - executor. + exec. TaskGroup("deploy1", "test1", "test2"). ShortDescription("Deploy code 1") - executor. + exec. TaskGroup("deploy2", "local", "test3"). ShortDescription("Deploy code 2") - executor. + exec. TaskGroup("deploy3", "get"). ShortDescription("Deploy code 3") - executor. + exec. Task("onservers:a", func() { - executor.RunIfNoBinary("docker", []string{ + exec.RunIfNoBinary("docker", []string{ "echo 'a'", "echo 'b'", }) @@ -183,9 +183,9 @@ func main() { return []string{"prod1", "prod2"} }) - executor. + exec. Task("onservers:b", func() { - executor.RunIfNoBinary("wget", []string{ + exec.RunIfNoBinary("wget", []string{ "echo 'a'", "echo 'b'", }) @@ -194,34 +194,34 @@ func main() { return []string{"prod1", "prod2"} }) - executor. + exec. Task("onservers:c", func() { - executor.RunIfNoBinary("docker", []string{ + exec.RunIfNoBinary("docker", []string{ "echo 'a'", "echo 'b'", }) }). OnlyOnServers([]string{"prod1"}) - executor. + exec. Task("servercontext:host", func() { - fmt.Println(executor.ServerContext.Name, executor.ServerContext.GetHost()) + fmt.Println(exec.ServerContext.Name, exec.ServerContext.GetHost()) }). OnServers(func() []string { return []string{"prod1", "prod2"} }) - executor. + exec. Task("onservers:read", func() { - fmt.Printf("`%s`\n", executor.Remote("git config --get %s", "user.name").String()) + fmt.Printf("`%s`\n", exec.Remote("git config --get %s", "user.name").String()) }). OnServers(func() []string { return []string{"prod1"} }) - executor. + exec. Task("ask", func() { - response := executor.Ask("How are you?", "better") + response := exec.Ask("How are you?", "better") color.Yellow("Your response is `%s`", response) }). @@ -229,13 +229,13 @@ func main() { return []string{"prod1"} }) - executor. + exec. Task("ask-confirmation", func() { - response := executor.AskWithConfirmation("Would you like to give it a shot?", true) + response := exec.AskWithConfirmation("Would you like to give it a shot?", true) color.Yellow("Your response is `%t`", response) - response = executor.AskWithConfirmation("Would you like to give it a shot again?", false) + response = exec.AskWithConfirmation("Would you like to give it a shot again?", false) color.Yellow("Your response is `%t`", response) }). @@ -243,9 +243,9 @@ func main() { return []string{"prod1"} }) - executor. + exec. Task("ask-choices", func() { - response := executor.AskWithChoices("What are your choices?", map[string]interface{}{ + response := exec.AskWithChoices("What are your choices?", map[string]interface{}{ "default": []string{ "agent", }, @@ -262,12 +262,12 @@ func main() { return []string{"prod1"} }) - executor.Before("test3", "local") - executor.Before("get3", "test3") - executor.Before("get3", "local") - executor.After("local", "onservers:a") - executor.After("local", "get3") - executor.After("onservers:a", "local") + exec.Before("test3", "local") + exec.Before("get3", "test3") + exec.Before("get3", "local") + exec.After("local", "onservers:a") + exec.After("local", "get3") + exec.After("onservers:a", "local") - executor.Init() + exec.Init() } diff --git a/examples/symfony/main.go b/examples/symfony/main.go index a34d138..092eec1 100644 --- a/examples/symfony/main.go +++ b/examples/symfony/main.go @@ -9,6 +9,7 @@ import ( Example of deploying a Symfony app using the deploy recipes */ func main() { + exec := exec.Instance exec.Set("repository", "git@github.com:namespace/app.git") exec.Set("shared_files", []string{}) exec.Set("shared_dirs", []string{"var/logs", "vendor", "web/uploads", "web/media", "node_modules"}) diff --git a/exec.go b/exec.go index 855afba..35b46ad 100644 --- a/exec.go +++ b/exec.go @@ -34,6 +34,7 @@ type Exec struct { argumentSequence int } +// New returns a new *Exec instance func New() *Exec { return &Exec{ Configs: make(map[string]*config), @@ -48,6 +49,10 @@ func New() *Exec { } } +// Instance is the default empty exported instance of *Exec +// used to be able to create external recipes easily +var Instance = New() + // Init initializes the exec and executes the current command // should be added to the end of all exec declarations func (e *Exec) Init() { @@ -367,70 +372,6 @@ func (e *Exec) OnServers(f func() []string) { e.serverContextF = f } -// remoteRun executes a command on a specific server -func (e *Exec) remoteRun(command string, server *server) (o Output) { - e.ServerContext = server - command = e.Parse(command) - - color.Green("[%s] %s %s", server.Name, ">", color.WhiteString("`%s`", command)) - - if !server.sshClient.connOpened { - err := server.sshClient.Connect(server.Dsn) - if err != nil { - color.Red("[%s] %s %q", "local", "<", err) - o.err = err - } - } - - if server.sshClient.connOpened { - err := server.sshClient.Run(command) - if err != nil { - o.err = err - color.Red("[%s] %s %q", server.Name, "<", err) - } - - output := "" - buf := make([]byte, 1024) - - n, err := server.sshClient.remoteStdout.Read(buf) - - if err != nil { - o.err = err - } else { - color.Green("[%s] %s\n", server.Name, "<") - for _, v := range buf[:n] { - fmt.Printf("%c", v) - } - output = string(buf[:n]) - } - for err == nil { - n, err = server.sshClient.remoteStdout.Read(buf) - output += string(buf[:n]) - for _, v := range buf[:n] { - fmt.Printf("%c", v) - } - if err != nil && err != io.EOF { - o.err = err - } - } - - o.text = strings.TrimSpace(output) - - if len(o.text) == 0 { - color.Red("[%s] %s\n", server.Name, "<") - bytesB, _ := ioutil.ReadAll(server.sshClient.remoteStderr) - fmt.Printf("%s\n", strings.TrimSpace(string(bytesB))) - } - - err = server.sshClient.Wait() - if err != nil { - color.Red("[%s] %s %q", server.Name, "<", err) - } - } - - return o -} - // Remote runs a command with args, in the ServerContext func (e *Exec) Remote(command string, args ...interface{}) (o Output) { run, onServers := e.shouldIRun() @@ -499,6 +440,70 @@ func (e *Exec) After(task string, tasksAfter ...string) { } } +// remoteRun executes a command on a specific server +func (e *Exec) remoteRun(command string, server *server) (o Output) { + e.ServerContext = server + command = e.Parse(command) + + color.Green("[%s] %s %s", server.Name, ">", color.WhiteString("`%s`", command)) + + if !server.sshClient.connOpened { + err := server.sshClient.Connect(server.Dsn) + if err != nil { + color.Red("[%s] %s %q", "local", "<", err) + o.err = err + } + } + + if server.sshClient.connOpened { + err := server.sshClient.Run(command) + if err != nil { + o.err = err + color.Red("[%s] %s %q", server.Name, "<", err) + } + + output := "" + buf := make([]byte, 1024) + + n, err := server.sshClient.remoteStdout.Read(buf) + + if err != nil { + o.err = err + } else { + color.Green("[%s] %s\n", server.Name, "<") + for _, v := range buf[:n] { + fmt.Printf("%c", v) + } + output = string(buf[:n]) + } + for err == nil { + n, err = server.sshClient.remoteStdout.Read(buf) + output += string(buf[:n]) + for _, v := range buf[:n] { + fmt.Printf("%c", v) + } + if err != nil && err != io.EOF { + o.err = err + } + } + + o.text = strings.TrimSpace(output) + + if len(o.text) == 0 { + color.Red("[%s] %s\n", server.Name, "<") + bytesB, _ := ioutil.ReadAll(server.sshClient.remoteStderr) + fmt.Printf("%s\n", strings.TrimSpace(string(bytesB))) + } + + err = server.sshClient.Wait() + if err != nil { + color.Red("[%s] %s %q", server.Name, "<", err) + } + } + + return o +} + func (e *Exec) shouldIRun() (run bool, onServers []string) { run = true diff --git a/exec_test.go b/exec_test.go index 8619642..07b6cef 100644 --- a/exec_test.go +++ b/exec_test.go @@ -5,17 +5,12 @@ import ( "testing" ) -var e *Exec - -func setupTestCase(t *testing.T) func(t *testing.T) { - e = New() - return func(t *testing.T) { - t.Log("teardown test case") - } +func setupTestCase(t *testing.T) (*Exec, func(t *testing.T)) { + return New(), func(t *testing.T) {} } func TestNewArgument(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) defer teardown(t) arg := &Argument{ @@ -27,11 +22,11 @@ func TestNewArgument(t *testing.T) { Value: nil, } - require.Equal(t, e.NewArgument(arg.Name, arg.Description), arg, "err") + require.Equal(t, e.NewArgument(arg.Name, arg.Description), arg) } func TestAddArgument(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) defer teardown(t) arg := &Argument{ @@ -70,7 +65,7 @@ func TestGetArgument(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) if testCase.arg != nil { e.AddArgument(testCase.arg) @@ -84,7 +79,7 @@ func TestGetArgument(t *testing.T) { } func TestNewOption(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) defer teardown(t) opt := &Option{ @@ -98,7 +93,7 @@ func TestNewOption(t *testing.T) { } func TestAddOption(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) defer teardown(t) opt := &Option{ @@ -136,7 +131,7 @@ func TestGetOption(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) if testCase.opt != nil { e.AddOption(testCase.opt) @@ -150,7 +145,7 @@ func TestGetOption(t *testing.T) { } func TestSet(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) defer teardown(t) cfg := &config{ @@ -199,7 +194,7 @@ func TestGet(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) if testCase.cfg != nil { e.Set(testCase.cfg.Name, testCase.cfg.value) @@ -258,7 +253,7 @@ func TestHas(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) if testCase.cfg != nil { e.Set(testCase.cfg.Name, testCase.cfg.value) @@ -276,7 +271,7 @@ func TestHas(t *testing.T) { } func TestServer(t *testing.T) { - teardown := setupTestCase(t) + e, teardown := setupTestCase(t) defer teardown(t) cfg := &server{ diff --git a/recipes/deploy/cleanup.go b/recipes/deploy/cleanup.go index 01a966d..cc59a9c 100644 --- a/recipes/deploy/cleanup.go +++ b/recipes/deploy/cleanup.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("cleanup", func() { releases := exec.Get("releases_list").Slice() keep := exec.Get("keep_releases").Int() diff --git a/recipes/deploy/clear_paths.go b/recipes/deploy/clear_paths.go index d7d9a74..28c1081 100644 --- a/recipes/deploy/clear_paths.go +++ b/recipes/deploy/clear_paths.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("deploy:clear_paths", func() { paths := exec.Get("clear_paths").Slice() sudo := "" diff --git a/recipes/deploy/copy_dirs.go b/recipes/deploy/copy_dirs.go index 3e914db..9cfa245 100644 --- a/recipes/deploy/copy_dirs.go +++ b/recipes/deploy/copy_dirs.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("deploy:copy_dirs", func() { dirs := exec.Get("copy_dirs").Slice() diff --git a/recipes/deploy/defaults.go b/recipes/deploy/defaults.go index 90714f1..575bae6 100644 --- a/recipes/deploy/defaults.go +++ b/recipes/deploy/defaults.go @@ -2,7 +2,7 @@ package deploy import ( "fmt" - "github.com/go-exec/exec" + e "github.com/go-exec/exec" "regexp" "strconv" "strings" @@ -10,6 +10,7 @@ import ( ) func init() { + exec := e.Instance exec.Set("keep_releases", 5) exec.Set("repository", "") // Repository to deploy. @@ -67,15 +68,15 @@ func init() { }) branch := exec.NewOption("branch", "Branch to deploy") - branch.Type = exec.String + branch.Type = e.String exec.AddOption(branch) tag := exec.NewOption("tag", "Tag to deploy") - tag.Type = exec.String + tag.Type = e.String exec.AddOption(tag) revision := exec.NewOption("revision", "Revision to deploy") - revision.Type = exec.String + revision.Type = e.String exec.AddOption(revision) exec.Task("current", func() { diff --git a/recipes/deploy/lock.go b/recipes/deploy/lock.go index 112bc4f..a3d53d8 100644 --- a/recipes/deploy/lock.go +++ b/recipes/deploy/lock.go @@ -3,6 +3,7 @@ package deploy import "github.com/go-exec/exec" func init() { + exec := exec.Instance exec.Task("deploy:lock", func() { locked := exec.Remote("if [ -f {{deploy_path}}/.dep/deploy.lock ]; then echo 'true'; fi").Bool() diff --git a/recipes/deploy/prepare.go b/recipes/deploy/prepare.go index 71bd41d..74d6a97 100644 --- a/recipes/deploy/prepare.go +++ b/recipes/deploy/prepare.go @@ -3,6 +3,7 @@ package deploy import "github.com/go-exec/exec" func init() { + exec := exec.Instance exec.Task("deploy:prepare", func() { exec.Remote("if [ ! -d {{deploy_path}} ]; then mkdir -p {{deploy_path}}; fi") diff --git a/recipes/deploy/release.go b/recipes/deploy/release.go index 80e507f..c7f9a73 100644 --- a/recipes/deploy/release.go +++ b/recipes/deploy/release.go @@ -9,6 +9,7 @@ import ( ) func init() { + exec := exec.Instance exec.Set("keep_releases", -1) exec.Set("release_name", func() interface{} { diff --git a/recipes/deploy/rollback.go b/recipes/deploy/rollback.go index 6077d51..09b1cbf 100644 --- a/recipes/deploy/rollback.go +++ b/recipes/deploy/rollback.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("rollback", func() { releases := exec.Get("releases_list").Slice() diff --git a/recipes/deploy/shared.go b/recipes/deploy/shared.go index 4edac0f..de2e4e3 100644 --- a/recipes/deploy/shared.go +++ b/recipes/deploy/shared.go @@ -8,6 +8,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("deploy:shared", func() { sharedPath := "{{deploy_path}}/shared" diff --git a/recipes/deploy/stage.go b/recipes/deploy/stage.go index 293b96d..23cbf93 100644 --- a/recipes/deploy/stage.go +++ b/recipes/deploy/stage.go @@ -3,6 +3,7 @@ package deploy import "github.com/go-exec/exec" func init() { + exec := exec.Instance stage := exec.NewArgument("stage", "Provide the running stage") stage.Default = "qa" diff --git a/recipes/deploy/symlink.go b/recipes/deploy/symlink.go index da0af48..ec2ace7 100644 --- a/recipes/deploy/symlink.go +++ b/recipes/deploy/symlink.go @@ -3,6 +3,7 @@ package deploy import "github.com/go-exec/exec" func init() { + exec := exec.Instance exec.Task("deploy:symlink", func() { if exec.Remote("if [[ \"$(man mv 2>/dev/null)\" =~ '--no-target-directory' ]]; then echo 'true'; fi").Bool() { exec.Remote("mv -T {{deploy_path}}/release {{deploy_path}}/current") diff --git a/recipes/deploy/update_code.go b/recipes/deploy/update_code.go index e066e94..c6e8446 100644 --- a/recipes/deploy/update_code.go +++ b/recipes/deploy/update_code.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("deploy:update_code", func() { repository := exec.Get("repository").String() branch := exec.Get("branch").String() diff --git a/recipes/deploy/writable.go b/recipes/deploy/writable.go index 7e67657..c06ac89 100644 --- a/recipes/deploy/writable.go +++ b/recipes/deploy/writable.go @@ -8,6 +8,7 @@ import ( ) func init() { + exec := exec.Instance exec.Task("deploy:writable", func() { dirs := strings.Join(exec.Get("writable_dirs").Slice(), " ") mode := exec.Get("writable_mode").String() diff --git a/recipes/php/defaults.go b/recipes/php/defaults.go index 67b9da9..3608be8 100644 --- a/recipes/php/defaults.go +++ b/recipes/php/defaults.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec.Set("http_user", false) exec.Set("http_group", false) diff --git a/recipes/php/vendors.go b/recipes/php/vendors.go index f214ac3..5fbc5f1 100644 --- a/recipes/php/vendors.go +++ b/recipes/php/vendors.go @@ -3,6 +3,7 @@ package php import "github.com/go-exec/exec" func init() { + exec := exec.Instance exec.Task("deploy:vendors", func() { exec.Remote("cd {{release_path}} && {{env_vars}} {{bin/composer}} {{composer_options}}") }).ShortDescription("Installing vendors") diff --git a/recipes/symfony/recipe.go b/recipes/symfony/recipe.go index bb70396..f542177 100644 --- a/recipes/symfony/recipe.go +++ b/recipes/symfony/recipe.go @@ -6,6 +6,7 @@ import ( ) func init() { + exec := exec.Instance exec. TaskGroup( "deploy", From 4074c70cd1227da023e528ee0e00e06fc4fc2132 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 13:39:40 +0200 Subject: [PATCH 10/21] more refactoring --- examples/simple/main.go | 4 ++-- examples/symfony/main.go | 2 +- exec.go | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/examples/simple/main.go b/examples/simple/main.go index c76fd13..3e320c2 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -18,7 +18,7 @@ func main() { }).Private() exec.Task("onEnd", func() { - exec.Println(fmt.Sprintf("Finished in %s!`", time.Now().Sub(exec.Get("startTime").Time()).String())) + exec.Println(fmt.Sprintf("Finished in %s!", time.Since(exec.Get("startTime").Time()).String())) }).Private() type F struct { @@ -269,5 +269,5 @@ func main() { exec.After("local", "get3") exec.After("onservers:a", "local") - exec.Init() + exec.Run() } diff --git a/examples/symfony/main.go b/examples/symfony/main.go index 092eec1..bafdc1b 100644 --- a/examples/symfony/main.go +++ b/examples/symfony/main.go @@ -40,5 +40,5 @@ func main() { return []string{exec.GetArgument("stage").String()} }) - exec.Init() + exec.Run() } diff --git a/exec.go b/exec.go index 35b46ad..c9e9fc0 100644 --- a/exec.go +++ b/exec.go @@ -53,9 +53,9 @@ func New() *Exec { // used to be able to create external recipes easily var Instance = New() -// Init initializes the exec and executes the current command +// Run initializes the exec and executes the current command // should be added to the end of all exec declarations -func (e *Exec) Init() { +func (e *Exec) Run() { subtasks := make(map[string]*task) for name, task := range e.Tasks { From 03d7eebfaf1bcd1e29b25a3999d9ccb423b7dac9 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 13:54:39 +0200 Subject: [PATCH 11/21] examples refactoring --- examples/simple/main.go | 9 +++++++-- examples/symfony/main.go | 4 ++-- recipes/deploy/rollback.go | 1 - 3 files changed, 9 insertions(+), 5 deletions(-) diff --git a/examples/simple/main.go b/examples/simple/main.go index 3e320c2..5a8f3c6 100644 --- a/examples/simple/main.go +++ b/examples/simple/main.go @@ -12,6 +12,7 @@ Example with general setup of tasks */ func main() { exec := e.Instance + defer exec.Run() exec.Task("onStart", func() { exec.Set("startTime", time.Now()) @@ -80,12 +81,18 @@ func main() { Task("upload", func() { exec.Remote("ls -la /") exec.Upload("test.txt", "~/test.txt") + }). + OnServers(func() []string { + return []string{"prod1"} }) exec. Task("download", func() { exec.Remote("ls -la /") exec.Download("~/test.txt", "test.txt") + }). + OnServers(func() []string { + return []string{"prod1"} }) exec. @@ -268,6 +275,4 @@ func main() { exec.After("local", "onservers:a") exec.After("local", "get3") exec.After("onservers:a", "local") - - exec.Run() } diff --git a/examples/symfony/main.go b/examples/symfony/main.go index bafdc1b..3c95356 100644 --- a/examples/symfony/main.go +++ b/examples/symfony/main.go @@ -10,6 +10,8 @@ Example of deploying a Symfony app using the deploy recipes */ func main() { exec := exec.Instance + defer exec.Run() + exec.Set("repository", "git@github.com:namespace/app.git") exec.Set("shared_files", []string{}) exec.Set("shared_dirs", []string{"var/logs", "vendor", "web/uploads", "web/media", "node_modules"}) @@ -39,6 +41,4 @@ func main() { exec.OnServers(func() []string { return []string{exec.GetArgument("stage").String()} }) - - exec.Run() } diff --git a/recipes/deploy/rollback.go b/recipes/deploy/rollback.go index 09b1cbf..94b520d 100644 --- a/recipes/deploy/rollback.go +++ b/recipes/deploy/rollback.go @@ -20,5 +20,4 @@ func init() { exec.Println(fmt.Sprintf("Rollback to `%s` release was successful.", releases[1])) } }).ShortDescription("Rollback to previous release") - } From a6a7f6106a999fe4c5f0d0c41fb5fd5fef5b68ac Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 14:43:06 +0200 Subject: [PATCH 12/21] some more tests --- exec.go | 1 + exec_test.go | 51 +++++++++++++++++++++++++++++++++++++++++---------- 2 files changed, 42 insertions(+), 10 deletions(-) diff --git a/exec.go b/exec.go index c9e9fc0..ab6554e 100644 --- a/exec.go +++ b/exec.go @@ -263,6 +263,7 @@ func (e *Exec) TaskGroup(name string, tasks ...string) *taskGroup { Name: name, removeArguments: make(map[string]string), removeOptions: make(map[string]string), + exec: e, run: func() { color.White("➤ Executing task group %s", color.YellowString(name)) for _, task := range tasks { diff --git a/exec_test.go b/exec_test.go index 07b6cef..0750387 100644 --- a/exec_test.go +++ b/exec_test.go @@ -9,7 +9,7 @@ func setupTestCase(t *testing.T) (*Exec, func(t *testing.T)) { return New(), func(t *testing.T) {} } -func TestNewArgument(t *testing.T) { +func TestExec_NewArgument(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) @@ -25,7 +25,7 @@ func TestNewArgument(t *testing.T) { require.Equal(t, e.NewArgument(arg.Name, arg.Description), arg) } -func TestAddArgument(t *testing.T) { +func TestExec_AddArgument(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) @@ -42,7 +42,7 @@ func TestAddArgument(t *testing.T) { require.Equal(t, arg, e.Arguments[arg.Name]) } -func TestGetArgument(t *testing.T) { +func TestExec_GetArgument(t *testing.T) { type testCase struct { test string name string @@ -78,7 +78,7 @@ func TestGetArgument(t *testing.T) { } } -func TestNewOption(t *testing.T) { +func TestExec_NewOption(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) @@ -92,7 +92,7 @@ func TestNewOption(t *testing.T) { require.Equal(t, e.NewOption(opt.Name, opt.Description), opt) } -func TestAddOption(t *testing.T) { +func TestExec_AddOption(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) @@ -108,7 +108,7 @@ func TestAddOption(t *testing.T) { require.Equal(t, opt, e.Options[opt.Name]) } -func TestGetOption(t *testing.T) { +func TestExec_GetOption(t *testing.T) { type testCase struct { test string name string @@ -144,7 +144,7 @@ func TestGetOption(t *testing.T) { } } -func TestSet(t *testing.T) { +func TestExec_Set(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) @@ -157,7 +157,7 @@ func TestSet(t *testing.T) { require.Equal(t, cfg, e.Configs[cfg.Name]) } -func TestGet(t *testing.T) { +func TestExec_Get(t *testing.T) { type testCase struct { test string name string @@ -211,7 +211,7 @@ func TestGet(t *testing.T) { } } -func TestHas(t *testing.T) { +func TestExec_Has(t *testing.T) { type testCase struct { test string name string @@ -270,7 +270,7 @@ func TestHas(t *testing.T) { } } -func TestServer(t *testing.T) { +func TestExec_Server(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) @@ -284,3 +284,34 @@ func TestServer(t *testing.T) { require.Equal(t, cfg, e.Servers[cfg.Name]) } + +func TestExec_Task(t *testing.T) { + e, teardown := setupTestCase(t) + defer teardown(t) + + task := &task{ + Name: "task", + Arguments: make(map[string]*Argument), + Options: make(map[string]*Option), + exec: e, + removeArguments: make(map[string]string), + removeOptions: make(map[string]string), + } + e.Task(task.Name, func() {}) + + require.Contains(t, e.Tasks, task.Name) + require.Equal(t, task.exec, e.Tasks[task.Name].exec) +} + +func TestExec_TaskGroup(t *testing.T) { + e, teardown := setupTestCase(t) + defer teardown(t) + + taskGroup := &taskGroup{ + Name: "taskGroup", + } + e.TaskGroup(taskGroup.Name) + + require.Contains(t, e.TaskGroups, taskGroup.Name) + require.Equal(t, e.TaskGroups[taskGroup.Name].task.exec, e) +} From 041777ada28e0497a7a0bfab641b621c63874406 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 15:08:48 +0200 Subject: [PATCH 13/21] more tests --- exec_test.go | 82 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 82 insertions(+) diff --git a/exec_test.go b/exec_test.go index 0750387..a0cb681 100644 --- a/exec_test.go +++ b/exec_test.go @@ -315,3 +315,85 @@ func TestExec_TaskGroup(t *testing.T) { require.Contains(t, e.TaskGroups, taskGroup.Name) require.Equal(t, e.TaskGroups[taskGroup.Name].task.exec, e) } + +func TestExec_Before(t *testing.T) { + type testCase struct { + test string + task *task + before []string + unique int + } + + testCases := []testCase{ + { + test: "valid", + task: &task{ + Name: "task", + }, + before: []string{"before 1"}, + unique: 1, + }, + { + test: "valid with unique before items", + task: &task{ + Name: "task", + }, + before: []string{"before 1", "before 2", "before 1"}, + unique: 2, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + e, teardown := setupTestCase(t) + + e.Before(testCase.task.Name, testCase.before...) + + require.Contains(t, e.before, testCase.task.Name) + require.Equal(t, len(e.before[testCase.task.Name]), testCase.unique) + + defer teardown(t) + }) + } +} + +func TestExec_After(t *testing.T) { + type testCase struct { + test string + task *task + after []string + unique int + } + + testCases := []testCase{ + { + test: "valid", + task: &task{ + Name: "task", + }, + after: []string{"after 1"}, + unique: 1, + }, + { + test: "valid with unique after items", + task: &task{ + Name: "task", + }, + after: []string{"after 1", "after 2", "after 1"}, + unique: 2, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + e, teardown := setupTestCase(t) + + e.Before(testCase.task.Name, testCase.after...) + + require.Contains(t, e.before, testCase.task.Name) + require.Equal(t, len(e.before[testCase.task.Name]), testCase.unique) + + defer teardown(t) + }) + } +} From af6a936fe9837ff0f2d1056885a7b205579272d1 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 16:21:09 +0200 Subject: [PATCH 14/21] added config tests --- config.go | 8 ++- config_test.go | 132 +++++++++++++++++++++++++++++++++++++++++++++++++ exec_test.go | 7 +++ 3 files changed, 145 insertions(+), 2 deletions(-) create mode 100644 config_test.go diff --git a/config.go b/config.go index 22d0e30..2419222 100644 --- a/config.go +++ b/config.go @@ -18,8 +18,12 @@ func (c *config) Value() interface{} { if v.Kind() == reflect.Func { t := v.Type() - if t.NumIn() != 0 && t.NumOut() != 1 { - panic("Function type must have no input parameters and a single return value") + if t.NumIn() != 0 { + panic("Function type must have no input parameters") + } + + if t.NumOut() != 1 { + panic("Function type must have a single return value") } if t.Out(0).Kind().String() != "interface" { diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000..e42f5ed --- /dev/null +++ b/config_test.go @@ -0,0 +1,132 @@ +package exec + +import ( + "github.com/stretchr/testify/require" + "reflect" + "testing" + "time" +) + +func TestConfig_Value(t *testing.T) { + type testCase struct { + test string + cfg *config + expectedPanic interface{} + } + + testCases := []testCase{ + { + test: "valid simple value", + cfg: &config{ + value: "", + }, + }, + { + test: "valid func value", + cfg: &config{ + value: func() interface{} { + return "value" + }, + }, + }, + { + test: "panics on invalid func with input param", + cfg: &config{ + value: func(s string) interface{} { + return "value" + }, + }, + expectedPanic: "Function type must have no input parameters", + }, + { + test: "panics on invalid func with more than one return param", + cfg: &config{ + value: func() (interface{}, string) { + return "value", "one" + }, + }, + expectedPanic: "Function type must have a single return value", + }, + { + test: "panics on invalid func with no input param", + cfg: &config{ + value: func() string { + return "value" + }, + }, + expectedPanic: "Function return value must be an interface{}", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic != nil { + require.PanicsWithValue(t, testCase.expectedPanic, func() { + testCase.cfg.Value() + }) + } else { + require.Equal(t, testCase.cfg.value, testCase.cfg.Value()) + } + }) + } +} + +func TestConfig_String(t *testing.T) { + cfg := &config{ + Name: "name", + value: "string", + } + + require.Equal(t, cfg.value, cfg.String()) + require.IsType(t, reflect.TypeOf(cfg.value), reflect.TypeOf(cfg.String())) +} + +func TestConfig_Int(t *testing.T) { + cfg := &config{ + Name: "name", + value: 1, + } + + require.Equal(t, cfg.value, cfg.Int()) + require.IsType(t, reflect.TypeOf(cfg.value), reflect.TypeOf(cfg.Int())) +} + +func TestConfig_Int64(t *testing.T) { + cfg := &config{ + Name: "name", + value: int64(1), + } + + require.Equal(t, cfg.value, cfg.Int64()) + require.IsType(t, reflect.TypeOf(cfg.value), reflect.TypeOf(cfg.Int64())) +} + +func TestConfig_Bool(t *testing.T) { + cfg := &config{ + Name: "name", + value: true, + } + + require.Equal(t, cfg.value, cfg.Bool()) + require.IsType(t, reflect.TypeOf(cfg.value), reflect.TypeOf(cfg.Bool())) +} + +func TestConfig_Slice(t *testing.T) { + cfg := &config{ + Name: "name", + value: []string{"a", "b"}, + } + + require.Equal(t, cfg.value, cfg.Slice()) + require.IsType(t, reflect.TypeOf(cfg.value), reflect.TypeOf(cfg.Slice())) +} + +func TestConfig_Time(t *testing.T) { + cfg := &config{ + Name: "name", + value: time.Now(), + } + + require.Equal(t, cfg.value, cfg.Time()) + require.IsType(t, reflect.TypeOf(cfg.value), reflect.TypeOf(cfg.Time())) +} diff --git a/exec_test.go b/exec_test.go index a0cb681..fed0e13 100644 --- a/exec_test.go +++ b/exec_test.go @@ -9,6 +9,13 @@ func setupTestCase(t *testing.T) (*Exec, func(t *testing.T)) { return New(), func(t *testing.T) {} } +func TestNew(t *testing.T) { + e, teardown := setupTestCase(t) + defer teardown(t) + + require.IsType(t, &Exec{}, e) +} + func TestExec_NewArgument(t *testing.T) { e, teardown := setupTestCase(t) defer teardown(t) From 3bf84d647f19dd2055efba363a0f83b56705a321 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 17:49:18 +0200 Subject: [PATCH 15/21] added opts & args tests --- options.go | 2 +- options_test.go | 363 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 364 insertions(+), 1 deletion(-) create mode 100644 options_test.go diff --git a/options.go b/options.go index fe3257f..f9eabc0 100644 --- a/options.go +++ b/options.go @@ -73,7 +73,7 @@ func (arg Argument) Explain() string { // String casts a value to a string and panics on failure. func (arg Argument) String() string { - return arg.Value.(string) + return *arg.Value.(*string) } // Bool casts a value to a bool and panics on failure. diff --git a/options_test.go b/options_test.go new file mode 100644 index 0000000..c2c4281 --- /dev/null +++ b/options_test.go @@ -0,0 +1,363 @@ +package exec + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestOption_Explain(t *testing.T) { + type testCase struct { + test string + opt *Option + expectedResult string + } + + testCases := []testCase{ + { + test: "single char option", + opt: &Option{ + Name: "r", + }, + expectedResult: "-r", + }, + { + test: "multi char option", + opt: &Option{ + Name: "run", + }, + expectedResult: "--run", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expectedResult, testCase.opt.Explain()) + }) + } +} + +func TestOption_String(t *testing.T) { + type testCase struct { + test string + opt *Option + expectedPanic bool + } + + testCases := []testCase{ + { + test: "valid with value string type pointer", + opt: &Option{ + Name: "option", + Type: String, + Value: new(string), + }, + }, + { + test: "invalid value string type with no pointer", + opt: &Option{ + Name: "option", + Type: String, + Value: "string", + }, + expectedPanic: true, + }, + { + test: "invalid value int type with no pointer", + opt: &Option{ + Name: "option", + Type: String, + Value: 0, + }, + expectedPanic: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.opt.String() + }) + } else { + require.Equal(t, *testCase.opt.Value.(*string), testCase.opt.String()) + } + }) + } +} + +func TestOption_Int(t *testing.T) { + type testCase struct { + test string + opt *Option + expectedPanic bool + } + + testCases := []testCase{ + { + test: "valid with value int type pointer", + opt: &Option{ + Name: "option", + Type: Int, + Value: new(int), + }, + }, + { + test: "invalid value int type with no pointer", + opt: &Option{ + Name: "option", + Type: Int, + Value: 0, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + opt: &Option{ + Name: "option", + Type: Int, + Value: "string", + }, + expectedPanic: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.opt.Int() + }) + } else { + require.Equal(t, *testCase.opt.Value.(*int), testCase.opt.Int()) + } + }) + } +} + +func TestOption_Bool(t *testing.T) { + type testCase struct { + test string + opt *Option + expectedPanic bool + } + + testCases := []testCase{ + { + test: "valid with value bool type pointer", + opt: &Option{ + Name: "option", + Type: Bool, + Value: new(bool), + }, + }, + { + test: "invalid value bool type with no pointer", + opt: &Option{ + Name: "option", + Type: Bool, + Value: false, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + opt: &Option{ + Name: "option", + Type: Bool, + Value: "string", + }, + expectedPanic: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.opt.Bool() + }) + } else { + require.Equal(t, *testCase.opt.Value.(*bool), testCase.opt.Bool()) + } + }) + } +} + +func TestArgument_Explain(t *testing.T) { + type testCase struct { + test string + arg *Argument + expectedResult string + } + + testCases := []testCase{ + { + test: "single arg", + arg: &Argument{ + Name: "arg", + }, + expectedResult: "", + }, + { + test: "multi arg", + arg: &Argument{ + Name: "arg", + Multiple: true, + }, + expectedResult: "...", + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expectedResult, testCase.arg.Explain()) + }) + } +} + +func TestArgument_String(t *testing.T) { + type testCase struct { + test string + arg *Argument + expectedPanic bool + } + + testCases := []testCase{ + { + test: "valid with value string type pointer", + arg: &Argument{ + Name: "argument", + Type: String, + Value: new(string), + }, + }, + { + test: "invalid value string type with no pointer", + arg: &Argument{ + Name: "argument", + Type: String, + Value: "string", + }, + expectedPanic: true, + }, + { + test: "invalid value int type with no pointer", + arg: &Argument{ + Name: "argument", + Type: String, + Value: 0, + }, + expectedPanic: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.arg.String() + }) + } else { + require.Equal(t, *testCase.arg.Value.(*string), testCase.arg.String()) + } + }) + } +} + +func TestArgument_Int(t *testing.T) { + type testCase struct { + test string + arg *Argument + expectedPanic bool + } + + testCases := []testCase{ + { + test: "valid with value int type pointer", + arg: &Argument{ + Name: "argument", + Type: Int, + Value: new(int), + }, + }, + { + test: "invalid value int type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Int, + Value: 0, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Int, + Value: "string", + }, + expectedPanic: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.arg.Int() + }) + } else { + require.Equal(t, *testCase.arg.Value.(*int), testCase.arg.Int()) + } + }) + } +} + +func TestArgument_Bool(t *testing.T) { + type testCase struct { + test string + arg *Argument + expectedPanic bool + } + + testCases := []testCase{ + { + test: "valid with value bool type pointer", + arg: &Argument{ + Name: "argument", + Type: Bool, + Value: new(bool), + }, + }, + { + test: "invalid value bool type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Bool, + Value: false, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Bool, + Value: "string", + }, + expectedPanic: true, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.arg.Bool() + }) + } else { + require.Equal(t, *testCase.arg.Value.(*bool), testCase.arg.Bool()) + } + }) + } +} From d1d19a601742ac41964aa4563ca3214dc32c8d07 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 17:52:43 +0200 Subject: [PATCH 16/21] lint fix --- options_test.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/options_test.go b/options_test.go index c2c4281..9bea7cf 100644 --- a/options_test.go +++ b/options_test.go @@ -76,7 +76,7 @@ func TestOption_String(t *testing.T) { t.Run(testCase.test, func(t *testing.T) { if testCase.expectedPanic { require.Panics(t, func() { - testCase.opt.String() + _ = testCase.opt.String() }) } else { require.Equal(t, *testCase.opt.Value.(*string), testCase.opt.String()) @@ -255,7 +255,7 @@ func TestArgument_String(t *testing.T) { t.Run(testCase.test, func(t *testing.T) { if testCase.expectedPanic { require.Panics(t, func() { - testCase.arg.String() + _ = testCase.arg.String() }) } else { require.Equal(t, *testCase.arg.Value.(*string), testCase.arg.String()) From 844183844decca968d99871106010660f1c73a6f Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 18:33:14 +0200 Subject: [PATCH 17/21] added output tests --- options_test.go | 638 ++++++++++++++++++++++++------------------------ output_test.go | 117 +++++++++ 2 files changed, 436 insertions(+), 319 deletions(-) create mode 100644 output_test.go diff --git a/options_test.go b/options_test.go index 9bea7cf..c04c41c 100644 --- a/options_test.go +++ b/options_test.go @@ -1,363 +1,363 @@ package exec import ( - "github.com/stretchr/testify/require" - "testing" + "github.com/stretchr/testify/require" + "testing" ) func TestOption_Explain(t *testing.T) { - type testCase struct { - test string - opt *Option - expectedResult string - } + type testCase struct { + test string + opt *Option + expectedResult string + } - testCases := []testCase{ - { - test: "single char option", - opt: &Option{ - Name: "r", - }, - expectedResult: "-r", - }, - { - test: "multi char option", - opt: &Option{ - Name: "run", - }, - expectedResult: "--run", - }, - } + testCases := []testCase{ + { + test: "single char option", + opt: &Option{ + Name: "r", + }, + expectedResult: "-r", + }, + { + test: "multi char option", + opt: &Option{ + Name: "run", + }, + expectedResult: "--run", + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - require.Equal(t, testCase.expectedResult, testCase.opt.Explain()) - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expectedResult, testCase.opt.Explain()) + }) + } } func TestOption_String(t *testing.T) { - type testCase struct { - test string - opt *Option - expectedPanic bool - } + type testCase struct { + test string + opt *Option + expectedPanic bool + } - testCases := []testCase{ - { - test: "valid with value string type pointer", - opt: &Option{ - Name: "option", - Type: String, - Value: new(string), - }, - }, - { - test: "invalid value string type with no pointer", - opt: &Option{ - Name: "option", - Type: String, - Value: "string", - }, - expectedPanic: true, - }, - { - test: "invalid value int type with no pointer", - opt: &Option{ - Name: "option", - Type: String, - Value: 0, - }, - expectedPanic: true, - }, - } + testCases := []testCase{ + { + test: "valid with value string type pointer", + opt: &Option{ + Name: "option", + Type: String, + Value: new(string), + }, + }, + { + test: "invalid value string type with no pointer", + opt: &Option{ + Name: "option", + Type: String, + Value: "string", + }, + expectedPanic: true, + }, + { + test: "invalid value int type with no pointer", + opt: &Option{ + Name: "option", + Type: String, + Value: 0, + }, + expectedPanic: true, + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - if testCase.expectedPanic { - require.Panics(t, func() { - _ = testCase.opt.String() - }) - } else { - require.Equal(t, *testCase.opt.Value.(*string), testCase.opt.String()) - } - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + _ = testCase.opt.String() + }) + } else { + require.Equal(t, *testCase.opt.Value.(*string), testCase.opt.String()) + } + }) + } } func TestOption_Int(t *testing.T) { - type testCase struct { - test string - opt *Option - expectedPanic bool - } + type testCase struct { + test string + opt *Option + expectedPanic bool + } - testCases := []testCase{ - { - test: "valid with value int type pointer", - opt: &Option{ - Name: "option", - Type: Int, - Value: new(int), - }, - }, - { - test: "invalid value int type with no pointer", - opt: &Option{ - Name: "option", - Type: Int, - Value: 0, - }, - expectedPanic: true, - }, - { - test: "invalid value string type with no pointer", - opt: &Option{ - Name: "option", - Type: Int, - Value: "string", - }, - expectedPanic: true, - }, - } + testCases := []testCase{ + { + test: "valid with value int type pointer", + opt: &Option{ + Name: "option", + Type: Int, + Value: new(int), + }, + }, + { + test: "invalid value int type with no pointer", + opt: &Option{ + Name: "option", + Type: Int, + Value: 0, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + opt: &Option{ + Name: "option", + Type: Int, + Value: "string", + }, + expectedPanic: true, + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - if testCase.expectedPanic { - require.Panics(t, func() { - testCase.opt.Int() - }) - } else { - require.Equal(t, *testCase.opt.Value.(*int), testCase.opt.Int()) - } - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.opt.Int() + }) + } else { + require.Equal(t, *testCase.opt.Value.(*int), testCase.opt.Int()) + } + }) + } } func TestOption_Bool(t *testing.T) { - type testCase struct { - test string - opt *Option - expectedPanic bool - } + type testCase struct { + test string + opt *Option + expectedPanic bool + } - testCases := []testCase{ - { - test: "valid with value bool type pointer", - opt: &Option{ - Name: "option", - Type: Bool, - Value: new(bool), - }, - }, - { - test: "invalid value bool type with no pointer", - opt: &Option{ - Name: "option", - Type: Bool, - Value: false, - }, - expectedPanic: true, - }, - { - test: "invalid value string type with no pointer", - opt: &Option{ - Name: "option", - Type: Bool, - Value: "string", - }, - expectedPanic: true, - }, - } + testCases := []testCase{ + { + test: "valid with value bool type pointer", + opt: &Option{ + Name: "option", + Type: Bool, + Value: new(bool), + }, + }, + { + test: "invalid value bool type with no pointer", + opt: &Option{ + Name: "option", + Type: Bool, + Value: false, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + opt: &Option{ + Name: "option", + Type: Bool, + Value: "string", + }, + expectedPanic: true, + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - if testCase.expectedPanic { - require.Panics(t, func() { - testCase.opt.Bool() - }) - } else { - require.Equal(t, *testCase.opt.Value.(*bool), testCase.opt.Bool()) - } - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.opt.Bool() + }) + } else { + require.Equal(t, *testCase.opt.Value.(*bool), testCase.opt.Bool()) + } + }) + } } func TestArgument_Explain(t *testing.T) { - type testCase struct { - test string - arg *Argument - expectedResult string - } + type testCase struct { + test string + arg *Argument + expectedResult string + } - testCases := []testCase{ - { - test: "single arg", - arg: &Argument{ - Name: "arg", - }, - expectedResult: "", - }, - { - test: "multi arg", - arg: &Argument{ - Name: "arg", - Multiple: true, - }, - expectedResult: "...", - }, - } + testCases := []testCase{ + { + test: "single arg", + arg: &Argument{ + Name: "arg", + }, + expectedResult: "", + }, + { + test: "multi arg", + arg: &Argument{ + Name: "arg", + Multiple: true, + }, + expectedResult: "...", + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - require.Equal(t, testCase.expectedResult, testCase.arg.Explain()) - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expectedResult, testCase.arg.Explain()) + }) + } } func TestArgument_String(t *testing.T) { - type testCase struct { - test string - arg *Argument - expectedPanic bool - } + type testCase struct { + test string + arg *Argument + expectedPanic bool + } - testCases := []testCase{ - { - test: "valid with value string type pointer", - arg: &Argument{ - Name: "argument", - Type: String, - Value: new(string), - }, - }, - { - test: "invalid value string type with no pointer", - arg: &Argument{ - Name: "argument", - Type: String, - Value: "string", - }, - expectedPanic: true, - }, - { - test: "invalid value int type with no pointer", - arg: &Argument{ - Name: "argument", - Type: String, - Value: 0, - }, - expectedPanic: true, - }, - } + testCases := []testCase{ + { + test: "valid with value string type pointer", + arg: &Argument{ + Name: "argument", + Type: String, + Value: new(string), + }, + }, + { + test: "invalid value string type with no pointer", + arg: &Argument{ + Name: "argument", + Type: String, + Value: "string", + }, + expectedPanic: true, + }, + { + test: "invalid value int type with no pointer", + arg: &Argument{ + Name: "argument", + Type: String, + Value: 0, + }, + expectedPanic: true, + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - if testCase.expectedPanic { - require.Panics(t, func() { - _ = testCase.arg.String() - }) - } else { - require.Equal(t, *testCase.arg.Value.(*string), testCase.arg.String()) - } - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + _ = testCase.arg.String() + }) + } else { + require.Equal(t, *testCase.arg.Value.(*string), testCase.arg.String()) + } + }) + } } func TestArgument_Int(t *testing.T) { - type testCase struct { - test string - arg *Argument - expectedPanic bool - } + type testCase struct { + test string + arg *Argument + expectedPanic bool + } - testCases := []testCase{ - { - test: "valid with value int type pointer", - arg: &Argument{ - Name: "argument", - Type: Int, - Value: new(int), - }, - }, - { - test: "invalid value int type with no pointer", - arg: &Argument{ - Name: "argument", - Type: Int, - Value: 0, - }, - expectedPanic: true, - }, - { - test: "invalid value string type with no pointer", - arg: &Argument{ - Name: "argument", - Type: Int, - Value: "string", - }, - expectedPanic: true, - }, - } + testCases := []testCase{ + { + test: "valid with value int type pointer", + arg: &Argument{ + Name: "argument", + Type: Int, + Value: new(int), + }, + }, + { + test: "invalid value int type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Int, + Value: 0, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Int, + Value: "string", + }, + expectedPanic: true, + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - if testCase.expectedPanic { - require.Panics(t, func() { - testCase.arg.Int() - }) - } else { - require.Equal(t, *testCase.arg.Value.(*int), testCase.arg.Int()) - } - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.arg.Int() + }) + } else { + require.Equal(t, *testCase.arg.Value.(*int), testCase.arg.Int()) + } + }) + } } func TestArgument_Bool(t *testing.T) { - type testCase struct { - test string - arg *Argument - expectedPanic bool - } + type testCase struct { + test string + arg *Argument + expectedPanic bool + } - testCases := []testCase{ - { - test: "valid with value bool type pointer", - arg: &Argument{ - Name: "argument", - Type: Bool, - Value: new(bool), - }, - }, - { - test: "invalid value bool type with no pointer", - arg: &Argument{ - Name: "argument", - Type: Bool, - Value: false, - }, - expectedPanic: true, - }, - { - test: "invalid value string type with no pointer", - arg: &Argument{ - Name: "argument", - Type: Bool, - Value: "string", - }, - expectedPanic: true, - }, - } + testCases := []testCase{ + { + test: "valid with value bool type pointer", + arg: &Argument{ + Name: "argument", + Type: Bool, + Value: new(bool), + }, + }, + { + test: "invalid value bool type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Bool, + Value: false, + }, + expectedPanic: true, + }, + { + test: "invalid value string type with no pointer", + arg: &Argument{ + Name: "argument", + Type: Bool, + Value: "string", + }, + expectedPanic: true, + }, + } - for _, testCase := range testCases { - t.Run(testCase.test, func(t *testing.T) { - if testCase.expectedPanic { - require.Panics(t, func() { - testCase.arg.Bool() - }) - } else { - require.Equal(t, *testCase.arg.Value.(*bool), testCase.arg.Bool()) - } - }) - } + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + if testCase.expectedPanic { + require.Panics(t, func() { + testCase.arg.Bool() + }) + } else { + require.Equal(t, *testCase.arg.Value.(*bool), testCase.arg.Bool()) + } + }) + } } diff --git a/output_test.go b/output_test.go new file mode 100644 index 0000000..94fd76f --- /dev/null +++ b/output_test.go @@ -0,0 +1,117 @@ +package exec + +import ( + "errors" + "github.com/stretchr/testify/require" + "testing" +) + +func TestOutput_HasError(t *testing.T) { + o := &Output{ + err: errors.New(""), + } + + require.True(t, o.HasError()) +} + +func TestOutput_String(t *testing.T) { + o := &Output{} + + require.True(t, o.String() == o.text) +} + +func TestOutput_Int(t *testing.T) { + type testCase struct { + test string + output *Output + expected int + } + + testCases := []testCase{ + { + test: "valid string to int via atoi", + output: &Output{ + text: "1", + }, + expected: 1, + }, + { + test: "invalid string to int via atoi", + output: &Output{ + text: "string", + }, + expected: 0, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expected, testCase.output.Int()) + }) + } +} + +func TestOutput_Bool(t *testing.T) { + type testCase struct { + test string + output *Output + expected bool + } + + testCases := []testCase{ + { + test: "valid true string to bool", + output: &Output{ + text: "true", + }, + expected: true, + }, + { + test: "valid !true string to bool", + output: &Output{ + text: "string", + }, + expected: false, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expected, testCase.output.Bool()) + }) + } +} + +func TestOutput_Slice(t *testing.T) { + type testCase struct { + test string + output *Output + separator string + expected []string + } + + testCases := []testCase{ + { + test: "valid splice a,b,c", + output: &Output{ + text: "a,b,c", + }, + separator: ",", + expected: []string{"a", "b", "c"}, + }, + { + test: "valid splice empty", + output: &Output{ + text: "", + }, + separator: ",", + expected: []string{""}, + }, + } + + for _, testCase := range testCases { + t.Run(testCase.test, func(t *testing.T) { + require.Equal(t, testCase.expected, testCase.output.Slice(testCase.separator)) + }) + } +} From 8216a2882f17b48f9ab588fb73453ed00c0af081 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 19:03:12 +0200 Subject: [PATCH 18/21] added server tests --- server.go | 2 +- server_test.go | 27 +++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 server_test.go diff --git a/server.go b/server.go index a967aec..b50063a 100644 --- a/server.go +++ b/server.go @@ -6,8 +6,8 @@ type server struct { Name string Dsn string Configs map[string]*config - key *string + key *string roles []string sshClient *sshClient } diff --git a/server_test.go b/server_test.go new file mode 100644 index 0000000..98bf1d8 --- /dev/null +++ b/server_test.go @@ -0,0 +1,27 @@ +package exec + +import ( + "github.com/stretchr/testify/require" + "testing" +) + +func TestServer_AddRole(t *testing.T) { + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + } + s.AddRole("qa").AddRole("test") + + require.Equal(t, s.roles, []string{"qa", "test"}) +} + +func TestServer_HasRole(t *testing.T) { + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + } + s.AddRole("qa") + + require.True(t, s.HasRole("qa")) + require.False(t, s.HasRole("test")) +} \ No newline at end of file From 4f5886dfc8584e7b149b3e7d50f143a0e874873d Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Sun, 2 Feb 2020 21:28:33 +0200 Subject: [PATCH 19/21] added server tests --- server.go | 2 +- server_test.go | 64 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 54 insertions(+), 12 deletions(-) diff --git a/server.go b/server.go index b50063a..39fb8b5 100644 --- a/server.go +++ b/server.go @@ -38,7 +38,7 @@ func (s *server) Key(file string) *server { } func (s *server) GetUser() string { - return s.Dsn[:strings.Index(s.Dsn, "@")-1] + return s.Dsn[:strings.Index(s.Dsn, "@")] } func (s *server) GetHost() string { diff --git a/server_test.go b/server_test.go index 98bf1d8..504a511 100644 --- a/server_test.go +++ b/server_test.go @@ -1,8 +1,8 @@ package exec import ( - "github.com/stretchr/testify/require" - "testing" + "github.com/stretchr/testify/require" + "testing" ) func TestServer_AddRole(t *testing.T) { @@ -16,12 +16,54 @@ func TestServer_AddRole(t *testing.T) { } func TestServer_HasRole(t *testing.T) { - s := &server{ - Name: "qa", - Dsn: "root@domain.com", - } - s.AddRole("qa") - - require.True(t, s.HasRole("qa")) - require.False(t, s.HasRole("test")) -} \ No newline at end of file + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + } + s.AddRole("qa") + + require.True(t, s.HasRole("qa")) + require.False(t, s.HasRole("test")) +} + +func TestServer_Set(t *testing.T) { + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + Configs: make(map[string]*config), + } + s.Set("config", "value") + + require.Contains(t, s.Configs, "config") + require.Equal(t, s.Configs["config"].Value(), "value") +} + +func TestServer_Key(t *testing.T) { + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + Configs: make(map[string]*config), + sshClient: &sshClient{}, + } + s.Key("key") + + require.Contains(t, s.sshClient.keys, "key") +} + +func TestServer_GetUser(t *testing.T) { + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + } + + require.Equal(t, s.GetUser(), "root") +} + +func TestServer_GetHost(t *testing.T) { + s := &server{ + Name: "qa", + Dsn: "root@domain.com", + } + + require.Equal(t, s.GetHost(), "domain.com") +} From 0bd451936bd7f0866fb8c2bc82686ebeed5c0ada Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Mon, 23 Mar 2020 20:21:05 +0200 Subject: [PATCH 20/21] ssh mocking, exec test update --- exec_test.go | 96 ++++++++------- ssh.go | 8 ++ ssh_mock/ssh_mock.go | 272 +++++++++++++++++++++++++++++++++++++++++++ ssh_mock/testdata.go | 66 +++++++++++ 4 files changed, 403 insertions(+), 39 deletions(-) create mode 100644 ssh_mock/ssh_mock.go create mode 100644 ssh_mock/testdata.go diff --git a/exec_test.go b/exec_test.go index fed0e13..52f19d4 100644 --- a/exec_test.go +++ b/exec_test.go @@ -1,24 +1,19 @@ package exec import ( + "github.com/go-exec/exec/ssh_mock" "github.com/stretchr/testify/require" "testing" ) -func setupTestCase(t *testing.T) (*Exec, func(t *testing.T)) { - return New(), func(t *testing.T) {} -} - func TestNew(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() require.IsType(t, &Exec{}, e) } func TestExec_NewArgument(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() arg := &Argument{ Name: "name", @@ -33,8 +28,7 @@ func TestExec_NewArgument(t *testing.T) { } func TestExec_AddArgument(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() arg := &Argument{ Name: "test", @@ -72,22 +66,19 @@ func TestExec_GetArgument(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - e, teardown := setupTestCase(t) + e := New() if testCase.arg != nil { e.AddArgument(testCase.arg) } require.Equal(t, e.GetArgument(testCase.name), testCase.arg) - - teardown(t) }) } } func TestExec_NewOption(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() opt := &Option{ Name: "name", @@ -100,8 +91,7 @@ func TestExec_NewOption(t *testing.T) { } func TestExec_AddOption(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() opt := &Option{ Name: "name", @@ -138,22 +128,19 @@ func TestExec_GetOption(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - e, teardown := setupTestCase(t) + e := New() if testCase.opt != nil { e.AddOption(testCase.opt) } require.Equal(t, e.GetOption(testCase.name), testCase.opt) - - teardown(t) }) } } func TestExec_Set(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() cfg := &config{ Name: "cfg", @@ -201,7 +188,7 @@ func TestExec_Get(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - e, teardown := setupTestCase(t) + e := New() if testCase.cfg != nil { e.Set(testCase.cfg.Name, testCase.cfg.value) @@ -212,8 +199,6 @@ func TestExec_Get(t *testing.T) { } require.Equal(t, e.Get(testCase.name), testCase.cfg) - - teardown(t) }) } } @@ -260,7 +245,7 @@ func TestExec_Has(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - e, teardown := setupTestCase(t) + e := New() if testCase.cfg != nil { e.Set(testCase.cfg.Name, testCase.cfg.value) @@ -271,15 +256,12 @@ func TestExec_Has(t *testing.T) { } require.Equal(t, e.Has(testCase.name), testCase.expectedResult) - - teardown(t) }) } } func TestExec_Server(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() cfg := &server{ Name: "server", @@ -293,8 +275,7 @@ func TestExec_Server(t *testing.T) { } func TestExec_Task(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() task := &task{ Name: "task", @@ -311,8 +292,7 @@ func TestExec_Task(t *testing.T) { } func TestExec_TaskGroup(t *testing.T) { - e, teardown := setupTestCase(t) - defer teardown(t) + e := New() taskGroup := &taskGroup{ Name: "taskGroup", @@ -352,14 +332,12 @@ func TestExec_Before(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - e, teardown := setupTestCase(t) + e := New() e.Before(testCase.task.Name, testCase.before...) require.Contains(t, e.before, testCase.task.Name) require.Equal(t, len(e.before[testCase.task.Name]), testCase.unique) - - defer teardown(t) }) } } @@ -393,14 +371,54 @@ func TestExec_After(t *testing.T) { for _, testCase := range testCases { t.Run(testCase.test, func(t *testing.T) { - e, teardown := setupTestCase(t) + e := New() e.Before(testCase.task.Name, testCase.after...) require.Contains(t, e.before, testCase.task.Name) require.Equal(t, len(e.before[testCase.task.Name]), testCase.unique) + }) + } +} + +func TestExec_Remote(t *testing.T) { + type args struct { + command string + args []interface{} + } + tests := []struct { + name string + args args + wantO Output + }{ + { + name: "test", + args: args{ + command: `echo hello`, + }, + wantO: Output{ + text: "hello", + }, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + server := ssh_mock.NewServer(t) + defer server.Shutdown() + conn := server.Dial(ssh_mock.ClientConfig()) + defer conn.Close() + + e := New() + + s := e.Server("mock", "") + + s.sshClient.WithConnection(conn) + + e.ServerContext = s + + gotO := e.Remote(tt.args.command, tt.args.args...) - defer teardown(t) + require.Equal(t, tt.wantO, gotO, "Remote() = %v, want %v", gotO, tt.wantO) }) } } diff --git a/ssh.go b/ssh.go index 36668be..c231c03 100644 --- a/ssh.go +++ b/ssh.go @@ -112,6 +112,14 @@ func (c *sshClient) initAuthMethod() { // SSHDialFunc can dial an ssh server and return a client type sshDialFunc func(net, addr string, config *ssh.ClientConfig) (*ssh.Client, error) +// WithConnection associate an existing connection +func (c *sshClient) WithConnection(conn *ssh.Client) { + if conn != nil { + c.conn = conn + c.connOpened = true + } +} + // Connect creates SSH connection to a specified host. // It expects the host of the form "[ssh://]host[:port]". func (c *sshClient) Connect(host string) error { diff --git a/ssh_mock/ssh_mock.go b/ssh_mock/ssh_mock.go new file mode 100644 index 0000000..6fb83a1 --- /dev/null +++ b/ssh_mock/ssh_mock.go @@ -0,0 +1,272 @@ +// Modified to support mocking for go-exec + +// Copyright 2012 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// +build darwin dragonfly freebsd linux netbsd openbsd plan9 + +package ssh_mock + +// functional test harness for unix. + +import ( + "bytes" + "fmt" + "io/ioutil" + "log" + "net" + "os" + "os/exec" + "os/user" + "path/filepath" + "testing" + "text/template" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" +) + +const sshd_config = ` +Protocol 2 +HostKey {{.Dir}}/id_rsa +HostKey {{.Dir}}/id_dsa +HostKey {{.Dir}}/id_ecdsa +Pidfile {{.Dir}}/sshd.pid +#UsePrivilegeSeparation no +KeyRegenerationInterval 3600 +ServerKeyBits 768 +SyslogFacility AUTH +#LogLevel DEBUG2 +LoginGraceTime 120 +PermitRootLogin no +StrictModes no +RSAAuthentication yes +PubkeyAuthentication yes +AuthorizedKeysFile {{.Dir}}/authorized_keys +TrustedUserCAKeys {{.Dir}}/id_ecdsa.pub +IgnoreRhosts yes +RhostsRSAAuthentication no +HostbasedAuthentication no +PubkeyAcceptedKeyTypes=* +` + +var configTmpl = template.Must(template.New("").Parse(sshd_config)) + +type Server struct { + t *testing.T + cleanup func() // executed during Shutdown + configfile string + cmd *exec.Cmd + output bytes.Buffer // holds stderr from sshd process + + // Client half of the network connection. + clientConn net.Conn +} + +func username() string { + var username string + if user, err := user.Current(); err == nil { + username = user.Username + } else { + // user.Current() currently requires cgo. If an error is + // returned attempt to get the username from the environment. + log.Printf("user.Current: %v; falling back on $USER", err) + username = os.Getenv("USER") + } + if username == "" { + panic("Unable to get username") + } + return username +} + +type storedHostKey struct { + // keys map from an algorithm string to binary key data. + keys map[string][]byte + + // checkCount counts the Check calls. Used for testing + // rekeying. + checkCount int +} + +func (k *storedHostKey) Add(key ssh.PublicKey) { + if k.keys == nil { + k.keys = map[string][]byte{} + } + k.keys[key.Type()] = key.Marshal() +} + +func (k *storedHostKey) Check(addr string, remote net.Addr, key ssh.PublicKey) error { + k.checkCount++ + algo := key.Type() + + if k.keys == nil || !bytes.Equal(key.Marshal(), k.keys[algo]) { + return fmt.Errorf("host key mismatch. Got %q, want %q", key, k.keys[algo]) + } + return nil +} + +func hostKeyDB() *storedHostKey { + keyChecker := &storedHostKey{} + keyChecker.Add(testPublicKeys["ecdsa"]) + keyChecker.Add(testPublicKeys["rsa"]) + keyChecker.Add(testPublicKeys["dsa"]) + return keyChecker +} + +func ClientConfig() *ssh.ClientConfig { + config := &ssh.ClientConfig{ + User: username(), + Auth: []ssh.AuthMethod{ + ssh.PublicKeys(testSigners["user"]), + }, + HostKeyCallback: hostKeyDB().Check, + } + return config +} + +// unixConnection creates two halves of a connected net.UnixConn. It +// is used for connecting the Go SSH client with sshd without opening +// ports. +func unixConnection() (*net.UnixConn, *net.UnixConn, error) { + dir, err := ioutil.TempDir("", "unixConnection") + if err != nil { + return nil, nil, err + } + defer os.Remove(dir) + + addr := filepath.Join(dir, "ssh") + listener, err := net.Listen("unix", addr) + if err != nil { + return nil, nil, err + } + defer listener.Close() + c1, err := net.Dial("unix", addr) + if err != nil { + return nil, nil, err + } + + c2, err := listener.Accept() + if err != nil { + c1.Close() + return nil, nil, err + } + + return c1.(*net.UnixConn), c2.(*net.UnixConn), nil +} + +func (s *Server) TryDial(config *ssh.ClientConfig) (*ssh.Client, error) { + sshd, err := exec.LookPath("sshd") + if err != nil { + s.t.Skipf("skipping test: %v", err) + } + + c1, c2, err := unixConnection() + if err != nil { + s.t.Fatalf("unixConnection: %v", err) + } + + s.cmd = exec.Command(sshd, "-f", s.configfile, "-i", "-e") + f, err := c2.File() + if err != nil { + s.t.Fatalf("UnixConn.File: %v", err) + } + defer f.Close() + s.cmd.Stdin = f + s.cmd.Stdout = f + s.cmd.Stderr = &s.output + if err := s.cmd.Start(); err != nil { + s.t.Fail() + s.Shutdown() + s.t.Fatalf("s.cmd.Start: %v", err) + } + s.clientConn = c1 + conn, chans, reqs, err := ssh.NewClientConn(c1, "", config) + if err != nil { + return nil, err + } + return ssh.NewClient(conn, chans, reqs), nil +} + +func (s *Server) Dial(config *ssh.ClientConfig) *ssh.Client { + conn, err := s.TryDial(config) + if err != nil { + s.t.Fail() + s.Shutdown() + s.t.Fatalf("ssh.Client: %v", err) + } + return conn +} + +func (s *Server) Shutdown() { + if s.cmd != nil && s.cmd.Process != nil { + // Don't check for errors; if it fails it's most + // likely "os: process already finished", and we don't + // care about that. Use os.Interrupt, so child + // processes are killed too. + _ = s.cmd.Process.Signal(os.Interrupt) + _ = s.cmd.Wait() + } + if s.t.Failed() { + // log any output from sshd process + s.t.Logf("sshd: %s", s.output.String()) + } + s.cleanup() +} + +func writeFile(path string, contents []byte) { + f, err := os.OpenFile(path, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0600) + if err != nil { + panic(err) + } + defer func() { + _ = f.Close() + }() + if _, err := f.Write(contents); err != nil { + panic(err) + } +} + +// NewServer returns a new mock ssh server. +func NewServer(t *testing.T) *Server { + if testing.Short() { + t.Skip("skipping test due to -short") + } + dir, err := ioutil.TempDir("", "sshtest") + if err != nil { + t.Fatal(err) + } + f, err := os.Create(filepath.Join(dir, "sshd_config")) + if err != nil { + t.Fatal(err) + } + err = configTmpl.Execute(f, map[string]string{ + "Dir": dir, + }) + if err != nil { + t.Fatal(err) + } + _ = f.Close() + + for k, v := range testdata.PEMBytes { + filename := "id_" + k + writeFile(filepath.Join(dir, filename), v) + writeFile(filepath.Join(dir, filename+".pub"), ssh.MarshalAuthorizedKey(testPublicKeys[k])) + } + + var authkeys bytes.Buffer + for k := range testdata.PEMBytes { + authkeys.Write(ssh.MarshalAuthorizedKey(testPublicKeys[k])) + } + writeFile(filepath.Join(dir, "authorized_keys"), authkeys.Bytes()) + + return &Server{ + t: t, + configfile: f.Name(), + cleanup: func() { + if err := os.RemoveAll(dir); err != nil { + t.Error(err) + } + }, + } +} diff --git a/ssh_mock/testdata.go b/ssh_mock/testdata.go new file mode 100644 index 0000000..54239be --- /dev/null +++ b/ssh_mock/testdata.go @@ -0,0 +1,66 @@ +// Modified to support mocking for go-exec + +// Copyright 2014 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +// IMPLEMENTATION NOTE: To avoid a package loop, this file is in three places: +// ssh/, ssh/agent, and ssh/test/. It should be kept in sync across all three +// instances. + +package ssh_mock + +import ( + "crypto/rand" + "fmt" + + "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/testdata" +) + +var ( + testPrivateKeys map[string]interface{} + testSigners map[string]ssh.Signer + testPublicKeys map[string]ssh.PublicKey +) + +func init() { + var err error + + n := len(testdata.PEMBytes) + testPrivateKeys = make(map[string]interface{}, n) + testSigners = make(map[string]ssh.Signer, n) + testPublicKeys = make(map[string]ssh.PublicKey, n) + for t, k := range testdata.PEMBytes { + testPrivateKeys[t], err = ssh.ParseRawPrivateKey(k) + if err != nil { + panic(fmt.Sprintf("Unable to parse test key %s: %v", t, err)) + } + testSigners[t], err = ssh.NewSignerFromKey(testPrivateKeys[t]) + if err != nil { + panic(fmt.Sprintf("Unable to create signer for test key %s: %v", t, err)) + } + testPublicKeys[t] = testSigners[t].PublicKey() + } + + // Create a cert and sign it for use in tests. + testCert := &ssh.Certificate{ + Nonce: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + ValidPrincipals: []string{"gopher1", "gopher2"}, // increases test coverage + ValidAfter: 0, // unix epoch + ValidBefore: ssh.CertTimeInfinity, // The end of currently representable time. + Reserved: []byte{}, // To pass reflect.DeepEqual after marshal & parse, this must be non-nil + Key: testPublicKeys["ecdsa"], + SignatureKey: testPublicKeys["rsa"], + Permissions: ssh.Permissions{ + CriticalOptions: map[string]string{}, + Extensions: map[string]string{}, + }, + } + _ = testCert.SignCert(rand.Reader, testSigners["rsa"]) + testPrivateKeys["cert"] = testPrivateKeys["ecdsa"] + testSigners["cert"], err = ssh.NewCertSigner(testCert, testSigners["ecdsa"]) + if err != nil { + panic(fmt.Sprintf("Unable to create certificate signer: %v", err)) + } +} From 9f0ff090710af5001dfd0dbd3d87851f7803d111 Mon Sep 17 00:00:00 2001 From: Radu Topala Date: Wed, 25 Mar 2020 19:43:15 +0200 Subject: [PATCH 21/21] tests up --- exec_test.go | 43 +++++++++++++++++++++++++++++++++++++------ 1 file changed, 37 insertions(+), 6 deletions(-) diff --git a/exec_test.go b/exec_test.go index 52f19d4..1d5d0e5 100644 --- a/exec_test.go +++ b/exec_test.go @@ -386,13 +386,13 @@ func TestExec_Remote(t *testing.T) { command string args []interface{} } - tests := []struct { + testCases := []struct { name string args args wantO Output }{ { - name: "test", + name: "echo remote test", args: args{ command: `echo hello`, }, @@ -401,8 +401,8 @@ func TestExec_Remote(t *testing.T) { }, }, } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { server := ssh_mock.NewServer(t) defer server.Shutdown() conn := server.Dial(ssh_mock.ClientConfig()) @@ -416,9 +416,40 @@ func TestExec_Remote(t *testing.T) { e.ServerContext = s - gotO := e.Remote(tt.args.command, tt.args.args...) + gotO := e.Remote(testCase.args.command, testCase.args.args...) - require.Equal(t, tt.wantO, gotO, "Remote() = %v, want %v", gotO, tt.wantO) + require.Equal(t, testCase.wantO, gotO, "Remote() = %v, want %v", gotO, testCase.wantO) }) } } + +func TestExec_Local(t *testing.T) { + type args struct { + command string + args []interface{} + } + testCases := []struct { + name string + args args + wantO Output + }{ + { + name: "echo local test", + args: args{ + command: `echo hello`, + }, + wantO: Output{ + text: "hello", + }, + }, + } + for _, testCase := range testCases { + t.Run(testCase.name, func(t *testing.T) { + e := New() + + gotO := e.Local(testCase.args.command, testCase.args.args...) + + require.Equal(t, testCase.wantO, gotO, "Remote() = %v, want %v", gotO, testCase.wantO) + }) + } +} \ No newline at end of file