Forráskód Böngészése

Merge pull request #14134 from vdemeester/runconfig-test-coverage

Refactor and add tests coverage on package runconfig
Alexander Morozov 10 éve
szülő
commit
be5e1498c3

+ 118 - 0
runconfig/compare_test.go

@@ -0,0 +1,118 @@
+package runconfig
+
+import (
+	"testing"
+
+	"github.com/docker/docker/pkg/nat"
+)
+
+func TestCompare(t *testing.T) {
+	ports1 := make(nat.PortSet)
+	ports1[nat.Port("1111/tcp")] = struct{}{}
+	ports1[nat.Port("2222/tcp")] = struct{}{}
+	ports2 := make(nat.PortSet)
+	ports2[nat.Port("3333/tcp")] = struct{}{}
+	ports2[nat.Port("4444/tcp")] = struct{}{}
+	ports3 := make(nat.PortSet)
+	ports3[nat.Port("1111/tcp")] = struct{}{}
+	ports3[nat.Port("2222/tcp")] = struct{}{}
+	ports3[nat.Port("5555/tcp")] = struct{}{}
+	volumes1 := make(map[string]struct{})
+	volumes1["/test1"] = struct{}{}
+	volumes2 := make(map[string]struct{})
+	volumes2["/test2"] = struct{}{}
+	volumes3 := make(map[string]struct{})
+	volumes3["/test1"] = struct{}{}
+	volumes3["/test3"] = struct{}{}
+	envs1 := []string{"ENV1=value1", "ENV2=value2"}
+	envs2 := []string{"ENV1=value1", "ENV3=value3"}
+	entrypoint1 := &Entrypoint{parts: []string{"/bin/sh", "-c"}}
+	entrypoint2 := &Entrypoint{parts: []string{"/bin/sh", "-d"}}
+	entrypoint3 := &Entrypoint{parts: []string{"/bin/sh", "-c", "echo"}}
+	cmd1 := &Command{parts: []string{"/bin/sh", "-c"}}
+	cmd2 := &Command{parts: []string{"/bin/sh", "-d"}}
+	cmd3 := &Command{parts: []string{"/bin/sh", "-c", "echo"}}
+	labels1 := map[string]string{"LABEL1": "value1", "LABEL2": "value2"}
+	labels2 := map[string]string{"LABEL1": "value1", "LABEL2": "value3"}
+	labels3 := map[string]string{"LABEL1": "value1", "LABEL2": "value2", "LABEL3": "value3"}
+
+	sameConfigs := map[*Config]*Config{
+		// Empty config
+		&Config{}: {},
+		// Does not compare hostname, domainname & image
+		&Config{
+			Hostname:   "host1",
+			Domainname: "domain1",
+			Image:      "image1",
+			User:       "user",
+		}: {
+			Hostname:   "host2",
+			Domainname: "domain2",
+			Image:      "image2",
+			User:       "user",
+		},
+		// only OpenStdin
+		&Config{OpenStdin: false}: {OpenStdin: false},
+		// only env
+		&Config{Env: envs1}: {Env: envs1},
+		// only cmd
+		&Config{Cmd: cmd1}: {Cmd: cmd1},
+		// only labels
+		&Config{Labels: labels1}: {Labels: labels1},
+		// only exposedPorts
+		&Config{ExposedPorts: ports1}: {ExposedPorts: ports1},
+		// only entrypoints
+		&Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint1},
+		// only volumes
+		&Config{Volumes: volumes1}: {Volumes: volumes1},
+	}
+	differentConfigs := map[*Config]*Config{
+		nil: nil,
+		&Config{
+			Hostname:   "host1",
+			Domainname: "domain1",
+			Image:      "image1",
+			User:       "user1",
+		}: {
+			Hostname:   "host1",
+			Domainname: "domain1",
+			Image:      "image1",
+			User:       "user2",
+		},
+		// only OpenStdin
+		&Config{OpenStdin: false}: {OpenStdin: true},
+		&Config{OpenStdin: true}:  {OpenStdin: false},
+		// only env
+		&Config{Env: envs1}: {Env: envs2},
+		// only cmd
+		&Config{Cmd: cmd1}: {Cmd: cmd2},
+		// not the same number of parts
+		&Config{Cmd: cmd1}: {Cmd: cmd3},
+		// only labels
+		&Config{Labels: labels1}: {Labels: labels2},
+		// not the same number of labels
+		&Config{Labels: labels1}: {Labels: labels3},
+		// only exposedPorts
+		&Config{ExposedPorts: ports1}: {ExposedPorts: ports2},
+		// not the same number of ports
+		&Config{ExposedPorts: ports1}: {ExposedPorts: ports3},
+		// only entrypoints
+		&Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint2},
+		// not the same number of parts
+		&Config{Entrypoint: entrypoint1}: {Entrypoint: entrypoint3},
+		// only volumes
+		&Config{Volumes: volumes1}: {Volumes: volumes2},
+		// not the same number of labels
+		&Config{Volumes: volumes1}: {Volumes: volumes3},
+	}
+	for config1, config2 := range sameConfigs {
+		if !Compare(config1, config2) {
+			t.Fatalf("Compare should be true for [%v] and [%v]", config1, config2)
+		}
+	}
+	for config1, config2 := range differentConfigs {
+		if Compare(config1, config2) {
+			t.Fatalf("Compare should be false for [%v] and [%v]", config1, config2)
+		}
+	}
+}

+ 76 - 241
runconfig/config_test.go

@@ -5,274 +5,109 @@ import (
 	"encoding/json"
 	"fmt"
 	"io/ioutil"
-	"strings"
 	"testing"
-
-	"github.com/docker/docker/pkg/nat"
 )
 
-func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
-	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
-	return config, hostConfig, err
-}
-
-func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
-	config, hostConfig, err := parse(t, args)
-	if err != nil {
-		t.Fatal(err)
+func TestEntrypointMarshalJSON(t *testing.T) {
+	entrypoints := map[*Entrypoint]string{
+		nil:                                            "",
+		&Entrypoint{}:                                  "null",
+		&Entrypoint{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
 	}
-	return config, hostConfig
-}
 
-// check if (a == c && b == d) || (a == d && b == c)
-// because maps are randomized
-func compareRandomizedStrings(a, b, c, d string) error {
-	if a == c && b == d {
-		return nil
-	}
-	if a == d && b == c {
-		return nil
-	}
-	return fmt.Errorf("strings don't match")
-}
-
-func TestParseRunLinks(t *testing.T) {
-	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
-		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
-	}
-	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
-		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
-	}
-	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
-		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
+	for entrypoint, expected := range entrypoints {
+		data, err := entrypoint.MarshalJSON()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if string(data) != expected {
+			t.Fatalf("Expected %v, got %v", expected, string(data))
+		}
 	}
 }
 
-func TestParseRunAttach(t *testing.T) {
-	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
-		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
-	}
-	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
-		t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
-	}
-	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
-		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
-	}
-	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
-		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+func TestEntrypointUnmarshalJSON(t *testing.T) {
+	parts := map[string][]string{
+		"":   {"default", "values"},
+		"[]": {},
+		`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
 	}
+	for json, expectedParts := range parts {
+		entrypoint := &Entrypoint{
+			[]string{"default", "values"},
+		}
+		if err := entrypoint.UnmarshalJSON([]byte(json)); err != nil {
+			t.Fatal(err)
+		}
 
-	if _, _, err := parse(t, "-a"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-a invalid"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-a stdin -d"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-a stdout -d"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-a stderr -d"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
-	}
-	if _, _, err := parse(t, "-d --rm"); err == nil {
-		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
+		actualParts := entrypoint.Slice()
+		if len(actualParts) != len(expectedParts) {
+			t.Fatalf("Expected %v parts, got %v (%v)", len(expectedParts), len(actualParts), expectedParts)
+		}
+		for index, part := range actualParts {
+			if part != expectedParts[index] {
+				t.Fatalf("Expected %v, got %v", expectedParts, actualParts)
+				break
+			}
+		}
 	}
 }
 
-func TestParseRunVolumes(t *testing.T) {
-	if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
-	} else if _, exists := config.Volumes["/tmp"]; !exists {
-		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
-	}
-
-	if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
-	} else if _, exists := config.Volumes["/tmp"]; !exists {
-		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
-	} else if _, exists := config.Volumes["/var"]; !exists {
-		t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
-	}
-
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
-	}
-
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp", "/hostVar:/containerVar") != nil {
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
-	}
-
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro", "/hostVar:/containerVar:rw") != nil {
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
-	}
-
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:roZ", "/hostVar:/containerVar:rwZ") != nil {
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
-	}
-
-	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+func TestCommandToString(t *testing.T) {
+	commands := map[*Command]string{
+		&Command{[]string{""}}:           "",
+		&Command{[]string{"one"}}:        "one",
+		&Command{[]string{"one", "two"}}: "one two",
 	}
-
-	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
-		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
-	} else if _, exists := config.Volumes["/containerVar"]; !exists {
-		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
-	}
-
-	if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
-		t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
-	} else if len(config.Volumes) != 0 {
-		t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
-	}
-
-	if _, _, err := parse(t, "-v /"); err == nil {
-		t.Fatalf("Expected error, but got none")
-	}
-
-	if _, _, err := parse(t, "-v /:/"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v /tmp:"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v /tmp::"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v :"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v ::"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
-	}
-	if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
-		t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
+	for command, expected := range commands {
+		toString := command.ToString()
+		if toString != expected {
+			t.Fatalf("Expected %v, got %v", expected, toString)
+		}
 	}
 }
 
-func TestCompare(t *testing.T) {
-	volumes1 := make(map[string]struct{})
-	volumes1["/test1"] = struct{}{}
-	ports1 := make(nat.PortSet)
-	ports1[nat.Port("1111/tcp")] = struct{}{}
-	ports1[nat.Port("2222/tcp")] = struct{}{}
-	config1 := Config{
-		ExposedPorts: ports1,
-		Env:          []string{"VAR1=1", "VAR2=2"},
-		Volumes:      volumes1,
-	}
-	ports3 := make(nat.PortSet)
-	ports3[nat.Port("0000/tcp")] = struct{}{}
-	ports3[nat.Port("2222/tcp")] = struct{}{}
-	config3 := Config{
-		ExposedPorts: ports3,
-		Volumes:      volumes1,
-	}
-	volumes2 := make(map[string]struct{})
-	volumes2["/test2"] = struct{}{}
-	config5 := Config{
-		Env:     []string{"VAR1=1", "VAR2=2"},
-		Volumes: volumes2,
+func TestCommandMarshalJSON(t *testing.T) {
+	commands := map[*Command]string{
+		nil:        "",
+		&Command{}: "null",
+		&Command{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
 	}
 
-	if Compare(&config1, &config3) {
-		t.Fatalf("Compare should return false, ExposedPorts are different")
-	}
-	if Compare(&config1, &config5) {
-		t.Fatalf("Compare should return false, Volumes are different")
-	}
-	if !Compare(&config1, &config1) {
-		t.Fatalf("Compare should return true")
+	for command, expected := range commands {
+		data, err := command.MarshalJSON()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if string(data) != expected {
+			t.Fatalf("Expected %v, got %v", expected, string(data))
+		}
 	}
 }
 
-func TestMerge(t *testing.T) {
-	volumesImage := make(map[string]struct{})
-	volumesImage["/test1"] = struct{}{}
-	volumesImage["/test2"] = struct{}{}
-	portsImage := make(nat.PortSet)
-	portsImage[nat.Port("1111/tcp")] = struct{}{}
-	portsImage[nat.Port("2222/tcp")] = struct{}{}
-	configImage := &Config{
-		ExposedPorts: portsImage,
-		Env:          []string{"VAR1=1", "VAR2=2"},
-		Volumes:      volumesImage,
-	}
-
-	portsUser := make(nat.PortSet)
-	portsUser[nat.Port("2222/tcp")] = struct{}{}
-	portsUser[nat.Port("3333/tcp")] = struct{}{}
-	volumesUser := make(map[string]struct{})
-	volumesUser["/test3"] = struct{}{}
-	configUser := &Config{
-		ExposedPorts: portsUser,
-		Env:          []string{"VAR2=3", "VAR3=3"},
-		Volumes:      volumesUser,
+func TestCommandUnmarshalJSON(t *testing.T) {
+	parts := map[string][]string{
+		"":   {"default", "values"},
+		"[]": {},
+		`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
 	}
-
-	if err := Merge(configUser, configImage); err != nil {
-		t.Error(err)
-	}
-
-	if len(configUser.ExposedPorts) != 3 {
-		t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
-	}
-	for portSpecs := range configUser.ExposedPorts {
-		if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
-			t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
+	for json, expectedParts := range parts {
+		command := &Command{
+			[]string{"default", "values"},
 		}
-	}
-	if len(configUser.Env) != 3 {
-		t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
-	}
-	for _, env := range configUser.Env {
-		if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
-			t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
+		if err := command.UnmarshalJSON([]byte(json)); err != nil {
+			t.Fatal(err)
 		}
-	}
 
-	if len(configUser.Volumes) != 3 {
-		t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
-	}
-	for v := range configUser.Volumes {
-		if v != "/test1" && v != "/test2" && v != "/test3" {
-			t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
+		actualParts := command.Slice()
+		if len(actualParts) != len(expectedParts) {
+			t.Fatalf("Expected %v parts, got %v (%v)", len(expectedParts), len(actualParts), expectedParts)
 		}
-	}
-
-	ports, _, err := nat.ParsePortSpecs([]string{"0000"})
-	if err != nil {
-		t.Error(err)
-	}
-	configImage2 := &Config{
-		ExposedPorts: ports,
-	}
-
-	if err := Merge(configUser, configImage2); err != nil {
-		t.Error(err)
-	}
-
-	if len(configUser.ExposedPorts) != 4 {
-		t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
-	}
-	for portSpecs := range configUser.ExposedPorts {
-		if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
-			t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
+		for index, part := range actualParts {
+			if part != expectedParts[index] {
+				t.Fatalf("Expected %v, got %v", expectedParts, actualParts)
+				break
+			}
 		}
 	}
 }

+ 129 - 0
runconfig/exec_test.go

@@ -0,0 +1,129 @@
+package runconfig
+
+import (
+	"fmt"
+	flag "github.com/docker/docker/pkg/mflag"
+	"io/ioutil"
+	"testing"
+)
+
+type arguments struct {
+	args []string
+}
+
+func TestParseExec(t *testing.T) {
+	invalids := map[*arguments]error{
+		&arguments{[]string{"-unknown"}}: fmt.Errorf("flag provided but not defined: -unknown"),
+		&arguments{[]string{"-u"}}:       fmt.Errorf("flag needs an argument: -u"),
+		&arguments{[]string{"--user"}}:   fmt.Errorf("flag needs an argument: --user"),
+	}
+	valids := map[*arguments]*ExecConfig{
+		&arguments{
+			[]string{"container", "command"},
+		}: {
+			Container:    "container",
+			Cmd:          []string{"command"},
+			AttachStdout: true,
+			AttachStderr: true,
+		},
+		&arguments{
+			[]string{"container", "command1", "command2"},
+		}: {
+			Container:    "container",
+			Cmd:          []string{"command1", "command2"},
+			AttachStdout: true,
+			AttachStderr: true,
+		},
+		&arguments{
+			[]string{"-i", "-t", "-u", "uid", "container", "command"},
+		}: {
+			User:         "uid",
+			AttachStdin:  true,
+			AttachStdout: true,
+			AttachStderr: true,
+			Tty:          true,
+			Container:    "container",
+			Cmd:          []string{"command"},
+		},
+		&arguments{
+			[]string{"-d", "container", "command"},
+		}: {
+			AttachStdin:  false,
+			AttachStdout: false,
+			AttachStderr: false,
+			Detach:       true,
+			Container:    "container",
+			Cmd:          []string{"command"},
+		},
+		&arguments{
+			[]string{"-t", "-i", "-d", "container", "command"},
+		}: {
+			AttachStdin:  false,
+			AttachStdout: false,
+			AttachStderr: false,
+			Detach:       true,
+			Tty:          true,
+			Container:    "container",
+			Cmd:          []string{"command"},
+		},
+	}
+	for invalid, expectedError := range invalids {
+		cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
+		cmd.ShortUsage = func() {}
+		cmd.SetOutput(ioutil.Discard)
+		_, err := ParseExec(cmd, invalid.args)
+		if err == nil || err.Error() != expectedError.Error() {
+			t.Fatalf("Expected an error [%v] for %v, got %v", expectedError, invalid, err)
+		}
+
+	}
+	for valid, expectedExecConfig := range valids {
+		cmd := flag.NewFlagSet("exec", flag.ContinueOnError)
+		cmd.ShortUsage = func() {}
+		cmd.SetOutput(ioutil.Discard)
+		execConfig, err := ParseExec(cmd, valid.args)
+		if err != nil {
+			t.Fatal(err)
+		}
+		if !compareExecConfig(expectedExecConfig, execConfig) {
+			t.Fatalf("Expected [%v] for %v, got [%v]", expectedExecConfig, valid, execConfig)
+		}
+	}
+}
+
+func compareExecConfig(config1 *ExecConfig, config2 *ExecConfig) bool {
+	if config1.AttachStderr != config2.AttachStderr {
+		return false
+	}
+	if config1.AttachStdin != config2.AttachStdin {
+		return false
+	}
+	if config1.AttachStdout != config2.AttachStdout {
+		return false
+	}
+	if config1.Container != config2.Container {
+		return false
+	}
+	if config1.Detach != config2.Detach {
+		return false
+	}
+	if config1.Privileged != config2.Privileged {
+		return false
+	}
+	if config1.Tty != config2.Tty {
+		return false
+	}
+	if config1.User != config2.User {
+		return false
+	}
+	if len(config1.Cmd) != len(config2.Cmd) {
+		return false
+	} else {
+		for index, value := range config1.Cmd {
+			if value != config2.Cmd[index] {
+				return false
+			}
+		}
+	}
+	return true
+}

+ 18 - 0
runconfig/fixtures/container_hostconfig_1_14.json

@@ -0,0 +1,18 @@
+{
+    "Binds": ["/tmp:/tmp"],
+    "ContainerIDFile": "",
+    "LxcConf": [],
+    "Privileged": false,
+    "PortBindings": {
+        "80/tcp": [
+            {
+                "HostIp": "0.0.0.0",
+                "HostPort": "49153"
+            }
+        ]
+    },
+    "Links": ["/name:alias"],
+    "PublishAllPorts": false,
+    "CapAdd": ["NET_ADMIN"],
+    "CapDrop": ["MKNOD"]
+}

+ 30 - 0
runconfig/fixtures/container_hostconfig_1_19.json

@@ -0,0 +1,30 @@
+{
+    "Binds": ["/tmp:/tmp"],
+    "Links": ["redis3:redis"],
+    "LxcConf": {"lxc.utsname":"docker"},
+    "Memory": 0,
+    "MemorySwap": 0,
+    "CpuShares": 512,
+    "CpuPeriod": 100000,
+    "CpusetCpus": "0,1",
+    "CpusetMems": "0,1",
+    "BlkioWeight": 300,
+    "OomKillDisable": false,
+    "PortBindings": { "22/tcp": [{ "HostPort": "11022" }] },
+    "PublishAllPorts": false,
+    "Privileged": false,
+    "ReadonlyRootfs": false,
+    "Dns": ["8.8.8.8"],
+    "DnsSearch": [""],
+    "ExtraHosts": null,
+    "VolumesFrom": ["parent", "other:ro"],
+    "CapAdd": ["NET_ADMIN"],
+    "CapDrop": ["MKNOD"],
+    "RestartPolicy": { "Name": "", "MaximumRetryCount": 0 },
+    "NetworkMode": "bridge",
+    "Devices": [],
+    "Ulimits": [{}],
+    "LogConfig": { "Type": "json-file", "Config": {} },
+    "SecurityOpt": [""],
+    "CgroupParent": ""
+}

+ 1 - 0
runconfig/fixtures/valid.env

@@ -0,0 +1 @@
+ENV1=value1

+ 1 - 0
runconfig/fixtures/valid.label

@@ -0,0 +1 @@
+LABEL1=value1

+ 265 - 0
runconfig/hostconfig_test.go

@@ -0,0 +1,265 @@
+package runconfig
+
+import (
+	"bytes"
+	"fmt"
+	"io/ioutil"
+	"testing"
+)
+
+func TestNetworkModeTest(t *testing.T) {
+	networkModes := map[NetworkMode][]bool{
+		// private, bridge, host, container, none, default
+		"":                         {true, false, false, false, false, false},
+		"something:weird":          {true, false, false, false, false, false},
+		"bridge":                   {true, true, false, false, false, false},
+		DefaultDaemonNetworkMode(): {true, true, false, false, false, false},
+		"host":           {false, false, true, false, false, false},
+		"container:name": {false, false, false, true, false, false},
+		"none":           {true, false, false, false, true, false},
+		"default":        {true, false, false, false, false, true},
+	}
+	networkModeNames := map[NetworkMode]string{
+		"":                         "",
+		"something:weird":          "",
+		"bridge":                   "bridge",
+		DefaultDaemonNetworkMode(): "bridge",
+		"host":           "host",
+		"container:name": "container",
+		"none":           "none",
+		"default":        "default",
+	}
+	for networkMode, state := range networkModes {
+		if networkMode.IsPrivate() != state[0] {
+			t.Fatalf("NetworkMode.IsPrivate for %v should have been %v but was %v", networkMode, state[0], networkMode.IsPrivate())
+		}
+		if networkMode.IsBridge() != state[1] {
+			t.Fatalf("NetworkMode.IsBridge for %v should have been %v but was %v", networkMode, state[1], networkMode.IsBridge())
+		}
+		if networkMode.IsHost() != state[2] {
+			t.Fatalf("NetworkMode.IsHost for %v should have been %v but was %v", networkMode, state[2], networkMode.IsHost())
+		}
+		if networkMode.IsContainer() != state[3] {
+			t.Fatalf("NetworkMode.IsContainer for %v should have been %v but was %v", networkMode, state[3], networkMode.IsContainer())
+		}
+		if networkMode.IsNone() != state[4] {
+			t.Fatalf("NetworkMode.IsNone for %v should have been %v but was %v", networkMode, state[4], networkMode.IsNone())
+		}
+		if networkMode.IsDefault() != state[5] {
+			t.Fatalf("NetworkMode.IsDefault for %v should have been %v but was %v", networkMode, state[5], networkMode.IsDefault())
+		}
+		if networkMode.NetworkName() != networkModeNames[networkMode] {
+			t.Fatalf("Expected name %v, got %v", networkModeNames[networkMode], networkMode.NetworkName())
+		}
+	}
+}
+
+func TestIpcModeTest(t *testing.T) {
+	ipcModes := map[IpcMode][]bool{
+		// private, host, container, valid
+		"":                         {true, false, false, true},
+		"something:weird":          {true, false, false, false},
+		":weird":                   {true, false, false, true},
+		"host":                     {false, true, false, true},
+		"container:name":           {false, false, true, true},
+		"container:name:something": {false, false, true, false},
+		"container:":               {false, false, true, false},
+	}
+	for ipcMode, state := range ipcModes {
+		if ipcMode.IsPrivate() != state[0] {
+			t.Fatalf("IpcMode.IsPrivate for %v should have been %v but was %v", ipcMode, state[0], ipcMode.IsPrivate())
+		}
+		if ipcMode.IsHost() != state[1] {
+			t.Fatalf("IpcMode.IsHost for %v should have been %v but was %v", ipcMode, state[1], ipcMode.IsHost())
+		}
+		if ipcMode.IsContainer() != state[2] {
+			t.Fatalf("IpcMode.IsContainer for %v should have been %v but was %v", ipcMode, state[2], ipcMode.IsContainer())
+		}
+		if ipcMode.Valid() != state[3] {
+			t.Fatalf("IpcMode.Valid for %v should have been %v but was %v", ipcMode, state[3], ipcMode.Valid())
+		}
+	}
+	containerIpcModes := map[IpcMode]string{
+		"":                      "",
+		"something":             "",
+		"something:weird":       "weird",
+		"container":             "",
+		"container:":            "",
+		"container:name":        "name",
+		"container:name1:name2": "name1:name2",
+	}
+	for ipcMode, container := range containerIpcModes {
+		if ipcMode.Container() != container {
+			t.Fatalf("Expected %v for %v but was %v", container, ipcMode, ipcMode.Container())
+		}
+	}
+}
+
+func TestUTSModeTest(t *testing.T) {
+	utsModes := map[UTSMode][]bool{
+		// private, host, valid
+		"":                {true, false, true},
+		"something:weird": {true, false, false},
+		"host":            {false, true, true},
+		"host:name":       {true, false, true},
+	}
+	for utsMode, state := range utsModes {
+		if utsMode.IsPrivate() != state[0] {
+			t.Fatalf("UtsMode.IsPrivate for %v should have been %v but was %v", utsMode, state[0], utsMode.IsPrivate())
+		}
+		if utsMode.IsHost() != state[1] {
+			t.Fatalf("UtsMode.IsHost for %v should have been %v but was %v", utsMode, state[1], utsMode.IsHost())
+		}
+		if utsMode.Valid() != state[2] {
+			t.Fatalf("UtsMode.Valid for %v should have been %v but was %v", utsMode, state[2], utsMode.Valid())
+		}
+	}
+}
+
+func TestPidModeTest(t *testing.T) {
+	pidModes := map[PidMode][]bool{
+		// private, host, valid
+		"":                {true, false, true},
+		"something:weird": {true, false, false},
+		"host":            {false, true, true},
+		"host:name":       {true, false, true},
+	}
+	for pidMode, state := range pidModes {
+		if pidMode.IsPrivate() != state[0] {
+			t.Fatalf("PidMode.IsPrivate for %v should have been %v but was %v", pidMode, state[0], pidMode.IsPrivate())
+		}
+		if pidMode.IsHost() != state[1] {
+			t.Fatalf("PidMode.IsHost for %v should have been %v but was %v", pidMode, state[1], pidMode.IsHost())
+		}
+		if pidMode.Valid() != state[2] {
+			t.Fatalf("PidMode.Valid for %v should have been %v but was %v", pidMode, state[2], pidMode.Valid())
+		}
+	}
+}
+
+func TestRestartPolicy(t *testing.T) {
+	restartPolicies := map[RestartPolicy][]bool{
+		// none, always, failure
+		RestartPolicy{}:                {false, false, false},
+		RestartPolicy{"something", 0}:  {false, false, false},
+		RestartPolicy{"no", 0}:         {true, false, false},
+		RestartPolicy{"always", 0}:     {false, true, false},
+		RestartPolicy{"on-failure", 0}: {false, false, true},
+	}
+	for restartPolicy, state := range restartPolicies {
+		if restartPolicy.IsNone() != state[0] {
+			t.Fatalf("RestartPolicy.IsNone for %v should have been %v but was %v", restartPolicy, state[0], restartPolicy.IsNone())
+		}
+		if restartPolicy.IsAlways() != state[1] {
+			t.Fatalf("RestartPolicy.IsAlways for %v should have been %v but was %v", restartPolicy, state[1], restartPolicy.IsAlways())
+		}
+		if restartPolicy.IsOnFailure() != state[2] {
+			t.Fatalf("RestartPolicy.IsOnFailure for %v should have been %v but was %v", restartPolicy, state[2], restartPolicy.IsOnFailure())
+		}
+	}
+}
+
+func TestLxcConfigMarshalJSON(t *testing.T) {
+	lxcConfigs := map[*LxcConfig]string{
+		nil:          "",
+		&LxcConfig{}: "null",
+		&LxcConfig{
+			[]KeyValuePair{{"key1", "value1"}},
+		}: `[{"Key":"key1","Value":"value1"}]`,
+	}
+
+	for lxcconfig, expected := range lxcConfigs {
+		data, err := lxcconfig.MarshalJSON()
+		if err != nil {
+			t.Fatal(err)
+		}
+		if string(data) != expected {
+			t.Fatalf("Expected %v, got %v", expected, string(data))
+		}
+	}
+}
+
+func TestLxcConfigUnmarshalJSON(t *testing.T) {
+	keyvaluePairs := map[string][]KeyValuePair{
+		"":   {{"key1", "value1"}},
+		"[]": {},
+		`[{"Key":"key2","Value":"value2"}]`: {{"key2", "value2"}},
+	}
+	for json, expectedParts := range keyvaluePairs {
+		lxcConfig := &LxcConfig{
+			[]KeyValuePair{{"key1", "value1"}},
+		}
+		if err := lxcConfig.UnmarshalJSON([]byte(json)); err != nil {
+			t.Fatal(err)
+		}
+
+		actualParts := lxcConfig.Slice()
+		if len(actualParts) != len(expectedParts) {
+			t.Fatalf("Expected %v keyvaluePairs, got %v (%v)", len(expectedParts), len(actualParts), expectedParts)
+		}
+		for index, part := range actualParts {
+			if part != expectedParts[index] {
+				t.Fatalf("Expected %v, got %v", expectedParts, actualParts)
+				break
+			}
+		}
+	}
+}
+
+func TestMergeConfigs(t *testing.T) {
+	expectedHostname := "hostname"
+	expectedContainerIDFile := "containerIdFile"
+	config := &Config{
+		Hostname: expectedHostname,
+	}
+	hostConfig := &HostConfig{
+		ContainerIDFile: expectedContainerIDFile,
+	}
+	containerConfigWrapper := MergeConfigs(config, hostConfig)
+	if containerConfigWrapper.Config.Hostname != expectedHostname {
+		t.Fatalf("containerConfigWrapper config hostname expected %v got %v", expectedHostname, containerConfigWrapper.Config.Hostname)
+	}
+	if containerConfigWrapper.InnerHostConfig.ContainerIDFile != expectedContainerIDFile {
+		t.Fatalf("containerConfigWrapper hostconfig containerIdfile expected %v got %v", expectedContainerIDFile, containerConfigWrapper.InnerHostConfig.ContainerIDFile)
+	}
+	if containerConfigWrapper.Cpuset != "" {
+		t.Fatalf("Expected empty Cpuset, got %v", containerConfigWrapper.Cpuset)
+	}
+}
+
+func TestDecodeHostConfig(t *testing.T) {
+	fixtures := []struct {
+		file string
+	}{
+		{"fixtures/container_hostconfig_1_14.json"},
+		{"fixtures/container_hostconfig_1_19.json"},
+	}
+
+	for _, f := range fixtures {
+		b, err := ioutil.ReadFile(f.file)
+		if err != nil {
+			t.Fatal(err)
+		}
+
+		c, err := DecodeHostConfig(bytes.NewReader(b))
+		if err != nil {
+			t.Fatal(fmt.Errorf("Error parsing %s: %v", f, err))
+		}
+
+		if c.Privileged != false {
+			t.Fatalf("Expected privileged false, found %s\n", c.Privileged)
+		}
+
+		if len(c.Binds) != 1 {
+			t.Fatalf("Expected 1 bind, found %v\n", c.Binds)
+		}
+
+		if len(c.CapAdd) != 1 && c.CapAdd[0] != "NET_ADMIN" {
+			t.Fatalf("Expected CapAdd NET_ADMIN, got %v", c.CapAdd)
+		}
+
+		if len(c.CapDrop) != 1 && c.CapDrop[0] != "NET_ADMIN" {
+			t.Fatalf("Expected CapDrop MKNOD, got %v", c.CapDrop)
+		}
+	}
+}

+ 83 - 0
runconfig/merge_test.go

@@ -0,0 +1,83 @@
+package runconfig
+
+import (
+	"testing"
+
+	"github.com/docker/docker/pkg/nat"
+)
+
+func TestMerge(t *testing.T) {
+	volumesImage := make(map[string]struct{})
+	volumesImage["/test1"] = struct{}{}
+	volumesImage["/test2"] = struct{}{}
+	portsImage := make(nat.PortSet)
+	portsImage[nat.Port("1111/tcp")] = struct{}{}
+	portsImage[nat.Port("2222/tcp")] = struct{}{}
+	configImage := &Config{
+		ExposedPorts: portsImage,
+		Env:          []string{"VAR1=1", "VAR2=2"},
+		Volumes:      volumesImage,
+	}
+
+	portsUser := make(nat.PortSet)
+	portsUser[nat.Port("2222/tcp")] = struct{}{}
+	portsUser[nat.Port("3333/tcp")] = struct{}{}
+	volumesUser := make(map[string]struct{})
+	volumesUser["/test3"] = struct{}{}
+	configUser := &Config{
+		ExposedPorts: portsUser,
+		Env:          []string{"VAR2=3", "VAR3=3"},
+		Volumes:      volumesUser,
+	}
+
+	if err := Merge(configUser, configImage); err != nil {
+		t.Error(err)
+	}
+
+	if len(configUser.ExposedPorts) != 3 {
+		t.Fatalf("Expected 3 ExposedPorts, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
+	}
+	for portSpecs := range configUser.ExposedPorts {
+		if portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
+			t.Fatalf("Expected 1111 or 2222 or 3333, found %s", portSpecs)
+		}
+	}
+	if len(configUser.Env) != 3 {
+		t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
+	}
+	for _, env := range configUser.Env {
+		if env != "VAR1=1" && env != "VAR2=3" && env != "VAR3=3" {
+			t.Fatalf("Expected VAR1=1 or VAR2=3 or VAR3=3, found %s", env)
+		}
+	}
+
+	if len(configUser.Volumes) != 3 {
+		t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
+	}
+	for v := range configUser.Volumes {
+		if v != "/test1" && v != "/test2" && v != "/test3" {
+			t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
+		}
+	}
+
+	ports, _, err := nat.ParsePortSpecs([]string{"0000"})
+	if err != nil {
+		t.Error(err)
+	}
+	configImage2 := &Config{
+		ExposedPorts: ports,
+	}
+
+	if err := Merge(configUser, configImage2); err != nil {
+		t.Error(err)
+	}
+
+	if len(configUser.ExposedPorts) != 4 {
+		t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
+	}
+	for portSpecs := range configUser.ExposedPorts {
+		if portSpecs.Port() != "0" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" {
+			t.Fatalf("Expected %q or %q or %q or %q, found %s", 0, 1111, 2222, 3333, portSpecs)
+		}
+	}
+}

+ 496 - 0
runconfig/parse_test.go

@@ -1,10 +1,13 @@
 package runconfig
 
 import (
+	"fmt"
 	"io/ioutil"
+	"strings"
 	"testing"
 
 	flag "github.com/docker/docker/pkg/mflag"
+	"github.com/docker/docker/pkg/nat"
 	"github.com/docker/docker/pkg/parsers"
 )
 
@@ -15,6 +18,162 @@ func parseRun(args []string) (*Config, *HostConfig, *flag.FlagSet, error) {
 	return Parse(cmd, args)
 }
 
+func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
+	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
+	return config, hostConfig, err
+}
+
+func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
+	config, hostConfig, err := parse(t, args)
+	if err != nil {
+		t.Fatal(err)
+	}
+	return config, hostConfig
+}
+
+// check if (a == c && b == d) || (a == d && b == c)
+// because maps are randomized
+func compareRandomizedStrings(a, b, c, d string) error {
+	if a == c && b == d {
+		return nil
+	}
+	if a == d && b == c {
+		return nil
+	}
+	return fmt.Errorf("strings don't match")
+}
+func TestParseRunLinks(t *testing.T) {
+	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
+	}
+	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
+		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
+	}
+	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
+		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
+	}
+}
+
+func TestParseRunAttach(t *testing.T) {
+	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect only Stdin and Stdout enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+	if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
+		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
+	}
+
+	if _, _, err := parse(t, "-a"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a invalid"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stdin -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stdout -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-a stderr -d"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
+	}
+	if _, _, err := parse(t, "-d --rm"); err == nil {
+		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
+	}
+}
+
+func TestParseRunVolumes(t *testing.T) {
+	if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/tmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/tmp"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
+	} else if _, exists := config.Volumes["/var"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
+	}
+
+	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /hostVar:/containerVar"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp", "/hostVar:/containerVar") != nil {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /hostVar:/containerVar` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+	}
+
+	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro", "/hostVar:/containerVar:rw") != nil {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro -v /hostVar:/containerVar:rw` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+	}
+
+	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:roZ", "/hostVar:/containerVar:rwZ") != nil {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:roZ -v /hostVar:/containerVar:rwZ` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+	}
+
+	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:Z", "/hostVar:/containerVar:z") != nil {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:Z -v /hostVar:/containerVar:z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
+	}
+
+	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
+		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
+	} else if _, exists := config.Volumes["/containerVar"]; !exists {
+		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
+	}
+
+	if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
+		t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
+	} else if len(config.Volumes) != 0 {
+		t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
+	}
+
+	if _, _, err := parse(t, "-v /"); err == nil {
+		t.Fatalf("Expected error, but got none")
+	}
+
+	if _, _, err := parse(t, "-v /:/"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp:"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp:ro"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp:ro` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp::"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v :"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v ::"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
+	}
+	if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
+		t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
+	}
+}
+
 func TestParseLxcConfOpt(t *testing.T) {
 	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
 
@@ -30,6 +189,18 @@ func TestParseLxcConfOpt(t *testing.T) {
 			t.Fail()
 		}
 	}
+
+	// With parseRun too
+	_, hostconfig, _, err := parseRun([]string{"lxc.utsname=docker", "lxc.utsname = docker ", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	for _, lxcConf := range hostconfig.LxcConf.Slice() {
+		if lxcConf.Key != "lxc.utsname" || lxcConf.Value != "docker" {
+			t.Fail()
+		}
+	}
+
 }
 
 func TestNetHostname(t *testing.T) {
@@ -56,10 +227,335 @@ func TestNetHostname(t *testing.T) {
 	if _, _, _, err := parseRun([]string{"-h=name", "--net=container:other", "img", "cmd"}); err != ErrConflictNetworkHostname {
 		t.Fatalf("Expected error ErrConflictNetworkHostname, got: %s", err)
 	}
+	if _, _, _, err := parseRun([]string{"--net=container", "img", "cmd"}); err == nil || err.Error() != "--net: invalid net mode: invalid container format container:<name|id>" {
+		t.Fatalf("Expected error with --net=container, got : %v", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=weird", "img", "cmd"}); err == nil || err.Error() != "--net: invalid net mode: invalid --net: weird" {
+		t.Fatalf("Expected error with --net=weird, got: %s", err)
+	}
 }
 
 func TestConflictContainerNetworkAndLinks(t *testing.T) {
 	if _, _, _, err := parseRun([]string{"--net=container:other", "--link=zip:zap", "img", "cmd"}); err != ErrConflictContainerNetworkAndLinks {
 		t.Fatalf("Expected error ErrConflictContainerNetworkAndLinks, got: %s", err)
 	}
+	if _, _, _, err := parseRun([]string{"--net=host", "--link=zip:zap", "img", "cmd"}); err != ErrConflictHostNetworkAndLinks {
+		t.Fatalf("Expected error ErrConflictHostNetworkAndLinks, got: %s", err)
+	}
+}
+
+func TestConflictNetworkModeAndOptions(t *testing.T) {
+	if _, _, _, err := parseRun([]string{"--net=host", "--dns=8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkAndDns {
+		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=container:other", "--dns=8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkAndDns {
+		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=host", "--add-host=name:8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkHosts {
+		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=container:other", "--add-host=name:8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkHosts {
+		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=host", "--mac-address=92:d0:c6:0a:29:33", "img", "cmd"}); err != ErrConflictContainerNetworkAndMac {
+		t.Fatalf("Expected error ErrConflictContainerNetworkAndMac, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=container:other", "--mac-address=92:d0:c6:0a:29:33", "img", "cmd"}); err != ErrConflictContainerNetworkAndMac {
+		t.Fatalf("Expected error ErrConflictContainerNetworkAndMac, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=container:other", "-P", "img", "cmd"}); err != ErrConflictNetworkPublishPorts {
+		t.Fatalf("Expected error ErrConflictNetworkPublishPorts, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=container:other", "-p", "8080", "img", "cmd"}); err != ErrConflictNetworkPublishPorts {
+		t.Fatalf("Expected error ErrConflictNetworkPublishPorts, got %s", err)
+	}
+	if _, _, _, err := parseRun([]string{"--net=container:other", "--expose", "8000-9000", "img", "cmd"}); err != ErrConflictNetworkExposePorts {
+		t.Fatalf("Expected error ErrConflictNetworkExposePorts, got %s", err)
+	}
+}
+
+// Simple parse with MacAddress validatation
+func TestParseWithMacAddress(t *testing.T) {
+	invalidMacAddress := "--mac-address=invalidMacAddress"
+	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
+	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
+		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
+	}
+	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
+		t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
+	}
+}
+
+func TestParseWithMemory(t *testing.T) {
+	invalidMemory := "--memory=invalid"
+	validMemory := "--memory=1G"
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
+		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
+	}
+	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
+		t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
+	}
+}
+
+func TestParseWithMemorySwap(t *testing.T) {
+	invalidMemory := "--memory-swap=invalid"
+	validMemory := "--memory-swap=1G"
+	anotherValidMemory := "--memory-swap=-1"
+	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
+		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
+	}
+	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
+		t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
+	}
+	if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
+		t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
+	}
+}
+
+func TestParseHostname(t *testing.T) {
+	hostname := "--hostname=hostname"
+	hostnameWithDomain := "--hostname=hostname.domainname"
+	hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
+	if config, _ := mustParse(t, hostname); config.Hostname != "hostname" && config.Domainname != "" {
+		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
+	}
+	if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname" && config.Domainname != "domainname" {
+		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
+	}
+	if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname" && config.Domainname != "domainname.tld" {
+		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
+	}
+}
+
+func TestParseWithExpose(t *testing.T) {
+	invalids := map[string]string{
+		":":                   "Invalid port format for --expose: :",
+		"8080:9090":           "Invalid port format for --expose: 8080:9090",
+		"/tcp":                "Invalid range format for --expose: /tcp, error: Empty string specified for ports.",
+		"/udp":                "Invalid range format for --expose: /udp, error: Empty string specified for ports.",
+		"NaN/tcp":             `Invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
+		"NaN-NaN/tcp":         `Invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
+		"8080-NaN/tcp":        `Invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
+		"1234567890-8080/tcp": `Invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
+	}
+	valids := map[string][]nat.Port{
+		"8080/tcp":      {"8080/tcp"},
+		"8080/udp":      {"8080/udp"},
+		"8080/ncp":      {"8080/ncp"},
+		"8080-8080/udp": {"8080/udp"},
+		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
+	}
+	for expose, expectedError := range invalids {
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
+			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
+		}
+	}
+	for expose, exposedPorts := range valids {
+		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(config.ExposedPorts) != len(exposedPorts) {
+			t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
+		}
+		for _, port := range exposedPorts {
+			if _, ok := config.ExposedPorts[port]; !ok {
+				t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
+			}
+		}
+	}
+	// Merge with actual published port
+	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(config.ExposedPorts) != 2 {
+		t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
+	}
+	ports := []nat.Port{"80/tcp", "81/tcp"}
+	for _, port := range ports {
+		if _, ok := config.ExposedPorts[port]; !ok {
+			t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
+		}
+	}
+}
+
+func TestParseDevice(t *testing.T) {
+	valids := map[string]DeviceMapping{
+		"/dev/snd": {
+			PathOnHost:        "/dev/snd",
+			PathInContainer:   "/dev/snd",
+			CgroupPermissions: "rwm",
+		},
+		"/dev/snd:/something": {
+			PathOnHost:        "/dev/snd",
+			PathInContainer:   "/something",
+			CgroupPermissions: "rwm",
+		},
+		"/dev/snd:/something:ro": {
+			PathOnHost:        "/dev/snd",
+			PathInContainer:   "/something",
+			CgroupPermissions: "ro",
+		},
+	}
+	for device, deviceMapping := range valids {
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if len(hostconfig.Devices) != 1 {
+			t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
+		}
+		if hostconfig.Devices[0] != deviceMapping {
+			t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
+		}
+	}
+
+}
+
+func TestParseModes(t *testing.T) {
+	// ipc ko
+	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
+		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
+	}
+	// ipc ok
+	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !hostconfig.IpcMode.Valid() {
+		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
+	}
+	// pid ko
+	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
+		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
+	}
+	// pid ok
+	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !hostconfig.PidMode.Valid() {
+		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
+	}
+	// uts ko
+	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
+		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
+	}
+	// uts ok
+	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if !hostconfig.UTSMode.Valid() {
+		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
+	}
+}
+
+func TestParseRestartPolicy(t *testing.T) {
+	invalids := map[string]string{
+		"something":          "invalid restart policy something",
+		"always:2":           "maximum restart count not valid with restart policy of \"always\"",
+		"on-failure:invalid": `strconv.ParseInt: parsing "invalid": invalid syntax`,
+	}
+	valids := map[string]RestartPolicy{
+		"": {},
+		// FIXME This feels not right
+		"always:1:2": {
+			Name:              "always",
+			MaximumRetryCount: 0,
+		},
+		"on-failure:1": {
+			Name:              "on-failure",
+			MaximumRetryCount: 1,
+		},
+		// FIXME This doesn't feel right
+		"on-failure:1:2": {
+			Name:              "on-failure",
+			MaximumRetryCount: 0,
+		},
+	}
+	for restart, expectedError := range invalids {
+		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
+			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
+		}
+	}
+	for restart, expected := range valids {
+		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
+		if err != nil {
+			t.Fatal(err)
+		}
+		if hostconfig.RestartPolicy != expected {
+			t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
+		}
+	}
+}
+
+func TestParseLoggingOpts(t *testing.T) {
+	// logging opts ko
+	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
+		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
+	}
+	// logging opts ok
+	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
+		t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
+	}
+}
+
+func TestParseEnvfileVariables(t *testing.T) {
+	// env ko
+	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
+		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
+	}
+	// env ok
+	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
+		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
+	}
+	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
+		t.Fatalf("Expected a a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
+	}
+}
+
+func TestParseLabelfileVariables(t *testing.T) {
+	// label ko
+	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
+		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
+	}
+	// label ok
+	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
+		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
+	}
+	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
+		t.Fatalf("Expected a a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
+	}
+}
+
+func TestParseEntryPoint(t *testing.T) {
+	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
+	if err != nil {
+		t.Fatal(err)
+	}
+	if config.Entrypoint.Len() != 1 && config.Entrypoint.parts[0] != "anything" {
+		t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
+	}
 }