github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/builder/dockerfile/dispatchers_test.go (about)

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