From 359a6f49b9ec704686d0cb0df7cb0d7c8d5f55a7 Mon Sep 17 00:00:00 2001 From: Solomon Hykes Date: Thu, 14 Nov 2013 06:08:08 +0000 Subject: [PATCH] Move integration tests to integration/, expose missing public methods in the core --- api.go | 16 +- commands.go | 4 +- config_test.go | 149 ++++++++++ container_unit_test.go | 257 ++++++++++++++++++ engine/job.go | 6 +- http_test.go | 52 ++++ api_test.go => integration/api_test.go | 0 .../buildfile_test.go | 0 .../commands_test.go | 0 .../container_test.go | 0 .../runtime_test.go | 0 server_test.go => integration/server_test.go | 0 sorter_test.go => integration/sorter_test.go | 0 utils_test.go => integration/utils_test.go | 0 .../z_final_test.go | 0 runtime.go | 34 +++ server.go | 13 +- server_unit_test.go | 110 ++++++++ sorter_unit_test.go | 41 +++ tags_unit_test.go | 81 ++++++ utils/utils.go | 40 +++ 21 files changed, 797 insertions(+), 6 deletions(-) create mode 100644 config_test.go create mode 100644 container_unit_test.go create mode 100644 http_test.go rename api_test.go => integration/api_test.go (100%) rename buildfile_test.go => integration/buildfile_test.go (100%) rename commands_test.go => integration/commands_test.go (100%) rename container_test.go => integration/container_test.go (100%) rename runtime_test.go => integration/runtime_test.go (100%) rename server_test.go => integration/server_test.go (100%) rename sorter_test.go => integration/sorter_test.go (100%) rename utils_test.go => integration/utils_test.go (100%) rename z_final_test.go => integration/z_final_test.go (100%) create mode 100644 server_unit_test.go create mode 100644 sorter_unit_test.go create mode 100644 tags_unit_test.go diff --git a/api.go b/api.go index c5a91de5b5..5e16e7778c 100644 --- a/api.go +++ b/api.go @@ -927,7 +927,7 @@ func postBuild(srv *Server, version float64, w http.ResponseWriter, r *http.Requ if err != nil { return err } - c, err := mkBuildContext(string(dockerFile), nil) + c, err := MkBuildContext(string(dockerFile), nil) if err != nil { return err } @@ -1105,6 +1105,20 @@ func createRouter(srv *Server, logging bool) (*mux.Router, error) { return r, nil } +// ServeRequest processes a single http request to the docker remote api. +// FIXME: refactor this to be part of Server and not require re-creating a new +// router each time. This requires first moving ListenAndServe into Server. +func ServeRequest(srv *Server, apiversion float64, w http.ResponseWriter, req *http.Request) error { + router, err := createRouter(srv, false) + if err != nil { + return err + } + // Insert APIVERSION into the request as a convenience + req.URL.Path = fmt.Sprintf("/v%g%s", apiversion, req.URL.Path) + router.ServeHTTP(w, req) + return nil +} + func ListenAndServe(proto, addr string, srv *Server, logging bool) error { log.Printf("Listening for HTTP on %s (%s)\n", addr, proto) diff --git a/commands.go b/commands.go index 9d144d8aeb..710a311c08 100644 --- a/commands.go +++ b/commands.go @@ -135,7 +135,7 @@ func (cli *DockerCli) CmdInsert(args ...string) error { // mkBuildContext returns an archive of an empty context with the contents // of `dockerfile` at the path ./Dockerfile -func mkBuildContext(dockerfile string, files [][2]string) (archive.Archive, error) { +func MkBuildContext(dockerfile string, files [][2]string) (archive.Archive, error) { buf := new(bytes.Buffer) tw := tar.NewWriter(buf) files = append(files, [2]string{"Dockerfile", dockerfile}) @@ -185,7 +185,7 @@ func (cli *DockerCli) CmdBuild(args ...string) error { if err != nil { return err } - context, err = mkBuildContext(string(dockerfile), nil) + context, err = MkBuildContext(string(dockerfile), nil) } else if utils.IsURL(cmd.Arg(0)) || utils.IsGIT(cmd.Arg(0)) { isRemote = true } else { diff --git a/config_test.go b/config_test.go new file mode 100644 index 0000000000..31c961135a --- /dev/null +++ b/config_test.go @@ -0,0 +1,149 @@ +package docker + +import ( + "testing" +) + +func TestCompareConfig(t *testing.T) { + volumes1 := make(map[string]struct{}) + volumes1["/test1"] = struct{}{} + config1 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, + } + config2 := Config{ + Dns: []string{"0.0.0.0", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, + } + config3 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes1, + } + config4 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "22222222", + Volumes: volumes1, + } + volumes2 := make(map[string]struct{}) + volumes2["/test2"] = struct{}{} + config5 := Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"0000:0000", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "11111111", + Volumes: volumes2, + } + if CompareConfig(&config1, &config2) { + t.Fatalf("CompareConfig should return false, Dns are different") + } + if CompareConfig(&config1, &config3) { + t.Fatalf("CompareConfig should return false, PortSpecs are different") + } + if CompareConfig(&config1, &config4) { + t.Fatalf("CompareConfig should return false, VolumesFrom are different") + } + if CompareConfig(&config1, &config5) { + t.Fatalf("CompareConfig should return false, Volumes are different") + } + if !CompareConfig(&config1, &config1) { + t.Fatalf("CompareConfig should return true") + } +} + +func TestMergeConfig(t *testing.T) { + volumesImage := make(map[string]struct{}) + volumesImage["/test1"] = struct{}{} + volumesImage["/test2"] = struct{}{} + configImage := &Config{ + Dns: []string{"1.1.1.1", "2.2.2.2"}, + PortSpecs: []string{"1111:1111", "2222:2222"}, + Env: []string{"VAR1=1", "VAR2=2"}, + VolumesFrom: "1111", + Volumes: volumesImage, + } + + volumesUser := make(map[string]struct{}) + volumesUser["/test3"] = struct{}{} + configUser := &Config{ + Dns: []string{"3.3.3.3"}, + PortSpecs: []string{"3333:2222", "3333:3333"}, + Env: []string{"VAR2=3", "VAR3=3"}, + Volumes: volumesUser, + } + + if err := MergeConfig(configUser, configImage); err != nil { + t.Error(err) + } + + if len(configUser.Dns) != 3 { + t.Fatalf("Expected 3 dns, 1.1.1.1, 2.2.2.2 and 3.3.3.3, found %d", len(configUser.Dns)) + } + for _, dns := range configUser.Dns { + if dns != "1.1.1.1" && dns != "2.2.2.2" && dns != "3.3.3.3" { + t.Fatalf("Expected 1.1.1.1 or 2.2.2.2 or 3.3.3.3, found %s", dns) + } + } + + 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) + } + } + + if configUser.VolumesFrom != "1111" { + t.Fatalf("Expected VolumesFrom to be 1111, found %s", configUser.VolumesFrom) + } + + ports, _, err := parsePortSpecs([]string{"0000"}) + if err != nil { + t.Error(err) + } + configImage2 := &Config{ + ExposedPorts: ports, + } + + if err := MergeConfig(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() != "0000" && portSpecs.Port() != "1111" && portSpecs.Port() != "2222" && portSpecs.Port() != "3333" { + t.Fatalf("Expected 0000 or 1111 or 2222 or 3333, found %s", portSpecs) + } + } + +} diff --git a/container_unit_test.go b/container_unit_test.go new file mode 100644 index 0000000000..9647327d98 --- /dev/null +++ b/container_unit_test.go @@ -0,0 +1,257 @@ +package docker + +import ( + "testing" +) + + + +func TestParseLxcConfOpt(t *testing.T) { + opts := []string{"lxc.utsname=docker", "lxc.utsname = docker "} + + for _, o := range opts { + k, v, err := parseLxcOpt(o) + if err != nil { + t.FailNow() + } + if k != "lxc.utsname" { + t.Fail() + } + if v != "docker" { + t.Fail() + } + } +} + +func TestParseNetworkOptsPrivateOnly(t *testing.T) { + ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::80"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "tcp" { + t.Logf("Expected tcp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "80" { + t.Logf("Expected 80 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "" { + t.Logf("Expected \"\" got %s", s.HostPort) + t.Fail() + } + if s.HostIp != "192.168.1.100" { + t.Fail() + } + } +} + +func TestParseNetworkOptsPublic(t *testing.T) { + ports, bindings, err := parsePortSpecs([]string{"192.168.1.100:8080:80"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "tcp" { + t.Logf("Expected tcp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "80" { + t.Logf("Expected 80 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "8080" { + t.Logf("Expected 8080 got %s", s.HostPort) + t.Fail() + } + if s.HostIp != "192.168.1.100" { + t.Fail() + } + } +} + +func TestParseNetworkOptsUdp(t *testing.T) { + ports, bindings, err := parsePortSpecs([]string{"192.168.1.100::6000/udp"}) + if err != nil { + t.Fatal(err) + } + if len(ports) != 1 { + t.Logf("Expected 1 got %d", len(ports)) + t.FailNow() + } + if len(bindings) != 1 { + t.Logf("Expected 1 got %d", len(bindings)) + t.FailNow() + } + for k := range ports { + if k.Proto() != "udp" { + t.Logf("Expected udp got %s", k.Proto()) + t.Fail() + } + if k.Port() != "6000" { + t.Logf("Expected 6000 got %s", k.Port()) + t.Fail() + } + b, exists := bindings[k] + if !exists { + t.Log("Binding does not exist") + t.FailNow() + } + if len(b) != 1 { + t.Logf("Expected 1 got %d", len(b)) + t.FailNow() + } + s := b[0] + if s.HostPort != "" { + t.Logf("Expected \"\" got %s", s.HostPort) + t.Fail() + } + if s.HostIp != "192.168.1.100" { + t.Fail() + } + } +} + + + + +// FIXME: test that destroying a container actually removes its root directory + + +/* +func TestLXCConfig(t *testing.T) { + // Memory is allocated randomly for testing + rand.Seed(time.Now().UTC().UnixNano()) + memMin := 33554432 + memMax := 536870912 + mem := memMin + rand.Intn(memMax-memMin) + // CPU shares as well + cpuMin := 100 + cpuMax := 10000 + cpu := cpuMin + rand.Intn(cpuMax-cpuMin) + container, _, err := runtime.Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"/bin/true"}, + + Hostname: "foobar", + Memory: int64(mem), + CpuShares: int64(cpu), + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + container.generateLXCConfig() + grepFile(t, container.lxcConfigPath(), "lxc.utsname = foobar") + grepFile(t, container.lxcConfigPath(), + fmt.Sprintf("lxc.cgroup.memory.limit_in_bytes = %d", mem)) + grepFile(t, container.lxcConfigPath(), + fmt.Sprintf("lxc.cgroup.memory.memsw.limit_in_bytes = %d", mem*2)) +} + + +func TestCustomLxcConfig(t *testing.T) { + runtime := mkRuntime(t) + defer nuke(runtime) + container, _, err := runtime.Create(&Config{ + Image: GetTestImage(runtime).ID, + Cmd: []string{"/bin/true"}, + + Hostname: "foobar", + }, + "", + ) + if err != nil { + t.Fatal(err) + } + defer runtime.Destroy(container) + container.hostConfig = &HostConfig{LxcConf: []KeyValuePair{ + { + Key: "lxc.utsname", + Value: "docker", + }, + { + Key: "lxc.cgroup.cpuset.cpus", + Value: "0,1", + }, + }} + + container.generateLXCConfig() + grepFile(t, container.lxcConfigPath(), "lxc.utsname = docker") + grepFile(t, container.lxcConfigPath(), "lxc.cgroup.cpuset.cpus = 0,1") +} + + +func grepFile(t *testing.T, path string, pattern string) { + f, err := os.Open(path) + if err != nil { + t.Fatal(err) + } + defer f.Close() + r := bufio.NewReader(f) + var ( + line string + ) + err = nil + for err == nil { + line, err = r.ReadString('\n') + if strings.Contains(line, pattern) == true { + return + } + } + t.Fatalf("grepFile: pattern \"%s\" not found in \"%s\"", pattern, path) +} +*/ + + +func TestGetFullName(t *testing.T) { + name, err := getFullName("testing") + if err != nil { + t.Fatal(err) + } + if name != "/testing" { + t.Fatalf("Expected /testing got %s", name) + } + if _, err := getFullName(""); err == nil { + t.Fatal("Error should not be nil") + } +} diff --git a/engine/job.go b/engine/job.go index c4a2c3ef52..3ccaa8d1a0 100644 --- a/engine/job.go +++ b/engine/job.go @@ -214,7 +214,7 @@ func (job *Job) GetenvList(key string) []string { return l } -func (job *Job) SetenvList(key string, value []string) error { +func (job *Job) SetenvJson(key string, value interface{}) error { sval, err := json.Marshal(value) if err != nil { return err @@ -223,6 +223,10 @@ func (job *Job) SetenvList(key string, value []string) error { return nil } +func (job *Job) SetenvList(key string, value []string) error { + return job.SetenvJson(key, value) +} + func (job *Job) Setenv(key, value string) { job.env = append(job.env, key+"="+value) } diff --git a/http_test.go b/http_test.go new file mode 100644 index 0000000000..64764424cb --- /dev/null +++ b/http_test.go @@ -0,0 +1,52 @@ +package docker + +import ( + "testing" + "fmt" + "net/http" + "net/http/httptest" +) + + +func TestGetBoolParam(t *testing.T) { + if ret, err := getBoolParam("true"); err != nil || !ret { + t.Fatalf("true -> true, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("True"); err != nil || !ret { + t.Fatalf("True -> true, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("1"); err != nil || !ret { + t.Fatalf("1 -> true, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam(""); err != nil || ret { + t.Fatalf("\"\" -> false, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("false"); err != nil || ret { + t.Fatalf("false -> false, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("0"); err != nil || ret { + t.Fatalf("0 -> false, nil | got %t %s", ret, err) + } + if ret, err := getBoolParam("faux"); err == nil || ret { + t.Fatalf("faux -> false, err | got %t %s", ret, err) + } +} + +func TesthttpError(t *testing.T) { + r := httptest.NewRecorder() + + httpError(r, fmt.Errorf("No such method")) + if r.Code != http.StatusNotFound { + t.Fatalf("Expected %d, got %d", http.StatusNotFound, r.Code) + } + + httpError(r, fmt.Errorf("This accound hasn't been activated")) + if r.Code != http.StatusForbidden { + t.Fatalf("Expected %d, got %d", http.StatusForbidden, r.Code) + } + + httpError(r, fmt.Errorf("Some error")) + if r.Code != http.StatusInternalServerError { + t.Fatalf("Expected %d, got %d", http.StatusInternalServerError, r.Code) + } +} diff --git a/api_test.go b/integration/api_test.go similarity index 100% rename from api_test.go rename to integration/api_test.go diff --git a/buildfile_test.go b/integration/buildfile_test.go similarity index 100% rename from buildfile_test.go rename to integration/buildfile_test.go diff --git a/commands_test.go b/integration/commands_test.go similarity index 100% rename from commands_test.go rename to integration/commands_test.go diff --git a/container_test.go b/integration/container_test.go similarity index 100% rename from container_test.go rename to integration/container_test.go diff --git a/runtime_test.go b/integration/runtime_test.go similarity index 100% rename from runtime_test.go rename to integration/runtime_test.go diff --git a/server_test.go b/integration/server_test.go similarity index 100% rename from server_test.go rename to integration/server_test.go diff --git a/sorter_test.go b/integration/sorter_test.go similarity index 100% rename from sorter_test.go rename to integration/sorter_test.go diff --git a/utils_test.go b/integration/utils_test.go similarity index 100% rename from utils_test.go rename to integration/utils_test.go diff --git a/z_final_test.go b/integration/z_final_test.go similarity index 100% rename from z_final_test.go rename to integration/z_final_test.go diff --git a/runtime.go b/runtime.go index f4744cc60a..bb90301d48 100644 --- a/runtime.go +++ b/runtime.go @@ -15,6 +15,7 @@ import ( "path" "sort" "strings" + "sync" "time" ) @@ -516,7 +517,12 @@ func (runtime *Runtime) Commit(container *Container, repository, tag, comment, a return img, nil } +// FIXME: this is deprecated by the getFullName *function* func (runtime *Runtime) getFullName(name string) (string, error) { + return getFullName(name) +} + +func getFullName(name string) (string, error) { if name == "" { return "", fmt.Errorf("Container name cannot be empty") } @@ -655,6 +661,26 @@ func (runtime *Runtime) Close() error { return runtime.containerGraph.Close() } +// Nuke kills all containers then removes all content +// from the content root, including images, volumes and +// container filesystems. +// Again: this will remove your entire docker runtime! +func (runtime *Runtime) Nuke() error { + var wg sync.WaitGroup + for _, container := range runtime.List() { + wg.Add(1) + go func(c *Container) { + c.Kill() + wg.Done() + }(container) + } + wg.Wait() + runtime.Close() + + return os.RemoveAll(runtime.config.Root) +} + + func linkLxcStart(root string) error { sourcePath, err := exec.LookPath("lxc-start") if err != nil { @@ -672,6 +698,14 @@ func linkLxcStart(root string) error { return os.Symlink(sourcePath, targetPath) } +// FIXME: this is a convenience function for integration tests +// which need direct access to runtime.graph. +// Once the tests switch to using engine and jobs, this method +// can go away. +func (runtime *Runtime) Graph() *Graph { + return runtime.graph +} + // History is a convenience type for storing a list of containers, // ordered by creation date. type History []*Container diff --git a/server.go b/server.go index 28858d6f6b..768f73a688 100644 --- a/server.go +++ b/server.go @@ -62,6 +62,8 @@ func jobInitApi(job *engine.Job) string { os.Exit(0) }() job.Eng.Hack_SetGlobalVar("httpapi.server", srv) + job.Eng.Hack_SetGlobalVar("httpapi.runtime", srv.runtime) + job.Eng.Hack_SetGlobalVar("httpapi.bridgeIP", srv.runtime.networkManager.bridgeNetwork.IP) if err := job.Eng.Register("create", srv.ContainerCreate); err != nil { return err.Error() } @@ -530,6 +532,7 @@ func (srv *Server) ContainerCommit(name, repo, tag, author, comment string, conf return img.ID, err } +// FIXME: this should be called ImageTag func (srv *Server) ContainerTag(name, repo, tag string, force bool) error { if err := srv.runtime.repositories.Set(repo, tag, name, force); err != nil { return err @@ -1062,7 +1065,12 @@ func (srv *Server) ContainerCreate(job *engine.Job) string { return err.Error() } srv.LogEvent("create", container.ID, srv.runtime.repositories.ImageName(container.Image)) - job.Printf("%s\n", container.ID) + // FIXME: this is necessary because runtime.Create might return a nil container + // with a non-nil error. This should not happen! Once it's fixed we + // can remove this workaround. + if container != nil { + job.Printf("%s\n", container.ID) + } for _, warning := range buildWarnings { job.Errorf("%s\n", warning) } @@ -1600,7 +1608,7 @@ func (srv *Server) HTTPRequestFactory(metaHeaders map[string][]string) *utils.HT return srv.reqFactory } -func (srv *Server) LogEvent(action, id, from string) { +func (srv *Server) LogEvent(action, id, from string) *utils.JSONMessage { now := time.Now().Unix() jm := utils.JSONMessage{Status: action, ID: id, From: from, Time: now} srv.events = append(srv.events, jm) @@ -1610,6 +1618,7 @@ func (srv *Server) LogEvent(action, id, from string) { default: } } + return &jm } type Server struct { diff --git a/server_unit_test.go b/server_unit_test.go new file mode 100644 index 0000000000..7ce03e3547 --- /dev/null +++ b/server_unit_test.go @@ -0,0 +1,110 @@ +package docker + +import ( + "github.com/dotcloud/docker/utils" + "testing" + "time" +) + +func TestPools(t *testing.T) { + srv := &Server{ + pullingPool: make(map[string]struct{}), + pushingPool: make(map[string]struct{}), + } + + err := srv.poolAdd("pull", "test1") + if err != nil { + t.Fatal(err) + } + err = srv.poolAdd("pull", "test2") + if err != nil { + t.Fatal(err) + } + err = srv.poolAdd("push", "test1") + if err == nil || err.Error() != "pull test1 is already in progress" { + t.Fatalf("Expected `pull test1 is already in progress`") + } + err = srv.poolAdd("pull", "test1") + if err == nil || err.Error() != "pull test1 is already in progress" { + t.Fatalf("Expected `pull test1 is already in progress`") + } + err = srv.poolAdd("wait", "test3") + if err == nil || err.Error() != "Unknown pool type" { + t.Fatalf("Expected `Unknown pool type`") + } + + err = srv.poolRemove("pull", "test2") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("pull", "test2") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("pull", "test1") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("push", "test1") + if err != nil { + t.Fatal(err) + } + err = srv.poolRemove("wait", "test3") + if err == nil || err.Error() != "Unknown pool type" { + t.Fatalf("Expected `Unknown pool type`") + } +} + + +func TestLogEvent(t *testing.T) { + srv := &Server{ + events: make([]utils.JSONMessage, 0, 64), + listeners: make(map[string]chan utils.JSONMessage), + } + + srv.LogEvent("fakeaction", "fakeid", "fakeimage") + + listener := make(chan utils.JSONMessage) + srv.Lock() + srv.listeners["test"] = listener + srv.Unlock() + + srv.LogEvent("fakeaction2", "fakeid", "fakeimage") + + if len(srv.events) != 2 { + t.Fatalf("Expected 2 events, found %d", len(srv.events)) + } + go func() { + time.Sleep(200 * time.Millisecond) + srv.LogEvent("fakeaction3", "fakeid", "fakeimage") + time.Sleep(200 * time.Millisecond) + srv.LogEvent("fakeaction4", "fakeid", "fakeimage") + }() + + setTimeout(t, "Listening for events timed out", 2*time.Second, func() { + for i := 2; i < 4; i++ { + event := <-listener + if event != srv.events[i] { + t.Fatalf("Event received it different than expected") + } + } + }) +} + +// FIXME: this is duplicated from integration/commands_test.go +func setTimeout(t *testing.T, msg string, d time.Duration, f func()) { + c := make(chan bool) + + // Make sure we are not too long + go func() { + time.Sleep(d) + c <- true + }() + go func() { + f() + c <- false + }() + if <-c && msg != "" { + t.Fatal(msg) + } +} diff --git a/sorter_unit_test.go b/sorter_unit_test.go new file mode 100644 index 0000000000..0669feedb3 --- /dev/null +++ b/sorter_unit_test.go @@ -0,0 +1,41 @@ +package docker + +import ( + "fmt" + "testing" +) + +func TestSortUniquePorts(t *testing.T) { + ports := []Port{ + Port("6379/tcp"), + Port("22/tcp"), + } + + sortPorts(ports, func(ip, jp Port) bool { + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") + }) + + first := ports[0] + if fmt.Sprint(first) != "22/tcp" { + t.Log(fmt.Sprint(first)) + t.Fail() + } +} + +func TestSortSamePortWithDifferentProto(t *testing.T) { + ports := []Port{ + Port("8888/tcp"), + Port("8888/udp"), + Port("6379/tcp"), + Port("6379/udp"), + } + + sortPorts(ports, func(ip, jp Port) bool { + return ip.Int() < jp.Int() || (ip.Int() == jp.Int() && ip.Proto() == "tcp") + }) + + first := ports[0] + if fmt.Sprint(first) != "6379/tcp" { + t.Fail() + } +} diff --git a/tags_unit_test.go b/tags_unit_test.go new file mode 100644 index 0000000000..89f31676f7 --- /dev/null +++ b/tags_unit_test.go @@ -0,0 +1,81 @@ +package docker + +import ( + "github.com/dotcloud/docker/utils" + "testing" + "path" + "os" +) + +const ( + testImageName string = "myapp" + testImageID string = "foo" +) + +func mkTestTagStore(root string, t *testing.T) *TagStore { + graph, err := NewGraph(root) + if err != nil { + t.Fatal(err) + } + store, err := NewTagStore(path.Join(root, "tags"), graph) + if err != nil { + t.Fatal(err) + } + archive, err := fakeTar() + if err != nil { + t.Fatal(err) + } + img := &Image{ID: testImageID} + if err := graph.Register(nil, archive, img); err != nil { + t.Fatal(err) + } + if err := store.Set(testImageName, "", testImageID, false); err != nil { + t.Fatal(err) + } + return store +} + + +func TestLookupImage(t *testing.T) { + tmp, err := utils.TestDirectory("") + if err != nil { + t.Fatal(err) + } + defer os.RemoveAll(tmp) + store := mkTestTagStore(tmp, t) + + if img, err := store.LookupImage(testImageName); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } + if img, err := store.LookupImage(testImageName + ":" + DEFAULTTAG); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } + + if img, err := store.LookupImage(testImageName + ":" + "fail"); err == nil { + t.Errorf("Expected error, none found") + } else if img != nil { + t.Errorf("Expected 0 image, 1 found") + } + + if img, err := store.LookupImage("fail:fail"); err == nil { + t.Errorf("Expected error, none found") + } else if img != nil { + t.Errorf("Expected 0 image, 1 found") + } + + if img, err := store.LookupImage(testImageID); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } + + if img, err := store.LookupImage(testImageName + ":" + testImageID); err != nil { + t.Fatal(err) + } else if img == nil { + t.Errorf("Expected 1 image, none found") + } +} diff --git a/utils/utils.go b/utils/utils.go index fe75aa68dc..0844abdde6 100644 --- a/utils/utils.go +++ b/utils/utils.go @@ -1207,3 +1207,43 @@ func PartParser(template, data string) (map[string]string, error) { } return out, nil } + + + +var globalTestID string + +// TestDirectory creates a new temporary directory and returns its path. +// The contents of directory at path `templateDir` is copied into the +// new directory. +func TestDirectory(templateDir string) (dir string, err error) { + if globalTestID == "" { + globalTestID = RandomString()[:4] + } + prefix := fmt.Sprintf("docker-test%s-%s-", globalTestID, GetCallerName(2)) + if prefix == "" { + prefix = "docker-test-" + } + dir, err = ioutil.TempDir("", prefix) + if err = os.Remove(dir); err != nil { + return + } + if templateDir != "" { + if err = CopyDirectory(templateDir, dir); err != nil { + return + } + } + return +} + +// GetCallerName introspects the call stack and returns the name of the +// function `depth` levels down in the stack. +func GetCallerName(depth int) string { + // Use the caller function name as a prefix. + // This helps trace temp directories back to their test. + pc, _, _, _ := runtime.Caller(depth + 1) + callerLongName := runtime.FuncForPC(pc).Name() + parts := strings.Split(callerLongName, ".") + callerShortName := parts[len(parts)-1] + return callerShortName +} +