github.com/secure-build/gitlab-runner@v12.5.0+incompatible/executors/custom/executor_integration_test.go (about) 1 package custom_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "os" 8 "os/exec" 9 "path/filepath" 10 "runtime" 11 "testing" 12 "time" 13 14 "github.com/stretchr/testify/assert" 15 "github.com/stretchr/testify/require" 16 17 "gitlab.com/gitlab-org/gitlab-runner/common" 18 "gitlab.com/gitlab-org/gitlab-runner/executors/custom/command" 19 "gitlab.com/gitlab-org/gitlab-runner/helpers" 20 "gitlab.com/gitlab-org/gitlab-runner/session" 21 "gitlab.com/gitlab-org/gitlab-runner/shells/shellstest" 22 ) 23 24 const ( 25 TestTimeout = 60 * time.Second 26 ) 27 28 var testExecutorFile string 29 30 func TestMain(m *testing.M) { 31 fmt.Println("Compiling test executor") 32 33 curDir, err := os.Getwd() 34 if err != nil { 35 panic(fmt.Sprintf("Error on getting the working directory")) 36 } 37 38 sourcesDir := filepath.Join(curDir, "testdata", "test_executor") 39 sourcesFile := filepath.Join(sourcesDir, "main.go") 40 41 targetDir, err := ioutil.TempDir("", "test_executor") 42 if err != nil { 43 panic(fmt.Sprintf("Error on preparing tmp directory for test executor binary")) 44 } 45 testExecutorFile = filepath.Join(targetDir, "main") 46 47 if runtime.GOOS == "windows" { 48 // Adding it here, explicitly, in if, to show that this OS 49 // requires a special treatment... 50 testExecutorFile += ".exe" 51 } 52 53 cmd := exec.Command("go", "build", "-o", testExecutorFile, sourcesFile) 54 cmd.Stdout = os.Stdout 55 cmd.Stderr = os.Stderr 56 57 fmt.Printf("Executing: %v", cmd) 58 fmt.Println() 59 60 err = cmd.Run() 61 if err != nil { 62 panic(fmt.Sprintf("Error on executing go build to prepare test custom executor")) 63 } 64 65 code := m.Run() 66 os.Exit(code) 67 } 68 69 func runBuildWithOptions(t *testing.T, build *common.Build, config *common.Config, trace *common.Trace) error { 70 timeoutTimer := time.AfterFunc(TestTimeout, func() { 71 t.Log("Timed out") 72 t.FailNow() 73 }) 74 defer timeoutTimer.Stop() 75 76 return build.Run(config, trace) 77 } 78 79 func runBuildWithTrace(t *testing.T, build *common.Build, trace *common.Trace) error { 80 return runBuildWithOptions(t, build, &common.Config{}, trace) 81 } 82 83 func runBuild(t *testing.T, build *common.Build) error { 84 err := runBuildWithTrace(t, build, &common.Trace{Writer: os.Stdout}) 85 assert.True(t, build.IsSharedEnv()) 86 87 return err 88 } 89 90 func runBuildReturningOutput(t *testing.T, build *common.Build) (string, error) { 91 buf := bytes.NewBuffer(nil) 92 err := runBuildWithTrace(t, build, &common.Trace{Writer: buf}) 93 output := buf.String() 94 t.Log(output) 95 96 return output, err 97 } 98 99 func newBuild(t *testing.T, jobResponse common.JobResponse, shell string) (*common.Build, func()) { 100 dir, err := ioutil.TempDir("", "gitlab-runner-custom-executor-test") 101 require.NoError(t, err) 102 103 t.Log("Build directory:", dir) 104 105 build := &common.Build{ 106 JobResponse: jobResponse, 107 Runner: &common.RunnerConfig{ 108 RunnerSettings: common.RunnerSettings{ 109 BuildsDir: filepath.Join(dir, "builds"), 110 CacheDir: filepath.Join(dir, "cache"), 111 Executor: "custom", 112 Shell: shell, 113 Custom: &common.CustomConfig{ 114 ConfigExec: testExecutorFile, 115 ConfigArgs: []string{shell, "config"}, 116 PrepareExec: testExecutorFile, 117 PrepareArgs: []string{shell, "prepare"}, 118 RunExec: testExecutorFile, 119 RunArgs: []string{shell, "run"}, 120 CleanupExec: testExecutorFile, 121 CleanupArgs: []string{shell, "cleanup"}, 122 GracefulKillTimeout: timeoutInSeconds(10 * time.Second), 123 ForceKillTimeout: timeoutInSeconds(10 * time.Second), 124 }, 125 }, 126 }, 127 SystemInterrupt: make(chan os.Signal, 1), 128 Session: &session.Session{ 129 DisconnectCh: make(chan error), 130 TimeoutCh: make(chan error), 131 }, 132 } 133 134 cleanup := func() { 135 _ = os.RemoveAll(dir) 136 } 137 138 return build, cleanup 139 } 140 141 func timeoutInSeconds(duration time.Duration) *int { 142 seconds := duration.Seconds() 143 secondsInInt := int(seconds) 144 145 return &secondsInInt 146 } 147 148 func TestBuildSuccess(t *testing.T) { 149 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 150 successfulBuild, err := common.GetSuccessfulBuild() 151 require.NoError(t, err) 152 153 build, cleanup := newBuild(t, successfulBuild, shell) 154 defer cleanup() 155 156 err = runBuild(t, build) 157 assert.NoError(t, err) 158 }) 159 } 160 161 func TestBuildBuildFailure(t *testing.T) { 162 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 163 successfulBuild, err := common.GetSuccessfulBuild() 164 require.NoError(t, err) 165 166 build, cleanup := newBuild(t, successfulBuild, shell) 167 defer cleanup() 168 169 build.Variables = append(build.Variables, common.JobVariable{ 170 Key: "IS_BUILD_ERROR", 171 Value: "true", 172 Public: true, 173 }) 174 175 err = runBuild(t, build) 176 assert.Error(t, err) 177 assert.IsType(t, &common.BuildError{}, err) 178 }) 179 } 180 181 func TestBuildSystemFailure(t *testing.T) { 182 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 183 successfulBuild, err := common.GetSuccessfulBuild() 184 require.NoError(t, err) 185 186 build, cleanup := newBuild(t, successfulBuild, shell) 187 defer cleanup() 188 189 build.Variables = append(build.Variables, common.JobVariable{ 190 Key: "IS_SYSTEM_ERROR", 191 Value: "true", 192 Public: true, 193 }) 194 195 err = runBuild(t, build) 196 assert.Error(t, err) 197 assert.IsType(t, &exec.ExitError{}, err) 198 t.Log(err) 199 }) 200 } 201 202 func TestBuildUnknownFailure(t *testing.T) { 203 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 204 successfulBuild, err := common.GetSuccessfulBuild() 205 require.NoError(t, err) 206 207 build, cleanup := newBuild(t, successfulBuild, shell) 208 defer cleanup() 209 210 build.Variables = append(build.Variables, common.JobVariable{ 211 Key: "IS_UNKNOWN_ERROR", 212 Value: "true", 213 Public: true, 214 }) 215 216 err = runBuild(t, build) 217 assert.Error(t, err) 218 assert.IsType(t, &command.ErrUnknownFailure{}, err) 219 }) 220 } 221 222 func TestBuildAbort(t *testing.T) { 223 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 224 longRunningBuild, err := common.GetLongRunningBuild() 225 require.NoError(t, err) 226 227 build, cleanup := newBuild(t, longRunningBuild, shell) 228 defer cleanup() 229 230 abortTimer := time.AfterFunc(time.Second, func() { 231 t.Log("Interrupt") 232 build.SystemInterrupt <- os.Interrupt 233 }) 234 defer abortTimer.Stop() 235 236 err = runBuild(t, build) 237 assert.EqualError(t, err, "aborted: interrupt") 238 }) 239 } 240 241 func TestBuildCancel(t *testing.T) { 242 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 243 longRunningBuild, err := common.GetLongRunningBuild() 244 require.NoError(t, err) 245 246 build, cleanup := newBuild(t, longRunningBuild, shell) 247 defer cleanup() 248 249 trace := &common.Trace{Writer: os.Stdout} 250 251 cancelTimer := time.AfterFunc(2*time.Second, func() { 252 t.Log("Cancel") 253 trace.CancelFunc() 254 }) 255 defer cancelTimer.Stop() 256 257 err = runBuildWithTrace(t, build, trace) 258 assert.EqualError(t, err, "canceled") 259 assert.IsType(t, err, &common.BuildError{}) 260 }) 261 } 262 263 func TestBuildWithGitStrategyCloneWithoutLFS(t *testing.T) { 264 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 265 successfulBuild, err := common.GetSuccessfulBuild() 266 require.NoError(t, err) 267 268 build, cleanup := newBuild(t, successfulBuild, shell) 269 defer cleanup() 270 271 build.Runner.PreCloneScript = "echo pre-clone-script" 272 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 273 274 out, err := runBuildReturningOutput(t, build) 275 assert.NoError(t, err) 276 assert.Contains(t, out, "Created fresh repository") 277 278 out, err = runBuildReturningOutput(t, build) 279 assert.NoError(t, err) 280 assert.Contains(t, out, "Created fresh repository") 281 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 282 assert.Contains(t, out, "pre-clone-script") 283 }) 284 } 285 286 func TestBuildWithGitStrategyCloneNoCheckoutWithoutLFS(t *testing.T) { 287 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 288 successfulBuild, err := common.GetSuccessfulBuild() 289 require.NoError(t, err) 290 291 build, cleanup := newBuild(t, successfulBuild, shell) 292 defer cleanup() 293 294 build.Runner.PreCloneScript = "echo pre-clone-script" 295 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 296 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_CHECKOUT", Value: "false"}) 297 298 out, err := runBuildReturningOutput(t, build) 299 assert.NoError(t, err) 300 assert.Contains(t, out, "Created fresh repository") 301 302 out, err = runBuildReturningOutput(t, build) 303 assert.NoError(t, err) 304 assert.Contains(t, out, "Created fresh repository") 305 assert.Contains(t, out, "Skipping Git checkout") 306 assert.Contains(t, out, "pre-clone-script") 307 }) 308 } 309 310 func TestBuildWithGitSubmoduleStrategyRecursiveAndGitStrategyNone(t *testing.T) { 311 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 312 successfulBuild, err := common.GetSuccessfulBuild() 313 require.NoError(t, err) 314 315 build, cleanup := newBuild(t, successfulBuild, shell) 316 defer cleanup() 317 318 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "none"}) 319 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "recursive"}) 320 321 out, err := runBuildReturningOutput(t, build) 322 assert.NoError(t, err) 323 assert.NotContains(t, out, "Created fresh repository") 324 assert.NotContains(t, out, "Fetching changes") 325 assert.Contains(t, out, "Skipping Git repository setup") 326 assert.NotContains(t, out, "Updating/initializing submodules...") 327 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 328 assert.Contains(t, out, "Skipping Git submodules setup") 329 }) 330 } 331 332 func TestBuildWithoutDebugTrace(t *testing.T) { 333 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 334 successfulBuild, err := common.GetSuccessfulBuild() 335 require.NoError(t, err) 336 337 build, cleanup := newBuild(t, successfulBuild, shell) 338 defer cleanup() 339 340 // The default build shouldn't have debug tracing enabled 341 out, err := runBuildReturningOutput(t, build) 342 assert.NoError(t, err) 343 assert.NotRegexp(t, `[^$] echo Hello World`, out) 344 }) 345 } 346 347 func TestBuildWithDebugTrace(t *testing.T) { 348 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 349 successfulBuild, err := common.GetSuccessfulBuild() 350 require.NoError(t, err) 351 352 build, cleanup := newBuild(t, successfulBuild, shell) 353 defer cleanup() 354 355 build.Variables = append(build.Variables, common.JobVariable{Key: "CI_DEBUG_TRACE", Value: "true"}) 356 357 out, err := runBuildReturningOutput(t, build) 358 assert.NoError(t, err) 359 assert.Regexp(t, `(>|[^$] )echo Hello World`, out) 360 }) 361 } 362 363 func TestBuildMultilineCommand(t *testing.T) { 364 buildGenerators := map[string]func() (common.JobResponse, error){ 365 "bash": common.GetMultilineBashBuild, 366 "powershell": common.GetMultilineBashBuildPowerShell, 367 "cmd": common.GetMultilineBashBuildCmd, 368 } 369 370 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 371 buildGenerator, ok := buildGenerators[shell] 372 require.Truef(t, ok, "Missing build generator for shell %q", shell) 373 374 multilineBuild, err := buildGenerator() 375 require.NoError(t, err) 376 377 build, cleanup := newBuild(t, multilineBuild, shell) 378 defer cleanup() 379 380 // The default build shouldn't have debug tracing enabled 381 out, err := runBuildReturningOutput(t, build) 382 assert.NoError(t, err) 383 assert.NotContains(t, out, "echo") 384 assert.Contains(t, out, "Hello World") 385 assert.Contains(t, out, "collapsed multi-line command") 386 }) 387 } 388 389 func TestBuildWithGoodGitSSLCAInfo(t *testing.T) { 390 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 391 if shell == "cmd" { 392 t.Skip("This test doesn't support Windows CMD (which is deprecated)") 393 } 394 395 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 396 require.NoError(t, err) 397 398 build, cleanup := newBuild(t, successfulBuild, shell) 399 defer cleanup() 400 401 build.Runner.URL = "https://gitlab.com" 402 403 out, err := runBuildReturningOutput(t, build) 404 assert.NoError(t, err) 405 assert.Contains(t, out, "Created fresh repository") 406 assert.Contains(t, out, "Updating/initializing submodules") 407 }) 408 } 409 410 // TestBuildWithGitSSLAndStrategyFetch describes issue https://gitlab.com/gitlab-org/gitlab-runner/issues/2991 411 func TestBuildWithGitSSLAndStrategyFetch(t *testing.T) { 412 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 413 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 414 require.NoError(t, err) 415 416 build, cleanup := newBuild(t, successfulBuild, shell) 417 defer cleanup() 418 419 build.Runner.PreCloneScript = "echo pre-clone-script" 420 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 421 422 out, err := runBuildReturningOutput(t, build) 423 assert.NoError(t, err) 424 assert.Contains(t, out, "Created fresh repository") 425 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 426 427 out, err = runBuildReturningOutput(t, build) 428 assert.NoError(t, err) 429 assert.Contains(t, out, "Fetching changes") 430 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 431 assert.Contains(t, out, "pre-clone-script") 432 }) 433 } 434 435 func TestBuildChangesBranchesWhenFetchingRepo(t *testing.T) { 436 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 437 successfulBuild, err := common.GetRemoteSuccessfulBuild() 438 require.NoError(t, err) 439 440 build, cleanup := newBuild(t, successfulBuild, shell) 441 defer cleanup() 442 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 443 444 out, err := runBuildReturningOutput(t, build) 445 assert.NoError(t, err) 446 assert.Contains(t, out, "Created fresh repository") 447 448 // Another build using the same repo but different branch. 449 build.GitInfo = common.GetLFSGitInfo(build.GitInfo.RepoURL) 450 out, err = runBuildReturningOutput(t, build) 451 assert.NoError(t, err) 452 assert.Contains(t, out, "Checking out 2371dd05 as add-lfs-object...") 453 }) 454 } 455 456 func TestBuildPowerShellCatchesExceptions(t *testing.T) { 457 helpers.SkipIntegrationTests(t, "powershell") 458 459 successfulBuild, err := common.GetRemoteSuccessfulBuild() 460 require.NoError(t, err) 461 462 build, cleanup := newBuild(t, successfulBuild, "powershell") 463 defer cleanup() 464 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "Stop"}) 465 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 466 467 out, err := runBuildReturningOutput(t, build) 468 assert.NoError(t, err) 469 assert.Contains(t, out, "Created fresh repository") 470 471 out, err = runBuildReturningOutput(t, build) 472 assert.NoError(t, err) 473 assert.NotContains(t, out, "Created fresh repository") 474 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 475 476 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "Continue"}) 477 out, err = runBuildReturningOutput(t, build) 478 assert.NoError(t, err) 479 assert.NotContains(t, out, "Created fresh repository") 480 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 481 482 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "SilentlyContinue"}) 483 out, err = runBuildReturningOutput(t, build) 484 assert.NoError(t, err) 485 assert.NotContains(t, out, "Created fresh repository") 486 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 487 } 488 489 func TestBuildOnCustomDirectory(t *testing.T) { 490 commands := map[string]string{ 491 "bash": "pwd", 492 "powershell": "pwd", 493 } 494 495 tests := map[string]bool{ 496 "custom directory defined": true, 497 "custom directory not defined": false, 498 } 499 500 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 501 if shell == "cmd" { 502 t.Skip("This test doesn't support Windows CMD (which is deprecated)") 503 } 504 505 for testName, tt := range tests { 506 t.Run(testName, func(t *testing.T) { 507 cmd, ok := commands[shell] 508 require.Truef(t, ok, "Missing command for shell %q", shell) 509 510 dir := filepath.Join(os.TempDir(), "custom", "directory") 511 expectedDirectory := filepath.Join(dir, "0") 512 513 successfulBuild, err := common.GetSuccessfulBuild() 514 require.NoError(t, err) 515 516 successfulBuild.Steps[0].Script = common.StepScript{cmd} 517 518 build, cleanup := newBuild(t, successfulBuild, shell) 519 defer cleanup() 520 521 if tt { 522 build.Variables = append(build.Variables, common.JobVariable{ 523 Key: "IS_RUN_ON_CUSTOM_DIR", 524 Value: dir, 525 Public: true, 526 }) 527 } 528 529 out, err := runBuildReturningOutput(t, build) 530 assert.NoError(t, err) 531 532 if tt { 533 assert.Contains(t, out, expectedDirectory) 534 } else { 535 assert.NotContains(t, out, expectedDirectory) 536 } 537 }) 538 } 539 }) 540 }