github.com/secure-build/gitlab-runner@v12.5.0+incompatible/executors/custom/command/command_test.go (about) 1 package command 2 3 import ( 4 "context" 5 "errors" 6 "os" 7 "os/exec" 8 "testing" 9 "time" 10 11 "github.com/sirupsen/logrus" 12 "github.com/stretchr/testify/assert" 13 14 "gitlab.com/gitlab-org/gitlab-runner/common" 15 "gitlab.com/gitlab-org/gitlab-runner/executors/custom/process" 16 ) 17 18 func newCommand(ctx context.Context, t *testing.T, executable string, options CreateOptions) (*mockCommander, *process.MockKiller, Command, func()) { 19 commanderMock := new(mockCommander) 20 processKillerMock := new(process.MockKiller) 21 22 oldNewCmd := newCmd 23 oldNewProcessKiller := newProcessKiller 24 25 cleanup := func() { 26 newCmd = oldNewCmd 27 newProcessKiller = oldNewProcessKiller 28 29 commanderMock.AssertExpectations(t) 30 processKillerMock.AssertExpectations(t) 31 } 32 33 newCmd = func(executable string, args []string, options CreateOptions) commander { 34 return commanderMock 35 } 36 37 newProcessKiller = func(logger common.BuildLogger, process *os.Process) process.Killer { 38 return processKillerMock 39 } 40 41 c := New(ctx, executable, []string{}, options) 42 43 return commanderMock, processKillerMock, c, cleanup 44 } 45 46 func TestCommand_Run(t *testing.T) { 47 tests := map[string]struct { 48 cmdStartErr error 49 cmdWaitErr error 50 getExitStatus func(err *exec.ExitError) int 51 contextClosed bool 52 process *os.Process 53 expectedError string 54 expectedErrorType interface{} 55 }{ 56 "error on cmd start()": { 57 cmdStartErr: errors.New("test-error"), 58 expectedError: "failed to start command: test-error", 59 }, 60 "command ends with a build failure": { 61 cmdWaitErr: &exec.ExitError{ProcessState: &os.ProcessState{}}, 62 getExitStatus: func(err *exec.ExitError) int { return BuildFailureExitCode }, 63 expectedError: "exit status 0", 64 expectedErrorType: &common.BuildError{}, 65 }, 66 "command ends with a system failure": { 67 cmdWaitErr: &exec.ExitError{ProcessState: &os.ProcessState{}}, 68 getExitStatus: func(err *exec.ExitError) int { return SystemFailureExitCode }, 69 expectedError: "exit status 0", 70 expectedErrorType: &exec.ExitError{}, 71 }, 72 "command ends with a unknown failure": { 73 cmdWaitErr: &exec.ExitError{ProcessState: &os.ProcessState{}}, 74 getExitStatus: func(err *exec.ExitError) int { return 255 }, 75 expectedError: "unknown Custom executor executable exit code 255; executable execution terminated with: exit status 0", 76 expectedErrorType: &ErrUnknownFailure{}, 77 }, 78 "command times out - nil process": { 79 contextClosed: true, 80 expectedError: "process not started yet", 81 }, 82 "command times out": { 83 contextClosed: true, 84 process: &os.Process{Pid: 1234}, 85 expectedError: "failed to kill process, likely process is dormant", 86 }, 87 } 88 89 for testName, tt := range tests { 90 t.Run(testName, func(t *testing.T) { 91 ctx, ctxCancel := context.WithCancel(context.Background()) 92 defer ctxCancel() 93 94 options := CreateOptions{ 95 Logger: common.NewBuildLogger(nil, logrus.NewEntry(logrus.New())), 96 GracefulKillTimeout: 100 * time.Millisecond, 97 ForceKillTimeout: 100 * time.Millisecond, 98 } 99 100 commanderMock, processKillerMock, c, cleanup := newCommand(ctx, t, "exec", options) 101 defer cleanup() 102 103 commanderMock.On("Start"). 104 Return(tt.cmdStartErr) 105 commanderMock.On("Wait"). 106 Return(func() error { 107 select { 108 case <-time.After(500 * time.Millisecond): 109 return tt.cmdWaitErr 110 } 111 }). 112 Maybe() 113 114 if tt.getExitStatus != nil { 115 oldGetExitStatus := getExitStatus 116 defer func() { 117 getExitStatus = oldGetExitStatus 118 }() 119 getExitStatus = tt.getExitStatus 120 } 121 122 if tt.contextClosed { 123 ctxCancel() 124 commanderMock.On("Process"). 125 Return(tt.process) 126 127 if tt.process != nil { 128 processKillerMock.On("Terminate") 129 processKillerMock.On("ForceKill") 130 } 131 } 132 133 err := c.Run() 134 135 if tt.expectedError == "" { 136 assert.NoError(t, err) 137 138 return 139 } 140 141 assert.EqualError(t, err, tt.expectedError) 142 if tt.expectedErrorType != nil { 143 assert.IsType(t, tt.expectedErrorType, err) 144 } 145 }) 146 } 147 }