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  }