Refactor test and add coverage to runconfig

Signed-off-by: Vincent Demeester <vincent@sbr.pm>
This commit is contained in:
Vincent Demeester 2015-07-01 10:16:36 +02:00
parent bb2cd98b28
commit d4aec5f0a6
10 changed files with 1224 additions and 248 deletions

118
runconfig/compare_test.go Normal file
View File

@ -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)
}
}
}

View File

@ -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)
}
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)
func TestEntrypointMarshalJSON(t *testing.T) {
entrypoints := map[*Entrypoint]string{
nil: "",
&Entrypoint{}: "null",
&Entrypoint{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
}
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 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,
}
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")
}
}
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)
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))
}
}
if len(configUser.Env) != 3 {
t.Fatalf("Expected 3 env var, VAR1=1, VAR2=3 and VAR3=3, found %d", len(configUser.Env))
}
func TestEntrypointUnmarshalJSON(t *testing.T) {
parts := map[string][]string{
"": {"default", "values"},
"[]": {},
`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
}
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)
for json, expectedParts := range parts {
entrypoint := &Entrypoint{
[]string{"default", "values"},
}
if err := entrypoint.UnmarshalJSON([]byte(json)); err != nil {
t.Fatal(err)
}
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
}
}
}
}
if len(configUser.Volumes) != 3 {
t.Fatalf("Expected 3 volumes, /test1, /test2 and /test3, found %d", len(configUser.Volumes))
func TestCommandToString(t *testing.T) {
commands := map[*Command]string{
&Command{[]string{""}}: "",
&Command{[]string{"one"}}: "one",
&Command{[]string{"one", "two"}}: "one two",
}
for v := range configUser.Volumes {
if v != "/test1" && v != "/test2" && v != "/test3" {
t.Fatalf("Expected /test1 or /test2 or /test3, found %s", v)
for command, expected := range commands {
toString := command.ToString()
if toString != expected {
t.Fatalf("Expected %v, got %v", expected, toString)
}
}
}
ports, _, err := nat.ParsePortSpecs([]string{"0000"})
if err != nil {
t.Error(err)
}
configImage2 := &Config{
ExposedPorts: ports,
func TestCommandMarshalJSON(t *testing.T) {
commands := map[*Command]string{
nil: "",
&Command{}: "null",
&Command{[]string{"/bin/sh", "-c", "echo"}}: `["/bin/sh","-c","echo"]`,
}
if err := Merge(configUser, configImage2); err != nil {
t.Error(err)
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))
}
}
}
if len(configUser.ExposedPorts) != 4 {
t.Fatalf("Expected 4 ExposedPorts, 0000, 1111, 2222 and 3333, found %d", len(configUser.ExposedPorts))
func TestCommandUnmarshalJSON(t *testing.T) {
parts := map[string][]string{
"": {"default", "values"},
"[]": {},
`["/bin/sh","-c","echo"]`: {"/bin/sh", "-c", "echo"},
}
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 json, expectedParts := range parts {
command := &Command{
[]string{"default", "values"},
}
if err := command.UnmarshalJSON([]byte(json)); err != nil {
t.Fatal(err)
}
actualParts := command.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
}
}
}
}

129
runconfig/exec_test.go Normal file
View File

@ -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
}

View File

@ -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"]
}

View File

@ -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": ""
}

View File

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

View File

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

View File

@ -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
runconfig/merge_test.go Normal file
View File

@ -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)
}
}
}

View File

@ -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)
}
}