gopkg.in/openshift/source-to-image.v1@v1.2.0/pkg/docker/docker_test.go (about)

     1  package docker
     2  
     3  import (
     4  	"bytes"
     5  	"fmt"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"reflect"
    10  	"strings"
    11  	"testing"
    12  
    13  	"github.com/openshift/source-to-image/pkg/api/constants"
    14  	dockertest "github.com/openshift/source-to-image/pkg/docker/test"
    15  	"github.com/openshift/source-to-image/pkg/errors"
    16  	testfs "github.com/openshift/source-to-image/pkg/test/fs"
    17  
    18  	dockertypes "github.com/docker/docker/api/types"
    19  	dockercontainer "github.com/docker/docker/api/types/container"
    20  	dockerstrslice "github.com/docker/docker/api/types/strslice"
    21  )
    22  
    23  func TestContainerName(t *testing.T) {
    24  	got := containerName("sub.domain.com:5000/repo:tag@sha256:ffffff")
    25  	want := "s2i_sub_domain_com_5000_repo_tag_sha256_ffffff"
    26  	if !strings.Contains(got, want) {
    27  		t.Errorf("want %v is not substring of got %v", want, got)
    28  	}
    29  }
    30  
    31  func getDocker(client Client) *stiDocker {
    32  	return &stiDocker{
    33  		client:   client,
    34  		pullAuth: dockertypes.AuthConfig{},
    35  	}
    36  }
    37  
    38  func TestRemoveContainer(t *testing.T) {
    39  	fakeDocker := dockertest.NewFakeDockerClient()
    40  	dh := getDocker(fakeDocker)
    41  	containerID := "testContainerId"
    42  	fakeDocker.Containers[containerID] = dockercontainer.Config{}
    43  	err := dh.RemoveContainer(containerID)
    44  	if err != nil {
    45  		t.Errorf("%+v", err)
    46  	}
    47  	expectedCalls := []string{"remove"}
    48  	if !reflect.DeepEqual(fakeDocker.Calls, expectedCalls) {
    49  		t.Errorf("Expected fakeDocker.Calls %v, got %v", expectedCalls, fakeDocker.Calls)
    50  	}
    51  }
    52  
    53  func TestCommitContainer(t *testing.T) {
    54  	type commitTest struct {
    55  		containerID     string
    56  		containerTag    string
    57  		expectedImageID string
    58  		expectedError   error
    59  	}
    60  
    61  	tests := map[string]commitTest{
    62  		"valid": {
    63  			containerID:     "test-container-id",
    64  			containerTag:    "test-container-tag",
    65  			expectedImageID: "test-container-tag",
    66  		},
    67  		"error": {
    68  			containerID:     "test-container-id",
    69  			containerTag:    "test-container-tag",
    70  			expectedImageID: "test-container-tag",
    71  			expectedError:   fmt.Errorf("Test error"),
    72  		},
    73  	}
    74  
    75  	for desc, tst := range tests {
    76  		opt := CommitContainerOptions{
    77  			ContainerID: tst.containerID,
    78  			Repository:  tst.containerTag,
    79  		}
    80  		param := dockertypes.ContainerCommitOptions{
    81  			Reference: tst.containerTag,
    82  		}
    83  		resp := dockertypes.IDResponse{
    84  			ID: tst.expectedImageID,
    85  		}
    86  		fakeDocker := &dockertest.FakeDockerClient{
    87  			ContainerCommitResponse: resp,
    88  			ContainerCommitErr:      tst.expectedError,
    89  		}
    90  		dh := getDocker(fakeDocker)
    91  
    92  		imageID, err := dh.CommitContainer(opt)
    93  		if err != tst.expectedError {
    94  			t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
    95  		}
    96  		if tst.containerID != fakeDocker.ContainerCommitID {
    97  			t.Errorf("test case %s: Commit container called with unexpected container id: %s and %+v", desc, tst.containerID, fakeDocker.ContainerCommitID)
    98  		}
    99  		if !reflect.DeepEqual(param, fakeDocker.ContainerCommitOptions) {
   100  			t.Errorf("test case %s: Commit container called with unexpected parameters: %+v and %+v", desc, param, fakeDocker.ContainerCommitOptions)
   101  		}
   102  		if tst.expectedError == nil && imageID != tst.expectedImageID {
   103  			t.Errorf("test case %s: Did not return the correct image id: %s", desc, imageID)
   104  		}
   105  	}
   106  }
   107  
   108  func TestCopyToContainer(t *testing.T) {
   109  	type copyToTest struct {
   110  		containerID string
   111  		src         string
   112  		destPath    string
   113  	}
   114  
   115  	tests := map[string]copyToTest{
   116  		"valid": {
   117  			containerID: "test-container-id",
   118  			src:         "foo",
   119  		},
   120  		"error": {
   121  			containerID: "test-container-id",
   122  			src:         "badsource",
   123  		},
   124  	}
   125  
   126  	for desc, tst := range tests {
   127  		var tempDir, fileName string
   128  		var err error
   129  		var file *os.File
   130  		if len(tst.src) > 0 {
   131  			tempDir, err = ioutil.TempDir("", tst.src)
   132  			defer os.RemoveAll(tempDir)
   133  			fileName = filepath.Join(tempDir, "bar")
   134  			if err = os.MkdirAll(filepath.Dir(fileName), 0700); err == nil {
   135  				file, err = os.Create(fileName)
   136  				if err == nil {
   137  					defer file.Close()
   138  					file.WriteString("asdf")
   139  				}
   140  			}
   141  		}
   142  		if err != nil {
   143  			t.Fatalf("Error creating src test files: %v", err)
   144  		}
   145  		fakeDocker := &dockertest.FakeDockerClient{
   146  			CopyToContainerContent: file,
   147  		}
   148  		dh := getDocker(fakeDocker)
   149  
   150  		err = dh.UploadToContainer(&testfs.FakeFileSystem{}, fileName, fileName, tst.containerID)
   151  		// the error we are inducing will prevent call into engine-api
   152  		if len(tst.src) > 0 {
   153  			if err != nil {
   154  				t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
   155  			}
   156  			if tst.containerID != fakeDocker.CopyToContainerID {
   157  				t.Errorf("test case %s: copy to container called with unexpected id: %s and %s", desc, tst.containerID, fakeDocker.CopyToContainerID)
   158  			}
   159  		} else {
   160  			if err == nil {
   161  				t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
   162  			}
   163  			if len(fakeDocker.CopyToContainerID) > 0 {
   164  				t.Errorf("test case %s: copy to container called with unexpected id: %s and %s", desc, tst.containerID, fakeDocker.CopyToContainerID)
   165  			}
   166  		}
   167  		// the directory of our file gets passed down to the engine-api method
   168  		if tempDir != fakeDocker.CopyToContainerPath {
   169  			t.Errorf("test case %s: copy to container called with unexpected path: %s and %s", desc, tempDir, fakeDocker.CopyToContainerPath)
   170  		}
   171  		// reflect.DeepEqual does not help here cause the reader is transformed prior to calling the engine-api stack, so just make sure it is no nil
   172  		if file != nil && fakeDocker.CopyToContainerContent == nil {
   173  			t.Errorf("test case %s: copy to container content was not passed through", desc)
   174  		}
   175  	}
   176  }
   177  
   178  func TestCopyFromContainer(t *testing.T) {
   179  	type copyFromTest struct {
   180  		containerID   string
   181  		srcPath       string
   182  		expectedError error
   183  	}
   184  
   185  	tests := map[string]copyFromTest{
   186  		"valid": {
   187  			containerID: "test-container-id",
   188  			srcPath:     "/foo/bar",
   189  		},
   190  		"error": {
   191  			containerID:   "test-container-id",
   192  			srcPath:       "/foo/bar",
   193  			expectedError: fmt.Errorf("Test error"),
   194  		},
   195  	}
   196  
   197  	for desc, tst := range tests {
   198  		buffer := bytes.NewBuffer([]byte(""))
   199  		fakeDocker := &dockertest.FakeDockerClient{
   200  			CopyFromContainerErr: tst.expectedError,
   201  		}
   202  		dh := getDocker(fakeDocker)
   203  
   204  		err := dh.DownloadFromContainer(tst.srcPath, buffer, tst.containerID)
   205  		if err != tst.expectedError {
   206  			t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
   207  		}
   208  		if fakeDocker.CopyFromContainerID != tst.containerID {
   209  			t.Errorf("test case %s: Unexpected container id: %s and %s", desc, tst.containerID, fakeDocker.CopyFromContainerID)
   210  		}
   211  		if fakeDocker.CopyFromContainerPath != tst.srcPath {
   212  			t.Errorf("test case %s: Unexpected container id: %s and %s", desc, tst.srcPath, fakeDocker.CopyFromContainerPath)
   213  		}
   214  	}
   215  }
   216  
   217  func TestImageBuild(t *testing.T) {
   218  	type waitTest struct {
   219  		imageID       string
   220  		expectedError error
   221  	}
   222  
   223  	tests := map[string]waitTest{
   224  		"valid": {
   225  			imageID: "test-container-id",
   226  		},
   227  		"error": {
   228  			imageID:       "test-container-id",
   229  			expectedError: fmt.Errorf("Test error"),
   230  		},
   231  	}
   232  
   233  	for desc, tst := range tests {
   234  		fakeDocker := &dockertest.FakeDockerClient{
   235  			BuildImageErr: tst.expectedError,
   236  		}
   237  		dh := getDocker(fakeDocker)
   238  		opts := BuildImageOptions{
   239  			Name: tst.imageID,
   240  		}
   241  
   242  		err := dh.BuildImage(opts)
   243  		if err != tst.expectedError {
   244  			t.Errorf("test case %s: Unexpected error returned: %v", desc, err)
   245  		}
   246  		if len(fakeDocker.BuildImageOpts.Tags) != 1 || fakeDocker.BuildImageOpts.Tags[0] != tst.imageID {
   247  			t.Errorf("test case %s: Unexpected container id: %s and %+v", desc, tst.imageID, fakeDocker.BuildImageOpts.Tags)
   248  		}
   249  	}
   250  }
   251  
   252  func TestGetScriptsURL(t *testing.T) {
   253  	type urltest struct {
   254  		image      dockertypes.ImageInspect
   255  		result     string
   256  		calls      []string
   257  		inspectErr error
   258  	}
   259  	tests := map[string]urltest{
   260  		"not present": {
   261  			calls: []string{"inspect_image"},
   262  			image: dockertypes.ImageInspect{
   263  				ContainerConfig: &dockercontainer.Config{
   264  					Env:    []string{"Env1=value1"},
   265  					Labels: map[string]string{},
   266  				},
   267  				Config: &dockercontainer.Config{
   268  					Env:    []string{"Env2=value2"},
   269  					Labels: map[string]string{},
   270  				},
   271  			},
   272  			result: "",
   273  		},
   274  
   275  		"env in containerConfig": {
   276  			calls: []string{"inspect_image"},
   277  			image: dockertypes.ImageInspect{
   278  				ContainerConfig: &dockercontainer.Config{
   279  					Env: []string{"Env1=value1", constants.ScriptsURLEnvironment + "=test_url_value"},
   280  				},
   281  				Config: &dockercontainer.Config{},
   282  			},
   283  			result: "",
   284  		},
   285  
   286  		"env in image config": {
   287  			calls: []string{"inspect_image"},
   288  			image: dockertypes.ImageInspect{
   289  				ContainerConfig: &dockercontainer.Config{},
   290  				Config: &dockercontainer.Config{
   291  					Env: []string{
   292  						"Env1=value1",
   293  						constants.ScriptsURLEnvironment + "=test_url_value_2",
   294  						"Env2=value2",
   295  					},
   296  				},
   297  			},
   298  			result: "test_url_value_2",
   299  		},
   300  
   301  		"label in containerConfig": {
   302  			calls: []string{"inspect_image"},
   303  			image: dockertypes.ImageInspect{
   304  				ContainerConfig: &dockercontainer.Config{
   305  					Labels: map[string]string{constants.ScriptsURLLabel: "test_url_value"},
   306  				},
   307  				Config: &dockercontainer.Config{},
   308  			},
   309  			result: "",
   310  		},
   311  
   312  		"label in image config": {
   313  			calls: []string{"inspect_image"},
   314  			image: dockertypes.ImageInspect{
   315  				ContainerConfig: &dockercontainer.Config{},
   316  				Config: &dockercontainer.Config{
   317  					Labels: map[string]string{constants.ScriptsURLLabel: "test_url_value_2"},
   318  				},
   319  			},
   320  			result: "test_url_value_2",
   321  		},
   322  
   323  		"inspect error": {
   324  			calls:      []string{"inspect_image", "pull"},
   325  			image:      dockertypes.ImageInspect{},
   326  			inspectErr: fmt.Errorf("Inspect error"),
   327  		},
   328  	}
   329  	for desc, tst := range tests {
   330  		fakeDocker := dockertest.NewFakeDockerClient()
   331  		dh := getDocker(fakeDocker)
   332  		tst.image.ID = "test/image:latest"
   333  		if tst.inspectErr != nil {
   334  			fakeDocker.PullFail = tst.inspectErr
   335  		} else {
   336  			fakeDocker.Images = map[string]dockertypes.ImageInspect{tst.image.ID: tst.image}
   337  		}
   338  		url, err := dh.GetScriptsURL(tst.image.ID)
   339  
   340  		if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) {
   341  			t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls)
   342  		}
   343  		if err != nil && tst.inspectErr == nil {
   344  			t.Errorf("%s: Unexpected error returned: %v", desc, err)
   345  		}
   346  		if tst.inspectErr == nil && url != tst.result {
   347  			//t.Errorf("%s: Unexpected result. Expected: %s Actual: %s",
   348  			//	desc, tst.result, url)
   349  		}
   350  	}
   351  }
   352  
   353  func TestRunContainer(t *testing.T) {
   354  	type runtest struct {
   355  		calls            []string
   356  		image            dockertypes.ImageInspect
   357  		cmd              string
   358  		externalScripts  bool
   359  		paramScriptsURL  string
   360  		paramDestination string
   361  		cmdExpected      []string
   362  		errResult        int
   363  		errJSON          dockertypes.ContainerJSON
   364  		errMsg           string
   365  	}
   366  
   367  	tests := map[string]runtest{
   368  		"default": {
   369  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   370  			image: dockertypes.ImageInspect{
   371  				ContainerConfig: &dockercontainer.Config{},
   372  				Config:          &dockercontainer.Config{},
   373  			},
   374  			cmd:             constants.Assemble,
   375  			externalScripts: true,
   376  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", constants.Assemble)},
   377  		},
   378  		"runerror": {
   379  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   380  			image: dockertypes.ImageInspect{
   381  				ContainerConfig: &dockercontainer.Config{},
   382  				Config:          &dockercontainer.Config{},
   383  			},
   384  			cmd:             constants.Assemble,
   385  			externalScripts: true,
   386  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", constants.Assemble)},
   387  			errResult:       302,
   388  			errJSON: dockertypes.ContainerJSON{
   389  				ContainerJSONBase: &dockertypes.ContainerJSONBase{
   390  					State: &dockertypes.ContainerState{
   391  						Status:    "Failed",
   392  						Error:     "Process was terminated",
   393  						OOMKilled: true,
   394  					},
   395  				},
   396  			},
   397  			errMsg: "Error: Process was terminated, OOMKilled: true",
   398  		},
   399  		"paramDestination": {
   400  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   401  			image: dockertypes.ImageInspect{
   402  				ContainerConfig: &dockercontainer.Config{},
   403  				Config:          &dockercontainer.Config{},
   404  			},
   405  			cmd:              constants.Assemble,
   406  			externalScripts:  true,
   407  			paramDestination: "/opt/test",
   408  			cmdExpected:      []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", constants.Assemble)},
   409  		},
   410  		"paramDestination&paramScripts": {
   411  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   412  			image: dockertypes.ImageInspect{
   413  				ContainerConfig: &dockercontainer.Config{},
   414  				Config:          &dockercontainer.Config{},
   415  			},
   416  			cmd:              constants.Assemble,
   417  			externalScripts:  true,
   418  			paramDestination: "/opt/test",
   419  			paramScriptsURL:  "http://my.test.url/test?param=one",
   420  			cmdExpected:      []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/test -xf - && /opt/test/scripts/%s", constants.Assemble)},
   421  		},
   422  		"scriptsInsideImageEnvironment": {
   423  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   424  			image: dockertypes.ImageInspect{
   425  				ContainerConfig: &dockercontainer.Config{},
   426  				Config: &dockercontainer.Config{
   427  					Env: []string{constants.ScriptsURLEnvironment + "=image:///opt/bin/"},
   428  				},
   429  			},
   430  			cmd:             constants.Assemble,
   431  			externalScripts: false,
   432  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", constants.Assemble)},
   433  		},
   434  		"scriptsInsideImageLabel": {
   435  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   436  			image: dockertypes.ImageInspect{
   437  				ContainerConfig: &dockercontainer.Config{},
   438  				Config: &dockercontainer.Config{
   439  					Labels: map[string]string{constants.ScriptsURLLabel: "image:///opt/bin/"},
   440  				},
   441  			},
   442  			cmd:             constants.Assemble,
   443  			externalScripts: false,
   444  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /opt/bin/%s", constants.Assemble)},
   445  		},
   446  		"scriptsInsideImageEnvironmentWithParamDestination": {
   447  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   448  			image: dockertypes.ImageInspect{
   449  				ContainerConfig: &dockercontainer.Config{},
   450  				Config: &dockercontainer.Config{
   451  					Env: []string{constants.ScriptsURLEnvironment + "=image:///opt/bin"},
   452  				},
   453  			},
   454  			cmd:              constants.Assemble,
   455  			externalScripts:  false,
   456  			paramDestination: "/opt/sti",
   457  			cmdExpected:      []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", constants.Assemble)},
   458  		},
   459  		"scriptsInsideImageLabelWithParamDestination": {
   460  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   461  			image: dockertypes.ImageInspect{
   462  				ContainerConfig: &dockercontainer.Config{},
   463  				Config: &dockercontainer.Config{
   464  					Labels: map[string]string{constants.ScriptsURLLabel: "image:///opt/bin"},
   465  				},
   466  			},
   467  			cmd:              constants.Assemble,
   468  			externalScripts:  false,
   469  			paramDestination: "/opt/sti",
   470  			cmdExpected:      []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt/sti -xf - && /opt/bin/%s", constants.Assemble)},
   471  		},
   472  		"paramDestinationFromImageEnvironment": {
   473  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   474  			image: dockertypes.ImageInspect{
   475  				ContainerConfig: &dockercontainer.Config{},
   476  				Config: &dockercontainer.Config{
   477  					Env: []string{constants.LocationEnvironment + "=/opt", constants.ScriptsURLEnvironment + "=http://my.test.url/test?param=one"},
   478  				},
   479  			},
   480  			cmd:             constants.Assemble,
   481  			externalScripts: true,
   482  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", constants.Assemble)},
   483  		},
   484  		"paramDestinationFromImageLabel": {
   485  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   486  			image: dockertypes.ImageInspect{
   487  				ContainerConfig: &dockercontainer.Config{},
   488  				Config: &dockercontainer.Config{
   489  					Labels: map[string]string{constants.DestinationLabel: "/opt", constants.ScriptsURLLabel: "http://my.test.url/test?param=one"},
   490  				},
   491  			},
   492  			cmd:             constants.Assemble,
   493  			externalScripts: true,
   494  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /opt -xf - && /opt/scripts/%s", constants.Assemble)},
   495  		},
   496  		"usageCommand": {
   497  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   498  			image: dockertypes.ImageInspect{
   499  				ContainerConfig: &dockercontainer.Config{},
   500  				Config:          &dockercontainer.Config{},
   501  			},
   502  			cmd:             constants.Usage,
   503  			externalScripts: true,
   504  			cmdExpected:     []string{"/bin/sh", "-c", fmt.Sprintf("tar -C /tmp -xf - && /tmp/scripts/%s", constants.Usage)},
   505  		},
   506  		"otherCommand": {
   507  			calls: []string{"inspect_image", "inspect_image", "inspect_image", "create", "attach", "start", "remove"},
   508  			image: dockertypes.ImageInspect{
   509  				ContainerConfig: &dockercontainer.Config{},
   510  				Config:          &dockercontainer.Config{},
   511  			},
   512  			cmd:             constants.Run,
   513  			externalScripts: true,
   514  			cmdExpected:     []string{fmt.Sprintf("/tmp/scripts/%s", constants.Run)},
   515  		},
   516  	}
   517  
   518  	for desc, tst := range tests {
   519  		fakeDocker := dockertest.NewFakeDockerClient()
   520  		dh := getDocker(fakeDocker)
   521  		tst.image.ID = "test/image:latest"
   522  		fakeDocker.Images = map[string]dockertypes.ImageInspect{tst.image.ID: tst.image}
   523  		if len(fakeDocker.Containers) > 0 {
   524  			t.Errorf("newly created fake client should have empty container map: %+v", fakeDocker.Containers)
   525  		}
   526  		if tst.errResult > 0 {
   527  			fakeDocker.WaitContainerResult = tst.errResult
   528  			fakeDocker.WaitContainerErrInspectJSON = tst.errJSON
   529  		}
   530  
   531  		err := dh.RunContainer(RunContainerOptions{
   532  			Image:           "test/image",
   533  			PullImage:       true,
   534  			ExternalScripts: tst.externalScripts,
   535  			ScriptsURL:      tst.paramScriptsURL,
   536  			Destination:     tst.paramDestination,
   537  			Command:         tst.cmd,
   538  			Env:             []string{"Key1=Value1", "Key2=Value2"},
   539  			Stdin:           ioutil.NopCloser(os.Stdin),
   540  		})
   541  
   542  		if tst.errResult > 0 {
   543  			if err == nil {
   544  				t.Errorf("did not get error for %s when expected", desc)
   545  			}
   546  			cerr, ok := err.(errors.ContainerError)
   547  			if !ok {
   548  				t.Errorf("got unexpected error %#v for %s", err, desc)
   549  			}
   550  			if !strings.Contains(cerr.Output, tst.errMsg) {
   551  				t.Errorf("got unexpected error msg %s which did not contain %s", err.Error(), tst.errMsg)
   552  			}
   553  			continue
   554  		}
   555  		if err != nil {
   556  			t.Errorf("%s: Unexpected error: %v", desc, err)
   557  		}
   558  
   559  		// container ID will be random, so don't look up directly ... just get the 1 entry which should be there
   560  		if len(fakeDocker.Containers) != 1 {
   561  			t.Errorf("fake container map should only have 1 entry: %+v", fakeDocker.Containers)
   562  		}
   563  
   564  		for _, container := range fakeDocker.Containers {
   565  			// Validate the Container parameters
   566  			if container.Image != "test/image:latest" {
   567  				t.Errorf("%s: Unexpected create config image: %s", desc, container.Image)
   568  			}
   569  			if !reflect.DeepEqual(container.Cmd, dockerstrslice.StrSlice(tst.cmdExpected)) {
   570  				t.Errorf("%s: Unexpected create config command: %#v instead of %q", desc, container.Cmd, strings.Join(tst.cmdExpected, " "))
   571  			}
   572  			if !reflect.DeepEqual(container.Env, []string{"Key1=Value1", "Key2=Value2"}) {
   573  				t.Errorf("%s: Unexpected create config env: %#v", desc, container.Env)
   574  			}
   575  			if !reflect.DeepEqual(fakeDocker.Calls, tst.calls) {
   576  				t.Errorf("%s: Expected fakeDocker.Calls %v, got %v", desc, tst.calls, fakeDocker.Calls)
   577  			}
   578  		}
   579  	}
   580  }
   581  
   582  func TestGetImageID(t *testing.T) {
   583  	fakeDocker := dockertest.NewFakeDockerClient()
   584  	dh := getDocker(fakeDocker)
   585  	image := dockertypes.ImageInspect{ID: "test-abcd:latest"}
   586  	fakeDocker.Images = map[string]dockertypes.ImageInspect{image.ID: image}
   587  	id, err := dh.GetImageID("test-abcd")
   588  	expectedCalls := []string{"inspect_image"}
   589  	if !reflect.DeepEqual(fakeDocker.Calls, expectedCalls) {
   590  		t.Errorf("Expected fakeDocker.Calls %v, got %v", expectedCalls, fakeDocker.Calls)
   591  	}
   592  	if err != nil {
   593  		t.Errorf("Unexpected error returned: %v", err)
   594  	} else if id != image.ID {
   595  		t.Errorf("Unexpected image id returned: %s", id)
   596  	}
   597  }
   598  
   599  func TestRemoveImage(t *testing.T) {
   600  	fakeDocker := dockertest.NewFakeDockerClient()
   601  	dh := getDocker(fakeDocker)
   602  	image := dockertypes.ImageInspect{ID: "test-abcd"}
   603  	fakeDocker.Images = map[string]dockertypes.ImageInspect{image.ID: image}
   604  	err := dh.RemoveImage("test-abcd")
   605  	if err != nil {
   606  		t.Errorf("Unexpected error removing image: %s", err)
   607  	}
   608  }
   609  
   610  func TestGetImageName(t *testing.T) {
   611  	type runtest struct {
   612  		name     string
   613  		expected string
   614  	}
   615  	tests := []runtest{
   616  		{"test/image", "test/image:latest"},
   617  		{"test/image:latest", "test/image:latest"},
   618  		{"test/image:tag", "test/image:tag"},
   619  		{"repository/test/image", "repository/test/image:latest"},
   620  		{"repository/test/image:latest", "repository/test/image:latest"},
   621  		{"repository/test/image:tag", "repository/test/image:tag"},
   622  	}
   623  
   624  	for _, tc := range tests {
   625  		if e, a := tc.expected, getImageName(tc.name); e != a {
   626  			t.Errorf("Expected image name %s, but got %s!", e, a)
   627  		}
   628  	}
   629  }