gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+incompatible/common/build_test.go (about)

     1  package common
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"net/http"
     8  	"net/http/httptest"
     9  	"net/url"
    10  	"os"
    11  	"path/filepath"
    12  	"testing"
    13  	"time"
    14  
    15  	"github.com/gorilla/websocket"
    16  	"github.com/sirupsen/logrus"
    17  	"github.com/sirupsen/logrus/hooks/test"
    18  	"github.com/stretchr/testify/assert"
    19  	"github.com/stretchr/testify/mock"
    20  	"github.com/stretchr/testify/require"
    21  
    22  	"gitlab.com/gitlab-org/gitlab-runner/helpers/docker"
    23  	"gitlab.com/gitlab-org/gitlab-runner/session"
    24  	"gitlab.com/gitlab-org/gitlab-runner/session/terminal"
    25  )
    26  
    27  func init() {
    28  	s := MockShell{}
    29  	s.On("GetName").Return("script-shell")
    30  	s.On("GenerateScript", mock.Anything, mock.Anything).Return("script", nil)
    31  	RegisterShell(&s)
    32  }
    33  
    34  func TestBuildRun(t *testing.T) {
    35  	e := MockExecutor{}
    36  	defer e.AssertExpectations(t)
    37  
    38  	p := MockExecutorProvider{}
    39  	defer p.AssertExpectations(t)
    40  
    41  	// Create executor only once
    42  	p.On("CanCreate").Return(true).Once()
    43  	p.On("GetDefaultShell").Return("bash").Once()
    44  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
    45  
    46  	p.On("Create").Return(&e).Once()
    47  
    48  	// We run everything once
    49  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
    50  	e.On("Finish", nil).Return().Once()
    51  	e.On("Cleanup").Return().Once()
    52  
    53  	// Run script successfully
    54  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
    55  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
    56  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
    57  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(nil).Once()
    58  	e.On("Run", matchBuildStage(BuildStageDownloadArtifacts)).Return(nil).Once()
    59  	e.On("Run", matchBuildStage(BuildStageUserScript)).Return(nil).Once()
    60  	e.On("Run", matchBuildStage(BuildStageAfterScript)).Return(nil).Once()
    61  	e.On("Run", matchBuildStage(BuildStageArchiveCache)).Return(nil).Once()
    62  	e.On("Run", matchBuildStage(BuildStageUploadOnSuccessArtifacts)).Return(nil).Once()
    63  
    64  	RegisterExecutor("build-run-test", &p)
    65  
    66  	successfulBuild, err := GetSuccessfulBuild()
    67  	assert.NoError(t, err)
    68  	build := &Build{
    69  		JobResponse: successfulBuild,
    70  		Runner: &RunnerConfig{
    71  			RunnerSettings: RunnerSettings{
    72  				Executor: "build-run-test",
    73  			},
    74  		},
    75  	}
    76  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
    77  	assert.NoError(t, err)
    78  }
    79  
    80  func TestBuildPredefinedVariables(t *testing.T) {
    81  	e := MockExecutor{}
    82  	defer e.AssertExpectations(t)
    83  
    84  	p := MockExecutorProvider{}
    85  	defer p.AssertExpectations(t)
    86  
    87  	// Create executor only once
    88  	p.On("CanCreate").Return(true).Once()
    89  	p.On("GetDefaultShell").Return("bash").Once()
    90  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
    91  
    92  	p.On("Create").Return(&e).Once()
    93  
    94  	// We run everything once
    95  	e.On("Prepare", mock.Anything).
    96  		Return(func(options ExecutorPrepareOptions) error {
    97  			options.Build.StartBuild("/root/dir", "/cache/dir", false, false)
    98  			return nil
    99  		}).Once()
   100  	e.On("Finish", nil).Return().Once()
   101  	e.On("Cleanup").Return().Once()
   102  
   103  	// Run script successfully
   104  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   105  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   106  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
   107  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(nil).Once()
   108  	e.On("Run", matchBuildStage(BuildStageDownloadArtifacts)).Return(nil).Once()
   109  	e.On("Run", matchBuildStage(BuildStageUserScript)).Return(nil).Once()
   110  	e.On("Run", matchBuildStage(BuildStageAfterScript)).Return(nil).Once()
   111  	e.On("Run", matchBuildStage(BuildStageArchiveCache)).Return(nil).Once()
   112  	e.On("Run", matchBuildStage(BuildStageUploadOnSuccessArtifacts)).Return(nil).Once()
   113  
   114  	RegisterExecutor(t.Name(), &p)
   115  
   116  	successfulBuild, err := GetSuccessfulBuild()
   117  	assert.NoError(t, err)
   118  	build := &Build{
   119  		JobResponse: successfulBuild,
   120  		Runner: &RunnerConfig{
   121  			RunnerSettings: RunnerSettings{
   122  				Executor: t.Name(),
   123  			},
   124  		},
   125  	}
   126  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   127  	assert.NoError(t, err)
   128  
   129  	projectDir := build.GetAllVariables().Get("CI_PROJECT_DIR")
   130  	assert.NotEmpty(t, projectDir, "should have CI_PROJECT_DIR")
   131  }
   132  
   133  func TestBuildRunNoModifyConfig(t *testing.T) {
   134  	e := MockExecutor{}
   135  	defer e.AssertExpectations(t)
   136  
   137  	p := MockExecutorProvider{}
   138  	defer p.AssertExpectations(t)
   139  
   140  	// Create executor only once
   141  	p.On("CanCreate").Return(true).Once()
   142  	p.On("GetDefaultShell").Return("bash").Once()
   143  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   144  	p.On("Create").Return(&e).Once()
   145  
   146  	// Attempt to modify the Config object
   147  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   148  		Return(func(options ExecutorPrepareOptions) error {
   149  			options.Config.Docker.DockerCredentials.Host = "10.0.0.2"
   150  			return nil
   151  		}).Once()
   152  
   153  	// We run everything else once
   154  	e.On("Finish", nil).Return().Once()
   155  	e.On("Cleanup").Return().Once()
   156  
   157  	// Run script successfully
   158  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   159  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   160  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
   161  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(nil).Once()
   162  	e.On("Run", matchBuildStage(BuildStageDownloadArtifacts)).Return(nil).Once()
   163  	e.On("Run", matchBuildStage(BuildStageUserScript)).Return(nil).Once()
   164  	e.On("Run", matchBuildStage(BuildStageAfterScript)).Return(nil).Once()
   165  	e.On("Run", matchBuildStage(BuildStageArchiveCache)).Return(nil).Once()
   166  	e.On("Run", matchBuildStage(BuildStageUploadOnSuccessArtifacts)).Return(nil).Once()
   167  
   168  	RegisterExecutor("build-run-nomodify-test", &p)
   169  
   170  	successfulBuild, err := GetSuccessfulBuild()
   171  	assert.NoError(t, err)
   172  	rc := &RunnerConfig{
   173  		RunnerSettings: RunnerSettings{
   174  			Executor: "build-run-nomodify-test",
   175  			Docker: &DockerConfig{
   176  				DockerCredentials: docker_helpers.DockerCredentials{
   177  					Host: "10.0.0.1",
   178  				},
   179  			},
   180  		},
   181  	}
   182  	build, err := NewBuild(successfulBuild, rc, nil, nil)
   183  	assert.NoError(t, err)
   184  
   185  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   186  	assert.NoError(t, err)
   187  	assert.Equal(t, "10.0.0.1", rc.Docker.DockerCredentials.Host)
   188  }
   189  
   190  func TestRetryPrepare(t *testing.T) {
   191  	PreparationRetryInterval = 0
   192  
   193  	e := MockExecutor{}
   194  	defer e.AssertExpectations(t)
   195  
   196  	p := MockExecutorProvider{}
   197  	defer p.AssertExpectations(t)
   198  
   199  	// Create executor
   200  	p.On("CanCreate").Return(true).Once()
   201  	p.On("GetDefaultShell").Return("bash").Once()
   202  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   203  
   204  	p.On("Create").Return(&e).Times(3)
   205  
   206  	// Prepare plan
   207  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   208  		Return(errors.New("prepare failed")).Twice()
   209  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   210  		Return(nil).Once()
   211  	e.On("Cleanup").Return().Times(3)
   212  
   213  	// Succeed a build script
   214  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   215  	e.On("Run", mock.Anything).Return(nil)
   216  	e.On("Finish", nil).Return().Once()
   217  
   218  	RegisterExecutor("build-run-retry-prepare", &p)
   219  
   220  	successfulBuild, err := GetSuccessfulBuild()
   221  	assert.NoError(t, err)
   222  	build := &Build{
   223  		JobResponse: successfulBuild,
   224  		Runner: &RunnerConfig{
   225  			RunnerSettings: RunnerSettings{
   226  				Executor: "build-run-retry-prepare",
   227  			},
   228  		},
   229  	}
   230  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   231  	assert.NoError(t, err)
   232  }
   233  
   234  func TestPrepareFailure(t *testing.T) {
   235  	PreparationRetryInterval = 0
   236  
   237  	e := MockExecutor{}
   238  	defer e.AssertExpectations(t)
   239  
   240  	p := MockExecutorProvider{}
   241  	defer p.AssertExpectations(t)
   242  
   243  	// Create executor
   244  	p.On("CanCreate").Return(true).Once()
   245  	p.On("GetDefaultShell").Return("bash").Once()
   246  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   247  
   248  	p.On("Create").Return(&e).Times(3)
   249  
   250  	// Prepare plan
   251  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   252  		Return(errors.New("prepare failed")).Times(3)
   253  	e.On("Cleanup").Return().Times(3)
   254  
   255  	RegisterExecutor("build-run-prepare-failure", &p)
   256  
   257  	successfulBuild, err := GetSuccessfulBuild()
   258  	assert.NoError(t, err)
   259  	build := &Build{
   260  		JobResponse: successfulBuild,
   261  		Runner: &RunnerConfig{
   262  			RunnerSettings: RunnerSettings{
   263  				Executor: "build-run-prepare-failure",
   264  			},
   265  		},
   266  	}
   267  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   268  	assert.EqualError(t, err, "prepare failed")
   269  }
   270  
   271  func TestPrepareFailureOnBuildError(t *testing.T) {
   272  	e := MockExecutor{}
   273  	defer e.AssertExpectations(t)
   274  
   275  	p := MockExecutorProvider{}
   276  	defer p.AssertExpectations(t)
   277  
   278  	// Create executor
   279  	p.On("CanCreate").Return(true).Once()
   280  	p.On("GetDefaultShell").Return("bash").Once()
   281  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   282  
   283  	p.On("Create").Return(&e).Times(1)
   284  
   285  	// Prepare plan
   286  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   287  		Return(&BuildError{}).Times(1)
   288  	e.On("Cleanup").Return().Times(1)
   289  
   290  	RegisterExecutor("build-run-prepare-failure-on-build-error", &p)
   291  
   292  	successfulBuild, err := GetSuccessfulBuild()
   293  	assert.NoError(t, err)
   294  	build := &Build{
   295  		JobResponse: successfulBuild,
   296  		Runner: &RunnerConfig{
   297  			RunnerSettings: RunnerSettings{
   298  				Executor: "build-run-prepare-failure-on-build-error",
   299  			},
   300  		},
   301  	}
   302  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   303  	assert.IsType(t, err, &BuildError{})
   304  }
   305  
   306  func TestJobFailure(t *testing.T) {
   307  	e := new(MockExecutor)
   308  	defer e.AssertExpectations(t)
   309  
   310  	p := new(MockExecutorProvider)
   311  	defer p.AssertExpectations(t)
   312  
   313  	// Create executor
   314  	p.On("CanCreate").Return(true).Once()
   315  	p.On("GetDefaultShell").Return("bash").Once()
   316  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   317  
   318  	p.On("Create").Return(e).Times(1)
   319  
   320  	// Prepare plan
   321  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   322  		Return(nil).Times(1)
   323  	e.On("Cleanup").Return().Times(1)
   324  
   325  	// Succeed a build script
   326  	thrownErr := &BuildError{Inner: errors.New("test error")}
   327  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   328  	e.On("Run", mock.Anything).Return(thrownErr)
   329  	e.On("Finish", thrownErr).Return().Once()
   330  
   331  	RegisterExecutor("build-run-job-failure", p)
   332  
   333  	failedBuild, err := GetFailedBuild()
   334  	assert.NoError(t, err)
   335  	build := &Build{
   336  		JobResponse: failedBuild,
   337  		Runner: &RunnerConfig{
   338  			RunnerSettings: RunnerSettings{
   339  				Executor: "build-run-job-failure",
   340  			},
   341  		},
   342  	}
   343  
   344  	trace := new(MockJobTrace)
   345  	defer trace.AssertExpectations(t)
   346  	trace.On("Write", mock.Anything).Return(0, nil)
   347  	trace.On("IsStdout").Return(true)
   348  	trace.On("SetCancelFunc", mock.Anything).Once()
   349  	trace.On("SetMasked", mock.Anything).Once()
   350  	trace.On("Fail", thrownErr, ScriptFailure).Once()
   351  
   352  	err = build.Run(&Config{}, trace)
   353  	require.IsType(t, &BuildError{}, err)
   354  }
   355  
   356  func TestJobFailureOnExecutionTimeout(t *testing.T) {
   357  	e := new(MockExecutor)
   358  	defer e.AssertExpectations(t)
   359  
   360  	p := new(MockExecutorProvider)
   361  	defer p.AssertExpectations(t)
   362  
   363  	// Create executor
   364  	p.On("CanCreate").Return(true).Once()
   365  	p.On("GetDefaultShell").Return("bash").Once()
   366  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   367  
   368  	p.On("Create").Return(e).Times(1)
   369  
   370  	// Prepare plan
   371  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).
   372  		Return(nil).Times(1)
   373  	e.On("Cleanup").Return().Times(1)
   374  
   375  	// Succeed a build script
   376  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   377  	e.On("Run", matchBuildStage(BuildStageUserScript)).Run(func(arguments mock.Arguments) {
   378  		time.Sleep(2 * time.Second)
   379  	}).Return(nil)
   380  	e.On("Run", mock.Anything).Return(nil)
   381  	e.On("Finish", mock.Anything).Return().Once()
   382  
   383  	RegisterExecutor("build-run-job-failure-on-execution-timeout", p)
   384  
   385  	successfulBuild, err := GetSuccessfulBuild()
   386  	assert.NoError(t, err)
   387  
   388  	successfulBuild.RunnerInfo.Timeout = 1
   389  
   390  	build := &Build{
   391  		JobResponse: successfulBuild,
   392  		Runner: &RunnerConfig{
   393  			RunnerSettings: RunnerSettings{
   394  				Executor: "build-run-job-failure-on-execution-timeout",
   395  			},
   396  		},
   397  	}
   398  
   399  	trace := new(MockJobTrace)
   400  	defer trace.AssertExpectations(t)
   401  	trace.On("Write", mock.Anything).Return(0, nil)
   402  	trace.On("IsStdout").Return(true)
   403  	trace.On("SetCancelFunc", mock.Anything).Once()
   404  	trace.On("SetMasked", mock.Anything).Once()
   405  	trace.On("Fail", mock.Anything, JobExecutionTimeout).Run(func(arguments mock.Arguments) {
   406  		assert.Error(t, arguments.Get(0).(error))
   407  	}).Once()
   408  
   409  	err = build.Run(&Config{}, trace)
   410  	require.IsType(t, &BuildError{}, err)
   411  }
   412  
   413  func matchBuildStage(buildStage BuildStage) interface{} {
   414  	return mock.MatchedBy(func(cmd ExecutorCommand) bool {
   415  		return cmd.Stage == buildStage
   416  	})
   417  }
   418  
   419  func TestRunFailureRunsAfterScriptAndArtifactsOnFailure(t *testing.T) {
   420  	e := MockExecutor{}
   421  	defer e.AssertExpectations(t)
   422  
   423  	p := MockExecutorProvider{}
   424  	defer p.AssertExpectations(t)
   425  
   426  	// Create executor
   427  	p.On("CanCreate").Return(true).Once()
   428  	p.On("GetDefaultShell").Return("bash").Once()
   429  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   430  
   431  	p.On("Create").Return(&e).Once()
   432  
   433  	// Prepare plan
   434  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   435  	e.On("Cleanup").Return().Once()
   436  
   437  	// Fail a build script
   438  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   439  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   440  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
   441  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(nil).Once()
   442  	e.On("Run", matchBuildStage(BuildStageDownloadArtifacts)).Return(nil).Once()
   443  	e.On("Run", matchBuildStage(BuildStageUserScript)).Return(errors.New("build fail")).Once()
   444  	e.On("Run", matchBuildStage(BuildStageAfterScript)).Return(nil).Once()
   445  	e.On("Run", matchBuildStage(BuildStageUploadOnFailureArtifacts)).Return(nil).Once()
   446  	e.On("Finish", errors.New("build fail")).Return().Once()
   447  
   448  	RegisterExecutor("build-run-run-failure", &p)
   449  
   450  	failedBuild, err := GetFailedBuild()
   451  	assert.NoError(t, err)
   452  	build := &Build{
   453  		JobResponse: failedBuild,
   454  		Runner: &RunnerConfig{
   455  			RunnerSettings: RunnerSettings{
   456  				Executor: "build-run-run-failure",
   457  			},
   458  		},
   459  	}
   460  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   461  	assert.EqualError(t, err, "build fail")
   462  }
   463  
   464  func TestGetSourcesRunFailure(t *testing.T) {
   465  	e := MockExecutor{}
   466  	defer e.AssertExpectations(t)
   467  
   468  	p := MockExecutorProvider{}
   469  	defer p.AssertExpectations(t)
   470  
   471  	// Create executor
   472  	p.On("CanCreate").Return(true).Once()
   473  	p.On("GetDefaultShell").Return("bash").Once()
   474  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   475  
   476  	p.On("Create").Return(&e).Once()
   477  
   478  	// Prepare plan
   479  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   480  	e.On("Cleanup").Return()
   481  
   482  	// Fail a build script
   483  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   484  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   485  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(errors.New("build fail")).Times(3)
   486  	e.On("Run", matchBuildStage(BuildStageUploadOnFailureArtifacts)).Return(nil).Once()
   487  	e.On("Finish", errors.New("build fail")).Return().Once()
   488  
   489  	RegisterExecutor("build-get-sources-run-failure", &p)
   490  
   491  	successfulBuild, err := GetSuccessfulBuild()
   492  	assert.NoError(t, err)
   493  	build := &Build{
   494  		JobResponse: successfulBuild,
   495  		Runner: &RunnerConfig{
   496  			RunnerSettings: RunnerSettings{
   497  				Executor: "build-get-sources-run-failure",
   498  			},
   499  		},
   500  	}
   501  
   502  	build.Variables = append(build.Variables, JobVariable{Key: "GET_SOURCES_ATTEMPTS", Value: "3"})
   503  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   504  	assert.EqualError(t, err, "build fail")
   505  }
   506  
   507  func TestArtifactDownloadRunFailure(t *testing.T) {
   508  	e := MockExecutor{}
   509  	defer e.AssertExpectations(t)
   510  
   511  	p := MockExecutorProvider{}
   512  	defer p.AssertExpectations(t)
   513  
   514  	// Create executor
   515  	p.On("CanCreate").Return(true).Once()
   516  	p.On("GetDefaultShell").Return("bash").Once()
   517  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   518  
   519  	p.On("Create").Return(&e).Once()
   520  
   521  	// Prepare plan
   522  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   523  	e.On("Cleanup").Return()
   524  
   525  	// Fail a build script
   526  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   527  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   528  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
   529  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(nil).Once()
   530  	e.On("Run", matchBuildStage(BuildStageDownloadArtifacts)).Return(errors.New("build fail")).Times(3)
   531  	e.On("Run", matchBuildStage(BuildStageUploadOnFailureArtifacts)).Return(nil).Once()
   532  	e.On("Finish", errors.New("build fail")).Return().Once()
   533  
   534  	RegisterExecutor("build-artifacts-run-failure", &p)
   535  
   536  	successfulBuild, err := GetSuccessfulBuild()
   537  	assert.NoError(t, err)
   538  	build := &Build{
   539  		JobResponse: successfulBuild,
   540  		Runner: &RunnerConfig{
   541  			RunnerSettings: RunnerSettings{
   542  				Executor: "build-artifacts-run-failure",
   543  			},
   544  		},
   545  	}
   546  
   547  	build.Variables = append(build.Variables, JobVariable{Key: "ARTIFACT_DOWNLOAD_ATTEMPTS", Value: "3"})
   548  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   549  	assert.EqualError(t, err, "build fail")
   550  }
   551  
   552  func TestArtifactUploadRunFailure(t *testing.T) {
   553  	e := MockExecutor{}
   554  	defer e.AssertExpectations(t)
   555  
   556  	p := MockExecutorProvider{}
   557  	defer p.AssertExpectations(t)
   558  
   559  	// Create executor
   560  	p.On("CanCreate").Return(true).Once()
   561  	p.On("GetDefaultShell").Return("bash").Once()
   562  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   563  
   564  	p.On("Create").Return(&e).Once()
   565  
   566  	// Prepare plan
   567  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   568  	e.On("Cleanup").Return()
   569  
   570  	// Successful build script
   571  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"}).Times(8)
   572  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   573  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
   574  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(nil).Once()
   575  	e.On("Run", matchBuildStage(BuildStageDownloadArtifacts)).Return(nil).Once()
   576  	e.On("Run", matchBuildStage(BuildStageUserScript)).Return(nil).Once()
   577  	e.On("Run", matchBuildStage(BuildStageAfterScript)).Return(nil).Once()
   578  	e.On("Run", matchBuildStage(BuildStageArchiveCache)).Return(nil).Once()
   579  	e.On("Run", matchBuildStage(BuildStageUploadOnSuccessArtifacts)).Return(errors.New("upload fail")).Once()
   580  	e.On("Finish", errors.New("upload fail")).Return().Once()
   581  
   582  	RegisterExecutor("build-upload-artifacts-run-failure", &p)
   583  
   584  	successfulBuild, err := GetSuccessfulBuild()
   585  	successfulBuild.Artifacts = make(Artifacts, 1)
   586  	successfulBuild.Artifacts[0] = Artifact{
   587  		Name:      "my-artifact",
   588  		Untracked: false,
   589  		Paths:     ArtifactPaths{"cached/*"},
   590  		When:      ArtifactWhenAlways,
   591  	}
   592  	assert.NoError(t, err)
   593  	build := &Build{
   594  		JobResponse: successfulBuild,
   595  		Runner: &RunnerConfig{
   596  			RunnerSettings: RunnerSettings{
   597  				Executor: "build-upload-artifacts-run-failure",
   598  			},
   599  		},
   600  	}
   601  
   602  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   603  	assert.EqualError(t, err, "upload fail")
   604  }
   605  
   606  func TestRestoreCacheRunFailure(t *testing.T) {
   607  	e := MockExecutor{}
   608  	defer e.AssertExpectations(t)
   609  
   610  	p := MockExecutorProvider{}
   611  	defer p.AssertExpectations(t)
   612  
   613  	// Create executor
   614  	p.On("CanCreate").Return(true).Once()
   615  	p.On("GetDefaultShell").Return("bash").Once()
   616  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   617  
   618  	p.On("Create").Return(&e).Once()
   619  
   620  	// Prepare plan
   621  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   622  	e.On("Cleanup").Return()
   623  
   624  	// Fail a build script
   625  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   626  	e.On("Run", matchBuildStage(BuildStagePrepare)).Return(nil).Once()
   627  	e.On("Run", matchBuildStage(BuildStageGetSources)).Return(nil).Once()
   628  	e.On("Run", matchBuildStage(BuildStageRestoreCache)).Return(errors.New("build fail")).Times(3)
   629  	e.On("Run", matchBuildStage(BuildStageUploadOnFailureArtifacts)).Return(nil).Once()
   630  	e.On("Finish", errors.New("build fail")).Return().Once()
   631  
   632  	RegisterExecutor("build-cache-run-failure", &p)
   633  
   634  	successfulBuild, err := GetSuccessfulBuild()
   635  	assert.NoError(t, err)
   636  	build := &Build{
   637  		JobResponse: successfulBuild,
   638  		Runner: &RunnerConfig{
   639  			RunnerSettings: RunnerSettings{
   640  				Executor: "build-cache-run-failure",
   641  			},
   642  		},
   643  	}
   644  
   645  	build.Variables = append(build.Variables, JobVariable{Key: "RESTORE_CACHE_ATTEMPTS", Value: "3"})
   646  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   647  	assert.EqualError(t, err, "build fail")
   648  }
   649  
   650  func TestRunWrongAttempts(t *testing.T) {
   651  	e := MockExecutor{}
   652  
   653  	p := MockExecutorProvider{}
   654  	defer p.AssertExpectations(t)
   655  
   656  	// Create executor
   657  	p.On("CanCreate").Return(true).Once()
   658  	p.On("GetDefaultShell").Return("bash").Once()
   659  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   660  
   661  	p.On("Create").Return(&e)
   662  
   663  	// Prepare plan
   664  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil)
   665  	e.On("Cleanup").Return()
   666  
   667  	// Fail a build script
   668  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   669  	e.On("Run", mock.Anything).Return(nil).Once()
   670  	e.On("Run", mock.Anything).Return(errors.New("Number of attempts out of the range [1, 10] for stage: get_sources"))
   671  	e.On("Finish", errors.New("Number of attempts out of the range [1, 10] for stage: get_sources")).Return()
   672  
   673  	RegisterExecutor("build-run-attempt-failure", &p)
   674  
   675  	successfulBuild, err := GetSuccessfulBuild()
   676  	assert.NoError(t, err)
   677  	build := &Build{
   678  		JobResponse: successfulBuild,
   679  		Runner: &RunnerConfig{
   680  			RunnerSettings: RunnerSettings{
   681  				Executor: "build-run-attempt-failure",
   682  			},
   683  		},
   684  	}
   685  
   686  	build.Variables = append(build.Variables, JobVariable{Key: "GET_SOURCES_ATTEMPTS", Value: "0"})
   687  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   688  	assert.EqualError(t, err, "Number of attempts out of the range [1, 10] for stage: get_sources")
   689  }
   690  
   691  func TestRunSuccessOnSecondAttempt(t *testing.T) {
   692  	e := MockExecutor{}
   693  	p := MockExecutorProvider{}
   694  
   695  	// Create executor only once
   696  	p.On("CanCreate").Return(true).Once()
   697  	p.On("GetDefaultShell").Return("bash").Once()
   698  	p.On("GetFeatures", mock.Anything).Return(nil).Twice()
   699  
   700  	p.On("Create").Return(&e).Once()
   701  
   702  	// We run everything once
   703  	e.On("Prepare", mock.Anything, mock.Anything, mock.Anything).Return(nil).Once()
   704  	e.On("Finish", mock.Anything).Return().Twice()
   705  	e.On("Cleanup").Return().Twice()
   706  
   707  	// Run script successfully
   708  	e.On("Shell").Return(&ShellScriptInfo{Shell: "script-shell"})
   709  
   710  	e.On("Run", mock.Anything).Return(nil)
   711  	e.On("Run", mock.Anything).Return(errors.New("build fail")).Once()
   712  	e.On("Run", mock.Anything).Return(nil)
   713  
   714  	RegisterExecutor("build-run-success-second-attempt", &p)
   715  
   716  	successfulBuild, err := GetSuccessfulBuild()
   717  	assert.NoError(t, err)
   718  	build := &Build{
   719  		JobResponse: successfulBuild,
   720  		Runner: &RunnerConfig{
   721  			RunnerSettings: RunnerSettings{
   722  				Executor: "build-run-success-second-attempt",
   723  			},
   724  		},
   725  	}
   726  
   727  	build.Variables = append(build.Variables, JobVariable{Key: "GET_SOURCES_ATTEMPTS", Value: "3"})
   728  	err = build.Run(&Config{}, &Trace{Writer: os.Stdout})
   729  	assert.NoError(t, err)
   730  }
   731  
   732  func TestDebugTrace(t *testing.T) {
   733  	testCases := map[string]struct {
   734  		debugTraceVariableValue   string
   735  		expectedValue             bool
   736  		debugTraceFeatureDisabled bool
   737  		expectedLogOutput         string
   738  	}{
   739  		"variable not set": {
   740  			expectedValue: false,
   741  		},
   742  		"variable set to false": {
   743  			debugTraceVariableValue: "false",
   744  			expectedValue:           false,
   745  		},
   746  		"variable set to true": {
   747  			debugTraceVariableValue: "true",
   748  			expectedValue:           true,
   749  		},
   750  		"variable set to a non-bool value": {
   751  			debugTraceVariableValue: "xyz",
   752  			expectedValue:           false,
   753  		},
   754  		"variable set to true and feature disabled from configuration": {
   755  			debugTraceVariableValue:   "true",
   756  			expectedValue:             false,
   757  			debugTraceFeatureDisabled: true,
   758  			expectedLogOutput:         "CI_DEBUG_TRACE usage is disabled on this Runner",
   759  		},
   760  	}
   761  
   762  	for testName, testCase := range testCases {
   763  		t.Run(testName, func(t *testing.T) {
   764  			logger, hooks := test.NewNullLogger()
   765  
   766  			build := &Build{
   767  				logger: NewBuildLogger(nil, logrus.NewEntry(logger)),
   768  				JobResponse: JobResponse{
   769  					Variables: JobVariables{},
   770  				},
   771  				Runner: &RunnerConfig{
   772  					RunnerSettings: RunnerSettings{
   773  						DebugTraceDisabled: testCase.debugTraceFeatureDisabled,
   774  					},
   775  				},
   776  			}
   777  
   778  			if testCase.debugTraceVariableValue != "" {
   779  				build.Variables = append(build.Variables, JobVariable{Key: "CI_DEBUG_TRACE", Value: testCase.debugTraceVariableValue, Public: true})
   780  			}
   781  
   782  			isTraceEnabled := build.IsDebugTraceEnabled()
   783  			assert.Equal(t, testCase.expectedValue, isTraceEnabled)
   784  
   785  			if testCase.expectedLogOutput != "" {
   786  				output, err := hooks.LastEntry().String()
   787  				require.NoError(t, err)
   788  				assert.Contains(t, output, testCase.expectedLogOutput)
   789  			}
   790  		})
   791  	}
   792  }
   793  
   794  func TestDefaultEnvVariables(t *testing.T) {
   795  	buildDir := "/tmp/test-build/dir"
   796  	build := Build{
   797  		BuildDir: buildDir,
   798  	}
   799  
   800  	vars := build.GetAllVariables().StringList()
   801  
   802  	assert.Contains(t, vars, "CI_PROJECT_DIR="+filepath.FromSlash(buildDir))
   803  	assert.Contains(t, vars, "CI_SERVER=yes")
   804  }
   805  
   806  func TestSharedEnvVariables(t *testing.T) {
   807  	for _, shared := range [...]bool{true, false} {
   808  		t.Run(fmt.Sprintf("Value:%v", shared), func(t *testing.T) {
   809  			assert := assert.New(t)
   810  			build := Build{
   811  				ExecutorFeatures: FeaturesInfo{Shared: shared},
   812  			}
   813  			vars := build.GetAllVariables().StringList()
   814  
   815  			assert.NotNil(vars)
   816  
   817  			present := "CI_SHARED_ENVIRONMENT=true"
   818  			absent := "CI_DISPOSABLE_ENVIRONMENT=true"
   819  			if !shared {
   820  				tmp := present
   821  				present = absent
   822  				absent = tmp
   823  			}
   824  
   825  			assert.Contains(vars, present)
   826  			assert.NotContains(vars, absent)
   827  			// we never expose false
   828  			assert.NotContains(vars, "CI_SHARED_ENVIRONMENT=false")
   829  			assert.NotContains(vars, "CI_DISPOSABLE_ENVIRONMENT=false")
   830  		})
   831  	}
   832  }
   833  
   834  func TestGetRemoteURL(t *testing.T) {
   835  	testCases := []struct {
   836  		runner RunnerSettings
   837  		result string
   838  	}{
   839  		{
   840  			runner: RunnerSettings{
   841  				CloneURL: "http://test.local/",
   842  			},
   843  			result: "http://gitlab-ci-token:1234567@test.local/h5bp/html5-boilerplate.git",
   844  		},
   845  		{
   846  			runner: RunnerSettings{
   847  				CloneURL: "https://test.local",
   848  			},
   849  			result: "https://gitlab-ci-token:1234567@test.local/h5bp/html5-boilerplate.git",
   850  		},
   851  		{
   852  			runner: RunnerSettings{},
   853  			result: "http://fallback.url",
   854  		},
   855  	}
   856  
   857  	for _, tc := range testCases {
   858  		build := &Build{
   859  			Runner: &RunnerConfig{
   860  				RunnerSettings: tc.runner,
   861  			},
   862  			allVariables: JobVariables{
   863  				JobVariable{Key: "CI_JOB_TOKEN", Value: "1234567"},
   864  				JobVariable{Key: "CI_PROJECT_PATH", Value: "h5bp/html5-boilerplate"},
   865  			},
   866  			JobResponse: JobResponse{
   867  				GitInfo: GitInfo{RepoURL: "http://fallback.url"},
   868  			},
   869  		}
   870  
   871  		assert.Equal(t, tc.result, build.GetRemoteURL())
   872  	}
   873  }
   874  
   875  type featureFlagOnTestCase struct {
   876  	value          string
   877  	expectedStatus bool
   878  	expectedError  bool
   879  }
   880  
   881  func TestIsFeatureFlagOn(t *testing.T) {
   882  	hook := test.NewGlobal()
   883  
   884  	tests := map[string]featureFlagOnTestCase{
   885  		"no value": {
   886  			value:          "",
   887  			expectedStatus: false,
   888  			expectedError:  false,
   889  		},
   890  		"true": {
   891  			value:          "true",
   892  			expectedStatus: true,
   893  			expectedError:  false,
   894  		},
   895  		"1": {
   896  			value:          "1",
   897  			expectedStatus: true,
   898  			expectedError:  false,
   899  		},
   900  		"false": {
   901  			value:          "false",
   902  			expectedStatus: false,
   903  			expectedError:  false,
   904  		},
   905  		"0": {
   906  			value:          "0",
   907  			expectedStatus: false,
   908  			expectedError:  false,
   909  		},
   910  		"invalid value": {
   911  			value:          "test",
   912  			expectedStatus: false,
   913  			expectedError:  true,
   914  		},
   915  	}
   916  
   917  	for name, testCase := range tests {
   918  		t.Run(name, func(t *testing.T) {
   919  			build := new(Build)
   920  			build.Variables = JobVariables{
   921  				{Key: "FF_TEST_FEATURE", Value: testCase.value},
   922  			}
   923  
   924  			status := build.IsFeatureFlagOn("FF_TEST_FEATURE")
   925  			assert.Equal(t, testCase.expectedStatus, status)
   926  
   927  			entry := hook.LastEntry()
   928  			if testCase.expectedError {
   929  				require.NotNil(t, entry)
   930  
   931  				logrusOutput, err := entry.String()
   932  				require.NoError(t, err)
   933  
   934  				assert.Contains(t, logrusOutput, "Error while parsing the value of feature flag")
   935  			} else {
   936  				assert.Nil(t, entry)
   937  			}
   938  
   939  			hook.Reset()
   940  		})
   941  	}
   942  }
   943  
   944  func TestAllowToOverwriteFeatureFlagWithRunnerVariables(t *testing.T) {
   945  	tests := map[string]struct {
   946  		variable      string
   947  		expectedValue bool
   948  	}{
   949  		"it has default value of FF": {
   950  			variable:      "",
   951  			expectedValue: true,
   952  		},
   953  		"it enables FF": {
   954  			variable:      "FF_K8S_USE_ENTRYPOINT_OVER_COMMAND=true",
   955  			expectedValue: true,
   956  		},
   957  		"it disable FF": {
   958  			variable:      "FF_K8S_USE_ENTRYPOINT_OVER_COMMAND=false",
   959  			expectedValue: false,
   960  		},
   961  	}
   962  
   963  	for name, test := range tests {
   964  		t.Run(name, func(t *testing.T) {
   965  			build := new(Build)
   966  			build.Runner = &RunnerConfig{
   967  				RunnerSettings: RunnerSettings{
   968  					Environment: []string{test.variable},
   969  				},
   970  			}
   971  
   972  			result := build.IsFeatureFlagOn("FF_K8S_USE_ENTRYPOINT_OVER_COMMAND")
   973  			assert.Equal(t, test.expectedValue, result)
   974  		})
   975  	}
   976  }
   977  
   978  func TestStartBuild(t *testing.T) {
   979  	type startBuildArgs struct {
   980  		rootDir               string
   981  		cacheDir              string
   982  		customBuildDirEnabled bool
   983  		sharedDir             bool
   984  	}
   985  
   986  	tests := map[string]struct {
   987  		args             startBuildArgs
   988  		jobVariables     JobVariables
   989  		expectedBuildDir string
   990  		expectedCacheDir string
   991  		expectedError    bool
   992  	}{
   993  		"no job specific build dir with no shared dir": {
   994  			args: startBuildArgs{
   995  				rootDir:               "/build",
   996  				cacheDir:              "/cache",
   997  				customBuildDirEnabled: true,
   998  				sharedDir:             false,
   999  			},
  1000  			jobVariables:     JobVariables{},
  1001  			expectedBuildDir: "/build/test-namespace/test-repo",
  1002  			expectedCacheDir: "/cache/test-namespace/test-repo",
  1003  			expectedError:    false,
  1004  		},
  1005  		"no job specified build dir with shared dir": {
  1006  			args: startBuildArgs{
  1007  				rootDir:               "/builds",
  1008  				cacheDir:              "/cache",
  1009  				customBuildDirEnabled: true,
  1010  				sharedDir:             true,
  1011  			},
  1012  			jobVariables:     JobVariables{},
  1013  			expectedBuildDir: "/builds/1234/0/test-namespace/test-repo",
  1014  			expectedCacheDir: "/cache/test-namespace/test-repo",
  1015  			expectedError:    false,
  1016  		},
  1017  		"valid GIT_CLONE_PATH was specified": {
  1018  			args: startBuildArgs{
  1019  				rootDir:               "/builds",
  1020  				cacheDir:              "/cache",
  1021  				customBuildDirEnabled: true,
  1022  				sharedDir:             false,
  1023  			},
  1024  			jobVariables: JobVariables{
  1025  				{Key: "GIT_CLONE_PATH", Value: "/builds/go/src/gitlab.com/test-namespace/test-repo", Public: true},
  1026  			},
  1027  			expectedBuildDir: "/builds/go/src/gitlab.com/test-namespace/test-repo",
  1028  			expectedCacheDir: "/cache/test-namespace/test-repo",
  1029  			expectedError:    false,
  1030  		},
  1031  		"valid GIT_CLONE_PATH using CI_BUILDS_DIR was specified": {
  1032  			args: startBuildArgs{
  1033  				rootDir:               "/builds",
  1034  				cacheDir:              "/cache",
  1035  				customBuildDirEnabled: true,
  1036  				sharedDir:             false,
  1037  			},
  1038  			jobVariables: JobVariables{
  1039  				{Key: "GIT_CLONE_PATH", Value: "$CI_BUILDS_DIR/go/src/gitlab.com/test-namespace/test-repo", Public: true},
  1040  			},
  1041  			expectedBuildDir: "/builds/go/src/gitlab.com/test-namespace/test-repo",
  1042  			expectedCacheDir: "/cache/test-namespace/test-repo",
  1043  			expectedError:    false,
  1044  		},
  1045  		"custom build disabled": {
  1046  			args: startBuildArgs{
  1047  				rootDir:               "/builds",
  1048  				cacheDir:              "/cache",
  1049  				customBuildDirEnabled: false,
  1050  				sharedDir:             false,
  1051  			},
  1052  			jobVariables: JobVariables{
  1053  				{Key: "GIT_CLONE_PATH", Value: "/builds/go/src/gitlab.com/test-namespace/test-repo", Public: true},
  1054  			},
  1055  			expectedBuildDir: "/builds/test-namespace/test-repo",
  1056  			expectedCacheDir: "/cache/test-namespace/test-repo",
  1057  			expectedError:    true,
  1058  		},
  1059  		"invalid GIT_CLONE_PATH was specified": {
  1060  			args: startBuildArgs{
  1061  				rootDir:               "/builds",
  1062  				cacheDir:              "/cache",
  1063  				customBuildDirEnabled: true,
  1064  				sharedDir:             false,
  1065  			},
  1066  			jobVariables: JobVariables{
  1067  				{Key: "GIT_CLONE_PATH", Value: "/go/src/gitlab.com/test-namespace/test-repo", Public: true},
  1068  			},
  1069  			expectedError: true,
  1070  		},
  1071  	}
  1072  
  1073  	for name, test := range tests {
  1074  		t.Run(name, func(t *testing.T) {
  1075  			build := Build{
  1076  				JobResponse: JobResponse{
  1077  					GitInfo: GitInfo{
  1078  						RepoURL: "https://gitlab.com/test-namespace/test-repo.git",
  1079  					},
  1080  					Variables: test.jobVariables,
  1081  				},
  1082  				Runner: &RunnerConfig{
  1083  					RunnerCredentials: RunnerCredentials{
  1084  						Token: "1234",
  1085  					},
  1086  				},
  1087  			}
  1088  
  1089  			err := build.StartBuild(test.args.rootDir, test.args.cacheDir, test.args.customBuildDirEnabled, test.args.sharedDir)
  1090  			if test.expectedError {
  1091  				assert.Error(t, err)
  1092  				return
  1093  			}
  1094  
  1095  			assert.NoError(t, err)
  1096  			assert.Equal(t, test.expectedBuildDir, build.BuildDir)
  1097  			assert.Equal(t, test.args.rootDir, build.RootDir)
  1098  			assert.Equal(t, test.expectedCacheDir, build.CacheDir)
  1099  		})
  1100  	}
  1101  }
  1102  
  1103  func TestWaitForTerminal(t *testing.T) {
  1104  	cases := []struct {
  1105  		name                   string
  1106  		cancelFn               func(ctxCancel context.CancelFunc, build *Build)
  1107  		jobTimeout             int
  1108  		waitForTerminalTimeout time.Duration
  1109  		expectedErr            string
  1110  	}{
  1111  		{
  1112  			name: "Cancel build",
  1113  			cancelFn: func(ctxCancel context.CancelFunc, build *Build) {
  1114  				ctxCancel()
  1115  			},
  1116  			jobTimeout:             3600,
  1117  			waitForTerminalTimeout: time.Hour,
  1118  			expectedErr:            "build cancelled, killing session",
  1119  		},
  1120  		{
  1121  			name: "Terminal Timeout",
  1122  			cancelFn: func(ctxCancel context.CancelFunc, build *Build) {
  1123  				// noop
  1124  			},
  1125  			jobTimeout:             3600,
  1126  			waitForTerminalTimeout: time.Second,
  1127  			expectedErr:            "Terminal session timed out (maximum time allowed - 1s)",
  1128  		},
  1129  		{
  1130  			name: "System Interrupt",
  1131  			cancelFn: func(ctxCancel context.CancelFunc, build *Build) {
  1132  				build.SystemInterrupt <- os.Interrupt
  1133  			},
  1134  			jobTimeout:             3600,
  1135  			waitForTerminalTimeout: time.Hour,
  1136  			expectedErr:            "terminal disconnected by system signal: interrupt",
  1137  		},
  1138  		{
  1139  			name: "Terminal Disconnect",
  1140  			cancelFn: func(ctxCancel context.CancelFunc, build *Build) {
  1141  				build.Session.DisconnectCh <- errors.New("user disconnect")
  1142  			},
  1143  			jobTimeout:             3600,
  1144  			waitForTerminalTimeout: time.Hour,
  1145  			expectedErr:            "terminal disconnected: user disconnect",
  1146  		},
  1147  	}
  1148  
  1149  	for _, c := range cases {
  1150  		t.Run(c.name, func(t *testing.T) {
  1151  			build := Build{
  1152  				Runner: &RunnerConfig{
  1153  					RunnerSettings: RunnerSettings{
  1154  						Executor: "shell",
  1155  					},
  1156  				},
  1157  				JobResponse: JobResponse{
  1158  					RunnerInfo: RunnerInfo{
  1159  						Timeout: c.jobTimeout,
  1160  					},
  1161  				},
  1162  				SystemInterrupt: make(chan os.Signal),
  1163  			}
  1164  
  1165  			trace := Trace{Writer: os.Stdout}
  1166  			build.logger = NewBuildLogger(&trace, build.Log())
  1167  			sess, err := session.NewSession(nil)
  1168  			require.NoError(t, err)
  1169  			build.Session = sess
  1170  
  1171  			srv := httptest.NewServer(build.Session.Mux())
  1172  			defer srv.Close()
  1173  
  1174  			mockConn := terminal.MockConn{}
  1175  			defer mockConn.AssertExpectations(t)
  1176  			mockConn.On("Close").Maybe().Return(nil)
  1177  			// On Start upgrade the web socket connection and wait for the
  1178  			// timeoutCh to exit, to mock real work made on the websocket.
  1179  			mockConn.On("Start", mock.Anything, mock.Anything, mock.Anything, mock.Anything).Run(func(args mock.Arguments) {
  1180  				upgrader := &websocket.Upgrader{}
  1181  				r := args[1].(*http.Request)
  1182  				w := args[0].(http.ResponseWriter)
  1183  
  1184  				_, _ = upgrader.Upgrade(w, r, nil)
  1185  				timeoutCh := args[2].(chan error)
  1186  
  1187  				<-timeoutCh
  1188  			}).Once()
  1189  
  1190  			mockTerminal := terminal.MockInteractiveTerminal{}
  1191  			defer mockTerminal.AssertExpectations(t)
  1192  			mockTerminal.On("Connect").Return(&mockConn, nil)
  1193  			sess.SetInteractiveTerminal(&mockTerminal)
  1194  
  1195  			u := url.URL{
  1196  				Scheme: "ws",
  1197  				Host:   srv.Listener.Addr().String(),
  1198  				Path:   build.Session.Endpoint + "/exec",
  1199  			}
  1200  			headers := http.Header{
  1201  				"Authorization": []string{build.Session.Token},
  1202  			}
  1203  
  1204  			conn, _, err := websocket.DefaultDialer.Dial(u.String(), headers)
  1205  			require.NotNil(t, conn)
  1206  			require.NoError(t, err)
  1207  			defer conn.Close()
  1208  
  1209  			ctx, cancel := context.WithTimeout(context.Background(), build.GetBuildTimeout())
  1210  
  1211  			errCh := make(chan error)
  1212  			go func() {
  1213  				errCh <- build.waitForTerminal(ctx, c.waitForTerminalTimeout)
  1214  			}()
  1215  
  1216  			c.cancelFn(cancel, &build)
  1217  
  1218  			assert.EqualError(t, <-errCh, c.expectedErr)
  1219  		})
  1220  	}
  1221  }
  1222  
  1223  func TestBuild_IsLFSSmudgeDisabled(t *testing.T) {
  1224  	testCases := map[string]struct {
  1225  		isVariableUnset bool
  1226  		variableValue   string
  1227  		expectedResult  bool
  1228  	}{
  1229  		"variable not set": {
  1230  			isVariableUnset: true,
  1231  			expectedResult:  false,
  1232  		},
  1233  		"variable empty": {
  1234  			variableValue:  "",
  1235  			expectedResult: false,
  1236  		},
  1237  		"variable set to true": {
  1238  			variableValue:  "true",
  1239  			expectedResult: true,
  1240  		},
  1241  		"variable set to false": {
  1242  			variableValue:  "false",
  1243  			expectedResult: false,
  1244  		},
  1245  		"variable set to 1": {
  1246  			variableValue:  "1",
  1247  			expectedResult: true,
  1248  		},
  1249  		"variable set to 0": {
  1250  			variableValue:  "0",
  1251  			expectedResult: false,
  1252  		},
  1253  	}
  1254  
  1255  	for testName, testCase := range testCases {
  1256  		t.Run(testName, func(t *testing.T) {
  1257  			b := &Build{
  1258  				JobResponse: JobResponse{
  1259  					Variables: JobVariables{},
  1260  				},
  1261  			}
  1262  
  1263  			if !testCase.isVariableUnset {
  1264  				b.Variables = append(b.Variables, JobVariable{Key: "GIT_LFS_SKIP_SMUDGE", Value: testCase.variableValue, Public: true})
  1265  			}
  1266  
  1267  			assert.Equal(t, testCase.expectedResult, b.IsLFSSmudgeDisabled())
  1268  		})
  1269  	}
  1270  }
  1271  
  1272  func TestGitCleanFlags(t *testing.T) {
  1273  	tests := map[string]struct {
  1274  		value          string
  1275  		expectedResult []string
  1276  	}{
  1277  		"empty clean flags": {
  1278  			value:          "",
  1279  			expectedResult: []string{"-ffdx"},
  1280  		},
  1281  		"use custom flags": {
  1282  			value:          "custom-flags",
  1283  			expectedResult: []string{"custom-flags"},
  1284  		},
  1285  		"use custom flags with multiple arguments": {
  1286  			value:          "-ffdx -e cache/",
  1287  			expectedResult: []string{"-ffdx", "-e", "cache/"},
  1288  		},
  1289  		"disabled": {
  1290  			value:          "none",
  1291  			expectedResult: []string{},
  1292  		},
  1293  	}
  1294  
  1295  	for name, test := range tests {
  1296  		t.Run(name, func(t *testing.T) {
  1297  			build := &Build{
  1298  				Runner: &RunnerConfig{},
  1299  				JobResponse: JobResponse{
  1300  					Variables: JobVariables{
  1301  						{Key: "GIT_CLEAN_FLAGS", Value: test.value},
  1302  					},
  1303  				},
  1304  			}
  1305  
  1306  			result := build.GetGitCleanFlags()
  1307  			assert.Equal(t, test.expectedResult, result)
  1308  		})
  1309  	}
  1310  }
  1311  
  1312  func TestDefaultVariables(t *testing.T) {
  1313  	tests := map[string]struct {
  1314  		jobVariables  JobVariables
  1315  		rootDir       string
  1316  		key           string
  1317  		expectedValue string
  1318  	}{
  1319  		"get default CI_SERVER value": {
  1320  			jobVariables:  JobVariables{},
  1321  			rootDir:       "/builds",
  1322  			key:           "CI_SERVER",
  1323  			expectedValue: "yes",
  1324  		},
  1325  		"get default CI_PROJECT_DIR value": {
  1326  			jobVariables:  JobVariables{},
  1327  			rootDir:       "/builds",
  1328  			key:           "CI_PROJECT_DIR",
  1329  			expectedValue: "/builds/test-namespace/test-repo",
  1330  		},
  1331  		"get overwritten CI_PROJECT_DIR value": {
  1332  			jobVariables: JobVariables{
  1333  				{Key: "GIT_CLONE_PATH", Value: "/builds/go/src/gitlab.com/gitlab-org/gitlab-runner", Public: true},
  1334  			},
  1335  			rootDir:       "/builds",
  1336  			key:           "CI_PROJECT_DIR",
  1337  			expectedValue: "/builds/go/src/gitlab.com/gitlab-org/gitlab-runner",
  1338  		},
  1339  	}
  1340  
  1341  	for name, test := range tests {
  1342  		t.Run(name, func(t *testing.T) {
  1343  			build := Build{
  1344  				JobResponse: JobResponse{
  1345  					GitInfo: GitInfo{
  1346  						RepoURL: "https://gitlab.com/test-namespace/test-repo.git",
  1347  					},
  1348  					Variables: test.jobVariables,
  1349  				},
  1350  				Runner: &RunnerConfig{
  1351  					RunnerCredentials: RunnerCredentials{
  1352  						Token: "1234",
  1353  					},
  1354  				},
  1355  			}
  1356  
  1357  			err := build.StartBuild(test.rootDir, "/cache", true, false)
  1358  			assert.NoError(t, err)
  1359  
  1360  			variable := build.GetAllVariables().Get(test.key)
  1361  			assert.Equal(t, test.expectedValue, variable)
  1362  		})
  1363  	}
  1364  }