github.com/nf/docker@v1.8.1/runconfig/parse_test.go (about)

     1  package runconfig
     2  
     3  import (
     4  	"fmt"
     5  	"io/ioutil"
     6  	"strings"
     7  	"testing"
     8  
     9  	flag "github.com/docker/docker/pkg/mflag"
    10  	"github.com/docker/docker/pkg/nat"
    11  	"github.com/docker/docker/pkg/parsers"
    12  )
    13  
    14  func parseRun(args []string) (*Config, *HostConfig, *flag.FlagSet, error) {
    15  	cmd := flag.NewFlagSet("run", flag.ContinueOnError)
    16  	cmd.SetOutput(ioutil.Discard)
    17  	cmd.Usage = nil
    18  	return Parse(cmd, args)
    19  }
    20  
    21  func parse(t *testing.T, args string) (*Config, *HostConfig, error) {
    22  	config, hostConfig, _, err := parseRun(strings.Split(args+" ubuntu bash", " "))
    23  	return config, hostConfig, err
    24  }
    25  
    26  func mustParse(t *testing.T, args string) (*Config, *HostConfig) {
    27  	config, hostConfig, err := parse(t, args)
    28  	if err != nil {
    29  		t.Fatal(err)
    30  	}
    31  	return config, hostConfig
    32  }
    33  
    34  // check if (a == c && b == d) || (a == d && b == c)
    35  // because maps are randomized
    36  func compareRandomizedStrings(a, b, c, d string) error {
    37  	if a == c && b == d {
    38  		return nil
    39  	}
    40  	if a == d && b == c {
    41  		return nil
    42  	}
    43  	return fmt.Errorf("strings don't match")
    44  }
    45  func TestParseRunLinks(t *testing.T) {
    46  	if _, hostConfig := mustParse(t, "--link a:b"); len(hostConfig.Links) == 0 || hostConfig.Links[0] != "a:b" {
    47  		t.Fatalf("Error parsing links. Expected []string{\"a:b\"}, received: %v", hostConfig.Links)
    48  	}
    49  	if _, hostConfig := mustParse(t, "--link a:b --link c:d"); len(hostConfig.Links) < 2 || hostConfig.Links[0] != "a:b" || hostConfig.Links[1] != "c:d" {
    50  		t.Fatalf("Error parsing links. Expected []string{\"a:b\", \"c:d\"}, received: %v", hostConfig.Links)
    51  	}
    52  	if _, hostConfig := mustParse(t, ""); len(hostConfig.Links) != 0 {
    53  		t.Fatalf("Error parsing links. No link expected, received: %v", hostConfig.Links)
    54  	}
    55  }
    56  
    57  func TestParseRunAttach(t *testing.T) {
    58  	if config, _ := mustParse(t, "-a stdin"); !config.AttachStdin || config.AttachStdout || config.AttachStderr {
    59  		t.Fatalf("Error parsing attach flags. Expect only Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
    60  	}
    61  	if config, _ := mustParse(t, "-a stdin -a stdout"); !config.AttachStdin || !config.AttachStdout || config.AttachStderr {
    62  		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)
    63  	}
    64  	if config, _ := mustParse(t, "-a stdin -a stdout -a stderr"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
    65  		t.Fatalf("Error parsing attach flags. Expect all attach enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
    66  	}
    67  	if config, _ := mustParse(t, ""); config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
    68  		t.Fatalf("Error parsing attach flags. Expect Stdin disabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
    69  	}
    70  	if config, _ := mustParse(t, "-i"); !config.AttachStdin || !config.AttachStdout || !config.AttachStderr {
    71  		t.Fatalf("Error parsing attach flags. Expect Stdin enabled. Received: in: %v, out: %v, err: %v", config.AttachStdin, config.AttachStdout, config.AttachStderr)
    72  	}
    73  
    74  	if _, _, err := parse(t, "-a"); err == nil {
    75  		t.Fatalf("Error parsing attach flags, `-a` should be an error but is not")
    76  	}
    77  	if _, _, err := parse(t, "-a invalid"); err == nil {
    78  		t.Fatalf("Error parsing attach flags, `-a invalid` should be an error but is not")
    79  	}
    80  	if _, _, err := parse(t, "-a invalid -a stdout"); err == nil {
    81  		t.Fatalf("Error parsing attach flags, `-a stdout -a invalid` should be an error but is not")
    82  	}
    83  	if _, _, err := parse(t, "-a stdout -a stderr -d"); err == nil {
    84  		t.Fatalf("Error parsing attach flags, `-a stdout -a stderr -d` should be an error but is not")
    85  	}
    86  	if _, _, err := parse(t, "-a stdin -d"); err == nil {
    87  		t.Fatalf("Error parsing attach flags, `-a stdin -d` should be an error but is not")
    88  	}
    89  	if _, _, err := parse(t, "-a stdout -d"); err == nil {
    90  		t.Fatalf("Error parsing attach flags, `-a stdout -d` should be an error but is not")
    91  	}
    92  	if _, _, err := parse(t, "-a stderr -d"); err == nil {
    93  		t.Fatalf("Error parsing attach flags, `-a stderr -d` should be an error but is not")
    94  	}
    95  	if _, _, err := parse(t, "-d --rm"); err == nil {
    96  		t.Fatalf("Error parsing attach flags, `-d --rm` should be an error but is not")
    97  	}
    98  }
    99  
   100  func TestParseRunVolumes(t *testing.T) {
   101  	if config, hostConfig := mustParse(t, "-v /tmp"); hostConfig.Binds != nil {
   102  		t.Fatalf("Error parsing volume flags, `-v /tmp` should not mount-bind anything. Received %v", hostConfig.Binds)
   103  	} else if _, exists := config.Volumes["/tmp"]; !exists {
   104  		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
   105  	}
   106  
   107  	if config, hostConfig := mustParse(t, "-v /tmp -v /var"); hostConfig.Binds != nil {
   108  		t.Fatalf("Error parsing volume flags, `-v /tmp -v /var` should not mount-bind anything. Received %v", hostConfig.Binds)
   109  	} else if _, exists := config.Volumes["/tmp"]; !exists {
   110  		t.Fatalf("Error parsing volume flags, `-v /tmp` is missing from volumes. Received %v", config.Volumes)
   111  	} else if _, exists := config.Volumes["/var"]; !exists {
   112  		t.Fatalf("Error parsing volume flags, `-v /var` is missing from volumes. Received %v", config.Volumes)
   113  	}
   114  
   115  	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp"); hostConfig.Binds == nil || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
   116  		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp` should mount-bind /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
   117  	}
   118  
   119  	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 {
   120  		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)
   121  	}
   122  
   123  	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 {
   124  		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)
   125  	}
   126  
   127  	if _, hostConfig := mustParse(t, "-v /containerTmp:ro -v /containerVar:rw"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/containerTmp:ro", "/containerVar:rw") != nil {
   128  		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)
   129  	}
   130  
   131  	if _, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z"); hostConfig.Binds == nil || compareRandomizedStrings(hostConfig.Binds[0], hostConfig.Binds[1], "/hostTmp:/containerTmp:ro,Z", "/hostVar:/containerVar:rw,Z") != nil {
   132  		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp:ro,Z -v /hostVar:/containerVar:rw,Z` should mount-bind /hostTmp into /containeTmp and /hostVar into /hostContainer. Received %v", hostConfig.Binds)
   133  	}
   134  
   135  	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 {
   136  		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)
   137  	}
   138  
   139  	if config, hostConfig := mustParse(t, "-v /hostTmp:/containerTmp -v /containerVar"); hostConfig.Binds == nil || len(hostConfig.Binds) > 1 || hostConfig.Binds[0] != "/hostTmp:/containerTmp" {
   140  		t.Fatalf("Error parsing volume flags, `-v /hostTmp:/containerTmp -v /containerVar` should mount-bind only /hostTmp into /containeTmp. Received %v", hostConfig.Binds)
   141  	} else if _, exists := config.Volumes["/containerVar"]; !exists {
   142  		t.Fatalf("Error parsing volume flags, `-v /containerVar` is missing from volumes. Received %v", config.Volumes)
   143  	}
   144  
   145  	if config, hostConfig := mustParse(t, ""); hostConfig.Binds != nil {
   146  		t.Fatalf("Error parsing volume flags, without volume, nothing should be mount-binded. Received %v", hostConfig.Binds)
   147  	} else if len(config.Volumes) != 0 {
   148  		t.Fatalf("Error parsing volume flags, without volume, no volume should be present. Received %v", config.Volumes)
   149  	}
   150  
   151  	if _, _, err := parse(t, "-v /"); err == nil {
   152  		t.Fatalf("Expected error, but got none")
   153  	}
   154  
   155  	if _, _, err := parse(t, "-v /:/"); err == nil {
   156  		t.Fatalf("Error parsing volume flags, `-v /:/` should fail but didn't")
   157  	}
   158  	if _, _, err := parse(t, "-v"); err == nil {
   159  		t.Fatalf("Error parsing volume flags, `-v` should fail but didn't")
   160  	}
   161  	if _, _, err := parse(t, "-v /tmp:"); err == nil {
   162  		t.Fatalf("Error parsing volume flags, `-v /tmp:` should fail but didn't")
   163  	}
   164  	if _, _, err := parse(t, "-v /tmp::"); err == nil {
   165  		t.Fatalf("Error parsing volume flags, `-v /tmp::` should fail but didn't")
   166  	}
   167  	if _, _, err := parse(t, "-v :"); err == nil {
   168  		t.Fatalf("Error parsing volume flags, `-v :` should fail but didn't")
   169  	}
   170  	if _, _, err := parse(t, "-v ::"); err == nil {
   171  		t.Fatalf("Error parsing volume flags, `-v ::` should fail but didn't")
   172  	}
   173  	if _, _, err := parse(t, "-v /tmp:/tmp:/tmp:/tmp"); err == nil {
   174  		t.Fatalf("Error parsing volume flags, `-v /tmp:/tmp:/tmp:/tmp` should fail but didn't")
   175  	}
   176  }
   177  
   178  func TestParseLxcConfOpt(t *testing.T) {
   179  	opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "}
   180  
   181  	for _, o := range opts {
   182  		k, v, err := parsers.ParseKeyValueOpt(o)
   183  		if err != nil {
   184  			t.FailNow()
   185  		}
   186  		if k != "lxc.utsname" {
   187  			t.Fail()
   188  		}
   189  		if v != "docker" {
   190  			t.Fail()
   191  		}
   192  	}
   193  
   194  	// With parseRun too
   195  	_, hostconfig, _, err := parseRun([]string{"lxc.utsname=docker", "lxc.utsname = docker ", "img", "cmd"})
   196  	if err != nil {
   197  		t.Fatal(err)
   198  	}
   199  	for _, lxcConf := range hostconfig.LxcConf.Slice() {
   200  		if lxcConf.Key != "lxc.utsname" || lxcConf.Value != "docker" {
   201  			t.Fail()
   202  		}
   203  	}
   204  
   205  }
   206  
   207  func TestNetHostname(t *testing.T) {
   208  	if _, _, _, err := parseRun([]string{"-h=name", "img", "cmd"}); err != nil {
   209  		t.Fatalf("Unexpected error: %s", err)
   210  	}
   211  
   212  	if _, _, _, err := parseRun([]string{"--net=host", "img", "cmd"}); err != nil {
   213  		t.Fatalf("Unexpected error: %s", err)
   214  	}
   215  
   216  	if _, _, _, err := parseRun([]string{"-h=name", "--net=bridge", "img", "cmd"}); err != nil {
   217  		t.Fatalf("Unexpected error: %s", err)
   218  	}
   219  
   220  	if _, _, _, err := parseRun([]string{"-h=name", "--net=none", "img", "cmd"}); err != nil {
   221  		t.Fatalf("Unexpected error: %s", err)
   222  	}
   223  
   224  	if _, _, _, err := parseRun([]string{"-h=name", "--net=host", "img", "cmd"}); err != ErrConflictNetworkHostname {
   225  		t.Fatalf("Expected error ErrConflictNetworkHostname, got: %s", err)
   226  	}
   227  
   228  	if _, _, _, err := parseRun([]string{"-h=name", "--net=container:other", "img", "cmd"}); err != ErrConflictNetworkHostname {
   229  		t.Fatalf("Expected error ErrConflictNetworkHostname, got: %s", err)
   230  	}
   231  	if _, _, _, err := parseRun([]string{"--net=container", "img", "cmd"}); err == nil || err.Error() != "--net: invalid net mode: invalid container format container:<name|id>" {
   232  		t.Fatalf("Expected error with --net=container, got : %v", err)
   233  	}
   234  	if _, _, _, err := parseRun([]string{"--net=weird", "img", "cmd"}); err == nil || err.Error() != "--net: invalid net mode: invalid --net: weird" {
   235  		t.Fatalf("Expected error with --net=weird, got: %s", err)
   236  	}
   237  }
   238  
   239  func TestConflictContainerNetworkAndLinks(t *testing.T) {
   240  	if _, _, _, err := parseRun([]string{"--net=container:other", "--link=zip:zap", "img", "cmd"}); err != ErrConflictContainerNetworkAndLinks {
   241  		t.Fatalf("Expected error ErrConflictContainerNetworkAndLinks, got: %s", err)
   242  	}
   243  	if _, _, _, err := parseRun([]string{"--net=host", "--link=zip:zap", "img", "cmd"}); err != ErrConflictHostNetworkAndLinks {
   244  		t.Fatalf("Expected error ErrConflictHostNetworkAndLinks, got: %s", err)
   245  	}
   246  }
   247  
   248  func TestConflictNetworkModeAndOptions(t *testing.T) {
   249  	if _, _, _, err := parseRun([]string{"--net=host", "--dns=8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkAndDns {
   250  		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
   251  	}
   252  	if _, _, _, err := parseRun([]string{"--net=container:other", "--dns=8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkAndDns {
   253  		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
   254  	}
   255  	if _, _, _, err := parseRun([]string{"--net=host", "--add-host=name:8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkHosts {
   256  		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
   257  	}
   258  	if _, _, _, err := parseRun([]string{"--net=container:other", "--add-host=name:8.8.8.8", "img", "cmd"}); err != ErrConflictNetworkHosts {
   259  		t.Fatalf("Expected error ErrConflictNetworkAndDns, got %s", err)
   260  	}
   261  	if _, _, _, err := parseRun([]string{"--net=host", "--mac-address=92:d0:c6:0a:29:33", "img", "cmd"}); err != ErrConflictContainerNetworkAndMac {
   262  		t.Fatalf("Expected error ErrConflictContainerNetworkAndMac, got %s", err)
   263  	}
   264  	if _, _, _, err := parseRun([]string{"--net=container:other", "--mac-address=92:d0:c6:0a:29:33", "img", "cmd"}); err != ErrConflictContainerNetworkAndMac {
   265  		t.Fatalf("Expected error ErrConflictContainerNetworkAndMac, got %s", err)
   266  	}
   267  	if _, _, _, err := parseRun([]string{"--net=container:other", "-P", "img", "cmd"}); err != ErrConflictNetworkPublishPorts {
   268  		t.Fatalf("Expected error ErrConflictNetworkPublishPorts, got %s", err)
   269  	}
   270  	if _, _, _, err := parseRun([]string{"--net=container:other", "-p", "8080", "img", "cmd"}); err != ErrConflictNetworkPublishPorts {
   271  		t.Fatalf("Expected error ErrConflictNetworkPublishPorts, got %s", err)
   272  	}
   273  	if _, _, _, err := parseRun([]string{"--net=container:other", "--expose", "8000-9000", "img", "cmd"}); err != ErrConflictNetworkExposePorts {
   274  		t.Fatalf("Expected error ErrConflictNetworkExposePorts, got %s", err)
   275  	}
   276  }
   277  
   278  // Simple parse with MacAddress validatation
   279  func TestParseWithMacAddress(t *testing.T) {
   280  	invalidMacAddress := "--mac-address=invalidMacAddress"
   281  	validMacAddress := "--mac-address=92:d0:c6:0a:29:33"
   282  	if _, _, _, err := parseRun([]string{invalidMacAddress, "img", "cmd"}); err != nil && err.Error() != "invalidMacAddress is not a valid mac address" {
   283  		t.Fatalf("Expected an error with %v mac-address, got %v", invalidMacAddress, err)
   284  	}
   285  	if config, _ := mustParse(t, validMacAddress); config.MacAddress != "92:d0:c6:0a:29:33" {
   286  		t.Fatalf("Expected the config to have '92:d0:c6:0a:29:33' as MacAddress, got '%v'", config.MacAddress)
   287  	}
   288  }
   289  
   290  func TestParseWithMemory(t *testing.T) {
   291  	invalidMemory := "--memory=invalid"
   292  	validMemory := "--memory=1G"
   293  	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err != nil && err.Error() != "invalid size: 'invalid'" {
   294  		t.Fatalf("Expected an error with '%v' Memory, got '%v'", invalidMemory, err)
   295  	}
   296  	if _, hostconfig := mustParse(t, validMemory); hostconfig.Memory != 1073741824 {
   297  		t.Fatalf("Expected the config to have '1G' as Memory, got '%v'", hostconfig.Memory)
   298  	}
   299  }
   300  
   301  func TestParseWithMemorySwap(t *testing.T) {
   302  	invalidMemory := "--memory-swap=invalid"
   303  	validMemory := "--memory-swap=1G"
   304  	anotherValidMemory := "--memory-swap=-1"
   305  	if _, _, _, err := parseRun([]string{invalidMemory, "img", "cmd"}); err == nil || err.Error() != "invalid size: 'invalid'" {
   306  		t.Fatalf("Expected an error with '%v' MemorySwap, got '%v'", invalidMemory, err)
   307  	}
   308  	if _, hostconfig := mustParse(t, validMemory); hostconfig.MemorySwap != 1073741824 {
   309  		t.Fatalf("Expected the config to have '1073741824' as MemorySwap, got '%v'", hostconfig.MemorySwap)
   310  	}
   311  	if _, hostconfig := mustParse(t, anotherValidMemory); hostconfig.MemorySwap != -1 {
   312  		t.Fatalf("Expected the config to have '-1' as MemorySwap, got '%v'", hostconfig.MemorySwap)
   313  	}
   314  }
   315  
   316  func TestParseHostname(t *testing.T) {
   317  	hostname := "--hostname=hostname"
   318  	hostnameWithDomain := "--hostname=hostname.domainname"
   319  	hostnameWithDomainTld := "--hostname=hostname.domainname.tld"
   320  	if config, _ := mustParse(t, hostname); config.Hostname != "hostname" && config.Domainname != "" {
   321  		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
   322  	}
   323  	if config, _ := mustParse(t, hostnameWithDomain); config.Hostname != "hostname" && config.Domainname != "domainname" {
   324  		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
   325  	}
   326  	if config, _ := mustParse(t, hostnameWithDomainTld); config.Hostname != "hostname" && config.Domainname != "domainname.tld" {
   327  		t.Fatalf("Expected the config to have 'hostname' as hostname, got '%v'", config.Hostname)
   328  	}
   329  }
   330  
   331  func TestParseWithExpose(t *testing.T) {
   332  	invalids := map[string]string{
   333  		":":                   "Invalid port format for --expose: :",
   334  		"8080:9090":           "Invalid port format for --expose: 8080:9090",
   335  		"/tcp":                "Invalid range format for --expose: /tcp, error: Empty string specified for ports.",
   336  		"/udp":                "Invalid range format for --expose: /udp, error: Empty string specified for ports.",
   337  		"NaN/tcp":             `Invalid range format for --expose: NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
   338  		"NaN-NaN/tcp":         `Invalid range format for --expose: NaN-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
   339  		"8080-NaN/tcp":        `Invalid range format for --expose: 8080-NaN/tcp, error: strconv.ParseUint: parsing "NaN": invalid syntax`,
   340  		"1234567890-8080/tcp": `Invalid range format for --expose: 1234567890-8080/tcp, error: strconv.ParseUint: parsing "1234567890": value out of range`,
   341  	}
   342  	valids := map[string][]nat.Port{
   343  		"8080/tcp":      {"8080/tcp"},
   344  		"8080/udp":      {"8080/udp"},
   345  		"8080/ncp":      {"8080/ncp"},
   346  		"8080-8080/udp": {"8080/udp"},
   347  		"8080-8082/tcp": {"8080/tcp", "8081/tcp", "8082/tcp"},
   348  	}
   349  	for expose, expectedError := range invalids {
   350  		if _, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"}); err == nil || err.Error() != expectedError {
   351  			t.Fatalf("Expected error '%v' with '--expose=%v', got '%v'", expectedError, expose, err)
   352  		}
   353  	}
   354  	for expose, exposedPorts := range valids {
   355  		config, _, _, err := parseRun([]string{fmt.Sprintf("--expose=%v", expose), "img", "cmd"})
   356  		if err != nil {
   357  			t.Fatal(err)
   358  		}
   359  		if len(config.ExposedPorts) != len(exposedPorts) {
   360  			t.Fatalf("Expected %v exposed port, got %v", len(exposedPorts), len(config.ExposedPorts))
   361  		}
   362  		for _, port := range exposedPorts {
   363  			if _, ok := config.ExposedPorts[port]; !ok {
   364  				t.Fatalf("Expected %v, got %v", exposedPorts, config.ExposedPorts)
   365  			}
   366  		}
   367  	}
   368  	// Merge with actual published port
   369  	config, _, _, err := parseRun([]string{"--publish=80", "--expose=80-81/tcp", "img", "cmd"})
   370  	if err != nil {
   371  		t.Fatal(err)
   372  	}
   373  	if len(config.ExposedPorts) != 2 {
   374  		t.Fatalf("Expected 2 exposed ports, got %v", config.ExposedPorts)
   375  	}
   376  	ports := []nat.Port{"80/tcp", "81/tcp"}
   377  	for _, port := range ports {
   378  		if _, ok := config.ExposedPorts[port]; !ok {
   379  			t.Fatalf("Expected %v, got %v", ports, config.ExposedPorts)
   380  		}
   381  	}
   382  }
   383  
   384  func TestParseDevice(t *testing.T) {
   385  	valids := map[string]DeviceMapping{
   386  		"/dev/snd": {
   387  			PathOnHost:        "/dev/snd",
   388  			PathInContainer:   "/dev/snd",
   389  			CgroupPermissions: "rwm",
   390  		},
   391  		"/dev/snd:/something": {
   392  			PathOnHost:        "/dev/snd",
   393  			PathInContainer:   "/something",
   394  			CgroupPermissions: "rwm",
   395  		},
   396  		"/dev/snd:/something:ro": {
   397  			PathOnHost:        "/dev/snd",
   398  			PathInContainer:   "/something",
   399  			CgroupPermissions: "ro",
   400  		},
   401  	}
   402  	for device, deviceMapping := range valids {
   403  		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--device=%v", device), "img", "cmd"})
   404  		if err != nil {
   405  			t.Fatal(err)
   406  		}
   407  		if len(hostconfig.Devices) != 1 {
   408  			t.Fatalf("Expected 1 devices, got %v", hostconfig.Devices)
   409  		}
   410  		if hostconfig.Devices[0] != deviceMapping {
   411  			t.Fatalf("Expected %v, got %v", deviceMapping, hostconfig.Devices)
   412  		}
   413  	}
   414  
   415  }
   416  
   417  func TestParseModes(t *testing.T) {
   418  	// ipc ko
   419  	if _, _, _, err := parseRun([]string{"--ipc=container:", "img", "cmd"}); err == nil || err.Error() != "--ipc: invalid IPC mode" {
   420  		t.Fatalf("Expected an error with message '--ipc: invalid IPC mode', got %v", err)
   421  	}
   422  	// ipc ok
   423  	_, hostconfig, _, err := parseRun([]string{"--ipc=host", "img", "cmd"})
   424  	if err != nil {
   425  		t.Fatal(err)
   426  	}
   427  	if !hostconfig.IpcMode.Valid() {
   428  		t.Fatalf("Expected a valid IpcMode, got %v", hostconfig.IpcMode)
   429  	}
   430  	// pid ko
   431  	if _, _, _, err := parseRun([]string{"--pid=container:", "img", "cmd"}); err == nil || err.Error() != "--pid: invalid PID mode" {
   432  		t.Fatalf("Expected an error with message '--pid: invalid PID mode', got %v", err)
   433  	}
   434  	// pid ok
   435  	_, hostconfig, _, err = parseRun([]string{"--pid=host", "img", "cmd"})
   436  	if err != nil {
   437  		t.Fatal(err)
   438  	}
   439  	if !hostconfig.PidMode.Valid() {
   440  		t.Fatalf("Expected a valid PidMode, got %v", hostconfig.PidMode)
   441  	}
   442  	// uts ko
   443  	if _, _, _, err := parseRun([]string{"--uts=container:", "img", "cmd"}); err == nil || err.Error() != "--uts: invalid UTS mode" {
   444  		t.Fatalf("Expected an error with message '--uts: invalid UTS mode', got %v", err)
   445  	}
   446  	// uts ok
   447  	_, hostconfig, _, err = parseRun([]string{"--uts=host", "img", "cmd"})
   448  	if err != nil {
   449  		t.Fatal(err)
   450  	}
   451  	if !hostconfig.UTSMode.Valid() {
   452  		t.Fatalf("Expected a valid UTSMode, got %v", hostconfig.UTSMode)
   453  	}
   454  }
   455  
   456  func TestParseRestartPolicy(t *testing.T) {
   457  	invalids := map[string]string{
   458  		"something":          "invalid restart policy something",
   459  		"always:2":           "maximum restart count not valid with restart policy of \"always\"",
   460  		"always:2:3":         "maximum restart count not valid with restart policy of \"always\"",
   461  		"on-failure:invalid": `strconv.ParseInt: parsing "invalid": invalid syntax`,
   462  		"on-failure:2:5":     "restart count format is not valid, usage: 'on-failure:N' or 'on-failure'",
   463  	}
   464  	valids := map[string]RestartPolicy{
   465  		"": {},
   466  		"always": {
   467  			Name:              "always",
   468  			MaximumRetryCount: 0,
   469  		},
   470  		"on-failure:1": {
   471  			Name:              "on-failure",
   472  			MaximumRetryCount: 1,
   473  		},
   474  	}
   475  	for restart, expectedError := range invalids {
   476  		if _, _, _, err := parseRun([]string{fmt.Sprintf("--restart=%s", restart), "img", "cmd"}); err == nil || err.Error() != expectedError {
   477  			t.Fatalf("Expected an error with message '%v' for %v, got %v", expectedError, restart, err)
   478  		}
   479  	}
   480  	for restart, expected := range valids {
   481  		_, hostconfig, _, err := parseRun([]string{fmt.Sprintf("--restart=%v", restart), "img", "cmd"})
   482  		if err != nil {
   483  			t.Fatal(err)
   484  		}
   485  		if hostconfig.RestartPolicy != expected {
   486  			t.Fatalf("Expected %v, got %v", expected, hostconfig.RestartPolicy)
   487  		}
   488  	}
   489  }
   490  
   491  func TestParseLoggingOpts(t *testing.T) {
   492  	// logging opts ko
   493  	if _, _, _, err := parseRun([]string{"--log-driver=none", "--log-opt=anything", "img", "cmd"}); err == nil || err.Error() != "Invalid logging opts for driver none" {
   494  		t.Fatalf("Expected an error with message 'Invalid logging opts for driver none', got %v", err)
   495  	}
   496  	// logging opts ok
   497  	_, hostconfig, _, err := parseRun([]string{"--log-driver=syslog", "--log-opt=something", "img", "cmd"})
   498  	if err != nil {
   499  		t.Fatal(err)
   500  	}
   501  	if hostconfig.LogConfig.Type != "syslog" || len(hostconfig.LogConfig.Config) != 1 {
   502  		t.Fatalf("Expected a 'syslog' LogConfig with one config, got %v", hostconfig.RestartPolicy)
   503  	}
   504  }
   505  
   506  func TestParseEnvfileVariables(t *testing.T) {
   507  	// env ko
   508  	if _, _, _, err := parseRun([]string{"--env-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
   509  		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
   510  	}
   511  	// env ok
   512  	config, _, _, err := parseRun([]string{"--env-file=fixtures/valid.env", "img", "cmd"})
   513  	if err != nil {
   514  		t.Fatal(err)
   515  	}
   516  	if len(config.Env) != 1 || config.Env[0] != "ENV1=value1" {
   517  		t.Fatalf("Expected a a config with [ENV1=value1], got %v", config.Env)
   518  	}
   519  	config, _, _, err = parseRun([]string{"--env-file=fixtures/valid.env", "--env=ENV2=value2", "img", "cmd"})
   520  	if err != nil {
   521  		t.Fatal(err)
   522  	}
   523  	if len(config.Env) != 2 || config.Env[0] != "ENV1=value1" || config.Env[1] != "ENV2=value2" {
   524  		t.Fatalf("Expected a a config with [ENV1=value1 ENV2=value2], got %v", config.Env)
   525  	}
   526  }
   527  
   528  func TestParseLabelfileVariables(t *testing.T) {
   529  	// label ko
   530  	if _, _, _, err := parseRun([]string{"--label-file=nonexistent", "img", "cmd"}); err == nil || err.Error() != "open nonexistent: no such file or directory" {
   531  		t.Fatalf("Expected an error with message 'open nonexistent: no such file or directory', got %v", err)
   532  	}
   533  	// label ok
   534  	config, _, _, err := parseRun([]string{"--label-file=fixtures/valid.label", "img", "cmd"})
   535  	if err != nil {
   536  		t.Fatal(err)
   537  	}
   538  	if len(config.Labels) != 1 || config.Labels["LABEL1"] != "value1" {
   539  		t.Fatalf("Expected a a config with [LABEL1:value1], got %v", config.Labels)
   540  	}
   541  	config, _, _, err = parseRun([]string{"--label-file=fixtures/valid.label", "--label=LABEL2=value2", "img", "cmd"})
   542  	if err != nil {
   543  		t.Fatal(err)
   544  	}
   545  	if len(config.Labels) != 2 || config.Labels["LABEL1"] != "value1" || config.Labels["LABEL2"] != "value2" {
   546  		t.Fatalf("Expected a a config with [LABEL1:value1 LABEL2:value2], got %v", config.Labels)
   547  	}
   548  }
   549  
   550  func TestParseEntryPoint(t *testing.T) {
   551  	config, _, _, err := parseRun([]string{"--entrypoint=anything", "cmd", "img"})
   552  	if err != nil {
   553  		t.Fatal(err)
   554  	}
   555  	if config.Entrypoint.Len() != 1 && config.Entrypoint.parts[0] != "anything" {
   556  		t.Fatalf("Expected entrypoint 'anything', got %v", config.Entrypoint)
   557  	}
   558  }