gitlab.com/jfprevost/gitlab-runner-notlscheck@v11.11.4+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 TestBuildWithGitSubmoduleStrategyInvalid(t *testing.T) { 691 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 692 successfulBuild, err := common.GetSuccessfulBuild() 693 assert.NoError(t, err) 694 build, cleanup := newBuild(t, successfulBuild, shell) 695 defer cleanup() 696 697 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "invalid"}) 698 699 out, err := runBuildReturningOutput(t, build) 700 assert.EqualError(t, err, "unknown GIT_SUBMODULE_STRATEGY") 701 assert.NotContains(t, out, "Skipping Git submodules setup") 702 assert.NotContains(t, out, "Updating/initializing submodules...") 703 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 704 }) 705 } 706 707 func TestBuildWithGitSubmoduleStrategyRecursiveAndGitStrategyNone(t *testing.T) { 708 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 709 successfulBuild, err := common.GetSuccessfulBuild() 710 assert.NoError(t, err) 711 build, cleanup := newBuild(t, successfulBuild, shell) 712 defer cleanup() 713 714 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "none"}) 715 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "recursive"}) 716 717 out, err := runBuildReturningOutput(t, build) 718 assert.NoError(t, err) 719 assert.NotContains(t, out, "Created fresh repository") 720 assert.NotContains(t, out, "Fetching changes") 721 assert.Contains(t, out, "Skipping Git repository setup") 722 assert.NotContains(t, out, "Updating/initializing submodules...") 723 assert.NotContains(t, out, "Updating/initializing submodules recursively...") 724 assert.Contains(t, out, "Skipping Git submodules setup") 725 }) 726 } 727 728 func TestBuildWithGitSubmoduleModified(t *testing.T) { 729 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 730 successfulBuild, err := common.GetSuccessfulBuild() 731 assert.NoError(t, err) 732 build, cleanup := newBuild(t, successfulBuild, shell) 733 defer cleanup() 734 735 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_SUBMODULE_STRATEGY", Value: "normal"}) 736 737 out, err := runBuildReturningOutput(t, build) 738 assert.NoError(t, err) 739 assert.Contains(t, out, "Updating/initializing submodules...") 740 741 submoduleDir := filepath.Join(build.BuildDir, "gitlab-grack") 742 submoduleReadme := filepath.Join(submoduleDir, "README.md") 743 744 // modify submodule and commit 745 modifySubmoduleBeforeCommit := "commited change" 746 err = ioutil.WriteFile(submoduleReadme, []byte(modifySubmoduleBeforeCommit), os.ModeSticky) 747 require.NoError(t, err) 748 _, err = gitInDir(submoduleDir, "add", "README.md") 749 assert.NoError(t, err) 750 _, err = gitInDir(submoduleDir, "config", "user.name", "test") 751 assert.NoError(t, err) 752 _, err = gitInDir(submoduleDir, "config", "user.email", "test@example.org") 753 assert.NoError(t, err) 754 _, err = gitInDir(submoduleDir, "commit", "-m", "modify submodule") 755 assert.NoError(t, err) 756 757 _, err = gitInDir(build.BuildDir, "add", "gitlab-grack") 758 assert.NoError(t, err) 759 _, err = gitInDir(build.BuildDir, "config", "user.name", "test") 760 assert.NoError(t, err) 761 _, err = gitInDir(build.BuildDir, "config", "user.email", "test@example.org") 762 assert.NoError(t, err) 763 _, err = gitInDir(build.BuildDir, "commit", "-m", "modify submodule") 764 assert.NoError(t, err) 765 766 // modify submodule without commit before second build 767 modifySubmoduleAfterCommit := "not committed change" 768 err = ioutil.WriteFile(submoduleReadme, []byte(modifySubmoduleAfterCommit), os.ModeSticky) 769 require.NoError(t, err) 770 771 build.JobResponse.AllowGitFetch = true 772 out, err = runBuildReturningOutput(t, build) 773 assert.NoError(t, err) 774 assert.NotContains(t, out, "Your local changes to the following files would be overwritten by checkout") 775 assert.NotContains(t, out, "Please commit your changes or stash them before you switch branches") 776 assert.NotContains(t, out, "Aborting") 777 assert.Contains(t, out, "Updating/initializing submodules...") 778 }) 779 } 780 781 func TestBuildWithoutDebugTrace(t *testing.T) { 782 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 783 successfulBuild, err := common.GetSuccessfulBuild() 784 assert.NoError(t, err) 785 build, cleanup := newBuild(t, successfulBuild, shell) 786 defer cleanup() 787 788 // The default build shouldn't have debug tracing enabled 789 out, err := runBuildReturningOutput(t, build) 790 assert.NoError(t, err) 791 assert.NotRegexp(t, `[^$] echo Hello World`, out) 792 }) 793 } 794 func TestBuildWithDebugTrace(t *testing.T) { 795 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 796 successfulBuild, err := common.GetSuccessfulBuild() 797 assert.NoError(t, err) 798 build, cleanup := newBuild(t, successfulBuild, shell) 799 defer cleanup() 800 801 build.Variables = append(build.Variables, common.JobVariable{Key: "CI_DEBUG_TRACE", Value: "true"}) 802 803 out, err := runBuildReturningOutput(t, build) 804 assert.NoError(t, err) 805 assert.Regexp(t, `[^$] echo Hello World`, out) 806 }) 807 } 808 809 func TestBuildMultilineCommand(t *testing.T) { 810 multilineBuild, err := common.GetMultilineBashBuild() 811 assert.NoError(t, err) 812 build, cleanup := newBuild(t, multilineBuild, "bash") 813 defer cleanup() 814 815 // The default build shouldn't have debug tracing enabled 816 out, err := runBuildReturningOutput(t, build) 817 assert.NoError(t, err) 818 assert.NotContains(t, out, "bash") 819 assert.Contains(t, out, "Hello World") 820 assert.Contains(t, out, "collapsed multi-line command") 821 } 822 823 func TestBuildWithBrokenGitSSLCAInfo(t *testing.T) { 824 skipOnGit17x(t) 825 skipOnGit(t, ">= 2.10.2") 826 827 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 828 successfulBuild, err := common.GetRemoteBrokenTLSBuild() 829 assert.NoError(t, err) 830 build, cleanup := newBuild(t, successfulBuild, shell) 831 defer cleanup() 832 833 build.Runner.URL = "https://gitlab.com" 834 835 out, err := runBuildReturningOutput(t, build) 836 assert.Error(t, err) 837 assert.Contains(t, out, "Created fresh repository") 838 assert.NotContains(t, out, "Updating/initializing submodules") 839 }) 840 } 841 842 func TestBuildWithGoodGitSSLCAInfo(t *testing.T) { 843 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 844 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 845 assert.NoError(t, err) 846 build, cleanup := newBuild(t, successfulBuild, shell) 847 defer cleanup() 848 849 build.Runner.URL = "https://gitlab.com" 850 851 out, err := runBuildReturningOutput(t, build) 852 assert.NoError(t, err) 853 assert.Contains(t, out, "Created fresh repository") 854 assert.Contains(t, out, "Updating/initializing submodules") 855 }) 856 } 857 858 // TestBuildWithGitSSLAndStrategyFetch describes issue https://gitlab.com/gitlab-org/gitlab-runner/issues/2991 859 func TestBuildWithGitSSLAndStrategyFetch(t *testing.T) { 860 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 861 successfulBuild, err := common.GetRemoteGitLabComTLSBuild() 862 assert.NoError(t, err) 863 build, cleanup := newBuild(t, successfulBuild, shell) 864 defer cleanup() 865 866 build.Runner.PreCloneScript = "echo pre-clone-script" 867 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 868 869 out, err := runBuildReturningOutput(t, build) 870 assert.NoError(t, err) 871 assert.Contains(t, out, "Created fresh repository") 872 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 873 874 out, err = runBuildReturningOutput(t, build) 875 assert.NoError(t, err) 876 assert.Contains(t, out, "Fetching changes") 877 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 878 assert.Contains(t, out, "pre-clone-script") 879 }) 880 } 881 882 func TestBuildWithUntrackedDirFromPreviousBuild(t *testing.T) { 883 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 884 successfulBuild, err := common.GetRemoteSuccessfulBuild() 885 assert.NoError(t, err) 886 build, cleanup := newBuild(t, successfulBuild, shell) 887 defer cleanup() 888 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 889 890 out, err := runBuildReturningOutput(t, build) 891 assert.NoError(t, err) 892 assert.Contains(t, out, "Created fresh repository") 893 894 err = os.MkdirAll(fmt.Sprintf("%s/.test", build.FullProjectDir()), 0644) 895 require.NoError(t, err) 896 897 out, err = runBuildReturningOutput(t, build) 898 assert.NoError(t, err) 899 assert.Contains(t, out, "Removing .test/") 900 }) 901 } 902 903 func TestBuildChangesBranchesWhenFetchingRepo(t *testing.T) { 904 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 905 successfulBuild, err := common.GetRemoteSuccessfulBuild() 906 assert.NoError(t, err) 907 build, cleanup := newBuild(t, successfulBuild, shell) 908 defer cleanup() 909 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 910 911 out, err := runBuildReturningOutput(t, build) 912 assert.NoError(t, err) 913 assert.Contains(t, out, "Created fresh repository") 914 915 // Another build using the same repo but different branch. 916 build.GitInfo = common.GetLFSGitInfo(build.GitInfo.RepoURL) 917 out, err = runBuildReturningOutput(t, build) 918 assert.NoError(t, err) 919 assert.Contains(t, out, "Checking out 2371dd05 as add-lfs-object...") 920 }) 921 } 922 923 func TestBuildPowerShellCatchesExceptions(t *testing.T) { 924 if helpers.SkipIntegrationTests(t, "powershell") { 925 t.Skip() 926 } 927 928 successfulBuild, err := common.GetRemoteSuccessfulBuild() 929 assert.NoError(t, err) 930 build, cleanup := newBuild(t, successfulBuild, "powershell") 931 defer cleanup() 932 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "Stop"}) 933 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}) 934 935 out, err := runBuildReturningOutput(t, build) 936 assert.NoError(t, err) 937 assert.Contains(t, out, "Created fresh repository") 938 939 out, err = runBuildReturningOutput(t, build) 940 assert.NoError(t, err) 941 assert.NotContains(t, out, "Created fresh repository") 942 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 943 944 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "Continue"}) 945 out, err = runBuildReturningOutput(t, build) 946 assert.NoError(t, err) 947 assert.NotContains(t, out, "Created fresh repository") 948 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 949 950 build.Variables = append(build.Variables, common.JobVariable{Key: "ErrorActionPreference", Value: "SilentlyContinue"}) 951 out, err = runBuildReturningOutput(t, build) 952 assert.NoError(t, err) 953 assert.NotContains(t, out, "Created fresh repository") 954 assert.Regexp(t, "Checking out [a-f0-9]+ as", out) 955 } 956 957 func TestInteractiveTerminal(t *testing.T) { 958 cases := []struct { 959 app string 960 shell string 961 command string 962 expectedStatusCode int 963 }{ 964 { 965 app: "bash", 966 shell: "bash", 967 command: "sleep 5", 968 expectedStatusCode: http.StatusSwitchingProtocols, 969 }, 970 { 971 app: "cmd.exe", 972 shell: "cmd", 973 command: "timeout 2", 974 expectedStatusCode: http.StatusInternalServerError, 975 }, 976 { 977 app: "powershell.exe", 978 shell: "powershell", 979 command: "Start-Sleep -s 2", 980 expectedStatusCode: http.StatusInternalServerError, 981 }, 982 } 983 984 for _, c := range cases { 985 t.Run(c.shell, func(t *testing.T) { 986 if helpers.SkipIntegrationTests(t, c.app) { 987 t.Skip() 988 } 989 990 successfulBuild, err := common.GetLocalBuildResponse(c.command) 991 require.NoError(t, err) 992 build, cleanup := newBuild(t, successfulBuild, c.shell) 993 defer cleanup() 994 sess, err := session.NewSession(nil) 995 build.Session = sess 996 require.NoError(t, err) 997 998 buildOut := make(chan string) 999 1000 go func() { 1001 buf := bytes.NewBuffer(nil) 1002 err := runBuildWithOptions( 1003 t, 1004 build, 1005 &common.Config{SessionServer: common.SessionServer{SessionTimeout: 2}}, 1006 &common.Trace{Writer: buf}, 1007 ) 1008 require.NoError(t, err) 1009 1010 buildOut <- buf.String() 1011 }() 1012 1013 // Wait until the build starts. 1014 for build.Session.Mux() == nil { 1015 time.Sleep(10 * time.Millisecond) 1016 } 1017 1018 srv := httptest.NewServer(build.Session.Mux()) 1019 defer srv.Close() 1020 1021 u := url.URL{ 1022 Scheme: "ws", 1023 Host: srv.Listener.Addr().String(), 1024 Path: build.Session.Endpoint + "/exec", 1025 } 1026 headers := http.Header{ 1027 "Authorization": []string{build.Session.Token}, 1028 } 1029 conn, resp, err := websocket.DefaultDialer.Dial(u.String(), headers) 1030 assert.NoError(t, err) 1031 assert.Equal(t, c.expectedStatusCode, resp.StatusCode) 1032 1033 defer func() { 1034 if conn != nil { 1035 conn.Close() 1036 } 1037 }() 1038 1039 if c.expectedStatusCode == http.StatusSwitchingProtocols { 1040 _, message, err := conn.ReadMessage() 1041 assert.NoError(t, err) 1042 assert.NotEmpty(t, string(message)) 1043 1044 out := <-buildOut 1045 t.Log(out) 1046 assert.Contains(t, out, "Terminal is connected, will time out in 2s...") 1047 return 1048 } 1049 1050 out := <-buildOut 1051 t.Log(out) 1052 assert.NotContains(t, out, "Terminal is connected, will time out in 2s...") 1053 }) 1054 } 1055 } 1056 1057 func TestBuildWithGitCleanFlags(t *testing.T) { 1058 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 1059 jobResponse, err := common.GetSuccessfulBuild() 1060 assert.NoError(t, err) 1061 1062 build, cleanup := newBuild(t, jobResponse, shell) 1063 defer cleanup() 1064 1065 build.Variables = append(build.Variables, 1066 common.JobVariable{Key: "GIT_STRATEGY", Value: "fetch"}, 1067 common.JobVariable{Key: "GIT_CLEAN_FLAGS", Value: "-ffdx cleanup_file"}) 1068 1069 // Run build and save file 1070 err = runBuild(t, build) 1071 require.NoError(t, err) 1072 1073 excludedFilePath := filepath.Join(build.BuildDir, "excluded_file") 1074 cleanUpFilePath := filepath.Join(build.BuildDir, "cleanup_file") 1075 1076 err = ioutil.WriteFile(excludedFilePath, []byte{}, os.ModePerm) 1077 require.NoError(t, err) 1078 err = ioutil.WriteFile(cleanUpFilePath, []byte{}, os.ModePerm) 1079 require.NoError(t, err) 1080 1081 // Re-run build and ensure that file still exists 1082 err = runBuild(t, build) 1083 require.NoError(t, err) 1084 1085 _, err = os.Stat(excludedFilePath) 1086 assert.NoError(t, err, "excluded_file does exist") 1087 _, err = os.Stat(cleanUpFilePath) 1088 assert.Error(t, err, "cleanup_file does not exist") 1089 }) 1090 } 1091 1092 // TODO: Remove in 12.0 1093 func TestBuildNoRefspecs(t *testing.T) { 1094 strategies := []string{"clone", "fetch", "none"} 1095 expectedOutputs := map[string]string{ 1096 "clone": "Cloning repository...", 1097 "fetch": "Fetching changes...", 1098 "none": "Skipping Git repository setup", 1099 } 1100 1101 shellstest.OnEachShell(t, func(t *testing.T, shell string) { 1102 for _, strategy := range strategies { 1103 t.Run(fmt.Sprintf("GIT_STRATEGY %s", strategy), func(t *testing.T) { 1104 apiBuild, err := common.GetSuccessfulBuild() 1105 assert.NoError(t, err) 1106 1107 apiBuild.GitInfo.Refspecs = []string{} 1108 build, cleanup := newBuild(t, apiBuild, shell) 1109 defer cleanup() 1110 1111 build.Variables = append(build.Variables, common.JobVariable{Key: "GIT_STRATEGY", Value: strategy}) 1112 1113 expectedOutput, ok := expectedOutputs[strategy] 1114 require.True(t, ok, "missing expectancies for %s strategy", strategy) 1115 1116 err = runBuild(t, build) 1117 require.NoError(t, err) 1118 1119 // run twice because script behavior changes based on build directory existence 1120 out, err := runBuildReturningOutput(t, build) 1121 require.NoError(t, err) 1122 require.Contains(t, out, expectedOutput) 1123 1124 for s, notExpected := range expectedOutputs { 1125 if s == strategy { 1126 continue 1127 } 1128 1129 require.NotContains(t, out, notExpected) 1130 } 1131 }) 1132 } 1133 }) 1134 }