From 990d1856d8e93ef305dda2c29c44809d21288f6f Mon Sep 17 00:00:00 2001 From: Erin Call Date: Wed, 4 Dec 2019 11:26:05 -0800 Subject: [PATCH] Very rough code that can `helm install` The recommended way to test code that uses exec.Cmd involves setting up a real exec.Cmd that invokes `go test` with additional arguments that fire off a specially-constructed test that behaves the way the mocked- out script would be expected to do. It's a sensible way to test exec.Cmd itself, but for code that merely invokes it, I think it makes more sense to use actual mocks. --- go.mod | 5 +- go.sum | 10 ++ internal/run/cmd.go | 55 +++++++ internal/run/cmd_test.go | 303 +++++++++++++++++++++++++++++++++++ internal/run/install.go | 19 +++ internal/run/install_test.go | 20 +++ 6 files changed, 411 insertions(+), 1 deletion(-) create mode 100644 internal/run/cmd.go create mode 100644 internal/run/cmd_test.go create mode 100644 internal/run/install.go create mode 100644 internal/run/install_test.go diff --git a/go.mod b/go.mod index 0368153..01751ee 100644 --- a/go.mod +++ b/go.mod @@ -2,4 +2,7 @@ module github.com/pelotech/drone-helm3 go 1.13 -require github.com/urfave/cli v1.22.0 +require ( + github.com/golang/mock v1.3.1 + github.com/urfave/cli v1.22.0 +) diff --git a/go.sum b/go.sum index e2599a4..15b5fe6 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,19 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= github.com/cpuguy83/go-md2man v1.0.10 h1:BSKMNlYxDvnunlTymqtgONjNnaRV1sTpcovwwjF22jk= github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/golang/mock v1.3.1 h1:qGJ6qTW+x6xX/my+8YUVl4WNpX9B7+/l2tRsHGZ7f2s= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= github.com/russross/blackfriday v1.5.2 h1:HyvC0ARfnZBqnXwABFeSZHpKvJHJJfPz81GNueLj0oo= github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= github.com/urfave/cli v1.22.0 h1:8nz/RUUotroXnOpYzT/Fy3sBp+2XEbXaY641/s3nbFI= github.com/urfave/cli v1.22.0/go.mod h1:b3D7uWrF2GilkNgYpgcg6J+JMUw7ehmNkE8sZdliGLc= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262 h1:qsl9y/CJx34tuA7QCPNp86JNJe4spst6Ff8MjvPUdPg= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/internal/run/cmd.go b/internal/run/cmd.go new file mode 100644 index 0000000..9344666 --- /dev/null +++ b/internal/run/cmd.go @@ -0,0 +1,55 @@ +package run + +import ( + "io" + "os" + "os/exec" + "syscall" +) + +// The cmd interface provides a generic form of exec.Cmd so that it can be mocked out in tests. +type cmd interface { + // methods that exist on exec.Cmd + Output() ([]byte, error) + Run() error + CombinedOutput() ([]byte, error) + Start() error + StderrPipe() (io.ReadCloser, error) + StdinPipe() (io.WriteCloser, error) + StdoutPipe() (io.ReadCloser, error) + String() string + Wait() error + + // setters for struct fields set by callers of exec.Cmd + Path(string) + Args([]string) + Env([]string) + Dir(string) + Stdin(io.Reader) + Stdout(io.Writer) + Stderr(io.Writer) + ExtraFiles([]*os.File) + SysProcAttr(*syscall.SysProcAttr) + + // getters for struct fields generated by exec.Cmd + Process() *os.Process + ProcessState() *os.ProcessState +} + +// execCmd wraps exec.Cmd along with setters for exec.Cmd's struct fields, implementing the cmd interface. +type execCmd struct { + *exec.Cmd +} + +func (c *execCmd) Path(p string) { c.Cmd.Path = p } +func (c *execCmd) Args(a []string) { c.Cmd.Args = a } +func (c *execCmd) Env(e []string) { c.Cmd.Env = e } +func (c *execCmd) Dir(d string) { c.Cmd.Dir = d } +func (c *execCmd) Stdin(s io.Reader) { c.Cmd.Stdin = s } +func (c *execCmd) Stdout(s io.Writer) { c.Cmd.Stdout = s } +func (c *execCmd) Stderr(s io.Writer) { c.Cmd.Stderr = s } +func (c *execCmd) ExtraFiles(f []*os.File) { c.Cmd.ExtraFiles = f } +func (c *execCmd) SysProcAttr(s *syscall.SysProcAttr) { c.Cmd.SysProcAttr = s } + +func (c *execCmd) Process() *os.Process { return c.Cmd.Process } +func (c *execCmd) ProcessState() *os.ProcessState { return c.Cmd.ProcessState } diff --git a/internal/run/cmd_test.go b/internal/run/cmd_test.go new file mode 100644 index 0000000..80f5beb --- /dev/null +++ b/internal/run/cmd_test.go @@ -0,0 +1,303 @@ +// Code generated by MockGen. DO NOT EDIT. +// Source: ./internal/run/cmd.go + +// Package run is a generated GoMock package. +package run + +import ( + gomock "github.com/golang/mock/gomock" + io "io" + os "os" + reflect "reflect" + syscall "syscall" +) + +// Mockcmd is a mock of cmd interface +type Mockcmd struct { + ctrl *gomock.Controller + recorder *MockcmdMockRecorder +} + +// MockcmdMockRecorder is the mock recorder for Mockcmd +type MockcmdMockRecorder struct { + mock *Mockcmd +} + +// NewMockcmd creates a new mock instance +func NewMockcmd(ctrl *gomock.Controller) *Mockcmd { + mock := &Mockcmd{ctrl: ctrl} + mock.recorder = &MockcmdMockRecorder{mock} + return mock +} + +// EXPECT returns an object that allows the caller to indicate expected use +func (m *Mockcmd) EXPECT() *MockcmdMockRecorder { + return m.recorder +} + +// Output mocks base method +func (m *Mockcmd) Output() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Output") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// Output indicates an expected call of Output +func (mr *MockcmdMockRecorder) Output() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Output", reflect.TypeOf((*Mockcmd)(nil).Output)) +} + +// Run mocks base method +func (m *Mockcmd) Run() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Run") + ret0, _ := ret[0].(error) + return ret0 +} + +// Run indicates an expected call of Run +func (mr *MockcmdMockRecorder) Run() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Run", reflect.TypeOf((*Mockcmd)(nil).Run)) +} + +// CombinedOutput mocks base method +func (m *Mockcmd) CombinedOutput() ([]byte, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "CombinedOutput") + ret0, _ := ret[0].([]byte) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// CombinedOutput indicates an expected call of CombinedOutput +func (mr *MockcmdMockRecorder) CombinedOutput() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CombinedOutput", reflect.TypeOf((*Mockcmd)(nil).CombinedOutput)) +} + +// Start mocks base method +func (m *Mockcmd) Start() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Start") + ret0, _ := ret[0].(error) + return ret0 +} + +// Start indicates an expected call of Start +func (mr *MockcmdMockRecorder) Start() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Start", reflect.TypeOf((*Mockcmd)(nil).Start)) +} + +// StderrPipe mocks base method +func (m *Mockcmd) StderrPipe() (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StderrPipe") + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StderrPipe indicates an expected call of StderrPipe +func (mr *MockcmdMockRecorder) StderrPipe() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StderrPipe", reflect.TypeOf((*Mockcmd)(nil).StderrPipe)) +} + +// StdinPipe mocks base method +func (m *Mockcmd) StdinPipe() (io.WriteCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StdinPipe") + ret0, _ := ret[0].(io.WriteCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StdinPipe indicates an expected call of StdinPipe +func (mr *MockcmdMockRecorder) StdinPipe() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StdinPipe", reflect.TypeOf((*Mockcmd)(nil).StdinPipe)) +} + +// StdoutPipe mocks base method +func (m *Mockcmd) StdoutPipe() (io.ReadCloser, error) { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "StdoutPipe") + ret0, _ := ret[0].(io.ReadCloser) + ret1, _ := ret[1].(error) + return ret0, ret1 +} + +// StdoutPipe indicates an expected call of StdoutPipe +func (mr *MockcmdMockRecorder) StdoutPipe() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StdoutPipe", reflect.TypeOf((*Mockcmd)(nil).StdoutPipe)) +} + +// String mocks base method +func (m *Mockcmd) String() string { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "String") + ret0, _ := ret[0].(string) + return ret0 +} + +// String indicates an expected call of String +func (mr *MockcmdMockRecorder) String() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "String", reflect.TypeOf((*Mockcmd)(nil).String)) +} + +// Wait mocks base method +func (m *Mockcmd) Wait() error { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Wait") + ret0, _ := ret[0].(error) + return ret0 +} + +// Wait indicates an expected call of Wait +func (mr *MockcmdMockRecorder) Wait() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Wait", reflect.TypeOf((*Mockcmd)(nil).Wait)) +} + +// Path mocks base method +func (m *Mockcmd) Path(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Path", arg0) +} + +// Path indicates an expected call of Path +func (mr *MockcmdMockRecorder) Path(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Path", reflect.TypeOf((*Mockcmd)(nil).Path), arg0) +} + +// Args mocks base method +func (m *Mockcmd) Args(arg0 []string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Args", arg0) +} + +// Args indicates an expected call of Args +func (mr *MockcmdMockRecorder) Args(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Args", reflect.TypeOf((*Mockcmd)(nil).Args), arg0) +} + +// Env mocks base method +func (m *Mockcmd) Env(arg0 []string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Env", arg0) +} + +// Env indicates an expected call of Env +func (mr *MockcmdMockRecorder) Env(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Env", reflect.TypeOf((*Mockcmd)(nil).Env), arg0) +} + +// Dir mocks base method +func (m *Mockcmd) Dir(arg0 string) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Dir", arg0) +} + +// Dir indicates an expected call of Dir +func (mr *MockcmdMockRecorder) Dir(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Dir", reflect.TypeOf((*Mockcmd)(nil).Dir), arg0) +} + +// Stdin mocks base method +func (m *Mockcmd) Stdin(arg0 io.Reader) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stdin", arg0) +} + +// Stdin indicates an expected call of Stdin +func (mr *MockcmdMockRecorder) Stdin(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stdin", reflect.TypeOf((*Mockcmd)(nil).Stdin), arg0) +} + +// Stdout mocks base method +func (m *Mockcmd) Stdout(arg0 io.Writer) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stdout", arg0) +} + +// Stdout indicates an expected call of Stdout +func (mr *MockcmdMockRecorder) Stdout(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stdout", reflect.TypeOf((*Mockcmd)(nil).Stdout), arg0) +} + +// Stderr mocks base method +func (m *Mockcmd) Stderr(arg0 io.Writer) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "Stderr", arg0) +} + +// Stderr indicates an expected call of Stderr +func (mr *MockcmdMockRecorder) Stderr(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Stderr", reflect.TypeOf((*Mockcmd)(nil).Stderr), arg0) +} + +// ExtraFiles mocks base method +func (m *Mockcmd) ExtraFiles(arg0 []*os.File) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "ExtraFiles", arg0) +} + +// ExtraFiles indicates an expected call of ExtraFiles +func (mr *MockcmdMockRecorder) ExtraFiles(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExtraFiles", reflect.TypeOf((*Mockcmd)(nil).ExtraFiles), arg0) +} + +// SysProcAttr mocks base method +func (m *Mockcmd) SysProcAttr(arg0 *syscall.SysProcAttr) { + m.ctrl.T.Helper() + m.ctrl.Call(m, "SysProcAttr", arg0) +} + +// SysProcAttr indicates an expected call of SysProcAttr +func (mr *MockcmdMockRecorder) SysProcAttr(arg0 interface{}) *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SysProcAttr", reflect.TypeOf((*Mockcmd)(nil).SysProcAttr), arg0) +} + +// Process mocks base method +func (m *Mockcmd) Process() *os.Process { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "Process") + ret0, _ := ret[0].(*os.Process) + return ret0 +} + +// Process indicates an expected call of Process +func (mr *MockcmdMockRecorder) Process() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Process", reflect.TypeOf((*Mockcmd)(nil).Process)) +} + +// ProcessState mocks base method +func (m *Mockcmd) ProcessState() *os.ProcessState { + m.ctrl.T.Helper() + ret := m.ctrl.Call(m, "ProcessState") + ret0, _ := ret[0].(*os.ProcessState) + return ret0 +} + +// ProcessState indicates an expected call of ProcessState +func (mr *MockcmdMockRecorder) ProcessState() *gomock.Call { + mr.mock.ctrl.T.Helper() + return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ProcessState", reflect.TypeOf((*Mockcmd)(nil).ProcessState)) +} diff --git a/internal/run/install.go b/internal/run/install.go new file mode 100644 index 0000000..2a2092a --- /dev/null +++ b/internal/run/install.go @@ -0,0 +1,19 @@ +package run + +import () + +const HELM_BIN = "/usr/bin/helm" + +func Install(args ...string) error { + cmd := &execCmd{} + cmd.Path(HELM_BIN) + + return install(cmd, args) +} + +func install(cmd cmd, args []string) error { + args = append([]string{"install"}, args...) + cmd.Args(args) + + return cmd.Run() +} diff --git a/internal/run/install_test.go b/internal/run/install_test.go new file mode 100644 index 0000000..64adc9c --- /dev/null +++ b/internal/run/install_test.go @@ -0,0 +1,20 @@ +package run + +import ( + "github.com/golang/mock/gomock" + "testing" +) + +func TestInstall(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cmd := NewMockcmd(ctrl) + cmd.EXPECT(). + Args(gomock.Eq([]string{"install", "arg1", "arg2"})) + cmd.EXPECT(). + Run(). + Times(1) + + install(cmd, []string{"arg1", "arg2"}) +}