github.com/secure-build/gitlab-runner@v12.5.0+incompatible/executors/shell/executor_shell_test.go (about) 1 package shell_test 2 3 import ( 4 "bytes" 5 "fmt" 6 "io/ioutil" 7 "net/http" 8 "net/http/httptest" 9 "net/url" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strings" 14 "testing" 15 "time" 16 17 "github.com/gorilla/websocket" 18 "github.com/hashicorp/go-version" 19 "github.com/stretchr/testify/assert" 20 "github.com/stretchr/testify/require" 21 22 "gitlab.com/gitlab-org/gitlab-runner/common" 23 "gitlab.com/gitlab-org/gitlab-runner/helpers" 24 "gitlab.com/gitlab-org/gitlab-runner/session" 25 "gitlab.com/gitlab-org/gitlab-runner/shells/shellstest" 26 ) 27 28 const ( 29 TestTimeout = 20 * time.Second 30 ) 31 32 func gitInDir(dir string, args ...string) ([]byte, error) { 33 cmd := exec.Command("git", args...) 34 cmd.Dir = dir 35 36 return cmd.Output() 37 } 38 39 func skipOnGitWithMessage(t *testing.T, constraints string, message string) { 40 out, err := gitInDir("", "version") 41 if err != nil { 42 t.Fatal("Can't detect git version", err) 43 return 44 } 45 46 gitVersionOut := string(out) 47 split := strings.SplitN(gitVersionOut, " ", 3) 48 if len(split) < 3 { 49 t.Fatal("Can't extract git version from", gitVersionOut) 50 return 51 } 52 53 gitVersion, err := version.NewVersion(strings.TrimSpace(split[2])) 54 if err != nil { 55 t.Fatal("Can't detect git version", err) 56 return 57 } 58 59 rules, err := version.NewConstraint(constraints) 60 if err != nil { 61 t.Fatal("Invalid constraint", err) 62 return 63 } 64 65 shouldSkip := rules.Check(gitVersion) 66 if shouldSkip { 67 t.Skipf("Git %q found, skipping the test; %s", constraints, message) 68 } 69 } 70 71 func skipIfGitDoesNotSupportLFS(t *testing.T) { 72 skipOnGitWithMessage(t, "< 1.8.2", "available git version doesn't support LFS") 73 } 74 75 func skipOnGit(t *testing.T, constraints string) { 76 skipOnGitWithMessage(t, constraints, "") 77 } 78 79 func skipOnGit17x(t *testing.T) { 80 skipOnGit(t, "< 1.8") 81 } 82 83 func runBuildWithOptions(t *testing.T, build *common.Build, config *common.Config, trace *common.Trace) error { 84 timeoutTimer := time.AfterFunc(TestTimeout, func() { 85 t.Log("Timed out") 86 t.FailNow() 87 }) 88 defer timeoutTimer.Stop() 89 90 return build.Run(config, trace) 91 } 92 93 func runBuildWithTrace(t *testing.T, build *common.Build, trace *common.Trace) error { 94 return runBuildWithOptions(t, build, &common.Config{}, trace) 95 } 96 97 func runBuild(t *testing.T, build *common.Build) error { 98 err := runBuildWithTrace(t, build, &common.Trace{Writer: os.Stdout}) 99 assert.True(t, build.IsSharedEnv()) 100 return err 101 } 102 103 func runBuildReturningOutput(t *testing.T, build *common.Build) (string, error) { 104 buf := bytes.NewBuffer(nil) 105 err := runBuildWithTrace(t, build, &common.Trace{Writer: buf}) 106 output := buf.String() 107 t.Log(output) 108 109 return output, err 110 } 111 112 func newBuild(t *testing.T, getBuildResponse common.JobResponse, shell string) (*common.Build, func()) { 113 dir, err := ioutil.TempDir("", "gitlab-runner-shell-executor-test") 114 if err != nil { 115 t.Fatal(err) 116 } 117 118 t.Log("Build directory:", dir) 119 120 build := &common.Build{ 121 JobResponse: getBuildResponse, 122 Runner: &common.RunnerConfig{ 123 RunnerSettings: common.RunnerSettings{ 124 BuildsDir: dir, 125 Executor: "shell", 126 Shell: shell, 127 }, 128 }, 129 SystemInterrupt: make(chan os.Signal, 1), 130 Session: &session.Session{ 131 DisconnectCh: make(chan error), 132 TimeoutCh: make(chan error), 133 }, 134 } 135 136 cleanup := func() { 137 os.RemoveAll(dir) 138 } 139 140 return build, cleanup 141 } 142 143 func TestBuildSuccess(t *testing.T) { 144 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 145 successfulBuild, err := common.GetSuccessfulBuild() 146 assert.NoError(t, err) 147 build, cleanup := newBuild(t, successfulBuild, shell) 148 defer cleanup() 149 150 err = runBuild(t, build) 151 assert.NoError(t, err) 152 }) 153 } 154 155 func TestBuildAbort(t *testing.T) { 156 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 157 longRunningBuild, err := common.GetLongRunningBuild() 158 assert.NoError(t, err) 159 build, cleanup := newBuild(t, longRunningBuild, shell) 160 defer cleanup() 161 162 abortTimer := time.AfterFunc(time.Second, func() { 163 t.Log("Interrupt") 164 build.SystemInterrupt <- os.Interrupt 165 }) 166 defer abortTimer.Stop() 167 168 err = runBuild(t, build) 169 assert.EqualError(t, err, "aborted: interrupt") 170 }) 171 } 172 173 func TestBuildCancel(t *testing.T) { 174 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 175 longRunningBuild, err := common.GetLongRunningBuild() 176 assert.NoError(t, err) 177 build, cleanup := newBuild(t, longRunningBuild, shell) 178 defer cleanup() 179 180 trace := &common.Trace{Writer: os.Stdout} 181 182 cancelTimer := time.AfterFunc(time.Second, func() { 183 t.Log("Cancel") 184 trace.CancelFunc() 185 }) 186 defer cancelTimer.Stop() 187 188 err = runBuildWithTrace(t, build, trace) 189 assert.EqualError(t, err, "canceled") 190 assert.IsType(t, err, &common.BuildError{}) 191 }) 192 } 193 194 func TestBuildWithIndexLock(t *testing.T) { 195 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 196 successfulBuild, err := common.GetSuccessfulBuild() 197 assert.NoError(t, err) 198 build, cleanup := newBuild(t, successfulBuild, shell) 199 defer cleanup() 200 201 err = runBuild(t, build) 202 assert.NoError(t, err) 203 204 build.JobResponse.AllowGitFetch = true 205 err = ioutil.WriteFile(build.BuildDir+"/.git/index.lock", []byte{}, os.ModeSticky) 206 require.NoError(t, err) 207 208 err = runBuild(t, build) 209 assert.NoError(t, err) 210 }) 211 } 212 213 func TestBuildWithShallowLock(t *testing.T) { 214 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 215 successfulBuild, err := common.GetSuccessfulBuild() 216 assert.NoError(t, err) 217 build, cleanup := newBuild(t, successfulBuild, shell) 218 defer cleanup() 219 220 build.Variables = append(build.Variables, 221 common.JobVariable{Key: "GIT_DEPTH", Value: "1"}, 222 common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 223 224 err = runBuild(t, build) 225 assert.NoError(t, err) 226 227 err = ioutil.WriteFile(build.BuildDir+"/.git/shallow.lock", []byte{}, os.ModeSticky) 228 require.NoError(t, err) 229 230 err = runBuild(t, build) 231 assert.NoError(t, err) 232 }) 233 } 234 235 func TestBuildWithHeadLock(t *testing.T) { 236 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 237 successfulBuild, err := common.GetSuccessfulBuild() 238 assert.NoError(t, err) 239 build, cleanup := newBuild(t, successfulBuild, shell) 240 defer cleanup() 241 242 err = runBuild(t, build) 243 assert.NoError(t, err) 244 245 build.JobResponse.AllowGitFetch = true 246 err = ioutil.WriteFile(build.BuildDir+"/.git/HEAD.lock", []byte{}, os.ModeSticky) 247 require.NoError(t, err) 248 249 err = runBuild(t, build) 250 assert.NoError(t, err) 251 }) 252 } 253 254 func TestBuildWithGitLFSHook(t *testing.T) { 255 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 256 successfulBuild, err := common.GetSuccessfulBuild() 257 assert.NoError(t, err) 258 build, cleanup := newBuild(t, successfulBuild, shell) 259 defer cleanup() 260 261 err = runBuild(t, build) 262 assert.NoError(t, err) 263 264 gitLFSPostCheckoutHook := "#!/bin/sh\necho 'running git lfs hook' >&2\nexit 2\n" 265 266 err = os.MkdirAll(build.BuildDir+"/.git/hooks/", 0755) 267 require.NoError(t, err) 268 err = ioutil.WriteFile(build.BuildDir+"/.git/hooks/post-checkout", []byte(gitLFSPostCheckoutHook), 0777) 269 require.NoError(t, err) 270 build.JobResponse.AllowGitFetch = true 271 272 err = runBuild(t, build) 273 assert.NoError(t, err) 274 }) 275 } 276 277 func assertLFSFileDownloaded(t *testing.T, build *common.Build) { 278 lfsFilePath := filepath.Join(build.FullProjectDir(), "files", "lfs", "file_1.lfs") 279 info, err := os.Stat(lfsFilePath) 280 require.NoError(t, err) 281 assert.Equal(t, common.FilesLFSFile1LFSsize, info.Size(), "invalid size of %q file", lfsFilePath) 282 } 283 284 func assertLFSFileNotDownloaded(t *testing.T, build *common.Build) { 285 lfsFilePath := filepath.Join(build.FullProjectDir(), "files", "lfs", "file_1.lfs") 286 info, err := os.Stat(lfsFilePath) 287 require.NoError(t, err) 288 assert.True(t, info.Size() < common.FilesLFSFile1LFSsize, "invalid size of %q file - expected to be less then downloaded LFS object", lfsFilePath) 289 } 290 291 func assertLFSFileNotPresent(t *testing.T, build *common.Build) { 292 lfsFilePath := filepath.Join(build.FullProjectDir(), "files", "lfs", "file_1.lfs") 293 _, err := os.Stat(lfsFilePath) 294 require.IsType(t, &os.PathError{}, err) 295 assert.Equal(t, lfsFilePath, err.(*os.PathError).Path) 296 } 297 298 func TestBuildWithGitStrategyNoneWithoutLFS(t *testing.T) { 299 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 300 successfulBuild, err := common.GetSuccessfulBuild() 301 assert.NoError(t, err) 302 build, cleanup := newBuild(t, successfulBuild, shell) 303 defer cleanup() 304 305 build.Runner.PreCloneScript = "echo pre-clone-script" 306 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "none"}) 307 308 out, err := runBuildReturningOutput(t, build) 309 assert.NoError(t, err) 310 assert.NotContains(t, out, "pre-clone-script") 311 assert.NotContains(t, out, "Created fresh repository") 312 assert.NotContains(t, out, "Fetching changes") 313 assert.Contains(t, out, "Skipping Git repository setup") 314 }) 315 } 316 317 func TestBuildWithGitStrategyNoneWithLFS(t *testing.T) { 318 skipIfGitDoesNotSupportLFS(t) 319 320 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 321 successfulBuild, err := common.GetRemoteSuccessfulLFSBuild() 322 assert.NoError(t, err) 323 build, cleanup := newBuild(t, successfulBuild, shell) 324 defer cleanup() 325 326 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "none"}) 327 328 out, err := runBuildReturningOutput(t, build) 329 assert.NoError(t, err) 330 assert.NotContains(t, out, "Created fresh repository") 331 assert.NotContains(t, out, "Fetching changes") 332 assert.Contains(t, out, "Skipping Git repository setup") 333 assertLFSFileNotPresent(t, build) 334 }) 335 } 336 337 func TestBuildWithGitStrategyFetchWithoutLFS(t *testing.T) { 338 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 339 successfulBuild, err := common.GetSuccessfulBuild() 340 assert.NoError(t, err) 341 build, cleanup := newBuild(t, successfulBuild, shell) 342 defer cleanup() 343 344 build.Runner.PreCloneScript = "echo pre-clone-script" 345 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 346 347 out, err := runBuildReturningOutput(t, build) 348 assert.NoError(t, err) 349 assert.Contains(t, out, "Created fresh repository") 350 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 351 352 out, err = runBuildReturningOutput(t, build) 353 assert.NoError(t, err) 354 assert.Contains(t, out, "Fetching changes") 355 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 356 assert.Contains(t, out, "pre-clone-script") 357 }) 358 } 359 360 func TestBuildWithGitStrategyFetchWithLFS(t *testing.T) { 361 skipIfGitDoesNotSupportLFS(t) 362 363 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 364 successfulBuild, err := common.GetRemoteSuccessfulBuild() 365 assert.NoError(t, err) 366 build, cleanup := newBuild(t, successfulBuild, shell) 367 defer cleanup() 368 369 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 370 371 out, err := runBuildReturningOutput(t, build) 372 assert.NoError(t, err) 373 assert.Contains(t, out, "Created fresh repository") 374 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 375 assertLFSFileNotPresent(t, build) 376 377 build.GitInfo = common.GetLFSGitInfo(build.GitInfo.RepoURL) 378 379 out, err = runBuildReturningOutput(t, build) 380 assert.NoError(t, err) 381 assert.Contains(t, out, "Fetching changes") 382 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 383 assertLFSFileDownloaded(t, build) 384 }) 385 } 386 387 func TestBuildWithGitStrategyFetchWithUserDisabledLFS(t *testing.T) { 388 skipIfGitDoesNotSupportLFS(t) 389 390 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 391 successfulBuild, err := common.GetRemoteSuccessfulBuild() 392 assert.NoError(t, err) 393 build, cleanup := newBuild(t, successfulBuild, shell) 394 defer cleanup() 395 396 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_LFS_SKIP_SMUDGE", Value: "1", Public: true}) 397 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 398 399 out, err := runBuildReturningOutput(t, build) 400 assert.NoError(t, err) 401 assert.Contains(t, out, "Created fresh repository") 402 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 403 assertLFSFileNotPresent(t, build) 404 405 build.GitInfo = common.GetLFSGitInfo(build.GitInfo.RepoURL) 406 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_LFS_SKIP_SMUDGE", Value: "1", Public: true}) 407 408 out, err = runBuildReturningOutput(t, build) 409 assert.NoError(t, err) 410 assert.Contains(t, out, "Fetching changes") 411 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 412 assertLFSFileNotDownloaded(t, build) 413 }) 414 } 415 416 func TestBuildWithGitStrategyFetchNoCheckoutWithoutLFS(t *testing.T) { 417 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 418 successfulBuild, err := common.GetSuccessfulBuild() 419 assert.NoError(t, err) 420 build, cleanup := newBuild(t, successfulBuild, shell) 421 defer cleanup() 422 423 build.Runner.PreCloneScript = "echo pre-clone-script" 424 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 425 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_CHECKOUT", Value: "false"}) 426 427 out, err := runBuildReturningOutput(t, build) 428 assert.NoError(t, err) 429 assert.Contains(t, out, "Created fresh repository") 430 assert.Contains(t, out, "Skipping Git checkout") 431 432 out, err = runBuildReturningOutput(t, build) 433 assert.NoError(t, err) 434 assert.Contains(t, out, "Fetching changes") 435 assert.Contains(t, out, "Skipping Git checkout") 436 assert.Contains(t, out, "pre-clone-script") 437 }) 438 } 439 440 func TestBuildWithGitStrategyFetchNoCheckoutWithLFS(t *testing.T) { 441 skipIfGitDoesNotSupportLFS(t) 442 443 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 444 successfulBuild, err := common.GetRemoteSuccessfulLFSBuild() 445 assert.NoError(t, err) 446 build, cleanup := newBuild(t, successfulBuild, shell) 447 defer cleanup() 448 449 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 450 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_CHECKOUT", Value: "false"}) 451 452 out, err := runBuildReturningOutput(t, build) 453 assert.NoError(t, err) 454 assert.Contains(t, out, "Created fresh repository") 455 assert.Contains(t, out, "Skipping Git checkout") 456 assertLFSFileNotPresent(t, build) 457 458 build.GitInfo = common.GetLFSGitInfo(build.GitInfo.RepoURL) 459 460 out, err = runBuildReturningOutput(t, build) 461 assert.NoError(t, err) 462 assert.Contains(t, out, "Fetching changes") 463 assert.Contains(t, out, "Skipping Git checkout") 464 assertLFSFileNotPresent(t, build) 465 }) 466 } 467 468 func TestBuildWithGitStrategyCloneWithoutLFS(t *testing.T) { 469 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 470 successfulBuild, err := common.GetSuccessfulBuild() 471 assert.NoError(t, err) 472 build, cleanup := newBuild(t, successfulBuild, shell) 473 defer cleanup() 474 475 build.Runner.PreCloneScript = "echo pre-clone-script" 476 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 477 478 out, err := runBuildReturningOutput(t, build) 479 assert.NoError(t, err) 480 assert.Contains(t, out, "Created fresh repository") 481 482 out, err = runBuildReturningOutput(t, build) 483 assert.NoError(t, err) 484 assert.Contains(t, out, "Created fresh repository") 485 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 486 assert.Contains(t, out, "pre-clone-script") 487 }) 488 } 489 490 func TestBuildWithGitStrategyCloneWithLFS(t *testing.T) { 491 skipIfGitDoesNotSupportLFS(t) 492 493 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 494 successfulBuild, err := common.GetRemoteSuccessfulLFSBuild() 495 assert.NoError(t, err) 496 build, cleanup := newBuild(t, successfulBuild, shell) 497 defer cleanup() 498 499 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 500 501 out, err := runBuildReturningOutput(t, build) 502 assert.NoError(t, err) 503 assert.Contains(t, out, "Created fresh repository") 504 assertLFSFileDownloaded(t, build) 505 }) 506 } 507 508 func TestBuildWithGitStrategyCloneWithUserDisabledLFS(t *testing.T) { 509 skipIfGitDoesNotSupportLFS(t) 510 511 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 512 successfulBuild, err := common.GetRemoteSuccessfulLFSBuild() 513 assert.NoError(t, err) 514 build, cleanup := newBuild(t, successfulBuild, shell) 515 defer cleanup() 516 517 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 518 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_LFS_SKIP_SMUDGE", Value: "1", Public: true}) 519 520 out, err := runBuildReturningOutput(t, build) 521 assert.NoError(t, err) 522 assert.Contains(t, out, "Created fresh repository") 523 assertLFSFileNotDownloaded(t, build) 524 }) 525 } 526 527 func TestBuildWithGitStrategyCloneNoCheckoutWithoutLFS(t *testing.T) { 528 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 529 successfulBuild, err := common.GetSuccessfulBuild() 530 assert.NoError(t, err) 531 build, cleanup := newBuild(t, successfulBuild, shell) 532 defer cleanup() 533 534 build.Runner.PreCloneScript = "echo pre-clone-script" 535 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 536 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_CHECKOUT", Value: "false"}) 537 538 out, err := runBuildReturningOutput(t, build) 539 assert.NoError(t, err) 540 assert.Contains(t, out, "Created fresh repository") 541 542 out, err = runBuildReturningOutput(t, build) 543 assert.NoError(t, err) 544 assert.Contains(t, out, "Created fresh repository") 545 assert.Contains(t, out, "Skipping Git checkout") 546 assert.Contains(t, out, "pre-clone-script") 547 }) 548 } 549 550 func TestBuildWithGitStrategyCloneNoCheckoutWithLFS(t *testing.T) { 551 skipIfGitDoesNotSupportLFS(t) 552 553 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 554 successfulBuild, err := common.GetRemoteSuccessfulLFSBuild() 555 assert.NoError(t, err) 556 build, cleanup := newBuild(t, successfulBuild, shell) 557 defer cleanup() 558 559 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "clone"}) 560 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_CHECKOUT", Value: "false"}) 561 562 out, err := runBuildReturningOutput(t, build) 563 assert.NoError(t, err) 564 assert.Contains(t, out, "Created fresh repository") 565 assert.Contains(t, out, "Skipping Git checkout") 566 assertLFSFileNotPresent(t, build) 567 }) 568 } 569 570 func TestBuildWithSubmoduleLFSPullsLFSObject(t *testing.T) { 571 skipIfGitDoesNotSupportLFS(t) 572 573 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 574 successfulBuild, err := common.GetRemoteSuccessfulBuild() 575 assert.NoError(t, err) 576 build, cleanup := newBuild(t, successfulBuild, shell) 577 defer cleanup() 578 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 579 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "normal"}) 580 build.GitInfo = common.GetSubmoduleLFSGitInfo(build.GitInfo.RepoURL) 581 582 out, err := runBuildReturningOutput(t, build) 583 assert.NoError(t, err) 584 assert.Contains(t, out, "Created fresh repository") 585 586 f, err := os.Stat(filepath.Join(build.FullProjectDir(), "lfs", "1.lfs")) 587 require.NoError(t, err) 588 assert.Equal(t, common.FilesLFSFile1LFSsize, f.Size()) 589 }) 590 } 591 592 func TestBuildWithSubmoduleLFSDisabledSmudging(t *testing.T) { 593 skipIfGitDoesNotSupportLFS(t) 594 595 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 596 successfulBuild, err := common.GetRemoteSuccessfulBuild() 597 assert.NoError(t, err) 598 build, cleanup := newBuild(t, successfulBuild, shell) 599 defer cleanup() 600 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 601 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "normal"}) 602 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_LFS_SKIP_SMUDGE", Value: "1", Public: true}) 603 build.GitInfo = common.GetSubmoduleLFSGitInfo(build.GitInfo.RepoURL) 604 605 out, err := runBuildReturningOutput(t, build) 606 assert.NoError(t, err) 607 assert.Contains(t, out, "Created fresh repository") 608 609 f, err := os.Stat(filepath.Join(build.FullProjectDir(), "lfs", "1.lfs")) 610 require.NoError(t, err) 611 assert.True(t, f.Size() < common.FilesLFSFile1LFSsize) 612 }) 613 } 614 615 func TestBuildWithGitSubmoduleStrategyNone(t *testing.T) { 616 for _, strategy := range []string{"none", ""} { 617 t.Run("strategy "+strategy, func(t *testing.T) { 618 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 619 successfulBuild, err := common.GetSuccessfulBuild() 620 assert.NoError(t, err) 621 build, cleanup := newBuild(t, successfulBuild, shell) 622 defer cleanup() 623 624 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "none"}) 625 626 out, err := runBuildReturningOutput(t, build) 627 assert.NoError(t, err) 628 assert.Contains(t, out, "Skipping Git submodules setup") 629 assert.NotContains(t, out, "Updating/initializing submodules...") 630 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 631 632 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", ".git")) 633 assert.Error(t, err, "Submodule not should have been initialized") 634 635 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", "tests", "example", ".git")) 636 assert.Error(t, err, "The submodule's submodule should not have been initialized") 637 }) 638 }) 639 } 640 } 641 642 func TestBuildWithGitSubmoduleStrategyNormal(t *testing.T) { 643 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 644 successfulBuild, err := common.GetSuccessfulBuild() 645 assert.NoError(t, err) 646 build, cleanup := newBuild(t, successfulBuild, shell) 647 defer cleanup() 648 649 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "normal"}) 650 651 out, err := runBuildReturningOutput(t, build) 652 assert.NoError(t, err) 653 assert.NotContains(t, out, "Skipping Git submodules setup") 654 assert.Contains(t, out, "Updating/initializing submodules...") 655 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 656 657 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", ".git")) 658 assert.NoError(t, err, "Submodule should have been initialized") 659 660 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", "tests", "example", ".git")) 661 assert.Error(t, err, "The submodule's submodule should not have been initialized") 662 }) 663 } 664 665 func TestBuildWithGitSubmoduleStrategyRecursive(t *testing.T) { 666 skipOnGit17x(t) 667 668 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 669 successfulBuild, err := common.GetSuccessfulBuild() 670 assert.NoError(t, err) 671 build, cleanup := newBuild(t, successfulBuild, shell) 672 defer cleanup() 673 674 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "recursive"}) 675 676 out, err := runBuildReturningOutput(t, build) 677 assert.NoError(t, err) 678 assert.NotContains(t, out, "Skipping Git submodules setup") 679 assert.NotContains(t, out, "Updating/initializing submodules...") 680 assert.Contains(t, out, "Updating/initializing submodules recursively...") 681 682 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", ".git")) 683 assert.NoError(t, err, "Submodule should have been initialized") 684 685 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", "tests", "example", ".git")) 686 assert.NoError(t, err, "The submodule's submodule should have been initialized") 687 }) 688 } 689 690 func TestBuildWithGitFetchSubmoduleStrategyRecursive(t *testing.T) { 691 skipOnGit17x(t) 692 693 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 694 successfulBuild, err := common.GetSuccessfulBuild() 695 assert.NoError(t, err) 696 build, cleanup := newBuild(t, successfulBuild, shell) 697 defer cleanup() 698 699 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 700 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "recursive"}) 701 702 out, err := runBuildReturningOutput(t, build) 703 assert.NoError(t, err) 704 assert.NotContains(t, out, "Skipping Git submodules setup") 705 assert.NotContains(t, out, "Updating/initializing submodules...") 706 assert.Contains(t, out, "Updating/initializing submodules recursively...") 707 708 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", ".git")) 709 assert.NoError(t, err, "Submodule should have been initialized") 710 711 _, err = os.Stat(filepath.Join(build.BuildDir, "gitlab-grack", "tests", "example", ".git")) 712 assert.NoError(t, err, "The submodule's submodule should have been initialized") 713 714 // Create a file not tracked that should be cleaned in submodule. 715 excludedFilePath := filepath.Join(build.BuildDir, "gitlab-grack", "excluded_file") 716 err = ioutil.WriteFile(excludedFilePath, []byte{}, os.ModePerm) 717 require.NoError(t, err) 718 719 // Run second build, to run fetch. 720 out, err = runBuildReturningOutput(t, build) 721 assert.NoError(t, err) 722 assert.NotContains(t, out, "Created fresh repository") 723 assert.Contains(t, out, "Removing excluded_file") 724 }) 725 } 726 727 func TestBuildWithGitSubmoduleStrategyInvalid(t *testing.T) { 728 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 729 successfulBuild, err := common.GetSuccessfulBuild() 730 assert.NoError(t, err) 731 build, cleanup := newBuild(t, successfulBuild, shell) 732 defer cleanup() 733 734 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "invalid"}) 735 736 out, err := runBuildReturningOutput(t, build) 737 assert.EqualError(t, err, "unknown GIT_SUBMODULE_STRATEGY") 738 assert.NotContains(t, out, "Skipping Git submodules setup") 739 assert.NotContains(t, out, "Updating/initializing submodules...") 740 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 741 }) 742 } 743 744 func TestBuildWithGitSubmoduleStrategyRecursiveAndGitStrategyNone(t *testing.T) { 745 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 746 successfulBuild, err := common.GetSuccessfulBuild() 747 assert.NoError(t, err) 748 build, cleanup := newBuild(t, successfulBuild, shell) 749 defer cleanup() 750 751 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "none"}) 752 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "recursive"}) 753 754 out, err := runBuildReturningOutput(t, build) 755 assert.NoError(t, err) 756 assert.NotContains(t, out, "Created fresh repository") 757 assert.NotContains(t, out, "Fetching changes") 758 assert.Contains(t, out, "Skipping Git repository setup") 759 assert.NotContains(t, out, "Updating/initializing submodules...") 760 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 761 assert.Contains(t, out, "Skipping Git submodules setup") 762 }) 763 } 764 765 func TestBuildWithGitSubmoduleModified(t *testing.T) { 766 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 767 successfulBuild, err := common.GetSuccessfulBuild() 768 assert.NoError(t, err) 769 build, cleanup := newBuild(t, successfulBuild, shell) 770 defer cleanup() 771 772 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "normal"}) 773 774 out, err := runBuildReturningOutput(t, build) 775 assert.NoError(t, err) 776 assert.Contains(t, out, "Updating/initializing submodules...") 777 778 submoduleDir := filepath.Join(build.BuildDir, "gitlab-grack") 779 submoduleReadme := filepath.Join(submoduleDir, "README.md") 780 781 // modify submodule and commit 782 modifySubmoduleBeforeCommit := "committed change" 783 err = ioutil.WriteFile(submoduleReadme, []byte(modifySubmoduleBeforeCommit), os.ModeSticky) 784 require.NoError(t, err) 785 _, err = gitInDir(submoduleDir, "add", "README.md") 786 assert.NoError(t, err) 787 _, err = gitInDir(submoduleDir, "config", "user.name", "test") 788 assert.NoError(t, err) 789 _, err = gitInDir(submoduleDir, "config", "user.email", "test@example.org") 790 assert.NoError(t, err) 791 _, err = gitInDir(submoduleDir, "commit", "-m", "modify submodule") 792 assert.NoError(t, err) 793 794 _, err = gitInDir(build.BuildDir, "add", "gitlab-grack") 795 assert.NoError(t, err) 796 _, err = gitInDir(build.BuildDir, "config", "user.name", "test") 797 assert.NoError(t, err) 798 _, err = gitInDir(build.BuildDir, "config", "user.email", "test@example.org") 799 assert.NoError(t, err) 800 _, err = gitInDir(build.BuildDir, "commit", "-m", "modify submodule") 801 assert.NoError(t, err) 802 803 // modify submodule without commit before second build 804 modifySubmoduleAfterCommit := "not committed change" 805 err = ioutil.WriteFile(submoduleReadme, []byte(modifySubmoduleAfterCommit), os.ModeSticky) 806 require.NoError(t, err) 807 808 build.JobResponse.AllowGitFetch = true 809 out, err = runBuildReturningOutput(t, build) 810 assert.NoError(t, err) 811 assert.NotContains(t, out, "Your local changes to the following files would be overwritten by checkout") 812 assert.NotContains(t, out, "Please commit your changes or stash them before you switch branches") 813 assert.NotContains(t, out, "Aborting") 814 assert.Contains(t, out, "Updating/initializing submodules...") 815 }) 816 } 817 818 func TestBuildWithoutDebugTrace(t *testing.T) { 819 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 820 successfulBuild, err := common.GetSuccessfulBuild() 821 assert.NoError(t, err) 822 build, cleanup := newBuild(t, successfulBuild, shell) 823 defer cleanup() 824 825 // The default build shouldn't have debug tracing enabled 826 out, err := runBuildReturningOutput(t, build) 827 assert.NoError(t, err) 828 assert.NotRegexp(t, `[^$] echo Hello World`, out) 829 }) 830 } 831 func TestBuildWithDebugTrace(t *testing.T) { 832 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 833 successfulBuild, err := common.GetSuccessfulBuild() 834 assert.NoError(t, err) 835 build, cleanup := newBuild(t, successfulBuild, shell) 836 defer cleanup() 837 838 build.Variables = append(build.Variables, common.JobVariable{Key: "CI_DEBUG_TRACE", Value: "true"}) 839 840 out, err := runBuildReturningOutput(t, build) 841 assert.NoError(t, err) 842 assert.Regexp(t, `[^$] echo Hello World`, out) 843 }) 844 } 845 846 func TestBuildMultilineCommand(t *testing.T) { 847 multilineBuild, err := common.GetMultilineBashBuild() 848 assert.NoError(t, err) 849 build, cleanup := newBuild(t, multilineBuild, "bash") 850 defer cleanup() 851 852 // The default build shouldn't have debug tracing enabled 853 out, err := runBuildReturningOutput(t, build) 854 assert.NoError(t, err) 855 assert.NotContains(t, out, "bash") 856 assert.Contains(t, out, "Hello World") 857 assert.Contains(t, out, "collapsed multi-line command") 858 } 859 860 func TestBuildWithBrokenGitSSLCAInfo(t *testing.T) { 861 skipOnGit17x(t) 862 skipOnGit(t, ">= 2.10.2") 863 864 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 865 successfulBuild, err := common.GetRemoteBrokenTLSBuild() 866 assert.NoError(t, err) 867 build, cleanup := newBuild(t, successfulBuild, shell) 868 defer cleanup() 869 870 build.Runner.URL = "https://gitlab.com" 871 872 out, err := runBuildReturningOutput(t, build) 873 assert.Error(t, err) 874 assert.Contains(t, out, "Created fresh repository") 875 assert.NotContains(t, out, "Updating/initializing submodules") 876 }) 877 } 878 879 func TestBuildWithGoodGitSSLCAInfo(t *testing.T) { 880 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 881 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 882 assert.NoError(t, err) 883 build, cleanup := newBuild(t, successfulBuild, shell) 884 defer cleanup() 885 886 build.Runner.URL = "https://gitlab.com" 887 888 out, err := runBuildReturningOutput(t, build) 889 assert.NoError(t, err) 890 assert.Contains(t, out, "Created fresh repository") 891 assert.Contains(t, out, "Updating/initializing submodules") 892 }) 893 } 894 895 // TestBuildWithGitSSLAndStrategyFetch describes issue https://gitlab.com/gitlab-org/gitlab-runner/issues/2991 896 func TestBuildWithGitSSLAndStrategyFetch(t *testing.T) { 897 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 898 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 899 assert.NoError(t, err) 900 build, cleanup := newBuild(t, successfulBuild, shell) 901 defer cleanup() 902 903 build.Runner.PreCloneScript = "echo pre-clone-script" 904 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 905 906 out, err := runBuildReturningOutput(t, build) 907 assert.NoError(t, err) 908 assert.Contains(t, out, "Created fresh repository") 909 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 910 911 out, err = runBuildReturningOutput(t, build) 912 assert.NoError(t, err) 913 assert.Contains(t, out, "Fetching changes") 914 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 915 assert.Contains(t, out, "pre-clone-script") 916 }) 917 } 918 919 func TestBuildWithUntrackedDirFromPreviousBuild(t *testing.T) { 920 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 921 successfulBuild, err := common.GetRemoteSuccessfulBuild() 922 assert.NoError(t, err) 923 build, cleanup := newBuild(t, successfulBuild, shell) 924 defer cleanup() 925 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 926 927 out, err := runBuildReturningOutput(t, build) 928 assert.NoError(t, err) 929 assert.Contains(t, out, "Created fresh repository") 930 931 err = os.MkdirAll(fmt.Sprintf("%s/.test", build.FullProjectDir()), 0644) 932 require.NoError(t, err) 933 934 out, err = runBuildReturningOutput(t, build) 935 assert.NoError(t, err) 936 assert.Contains(t, out, "Removing .test/") 937 }) 938 } 939 940 func TestBuildChangesBranchesWhenFetchingRepo(t *testing.T) { 941 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 942 successfulBuild, err := common.GetRemoteSuccessfulBuild() 943 assert.NoError(t, err) 944 build, cleanup := newBuild(t, successfulBuild, shell) 945 defer cleanup() 946 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 947 948 out, err := runBuildReturningOutput(t, build) 949 assert.NoError(t, err) 950 assert.Contains(t, out, "Created fresh repository") 951 952 // Another build using the same repo but different branch. 953 build.GitInfo = common.GetLFSGitInfo(build.GitInfo.RepoURL) 954 out, err = runBuildReturningOutput(t, build) 955 assert.NoError(t, err) 956 assert.Contains(t, out, "Checking out 2371dd05 as add-lfs-object...") 957 }) 958 } 959 960 func TestBuildPowerShellCatchesExceptions(t *testing.T) { 961 if helpers.SkipIntegrationTests(t, "powershell") { 962 t.Skip() 963 } 964 965 successfulBuild, err := common.GetRemoteSuccessfulBuild() 966 assert.NoError(t, err) 967 build, cleanup := newBuild(t, successfulBuild, "powershell") 968 defer cleanup() 969 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "Stop"}) 970 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 971 972 out, err := runBuildReturningOutput(t, build) 973 assert.NoError(t, err) 974 assert.Contains(t, out, "Created fresh repository") 975 976 out, err = runBuildReturningOutput(t, build) 977 assert.NoError(t, err) 978 assert.NotContains(t, out, "Created fresh repository") 979 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 980 981 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "Continue"}) 982 out, err = runBuildReturningOutput(t, build) 983 assert.NoError(t, err) 984 assert.NotContains(t, out, "Created fresh repository") 985 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 986 987 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "SilentlyContinue"}) 988 out, err = runBuildReturningOutput(t, build) 989 assert.NoError(t, err) 990 assert.NotContains(t, out, "Created fresh repository") 991 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 992 } 993 994 func TestInteractiveTerminal(t *testing.T) { 995 cases := []struct { 996 app string 997 shell string 998 command string 999 expectedStatusCode int 1000 }{ 1001 { 1002 app: "bash", 1003 shell: "bash", 1004 command: "sleep 5", 1005 expectedStatusCode: http.StatusSwitchingProtocols, 1006 }, 1007 { 1008 app: "cmd.exe", 1009 shell: "cmd", 1010 command: "timeout 2", 1011 expectedStatusCode: http.StatusInternalServerError, 1012 }, 1013 { 1014 app: "powershell.exe", 1015 shell: "powershell", 1016 command: "Start-Sleep -s 2", 1017 expectedStatusCode: http.StatusInternalServerError, 1018 }, 1019 } 1020 1021 for _, c := range cases { 1022 t.Run(c.shell, func(t *testing.T) { 1023 if helpers.SkipIntegrationTests(t, c.app) { 1024 t.Skip() 1025 } 1026 1027 successfulBuild, err := common.GetLocalBuildResponse(c.command) 1028 require.NoError(t, err) 1029 build, cleanup := newBuild(t, successfulBuild, c.shell) 1030 defer cleanup() 1031 sess, err := session.NewSession(nil) 1032 build.Session = sess 1033 require.NoError(t, err) 1034 1035 buildOut := make(chan string) 1036 1037 go func() { 1038 buf := bytes.NewBuffer(nil) 1039 err := runBuildWithOptions( 1040 t, 1041 build, 1042 &common.Config{SessionServer: common.SessionServer{SessionTimeout: 2}}, 1043 &common.Trace{Writer: buf}, 1044 ) 1045 require.NoError(t, err) 1046 1047 buildOut <- buf.String() 1048 }() 1049 1050 // Wait until the build starts. 1051 for build.Session.Mux() == nil { 1052 time.Sleep(10 * time.Millisecond) 1053 } 1054 1055 srv := httptest.NewServer(build.Session.Mux()) 1056 defer srv.Close() 1057 1058 u := url.URL{ 1059 Scheme: "ws", 1060 Host: srv.Listener.Addr().String(), 1061 Path: build.Session.Endpoint + "/exec", 1062 } 1063 headers := http.Header{ 1064 "Authorization": []string{build.Session.Token}, 1065 } 1066 conn, resp, err := websocket.DefaultDialer.Dial(u.String(), headers) 1067 assert.NoError(t, err) 1068 assert.Equal(t, c.expectedStatusCode, resp.StatusCode) 1069 1070 defer func() { 1071 if conn != nil { 1072 conn.Close() 1073 } 1074 }() 1075 1076 if c.expectedStatusCode == http.StatusSwitchingProtocols { 1077 _, message, err := conn.ReadMessage() 1078 assert.NoError(t, err) 1079 assert.NotEmpty(t, string(message)) 1080 1081 out := <-buildOut 1082 t.Log(out) 1083 assert.Contains(t, out, "Terminal is connected, will time out in 2s...") 1084 return 1085 } 1086 1087 out := <-buildOut 1088 t.Log(out) 1089 assert.NotContains(t, out, "Terminal is connected, will time out in 2s...") 1090 }) 1091 } 1092 } 1093 1094 func TestBuildWithGitCleanFlags(t *testing.T) { 1095 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 1096 jobResponse, err := common.GetSuccessfulBuild() 1097 assert.NoError(t, err) 1098 1099 build, cleanup := newBuild(t, jobResponse, shell) 1100 defer cleanup() 1101 1102 build.Variables = append(build.Variables, 1103 common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}, 1104 common.JobVariable{Key: "GIT_CLEAN_FLAGS", Value: "-ffdx cleanup_file"}) 1105 1106 // Run build and save file 1107 err = runBuild(t, build) 1108 require.NoError(t, err) 1109 1110 excludedFilePath := filepath.Join(build.BuildDir, "excluded_file") 1111 cleanUpFilePath := filepath.Join(build.BuildDir, "cleanup_file") 1112 1113 err = ioutil.WriteFile(excludedFilePath, []byte{}, os.ModePerm) 1114 require.NoError(t, err) 1115 err = ioutil.WriteFile(cleanUpFilePath, []byte{}, os.ModePerm) 1116 require.NoError(t, err) 1117 1118 // Re-run build and ensure that file still exists 1119 err = runBuild(t, build) 1120 require.NoError(t, err) 1121 1122 _, err = os.Stat(excludedFilePath) 1123 assert.NoError(t, err, "excluded_file does exist") 1124 _, err = os.Stat(cleanUpFilePath) 1125 assert.Error(t, err, "cleanup_file does not exist") 1126 }) 1127 }