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