1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160
|
//go:build !integration
// +build !integration
package command
import (
"context"
"errors"
"os"
"os/exec"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/mock"
"gitlab.com/gitlab-org/gitlab-runner/common"
"gitlab.com/gitlab-org/gitlab-runner/helpers/process"
)
func newCommand(
ctx context.Context,
t *testing.T,
executable string,
cmdOpts process.CommandOptions,
options Options,
) (*process.MockCommander, *process.MockKillWaiter, Command, func()) {
commanderMock := new(process.MockCommander)
processKillWaiterMock := new(process.MockKillWaiter)
oldNewCmd := newCommander
oldNewProcessKillWaiter := newProcessKillWaiter
cleanup := func() {
newCommander = oldNewCmd
newProcessKillWaiter = oldNewProcessKillWaiter
commanderMock.AssertExpectations(t)
processKillWaiterMock.AssertExpectations(t)
}
newCommander = func(string, []string, process.CommandOptions) process.Commander {
return commanderMock
}
newProcessKillWaiter = func(process.Logger, time.Duration, time.Duration) process.KillWaiter {
return processKillWaiterMock
}
c := New(ctx, executable, []string{}, cmdOpts, options)
return commanderMock, processKillWaiterMock, c, cleanup
}
func TestCommand_Run(t *testing.T) {
testErr := errors.New("test error")
tests := map[string]struct {
cmdStartErr error
cmdWaitErr error
getExitCode func(err *exec.ExitError) int
contextClosed bool
process *os.Process
expectedError string
expectedErrorType interface{}
expectedExitCode int
}{
"error on cmd start()": {
cmdStartErr: errors.New("test-error"),
expectedError: "failed to start command: test-error",
},
"command ends with a build failure": {
cmdWaitErr: &exec.ExitError{ProcessState: &os.ProcessState{}},
getExitCode: func(err *exec.ExitError) int { return BuildFailureExitCode },
expectedError: "exit status 0",
expectedErrorType: &common.BuildError{},
expectedExitCode: BuildFailureExitCode,
},
"command ends with a system failure": {
cmdWaitErr: &exec.ExitError{ProcessState: &os.ProcessState{}},
getExitCode: func(err *exec.ExitError) int { return SystemFailureExitCode },
expectedError: "exit status 0",
expectedErrorType: &exec.ExitError{},
},
"command ends with a unknown failure": {
cmdWaitErr: &exec.ExitError{ProcessState: &os.ProcessState{}},
getExitCode: func(err *exec.ExitError) int { return 255 },
expectedError: "unknown Custom executor executable exit code 255; " +
"executable execution terminated with: exit status 0",
expectedErrorType: &ErrUnknownFailure{},
},
"command times out": {
contextClosed: true,
process: &os.Process{Pid: 1234},
expectedError: testErr.Error(),
},
}
for testName, tt := range tests {
tt := tt
t.Run(testName, func(t *testing.T) {
ctx, ctxCancel := context.WithCancel(context.Background())
defer ctxCancel()
cmdOpts := process.CommandOptions{
Logger: new(process.MockLogger),
GracefulKillTimeout: 100 * time.Millisecond,
ForceKillTimeout: 100 * time.Millisecond,
}
commanderMock, processKillWaiterMock, c, cleanup := newCommand(ctx, t, "exec", cmdOpts, Options{})
defer cleanup()
commanderMock.On("Start").
Return(tt.cmdStartErr)
commanderMock.On("Wait").
Return(func() error {
<-time.After(500 * time.Millisecond)
return tt.cmdWaitErr
}).
Maybe()
if tt.getExitCode != nil {
oldGetExitCode := getExitCode
defer func() {
getExitCode = oldGetExitCode
}()
getExitCode = tt.getExitCode
}
if tt.contextClosed {
ctxCancel()
processKillWaiterMock.
On("KillAndWait", commanderMock, mock.Anything).
Return(testErr).
Once()
}
err := c.Run()
if tt.expectedError == "" {
assert.NoError(t, err)
return
}
assert.EqualError(t, err, tt.expectedError)
if tt.expectedErrorType != nil {
assert.IsType(t, tt.expectedErrorType, err)
}
if tt.expectedExitCode != 0 {
var buildError *common.BuildError
if errors.As(err, &buildError) {
assert.Equal(t, tt.expectedExitCode, buildError.ExitCode)
}
}
})
}
}
|