github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/builder/dockerfile/dispatchers_test.go (about)

     1  package dockerfile // import "github.com/docker/docker/builder/dockerfile"
     2  
     3  import (
     4  	"bytes"
     5  	"context"
     6  	"runtime"
     7  	"testing"
     8  
     9  	"github.com/docker/docker/api/types"
    10  	"github.com/docker/docker/api/types/backend"
    11  	"github.com/docker/docker/api/types/container"
    12  	"github.com/docker/docker/api/types/strslice"
    13  	"github.com/docker/docker/builder"
    14  	"github.com/docker/docker/image"
    15  	"github.com/docker/docker/pkg/system"
    16  	"github.com/docker/go-connections/nat"
    17  	"github.com/moby/buildkit/frontend/dockerfile/instructions"
    18  	"github.com/moby/buildkit/frontend/dockerfile/shell"
    19  	"gotest.tools/assert"
    20  	is "gotest.tools/assert/cmp"
    21  )
    22  
    23  func newBuilderWithMockBackend() *Builder {
    24  	mockBackend := &MockBackend{}
    25  	opts := &types.ImageBuildOptions{}
    26  	ctx := context.Background()
    27  	b := &Builder{
    28  		options:       opts,
    29  		docker:        mockBackend,
    30  		Stdout:        new(bytes.Buffer),
    31  		clientCtx:     ctx,
    32  		disableCommit: true,
    33  		imageSources: newImageSources(ctx, builderOptions{
    34  			Options: opts,
    35  			Backend: mockBackend,
    36  		}),
    37  		imageProber:      newImageProber(mockBackend, nil, false),
    38  		containerManager: newContainerManager(mockBackend),
    39  	}
    40  	return b
    41  }
    42  
    43  func TestEnv2Variables(t *testing.T) {
    44  	b := newBuilderWithMockBackend()
    45  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    46  	envCommand := &instructions.EnvCommand{
    47  		Env: instructions.KeyValuePairs{
    48  			instructions.KeyValuePair{Key: "var1", Value: "val1"},
    49  			instructions.KeyValuePair{Key: "var2", Value: "val2"},
    50  		},
    51  	}
    52  	err := dispatch(sb, envCommand)
    53  	assert.NilError(t, err)
    54  
    55  	expected := []string{
    56  		"var1=val1",
    57  		"var2=val2",
    58  	}
    59  	assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
    60  }
    61  
    62  func TestEnvValueWithExistingRunConfigEnv(t *testing.T) {
    63  	b := newBuilderWithMockBackend()
    64  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    65  	sb.state.runConfig.Env = []string{"var1=old", "var2=fromenv"}
    66  	envCommand := &instructions.EnvCommand{
    67  		Env: instructions.KeyValuePairs{
    68  			instructions.KeyValuePair{Key: "var1", Value: "val1"},
    69  		},
    70  	}
    71  	err := dispatch(sb, envCommand)
    72  	assert.NilError(t, err)
    73  	expected := []string{
    74  		"var1=val1",
    75  		"var2=fromenv",
    76  	}
    77  	assert.Check(t, is.DeepEqual(expected, sb.state.runConfig.Env))
    78  }
    79  
    80  func TestMaintainer(t *testing.T) {
    81  	maintainerEntry := "Some Maintainer <maintainer@example.com>"
    82  	b := newBuilderWithMockBackend()
    83  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    84  	cmd := &instructions.MaintainerCommand{Maintainer: maintainerEntry}
    85  	err := dispatch(sb, cmd)
    86  	assert.NilError(t, err)
    87  	assert.Check(t, is.Equal(maintainerEntry, sb.state.maintainer))
    88  }
    89  
    90  func TestLabel(t *testing.T) {
    91  	labelName := "label"
    92  	labelValue := "value"
    93  
    94  	b := newBuilderWithMockBackend()
    95  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
    96  	cmd := &instructions.LabelCommand{
    97  		Labels: instructions.KeyValuePairs{
    98  			instructions.KeyValuePair{Key: labelName, Value: labelValue},
    99  		},
   100  	}
   101  	err := dispatch(sb, cmd)
   102  	assert.NilError(t, err)
   103  
   104  	assert.Assert(t, is.Contains(sb.state.runConfig.Labels, labelName))
   105  	assert.Check(t, is.Equal(sb.state.runConfig.Labels[labelName], labelValue))
   106  }
   107  
   108  func TestFromScratch(t *testing.T) {
   109  	b := newBuilderWithMockBackend()
   110  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   111  	cmd := &instructions.Stage{
   112  		BaseName: "scratch",
   113  	}
   114  	err := initializeStage(sb, cmd)
   115  
   116  	if runtime.GOOS == "windows" && !system.LCOWSupported() {
   117  		assert.Check(t, is.Error(err, "Linux containers are not supported on this system"))
   118  		return
   119  	}
   120  
   121  	assert.NilError(t, err)
   122  	assert.Check(t, sb.state.hasFromImage())
   123  	assert.Check(t, is.Equal("", sb.state.imageID))
   124  	expected := "PATH=" + system.DefaultPathEnv(runtime.GOOS)
   125  	assert.Check(t, is.DeepEqual([]string{expected}, sb.state.runConfig.Env))
   126  }
   127  
   128  func TestFromWithArg(t *testing.T) {
   129  	tag, expected := ":sometag", "expectedthisid"
   130  
   131  	getImage := func(name string) (builder.Image, builder.ROLayer, error) {
   132  		assert.Check(t, is.Equal("alpine"+tag, name))
   133  		return &mockImage{id: "expectedthisid"}, nil, nil
   134  	}
   135  	b := newBuilderWithMockBackend()
   136  	b.docker.(*MockBackend).getImageFunc = getImage
   137  	args := NewBuildArgs(make(map[string]*string))
   138  
   139  	val := "sometag"
   140  	metaArg := instructions.ArgCommand{KeyValuePairOptional: instructions.KeyValuePairOptional{
   141  		Key:   "THETAG",
   142  		Value: &val,
   143  	}}
   144  	cmd := &instructions.Stage{
   145  		BaseName: "alpine:${THETAG}",
   146  	}
   147  	err := processMetaArg(metaArg, shell.NewLex('\\'), args)
   148  
   149  	sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
   150  	assert.NilError(t, err)
   151  	err = initializeStage(sb, cmd)
   152  	assert.NilError(t, err)
   153  
   154  	assert.Check(t, is.Equal(expected, sb.state.imageID))
   155  	assert.Check(t, is.Equal(expected, sb.state.baseImage.ImageID()))
   156  	assert.Check(t, is.Len(sb.state.buildArgs.GetAllAllowed(), 0))
   157  	assert.Check(t, is.Len(sb.state.buildArgs.GetAllMeta(), 1))
   158  }
   159  
   160  func TestFromWithArgButBuildArgsNotGiven(t *testing.T) {
   161  	b := newBuilderWithMockBackend()
   162  	args := NewBuildArgs(make(map[string]*string))
   163  
   164  	metaArg := instructions.ArgCommand{}
   165  	cmd := &instructions.Stage{
   166  		BaseName: "${THETAG}",
   167  	}
   168  	err := processMetaArg(metaArg, shell.NewLex('\\'), args)
   169  
   170  	sb := newDispatchRequest(b, '\\', nil, args, newStagesBuildResults())
   171  	assert.NilError(t, err)
   172  	err = initializeStage(sb, cmd)
   173  	assert.Error(t, err, "base name (${THETAG}) should not be blank")
   174  }
   175  
   176  func TestFromWithUndefinedArg(t *testing.T) {
   177  	tag, expected := "sometag", "expectedthisid"
   178  
   179  	getImage := func(name string) (builder.Image, builder.ROLayer, error) {
   180  		assert.Check(t, is.Equal("alpine", name))
   181  		return &mockImage{id: "expectedthisid"}, nil, nil
   182  	}
   183  	b := newBuilderWithMockBackend()
   184  	b.docker.(*MockBackend).getImageFunc = getImage
   185  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   186  
   187  	b.options.BuildArgs = map[string]*string{"THETAG": &tag}
   188  
   189  	cmd := &instructions.Stage{
   190  		BaseName: "alpine${THETAG}",
   191  	}
   192  	err := initializeStage(sb, cmd)
   193  	assert.NilError(t, err)
   194  	assert.Check(t, is.Equal(expected, sb.state.imageID))
   195  }
   196  
   197  func TestFromMultiStageWithNamedStage(t *testing.T) {
   198  	b := newBuilderWithMockBackend()
   199  	firstFrom := &instructions.Stage{BaseName: "someimg", Name: "base"}
   200  	secondFrom := &instructions.Stage{BaseName: "base"}
   201  	previousResults := newStagesBuildResults()
   202  	firstSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
   203  	secondSB := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), previousResults)
   204  	err := initializeStage(firstSB, firstFrom)
   205  	assert.NilError(t, err)
   206  	assert.Check(t, firstSB.state.hasFromImage())
   207  	previousResults.indexed["base"] = firstSB.state.runConfig
   208  	previousResults.flat = append(previousResults.flat, firstSB.state.runConfig)
   209  	err = initializeStage(secondSB, secondFrom)
   210  	assert.NilError(t, err)
   211  	assert.Check(t, secondSB.state.hasFromImage())
   212  }
   213  
   214  func TestOnbuild(t *testing.T) {
   215  	b := newBuilderWithMockBackend()
   216  	sb := newDispatchRequest(b, '\\', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   217  	cmd := &instructions.OnbuildCommand{
   218  		Expression: "ADD . /app/src",
   219  	}
   220  	err := dispatch(sb, cmd)
   221  	assert.NilError(t, err)
   222  	assert.Check(t, is.Equal("ADD . /app/src", sb.state.runConfig.OnBuild[0]))
   223  }
   224  
   225  func TestWorkdir(t *testing.T) {
   226  	b := newBuilderWithMockBackend()
   227  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   228  	sb.state.baseImage = &mockImage{}
   229  	workingDir := "/app"
   230  	if runtime.GOOS == "windows" {
   231  		workingDir = "C:\\app"
   232  	}
   233  	cmd := &instructions.WorkdirCommand{
   234  		Path: workingDir,
   235  	}
   236  
   237  	err := dispatch(sb, cmd)
   238  	assert.NilError(t, err)
   239  	assert.Check(t, is.Equal(workingDir, sb.state.runConfig.WorkingDir))
   240  }
   241  
   242  func TestCmd(t *testing.T) {
   243  	b := newBuilderWithMockBackend()
   244  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   245  	sb.state.baseImage = &mockImage{}
   246  	command := "./executable"
   247  
   248  	cmd := &instructions.CmdCommand{
   249  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   250  			CmdLine:      strslice.StrSlice{command},
   251  			PrependShell: true,
   252  		},
   253  	}
   254  	err := dispatch(sb, cmd)
   255  	assert.NilError(t, err)
   256  
   257  	var expectedCommand strslice.StrSlice
   258  	if runtime.GOOS == "windows" {
   259  		expectedCommand = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", command))
   260  	} else {
   261  		expectedCommand = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", command))
   262  	}
   263  
   264  	assert.Check(t, is.DeepEqual(expectedCommand, sb.state.runConfig.Cmd))
   265  	assert.Check(t, sb.state.cmdSet)
   266  }
   267  
   268  func TestHealthcheckNone(t *testing.T) {
   269  	b := newBuilderWithMockBackend()
   270  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   271  	cmd := &instructions.HealthCheckCommand{
   272  		Health: &container.HealthConfig{
   273  			Test: []string{"NONE"},
   274  		},
   275  	}
   276  	err := dispatch(sb, cmd)
   277  	assert.NilError(t, err)
   278  
   279  	assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
   280  	assert.Check(t, is.DeepEqual([]string{"NONE"}, sb.state.runConfig.Healthcheck.Test))
   281  }
   282  
   283  func TestHealthcheckCmd(t *testing.T) {
   284  
   285  	b := newBuilderWithMockBackend()
   286  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   287  	expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
   288  	cmd := &instructions.HealthCheckCommand{
   289  		Health: &container.HealthConfig{
   290  			Test: expectedTest,
   291  		},
   292  	}
   293  	err := dispatch(sb, cmd)
   294  	assert.NilError(t, err)
   295  
   296  	assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
   297  	assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
   298  }
   299  
   300  func TestEntrypoint(t *testing.T) {
   301  	b := newBuilderWithMockBackend()
   302  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   303  	sb.state.baseImage = &mockImage{}
   304  	entrypointCmd := "/usr/sbin/nginx"
   305  
   306  	cmd := &instructions.EntrypointCommand{
   307  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   308  			CmdLine:      strslice.StrSlice{entrypointCmd},
   309  			PrependShell: true,
   310  		},
   311  	}
   312  	err := dispatch(sb, cmd)
   313  	assert.NilError(t, err)
   314  	assert.Assert(t, sb.state.runConfig.Entrypoint != nil)
   315  
   316  	var expectedEntrypoint strslice.StrSlice
   317  	if runtime.GOOS == "windows" {
   318  		expectedEntrypoint = strslice.StrSlice(append([]string{"cmd"}, "/S", "/C", entrypointCmd))
   319  	} else {
   320  		expectedEntrypoint = strslice.StrSlice(append([]string{"/bin/sh"}, "-c", entrypointCmd))
   321  	}
   322  	assert.Check(t, is.DeepEqual(expectedEntrypoint, sb.state.runConfig.Entrypoint))
   323  }
   324  
   325  func TestExpose(t *testing.T) {
   326  	b := newBuilderWithMockBackend()
   327  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   328  
   329  	exposedPort := "80"
   330  	cmd := &instructions.ExposeCommand{
   331  		Ports: []string{exposedPort},
   332  	}
   333  	err := dispatch(sb, cmd)
   334  	assert.NilError(t, err)
   335  
   336  	assert.Assert(t, sb.state.runConfig.ExposedPorts != nil)
   337  	assert.Assert(t, is.Len(sb.state.runConfig.ExposedPorts, 1))
   338  
   339  	portsMapping, err := nat.ParsePortSpec(exposedPort)
   340  	assert.NilError(t, err)
   341  	assert.Check(t, is.Contains(sb.state.runConfig.ExposedPorts, portsMapping[0].Port))
   342  }
   343  
   344  func TestUser(t *testing.T) {
   345  	b := newBuilderWithMockBackend()
   346  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   347  
   348  	cmd := &instructions.UserCommand{
   349  		User: "test",
   350  	}
   351  	err := dispatch(sb, cmd)
   352  	assert.NilError(t, err)
   353  	assert.Check(t, is.Equal("test", sb.state.runConfig.User))
   354  }
   355  
   356  func TestVolume(t *testing.T) {
   357  	b := newBuilderWithMockBackend()
   358  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   359  
   360  	exposedVolume := "/foo"
   361  
   362  	cmd := &instructions.VolumeCommand{
   363  		Volumes: []string{exposedVolume},
   364  	}
   365  	err := dispatch(sb, cmd)
   366  	assert.NilError(t, err)
   367  	assert.Assert(t, sb.state.runConfig.Volumes != nil)
   368  	assert.Check(t, is.Len(sb.state.runConfig.Volumes, 1))
   369  	assert.Check(t, is.Contains(sb.state.runConfig.Volumes, exposedVolume))
   370  }
   371  
   372  func TestStopSignal(t *testing.T) {
   373  	if runtime.GOOS == "windows" {
   374  		t.Skip("Windows does not support stopsignal")
   375  		return
   376  	}
   377  	b := newBuilderWithMockBackend()
   378  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   379  	sb.state.baseImage = &mockImage{}
   380  	signal := "SIGKILL"
   381  
   382  	cmd := &instructions.StopSignalCommand{
   383  		Signal: signal,
   384  	}
   385  	err := dispatch(sb, cmd)
   386  	assert.NilError(t, err)
   387  	assert.Check(t, is.Equal(signal, sb.state.runConfig.StopSignal))
   388  }
   389  
   390  func TestArg(t *testing.T) {
   391  	b := newBuilderWithMockBackend()
   392  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   393  
   394  	argName := "foo"
   395  	argVal := "bar"
   396  	cmd := &instructions.ArgCommand{KeyValuePairOptional: instructions.KeyValuePairOptional{Key: argName, Value: &argVal}}
   397  	err := dispatch(sb, cmd)
   398  	assert.NilError(t, err)
   399  
   400  	expected := map[string]string{argName: argVal}
   401  	assert.Check(t, is.DeepEqual(expected, sb.state.buildArgs.GetAllAllowed()))
   402  }
   403  
   404  func TestShell(t *testing.T) {
   405  	b := newBuilderWithMockBackend()
   406  	sb := newDispatchRequest(b, '`', nil, NewBuildArgs(make(map[string]*string)), newStagesBuildResults())
   407  
   408  	shellCmd := "powershell"
   409  	cmd := &instructions.ShellCommand{Shell: strslice.StrSlice{shellCmd}}
   410  
   411  	err := dispatch(sb, cmd)
   412  	assert.NilError(t, err)
   413  
   414  	expectedShell := strslice.StrSlice([]string{shellCmd})
   415  	assert.Check(t, is.DeepEqual(expectedShell, sb.state.runConfig.Shell))
   416  }
   417  
   418  func TestPrependEnvOnCmd(t *testing.T) {
   419  	buildArgs := NewBuildArgs(nil)
   420  	buildArgs.AddArg("NO_PROXY", nil)
   421  
   422  	args := []string{"sorted=nope", "args=not", "http_proxy=foo", "NO_PROXY=YA"}
   423  	cmd := []string{"foo", "bar"}
   424  	cmdWithEnv := prependEnvOnCmd(buildArgs, args, cmd)
   425  	expected := strslice.StrSlice([]string{
   426  		"|3", "NO_PROXY=YA", "args=not", "sorted=nope", "foo", "bar"})
   427  	assert.Check(t, is.DeepEqual(expected, cmdWithEnv))
   428  }
   429  
   430  func TestRunWithBuildArgs(t *testing.T) {
   431  	b := newBuilderWithMockBackend()
   432  	args := NewBuildArgs(make(map[string]*string))
   433  	args.argsFromOptions["HTTP_PROXY"] = strPtr("FOO")
   434  	b.disableCommit = false
   435  	sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
   436  
   437  	runConfig := &container.Config{}
   438  	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
   439  	cmdWithShell := strslice.StrSlice(append(getShell(runConfig, runtime.GOOS), "echo foo"))
   440  	envVars := []string{"|1", "one=two"}
   441  	cachedCmd := strslice.StrSlice(append(envVars, cmdWithShell...))
   442  
   443  	imageCache := &mockImageCache{
   444  		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
   445  			// Check the runConfig.Cmd sent to probeCache()
   446  			assert.Check(t, is.DeepEqual(cachedCmd, cfg.Cmd))
   447  			assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Entrypoint))
   448  			return "", nil
   449  		},
   450  	}
   451  
   452  	mockBackend := b.docker.(*MockBackend)
   453  	mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
   454  		return imageCache
   455  	}
   456  	b.imageProber = newImageProber(mockBackend, nil, false)
   457  	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
   458  		return &mockImage{
   459  			id:     "abcdef",
   460  			config: &container.Config{Cmd: origCmd},
   461  		}, nil, nil
   462  	}
   463  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   464  		// Check the runConfig.Cmd sent to create()
   465  		assert.Check(t, is.DeepEqual(cmdWithShell, config.Config.Cmd))
   466  		assert.Check(t, is.Contains(config.Config.Env, "one=two"))
   467  		assert.Check(t, is.DeepEqual(strslice.StrSlice{""}, config.Config.Entrypoint))
   468  		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
   469  	}
   470  	mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
   471  		// Check the runConfig.Cmd sent to commit()
   472  		assert.Check(t, is.DeepEqual(origCmd, cfg.Config.Cmd))
   473  		assert.Check(t, is.DeepEqual(cachedCmd, cfg.ContainerConfig.Cmd))
   474  		assert.Check(t, is.DeepEqual(strslice.StrSlice(nil), cfg.Config.Entrypoint))
   475  		return "", nil
   476  	}
   477  	from := &instructions.Stage{BaseName: "abcdef"}
   478  	err := initializeStage(sb, from)
   479  	assert.NilError(t, err)
   480  	sb.state.buildArgs.AddArg("one", strPtr("two"))
   481  	run := &instructions.RunCommand{
   482  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   483  			CmdLine:      strslice.StrSlice{"echo foo"},
   484  			PrependShell: true,
   485  		},
   486  	}
   487  	assert.NilError(t, dispatch(sb, run))
   488  
   489  	// Check that runConfig.Cmd has not been modified by run
   490  	assert.Check(t, is.DeepEqual(origCmd, sb.state.runConfig.Cmd))
   491  }
   492  
   493  func TestRunIgnoresHealthcheck(t *testing.T) {
   494  	b := newBuilderWithMockBackend()
   495  	args := NewBuildArgs(make(map[string]*string))
   496  	sb := newDispatchRequest(b, '`', nil, args, newStagesBuildResults())
   497  	b.disableCommit = false
   498  
   499  	origCmd := strslice.StrSlice([]string{"cmd", "in", "from", "image"})
   500  
   501  	imageCache := &mockImageCache{
   502  		getCacheFunc: func(parentID string, cfg *container.Config) (string, error) {
   503  			return "", nil
   504  		},
   505  	}
   506  
   507  	mockBackend := b.docker.(*MockBackend)
   508  	mockBackend.makeImageCacheFunc = func(_ []string) builder.ImageCache {
   509  		return imageCache
   510  	}
   511  	b.imageProber = newImageProber(mockBackend, nil, false)
   512  	mockBackend.getImageFunc = func(_ string) (builder.Image, builder.ROLayer, error) {
   513  		return &mockImage{
   514  			id:     "abcdef",
   515  			config: &container.Config{Cmd: origCmd},
   516  		}, nil, nil
   517  	}
   518  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   519  		return container.ContainerCreateCreatedBody{ID: "12345"}, nil
   520  	}
   521  	mockBackend.commitFunc = func(cfg backend.CommitConfig) (image.ID, error) {
   522  		return "", nil
   523  	}
   524  	from := &instructions.Stage{BaseName: "abcdef"}
   525  	err := initializeStage(sb, from)
   526  	assert.NilError(t, err)
   527  
   528  	expectedTest := []string{"CMD-SHELL", "curl -f http://localhost/ || exit 1"}
   529  	cmd := &instructions.HealthCheckCommand{
   530  		Health: &container.HealthConfig{
   531  			Test: expectedTest,
   532  		},
   533  	}
   534  	assert.NilError(t, dispatch(sb, cmd))
   535  	assert.Assert(t, sb.state.runConfig.Healthcheck != nil)
   536  
   537  	mockBackend.containerCreateFunc = func(config types.ContainerCreateConfig) (container.ContainerCreateCreatedBody, error) {
   538  		// Check the Healthcheck is disabled.
   539  		assert.Check(t, is.DeepEqual([]string{"NONE"}, config.Config.Healthcheck.Test))
   540  		return container.ContainerCreateCreatedBody{ID: "123456"}, nil
   541  	}
   542  
   543  	sb.state.buildArgs.AddArg("one", strPtr("two"))
   544  	run := &instructions.RunCommand{
   545  		ShellDependantCmdLine: instructions.ShellDependantCmdLine{
   546  			CmdLine:      strslice.StrSlice{"echo foo"},
   547  			PrependShell: true,
   548  		},
   549  	}
   550  	assert.NilError(t, dispatch(sb, run))
   551  	assert.Check(t, is.DeepEqual(expectedTest, sb.state.runConfig.Healthcheck.Test))
   552  }