kubesphere.io/s2irun@v3.2.1+incompatible/pkg/build/strategies/sti/sti_test.go (about) 1 package sti 2 3 import ( 4 "errors" 5 "fmt" 6 "io" 7 "path/filepath" 8 "reflect" 9 "regexp/syntax" 10 "strings" 11 "testing" 12 13 "github.com/kubesphere/s2irun/pkg/api" 14 "github.com/kubesphere/s2irun/pkg/api/constants" 15 "github.com/kubesphere/s2irun/pkg/build" 16 "github.com/kubesphere/s2irun/pkg/docker" 17 s2ierr "github.com/kubesphere/s2irun/pkg/errors" 18 "github.com/kubesphere/s2irun/pkg/ignore" 19 "github.com/kubesphere/s2irun/pkg/scm/downloaders/empty" 20 "github.com/kubesphere/s2irun/pkg/scm/downloaders/file" 21 gitdownloader "github.com/kubesphere/s2irun/pkg/scm/downloaders/git" 22 "github.com/kubesphere/s2irun/pkg/scm/git" 23 "github.com/kubesphere/s2irun/pkg/test" 24 testfs "github.com/kubesphere/s2irun/pkg/test/fs" 25 "github.com/kubesphere/s2irun/pkg/utils/fs" 26 ) 27 28 type FakeSTI struct { 29 CleanupCalled bool 30 PrepareCalled bool 31 SetupRequired []string 32 SetupOptional []string 33 SetupError error 34 ExistsCalled bool 35 ExistsError error 36 BuildRequest *api.Config 37 BuildResult *api.Result 38 DownloadError error 39 SaveArtifactsCalled bool 40 SaveArtifactsError error 41 FetchSourceCalled bool 42 FetchSourceError error 43 ExecuteCommand string 44 ExecuteUser string 45 ExecuteError error 46 ExpectedError bool 47 LayeredBuildCalled bool 48 LayeredBuildError error 49 PostExecuteDestination string 50 PostExecuteContainerID string 51 PostExecuteError error 52 } 53 54 func newFakeBaseSTI() *STI { 55 return &STI{ 56 config: &api.Config{}, 57 result: &api.Result{}, 58 docker: &docker.FakeDocker{}, 59 installer: &test.FakeInstaller{}, 60 git: &test.FakeGit{}, 61 fs: &testfs.FakeFileSystem{}, 62 tar: &test.FakeTar{}, 63 } 64 } 65 66 func newFakeSTI(f *FakeSTI) *STI { 67 s := &STI{ 68 config: &api.Config{}, 69 result: &api.Result{}, 70 docker: &docker.FakeDocker{}, 71 runtimeDocker: &docker.FakeDocker{}, 72 installer: &test.FakeInstaller{}, 73 git: &test.FakeGit{}, 74 fs: &testfs.FakeFileSystem{}, 75 tar: &test.FakeTar{}, 76 preparer: f, 77 ignorer: &ignore.DockerIgnorer{}, 78 artifacts: f, 79 scripts: f, 80 garbage: f, 81 layered: &FakeDockerBuild{f}, 82 } 83 s.source = &gitdownloader.Clone{Git: s.git, FileSystem: s.fs} 84 return s 85 } 86 87 func (f *FakeSTI) Cleanup(*api.Config) { 88 f.CleanupCalled = true 89 } 90 91 func (f *FakeSTI) Prepare(*api.Config) error { 92 f.PrepareCalled = true 93 f.SetupRequired = []string{constants.Assemble, constants.Run} 94 f.SetupOptional = []string{constants.SaveArtifacts} 95 return nil 96 } 97 98 func (f *FakeSTI) Exists(*api.Config) bool { 99 f.ExistsCalled = true 100 return true 101 } 102 103 func (f *FakeSTI) Request() *api.Config { 104 return f.BuildRequest 105 } 106 107 func (f *FakeSTI) Result() *api.Result { 108 return f.BuildResult 109 } 110 111 func (f *FakeSTI) Save(*api.Config) error { 112 f.SaveArtifactsCalled = true 113 return f.SaveArtifactsError 114 } 115 116 func (f *FakeSTI) fetchSource() error { 117 return f.FetchSourceError 118 } 119 120 func (f *FakeSTI) Download(*api.Config) (*git.SourceInfo, error) { 121 return nil, f.DownloadError 122 } 123 124 func (f *FakeSTI) Execute(command string, user string, r *api.Config) error { 125 f.ExecuteCommand = command 126 f.ExecuteUser = user 127 return f.ExecuteError 128 } 129 130 func (f *FakeSTI) wasExpectedError(text string) bool { 131 return f.ExpectedError 132 } 133 134 func (f *FakeSTI) PostExecute(id, destination string) error { 135 f.PostExecuteContainerID = id 136 f.PostExecuteDestination = destination 137 return f.PostExecuteError 138 } 139 140 type FakeDockerBuild struct { 141 *FakeSTI 142 } 143 144 func (f *FakeDockerBuild) Build(*api.Config) (*api.Result, error) { 145 f.LayeredBuildCalled = true 146 return &api.Result{}, f.LayeredBuildError 147 } 148 149 func TestDefaultSource(t *testing.T) { 150 config := &api.Config{ 151 Source: git.MustParse("."), 152 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 153 } 154 client, err := docker.NewEngineAPIClient(config.DockerConfig) 155 if err != nil { 156 t.Fatal(err) 157 } 158 sti, err := New(client, config, fs.NewFileSystem(), build.Overrides{}) 159 if err != nil { 160 t.Fatal(err) 161 } 162 if config.Source == nil { 163 t.Errorf("Config.Source not set: %v", config.Source) 164 } 165 if _, ok := sti.source.(*file.File); !ok || sti.source == nil { 166 t.Errorf("Source interface not set: %#v", sti.source) 167 } 168 } 169 170 func TestEmptySource(t *testing.T) { 171 config := &api.Config{ 172 Source: nil, 173 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 174 } 175 client, err := docker.NewEngineAPIClient(config.DockerConfig) 176 if err != nil { 177 t.Fatal(err) 178 } 179 sti, err := New(client, config, fs.NewFileSystem(), build.Overrides{}) 180 if err != nil { 181 t.Fatal(err) 182 } 183 if config.Source != nil { 184 t.Errorf("Config.Source unexpectantly changed: %v", config.Source) 185 } 186 if _, ok := sti.source.(*empty.Noop); !ok || sti.source == nil { 187 t.Errorf("Source interface not set: %#v", sti.source) 188 } 189 } 190 191 func TestOverrides(t *testing.T) { 192 fd := &FakeSTI{} 193 client, err := docker.NewEngineAPIClient(&api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}) 194 if err != nil { 195 t.Fatal(err) 196 } 197 sti, err := New(client, 198 &api.Config{ 199 DockerConfig: &api.DockerConfig{Endpoint: "unix:///var/run/docker.sock"}, 200 }, 201 fs.NewFileSystem(), 202 build.Overrides{ 203 Downloader: fd, 204 }, 205 ) 206 if err != nil { 207 t.Fatal(err) 208 } 209 if sti.source != fd { 210 t.Errorf("Override of downloader not set: %#v", sti) 211 } 212 } 213 214 func TestBuild(t *testing.T) { 215 incrementalTest := []bool{false, true} 216 for _, incremental := range incrementalTest { 217 fh := &FakeSTI{ 218 BuildRequest: &api.Config{Incremental: incremental}, 219 BuildResult: &api.Result{}, 220 } 221 222 builder := newFakeSTI(fh) 223 builder.Build(&api.Config{Incremental: incremental}) 224 225 // Verify the right scripts were configed 226 if !reflect.DeepEqual(fh.SetupRequired, []string{constants.Assemble, constants.Run}) { 227 t.Errorf("Unexpected required scripts configured: %#v", fh.SetupRequired) 228 } 229 if !reflect.DeepEqual(fh.SetupOptional, []string{constants.SaveArtifacts}) { 230 t.Errorf("Unexpected optional scripts configured: %#v", fh.SetupOptional) 231 } 232 233 // Verify that Exists was called 234 if !fh.ExistsCalled { 235 t.Errorf("Exists was not called.") 236 } 237 238 // Verify that Save was called for an incremental build 239 if incremental && !fh.SaveArtifactsCalled { 240 t.Errorf("Save artifacts was not called for an incremental build") 241 } 242 243 // Verify that Execute was called with the right script 244 if fh.ExecuteCommand != constants.Assemble { 245 t.Errorf("Unexpected execute command: %s", fh.ExecuteCommand) 246 } 247 } 248 } 249 250 func TestLayeredBuild(t *testing.T) { 251 fh := &FakeSTI{ 252 BuildRequest: &api.Config{ 253 BuilderImage: "testimage", 254 }, 255 BuildResult: &api.Result{ 256 BuildInfo: api.BuildInfo{ 257 Stages: []api.StageInfo{}, 258 }, 259 }, 260 ExecuteError: errMissingRequirements, 261 ExpectedError: true, 262 } 263 builder := newFakeSTI(fh) 264 builder.Build(&api.Config{BuilderImage: "testimage"}) 265 // Verify layered build 266 if !fh.LayeredBuildCalled { 267 t.Errorf("Layered build was not called.") 268 } 269 } 270 271 func TestBuildErrorExecute(t *testing.T) { 272 fh := &FakeSTI{ 273 BuildRequest: &api.Config{ 274 BuilderImage: "testimage", 275 }, 276 BuildResult: &api.Result{}, 277 ExecuteError: errors.New("ExecuteError"), 278 ExpectedError: false, 279 } 280 builder := newFakeSTI(fh) 281 _, err := builder.Build(&api.Config{BuilderImage: "testimage"}) 282 if err == nil || err.Error() != "ExecuteError" { 283 t.Errorf("An error was expected, but got different %v", err) 284 } 285 } 286 287 func TestWasExpectedError(t *testing.T) { 288 type expErr struct { 289 text string 290 expected bool 291 } 292 293 tests := []expErr{ 294 { // 0 - tar error 295 text: `/bin/sh: tar: not found`, 296 expected: true, 297 }, 298 { // 1 - tar error 299 text: `/bin/sh: tar: command not found`, 300 expected: true, 301 }, 302 { // 2 - /bin/sh error 303 text: `exec: "/bin/sh": stat /bin/sh: no such file or directory`, 304 expected: true, 305 }, 306 { // 3 - non container error 307 text: "other error", 308 expected: false, 309 }, 310 } 311 312 for i, ti := range tests { 313 result := isMissingRequirements(ti.text) 314 if result != ti.expected { 315 t.Errorf("(%d) Unexpected result: %v. Expected: %v", i, result, ti.expected) 316 } 317 } 318 } 319 320 func testBuildHandler() *STI { 321 s := &STI{ 322 docker: &docker.FakeDocker{}, 323 incrementalDocker: &docker.FakeDocker{}, 324 installer: &test.FakeInstaller{}, 325 git: &test.FakeGit{}, 326 fs: &testfs.FakeFileSystem{ExistsResult: map[string]bool{filepath.FromSlash("a-repo-source"): true}}, 327 tar: &test.FakeTar{}, 328 config: &api.Config{}, 329 result: &api.Result{}, 330 callbackInvoker: &test.FakeCallbackInvoker{}, 331 } 332 s.source = &gitdownloader.Clone{Git: s.git, FileSystem: s.fs} 333 s.initPostExecutorSteps() 334 return s 335 } 336 337 func TestPostExecute(t *testing.T) { 338 type postExecuteTest struct { 339 tag string 340 incremental bool 341 previousImageID string 342 scriptsFromImage bool 343 } 344 testCases := []postExecuteTest{ 345 // 0: tagged, incremental, without previous image 346 {"test/tag", true, "", true}, 347 // 1: tagged, incremental, with previous image 348 {"test/tag", true, "test-image", true}, 349 // 2: tagged, no incremental, without previous image 350 {"test/tag", false, "", true}, 351 // 3: tagged, no incremental, with previous image 352 {"test/tag", false, "test-image", true}, 353 354 // 4: no tag, incremental, without previous image 355 {"", true, "", false}, 356 // 5: no tag, incremental, with previous image 357 {"", true, "test-image", false}, 358 // 6: no tag, no incremental, without previous image 359 {"", false, "", false}, 360 // 7: no tag, no incremental, with previous image 361 {"", false, "test-image", false}, 362 } 363 364 for i, tc := range testCases { 365 bh := testBuildHandler() 366 containerID := "test-container-id" 367 bh.result.Messages = []string{"one", "two"} 368 bh.config.Tag = tc.tag 369 bh.config.Incremental = tc.incremental 370 dh := bh.docker.(*docker.FakeDocker) 371 if tc.previousImageID != "" { 372 bh.config.RemovePreviousImage = true 373 bh.incremental = tc.incremental 374 bh.docker.(*docker.FakeDocker).GetImageIDResult = tc.previousImageID 375 } 376 if tc.scriptsFromImage { 377 bh.scriptsURL = map[string]string{constants.Run: "image:///usr/libexec/s2i/run"} 378 } 379 err := bh.PostExecute(containerID, "cmd1") 380 if err != nil { 381 t.Errorf("(%d) Unexpected error from postExecute: %v", i, err) 382 } 383 // Ensure CommitContainer was called with the right parameters 384 expectedCmd := []string{"cmd1/scripts/" + constants.Run} 385 if tc.scriptsFromImage { 386 expectedCmd = []string{"/usr/libexec/s2i/" + constants.Run} 387 } 388 if !reflect.DeepEqual(dh.CommitContainerOpts.Command, expectedCmd) { 389 t.Errorf("(%d) Unexpected commit container command: %#v, expected %q", i, dh.CommitContainerOpts.Command, expectedCmd) 390 } 391 if dh.CommitContainerOpts.Repository != tc.tag { 392 t.Errorf("(%d) Unexpected tag committed, expected %q, got %q", i, tc.tag, dh.CommitContainerOpts.Repository) 393 } 394 // Ensure image removal when incremental and previousImageID present 395 if tc.incremental && tc.previousImageID != "" { 396 if dh.RemoveImageName != "test-image" { 397 t.Errorf("(%d) Previous image was not removed: %q", i, dh.RemoveImageName) 398 } 399 } else { 400 if dh.RemoveImageName != "" { 401 t.Errorf("(%d) Unexpected image removed: %s", i, dh.RemoveImageName) 402 } 403 } 404 } 405 } 406 407 func TestExists(t *testing.T) { 408 type incrementalTest struct { 409 // incremental flag was passed 410 incremental bool 411 // previous image existence 412 previousImage bool 413 // script installed 414 scriptInstalled bool 415 // expected result 416 expected bool 417 } 418 419 tests := []incrementalTest{ 420 // 0-1: incremental, no image, no matter what with scripts 421 {true, false, false, false}, 422 {true, false, true, false}, 423 424 // 2: incremental, previous image, no scripts 425 {true, true, false, false}, 426 // 3: incremental, previous image, scripts installed 427 {true, true, true, true}, 428 429 // 4-7: no incremental build - should always return false no matter what other flags are 430 {false, false, false, false}, 431 {false, false, true, false}, 432 {false, true, false, false}, 433 {false, true, true, false}, 434 } 435 436 for i, ti := range tests { 437 bh := testBuildHandler() 438 bh.config.WorkingDir = "/working-dir" 439 bh.config.Incremental = ti.incremental 440 bh.config.BuilderPullPolicy = api.PullAlways 441 bh.installedScripts = map[string]bool{constants.SaveArtifacts: ti.scriptInstalled} 442 bh.incrementalDocker.(*docker.FakeDocker).PullResult = ti.previousImage 443 bh.config.DockerConfig = &api.DockerConfig{Endpoint: "http://localhost:4243"} 444 incremental := bh.Exists(bh.config) 445 if incremental != ti.expected { 446 t.Errorf("(%d) Unexpected incremental result: %v. Expected: %v", 447 i, incremental, ti.expected) 448 } 449 if ti.incremental && ti.previousImage && ti.scriptInstalled { 450 if len(bh.fs.(*testfs.FakeFileSystem).ExistsFile) == 0 { 451 continue 452 } 453 scriptChecked := bh.fs.(*testfs.FakeFileSystem).ExistsFile[0] 454 expectedScript := "/working-dir/upload/scripts/save-artifacts" 455 if scriptChecked != expectedScript { 456 t.Errorf("(%d) Unexpected script checked. Actual: %s. Expected: %s", 457 i, scriptChecked, expectedScript) 458 } 459 } 460 } 461 } 462 463 func TestSaveArtifacts(t *testing.T) { 464 bh := testBuildHandler() 465 bh.config.WorkingDir = "/working-dir" 466 bh.config.Tag = "image/tag" 467 fakeFS := bh.fs.(*testfs.FakeFileSystem) 468 fd := bh.docker.(*docker.FakeDocker) 469 th := bh.tar.(*test.FakeTar) 470 err := bh.Save(bh.config) 471 if err != nil { 472 t.Errorf("Unexpected error when saving artifacts: %v", err) 473 } 474 expectedArtifactDir := "/working-dir/upload/artifacts" 475 if filepath.ToSlash(fakeFS.MkdirDir) != expectedArtifactDir { 476 t.Errorf("Mkdir was not called with the expected directory: %s", 477 fakeFS.MkdirDir) 478 } 479 if fd.RunContainerOpts.Image != bh.config.Tag { 480 t.Errorf("Unexpected image sent to RunContainer: %s", 481 fd.RunContainerOpts.Image) 482 } 483 if filepath.ToSlash(th.ExtractTarDir) != expectedArtifactDir || th.ExtractTarReader == nil { 484 t.Errorf("ExtractTar was not called with the expected parameters.") 485 } 486 } 487 488 func TestSaveArtifactsCustomTag(t *testing.T) { 489 bh := testBuildHandler() 490 bh.config.WorkingDir = "/working-dir" 491 bh.config.IncrementalFromTag = "custom/tag" 492 bh.config.Tag = "image/tag" 493 fakeFS := bh.fs.(*testfs.FakeFileSystem) 494 fd := bh.docker.(*docker.FakeDocker) 495 th := bh.tar.(*test.FakeTar) 496 err := bh.Save(bh.config) 497 if err != nil { 498 t.Errorf("Unexpected error when saving artifacts: %v", err) 499 } 500 expectedArtifactDir := "/working-dir/upload/artifacts" 501 if filepath.ToSlash(fakeFS.MkdirDir) != expectedArtifactDir { 502 t.Errorf("Mkdir was not called with the expected directory: %s", 503 fakeFS.MkdirDir) 504 } 505 if fd.RunContainerOpts.Image != bh.config.IncrementalFromTag { 506 t.Errorf("Unexpected image sent to RunContainer: %s", 507 fd.RunContainerOpts.Image) 508 } 509 if filepath.ToSlash(th.ExtractTarDir) != expectedArtifactDir || th.ExtractTarReader == nil { 510 t.Errorf("ExtractTar was not called with the expected parameters.") 511 } 512 } 513 514 func TestSaveArtifactsRunError(t *testing.T) { 515 tests := []error{ 516 fmt.Errorf("Run error"), 517 s2ierr.NewContainerError("", -1, ""), 518 } 519 expected := []error{ 520 tests[0], 521 s2ierr.NewSaveArtifactsError("", "", tests[1]), 522 } 523 // test with tar extract error or not 524 tarError := []bool{true, false} 525 for i := range tests { 526 for _, te := range tarError { 527 bh := testBuildHandler() 528 fd := bh.docker.(*docker.FakeDocker) 529 th := bh.tar.(*test.FakeTar) 530 fd.RunContainerError = tests[i] 531 if te { 532 th.ExtractTarError = fmt.Errorf("tar error") 533 } 534 err := bh.Save(bh.config) 535 if !te && err != expected[i] { 536 t.Errorf("Unexpected error returned from saveArtifacts: %v", err) 537 } else if te && err != th.ExtractTarError { 538 t.Errorf("Expected tar error. Got %v", err) 539 } 540 } 541 } 542 } 543 544 func TestSaveArtifactsErrorBeforeStart(t *testing.T) { 545 bh := testBuildHandler() 546 fd := bh.docker.(*docker.FakeDocker) 547 expected := fmt.Errorf("run error") 548 fd.RunContainerError = expected 549 fd.RunContainerErrorBeforeStart = true 550 err := bh.Save(bh.config) 551 if err != expected { 552 t.Errorf("Unexpected error returned from saveArtifacts: %v", err) 553 } 554 } 555 556 func TestSaveArtifactsExtractError(t *testing.T) { 557 bh := testBuildHandler() 558 th := bh.tar.(*test.FakeTar) 559 expected := fmt.Errorf("extract error") 560 th.ExtractTarError = expected 561 err := bh.Save(bh.config) 562 if err != expected { 563 t.Errorf("Unexpected error returned from saveArtifacts: %v", err) 564 } 565 } 566 567 func TestFetchSource(t *testing.T) { 568 type fetchTest struct { 569 refSpecified bool 570 checkoutExpected bool 571 } 572 573 tests := []fetchTest{ 574 // 0 575 { 576 refSpecified: false, 577 checkoutExpected: false, 578 }, 579 // 1 580 { 581 refSpecified: true, 582 checkoutExpected: true, 583 }, 584 } 585 586 for testNum, ft := range tests { 587 bh := testBuildHandler() 588 gh := bh.git.(*test.FakeGit) 589 590 bh.config.WorkingDir = "/working-dir" 591 bh.config.Source = git.MustParse("a-repo-source") 592 if ft.refSpecified { 593 bh.config.RevisionId = "a-branch" 594 } 595 596 expectedTargetDir := "/working-dir/upload/src" 597 _, e := bh.source.Download(bh.config) 598 if e != nil { 599 t.Errorf("Unexpected error %v [%d]", e, testNum) 600 } 601 if gh.CloneSource.StringNoFragment() != "a-repo-source" { 602 t.Errorf("Clone was not called with the expected source. Got %s, expected %s [%d]", gh.CloneSource, "a-source-repo-source", testNum) 603 } 604 if filepath.ToSlash(gh.CloneTarget) != expectedTargetDir { 605 t.Errorf("Unexpected target directory for clone operation. Got %s, expected %s [%d]", gh.CloneTarget, expectedTargetDir, testNum) 606 } 607 if ft.checkoutExpected { 608 if gh.CheckoutRef != "a-branch" { 609 t.Errorf("Checkout was not called with the expected branch. Got %s, expected %s [%d]", gh.CheckoutRef, "a-branch", testNum) 610 } 611 if filepath.ToSlash(gh.CheckoutRepo) != expectedTargetDir { 612 t.Errorf("Unexpected target repository for checkout operation. Got %s, expected %s [%d]", gh.CheckoutRepo, expectedTargetDir, testNum) 613 } 614 } 615 } 616 } 617 618 func TestPrepareOK(t *testing.T) { 619 rh := newFakeSTI(&FakeSTI{}) 620 rh.SetScripts([]string{constants.Assemble, constants.Run}, []string{constants.SaveArtifacts}) 621 rh.fs.(*testfs.FakeFileSystem).WorkingDirResult = "/working-dir" 622 err := rh.Prepare(rh.config) 623 if err != nil { 624 t.Errorf("An error occurred setting up the config handler: %v", err) 625 } 626 if !rh.fs.(*testfs.FakeFileSystem).WorkingDirCalled { 627 t.Errorf("Working directory was not created.") 628 } 629 var expected []string 630 for _, dir := range workingDirs { 631 expected = append(expected, filepath.FromSlash("/working-dir/"+dir)) 632 } 633 mkdirs := rh.fs.(*testfs.FakeFileSystem).MkdirAllDir 634 if !reflect.DeepEqual(mkdirs, expected) { 635 t.Errorf("Unexpected set of MkdirAll calls: %#v", mkdirs) 636 } 637 scripts := rh.installer.(*test.FakeInstaller).Scripts 638 if !reflect.DeepEqual(scripts[0], []string{constants.Assemble, constants.Run}) { 639 t.Errorf("Unexpected set of required scripts: %#v", scripts[0]) 640 } 641 if !reflect.DeepEqual(scripts[1], []string{constants.SaveArtifacts}) { 642 t.Errorf("Unexpected set of optional scripts: %#v", scripts[1]) 643 } 644 } 645 646 func TestPrepareErrorCreatingWorkingDir(t *testing.T) { 647 rh := newFakeSTI(&FakeSTI{}) 648 rh.fs.(*testfs.FakeFileSystem).WorkingDirError = errors.New("WorkingDirError") 649 err := rh.Prepare(rh.config) 650 if err == nil || err.Error() != "WorkingDirError" { 651 t.Errorf("An error was expected for WorkingDir, but got different: %v", err) 652 } 653 } 654 655 func TestPrepareErrorMkdirAll(t *testing.T) { 656 rh := newFakeSTI(&FakeSTI{}) 657 rh.fs.(*testfs.FakeFileSystem).MkdirAllError = errors.New("MkdirAllError") 658 err := rh.Prepare(rh.config) 659 if err == nil || err.Error() != "MkdirAllError" { 660 t.Errorf("An error was expected for MkdirAll, but got different: %v", err) 661 } 662 } 663 664 func TestPrepareErrorRequiredDownloadAndInstall(t *testing.T) { 665 rh := newFakeSTI(&FakeSTI{}) 666 rh.SetScripts([]string{constants.Assemble, constants.Run}, []string{constants.SaveArtifacts}) 667 rh.installer.(*test.FakeInstaller).Error = fmt.Errorf("%v", constants.Assemble) 668 err := rh.Prepare(rh.config) 669 if err == nil || err.Error() != constants.Assemble { 670 t.Errorf("An error was expected for required DownloadAndInstall, but got different: %v", err) 671 } 672 } 673 674 func TestPrepareErrorOptionalDownloadAndInstall(t *testing.T) { 675 rh := newFakeSTI(&FakeSTI{}) 676 rh.SetScripts([]string{constants.Assemble, constants.Run}, []string{constants.SaveArtifacts}) 677 err := rh.Prepare(rh.config) 678 if err != nil { 679 t.Errorf("Unexpected error when downloading optional scripts: %v", err) 680 } 681 } 682 683 func TestPrepareUseCustomRuntimeArtifacts(t *testing.T) { 684 expectedMapping := filepath.FromSlash("/src") + ":dst" 685 686 builder := newFakeSTI(&FakeSTI{}) 687 688 config := builder.config 689 config.RuntimeImage = "my-app" 690 config.RuntimeArtifacts.Set(expectedMapping) 691 692 if err := builder.Prepare(config); err != nil { 693 t.Fatalf("Prepare() unexpectedly failed with error: %v", err) 694 } 695 696 if actualMapping := config.RuntimeArtifacts.String(); actualMapping != expectedMapping { 697 t.Errorf("Prepare() shouldn't change mapping, but it was modified from %v to %v", expectedMapping, actualMapping) 698 } 699 } 700 701 func TestPrepareFailForEmptyRuntimeArtifacts(t *testing.T) { 702 builder := newFakeSTI(&FakeSTI{}) 703 704 fakeDocker := builder.docker.(*docker.FakeDocker) 705 fakeDocker.AssembleInputFilesResult = "" 706 707 config := builder.config 708 config.RuntimeImage = "my-app" 709 710 if len(config.RuntimeArtifacts) > 0 { 711 t.Fatalf("RuntimeArtifacts must be empty by default") 712 } 713 714 err := builder.Prepare(config) 715 if err == nil { 716 t.Errorf("Prepare() should fail but it didn't") 717 718 } else if expectedError := "no runtime artifacts to copy"; !strings.Contains(err.Error(), expectedError) { 719 t.Errorf("Prepare() should fail with error that contains text %q but failed with error: %q", expectedError, err) 720 } 721 } 722 723 func TestPrepareRuntimeArtifactsValidation(t *testing.T) { 724 testCases := []struct { 725 mapping string 726 expectedError string 727 }{ 728 { 729 mapping: "src:dst", 730 expectedError: "source must be an absolute path", 731 }, 732 { 733 mapping: "/src:/dst", 734 expectedError: "destination must be a relative path", 735 }, 736 { 737 mapping: "/src:../dst", 738 expectedError: "destination cannot start with '..'", 739 }, 740 } 741 742 for _, testCase := range testCases { 743 for _, mappingFromUser := range []bool{true, false} { 744 builder := newFakeSTI(&FakeSTI{}) 745 746 config := builder.config 747 config.RuntimeImage = "my-app" 748 749 if mappingFromUser { 750 config.RuntimeArtifacts.Set(testCase.mapping) 751 } else { 752 fakeDocker := builder.docker.(*docker.FakeDocker) 753 fakeDocker.AssembleInputFilesResult = testCase.mapping 754 } 755 756 err := builder.Prepare(config) 757 if err == nil { 758 t.Errorf("Prepare() should fail but it didn't") 759 760 } else if !strings.Contains(err.Error(), testCase.expectedError) { 761 t.Errorf("Prepare() should fail to validate mapping %q with error that contains text %q but failed with error: %q", testCase.mapping, testCase.expectedError, err) 762 } 763 } 764 } 765 } 766 767 func TestPrepareSetRuntimeArtifacts(t *testing.T) { 768 for _, mapping := range []string{filepath.FromSlash("/src") + ":dst", filepath.FromSlash("/src1") + ":dst1;" + filepath.FromSlash("/src1") + ":dst1"} { 769 expectedMapping := strings.Replace(mapping, ";", ",", -1) 770 771 builder := newFakeSTI(&FakeSTI{}) 772 773 fakeDocker := builder.docker.(*docker.FakeDocker) 774 fakeDocker.AssembleInputFilesResult = mapping 775 776 config := builder.config 777 config.RuntimeImage = "my-app" 778 779 if len(config.RuntimeArtifacts) > 0 { 780 t.Fatalf("RuntimeArtifacts must be empty by default") 781 } 782 783 if err := builder.Prepare(config); err != nil { 784 t.Fatalf("Prepare() unexpectedly failed with error: %v", err) 785 } 786 787 if actualMapping := config.RuntimeArtifacts.String(); actualMapping != expectedMapping { 788 t.Errorf("Prepare() shouldn't change mapping, but it was modified from %v to %v", expectedMapping, actualMapping) 789 } 790 } 791 } 792 793 func TestPrepareDownloadAssembleRuntime(t *testing.T) { 794 installer := &test.FakeInstaller{} 795 796 builder := newFakeSTI(&FakeSTI{}) 797 builder.runtimeInstaller = installer 798 builder.optionalRuntimeScripts = []string{constants.AssembleRuntime} 799 800 config := builder.config 801 config.RuntimeImage = "my-app" 802 config.RuntimeArtifacts.Set("/src:dst") 803 804 if err := builder.Prepare(config); err != nil { 805 t.Fatalf("Prepare() unexpectedly failed with error: %v", err) 806 } 807 808 if len(installer.Scripts) != 1 || installer.Scripts[0][0] != constants.AssembleRuntime { 809 t.Errorf("Prepare() should download %q script but it downloaded %v", constants.AssembleRuntime, installer.Scripts) 810 } 811 } 812 813 func TestExecuteOK(t *testing.T) { 814 rh := newFakeBaseSTI() 815 pe := &FakeSTI{} 816 rh.postExecutor = pe 817 rh.config.WorkingDir = "/working-dir" 818 rh.config.BuilderImage = "test/image" 819 rh.config.BuilderPullPolicy = api.PullAlways 820 rh.config.Environment = api.EnvironmentList{ 821 api.EnvironmentSpec{ 822 Name: "Key1", 823 Value: "Value1", 824 }, 825 api.EnvironmentSpec{ 826 Name: "Key2", 827 Value: "Value2", 828 }, 829 } 830 expectedEnv := []string{"Key1=Value1", "Key2=Value2"} 831 th := rh.tar.(*test.FakeTar) 832 th.CreateTarResult = "/working-dir/test.tar" 833 fd := rh.docker.(*docker.FakeDocker) 834 fd.RunContainerContainerID = "1234" 835 fd.RunContainerCmd = []string{"one", "two"} 836 837 err := rh.Execute("test-command", "foo", rh.config) 838 if err != nil { 839 t.Errorf("Unexpected error returned: %v", err) 840 } 841 th = rh.tar.(*test.FakeTar).Copy() 842 if th.CreateTarBase != "" { 843 t.Errorf("Unexpected tar base directory: %s", th.CreateTarBase) 844 } 845 if filepath.ToSlash(th.CreateTarDir) != "/working-dir/upload" { 846 t.Errorf("Unexpected tar directory: %s", th.CreateTarDir) 847 } 848 fh, ok := rh.fs.(*testfs.FakeFileSystem) 849 if !ok { 850 t.Fatalf("Unable to convert %v to FakeFilesystem", rh.fs) 851 } 852 if fh.OpenFile != "" { 853 t.Fatalf("Unexpected file opened: %s", fh.OpenFile) 854 } 855 if fh.OpenFileResult != nil { 856 t.Errorf("Tar file was opened.") 857 } 858 ro := fd.RunContainerOpts 859 860 if ro.User != "foo" { 861 t.Errorf("Expected user to be foo, got %q", ro.User) 862 } 863 864 if ro.Image != rh.config.BuilderImage { 865 t.Errorf("Unexpected Image passed to RunContainer") 866 } 867 if _, ok := ro.Stdin.(*io.PipeReader); !ok { 868 t.Errorf("Unexpected input stream: %#v", ro.Stdin) 869 } 870 if ro.PullImage { 871 t.Errorf("PullImage is true for RunContainer, should be false") 872 } 873 if ro.Command != "test-command" { 874 t.Errorf("Unexpected command passed to RunContainer: %s", 875 ro.Command) 876 } 877 if pe.PostExecuteContainerID != "1234" { 878 t.Errorf("PostExecutor not called with expected ID: %s", 879 pe.PostExecuteContainerID) 880 } 881 if !reflect.DeepEqual(ro.Env, expectedEnv) { 882 t.Errorf("Unexpected container environment passed to RunContainer: %v, should be %v", ro.Env, expectedEnv) 883 } 884 if !reflect.DeepEqual(pe.PostExecuteDestination, "test-command") { 885 t.Errorf("PostExecutor not called with expected command: %s", pe.PostExecuteDestination) 886 } 887 } 888 889 func TestExecuteRunContainerError(t *testing.T) { 890 rh := newFakeSTI(&FakeSTI{}) 891 fd := rh.docker.(*docker.FakeDocker) 892 runContainerError := fmt.Errorf("an error") 893 fd.RunContainerError = runContainerError 894 err := rh.Execute("test-command", "", rh.config) 895 if err != runContainerError { 896 t.Errorf("Did not get expected error, got %v", err) 897 } 898 } 899 900 func TestExecuteErrorCreateTarFile(t *testing.T) { 901 rh := newFakeSTI(&FakeSTI{}) 902 rh.tar.(*test.FakeTar).CreateTarError = errors.New("CreateTarError") 903 err := rh.Execute("test-command", "", rh.config) 904 if err == nil || err.Error() != "CreateTarError" { 905 t.Errorf("An error was expected for CreateTarFile, but got different: %#v", err) 906 } 907 } 908 909 func TestCleanup(t *testing.T) { 910 rh := newFakeBaseSTI() 911 912 rh.config.WorkingDir = "/working-dir" 913 preserve := []bool{false, true} 914 for _, p := range preserve { 915 rh.config.PreserveWorkingDir = p 916 rh.fs = &testfs.FakeFileSystem{} 917 rh.garbage = build.NewDefaultCleaner(rh.fs, rh.docker) 918 rh.garbage.Cleanup(rh.config) 919 removedDir := rh.fs.(*testfs.FakeFileSystem).RemoveDirName 920 if p && removedDir != "" { 921 t.Errorf("Expected working directory to be preserved, but it was removed.") 922 } else if !p && removedDir == "" { 923 t.Errorf("Expected working directory to be removed, but it was preserved.") 924 } 925 } 926 } 927 928 func TestNewWithInvalidExcludeRegExp(t *testing.T) { 929 _, err := New(nil, &api.Config{ 930 DockerConfig: docker.GetDefaultDockerConfig(), 931 ExcludeRegExp: "(", 932 }, nil, build.Overrides{}) 933 if syntaxErr, ok := err.(*syntax.Error); ok && syntaxErr.Code != syntax.ErrMissingParen { 934 t.Errorf("expected regexp compilation error, got %v", err) 935 } 936 }