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  }