From 6baf65d1a69b6e07861c3db5b5f6ed3b8256b4a8 Mon Sep 17 00:00:00 2001 From: Vincent Demeester Date: Sun, 6 Sep 2015 22:05:57 +0200 Subject: [PATCH] Complete unit tests on api/client/ps package Signed-off-by: Vincent Demeester --- api/client/ps/custom.go | 65 ---------- api/client/ps/custom_test.go | 30 +++-- api/client/ps/formatter.go | 67 ++++++++++ api/client/ps/formatter_test.go | 208 ++++++++++++++++++++++++++++++++ 4 files changed, 293 insertions(+), 77 deletions(-) create mode 100644 api/client/ps/formatter_test.go diff --git a/api/client/ps/custom.go b/api/client/ps/custom.go index 261493ab9a..6070b2fe3a 100644 --- a/api/client/ps/custom.go +++ b/api/client/ps/custom.go @@ -1,12 +1,9 @@ package ps import ( - "bytes" "fmt" "strconv" "strings" - "text/tabwriter" - "text/template" "time" "github.com/docker/docker/api" @@ -152,68 +149,6 @@ func (c *containerContext) addHeader(header string) { c.header = append(c.header, strings.ToUpper(header)) } -func customFormat(ctx Context, containers []types.Container) { - var ( - table bool - header string - format = ctx.Format - buffer = bytes.NewBufferString("") - ) - - if strings.HasPrefix(ctx.Format, tableKey) { - table = true - format = format[len(tableKey):] - } - - format = strings.Trim(format, " ") - r := strings.NewReplacer(`\t`, "\t", `\n`, "\n") - format = r.Replace(format) - - if table && ctx.Size { - format += "\t{{.Size}}" - } - - tmpl, err := template.New("").Parse(format) - if err != nil { - buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err)) - buffer.WriteTo(ctx.Output) - return - } - - for _, container := range containers { - containerCtx := &containerContext{ - trunc: ctx.Trunc, - c: container, - } - if err := tmpl.Execute(buffer, containerCtx); err != nil { - buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err)) - buffer.WriteTo(ctx.Output) - return - } - if table && len(header) == 0 { - header = containerCtx.fullHeader() - } - buffer.WriteString("\n") - } - - if table { - if len(header) == 0 { - // if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template - containerCtx := &containerContext{} - tmpl.Execute(bytes.NewBufferString(""), containerCtx) - header = containerCtx.fullHeader() - } - - t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0) - t.Write([]byte(header)) - t.Write([]byte("\n")) - buffer.WriteTo(t) - t.Flush() - } else { - buffer.WriteTo(ctx.Output) - } -} - func stripNamePrefix(ss []string) []string { for i, s := range ss { ss[i] = s[1:] diff --git a/api/client/ps/custom_test.go b/api/client/ps/custom_test.go index 706b86dc01..5c80d9cdb6 100644 --- a/api/client/ps/custom_test.go +++ b/api/client/ps/custom_test.go @@ -1,7 +1,6 @@ package ps import ( - "bytes" "reflect" "strings" "testing" @@ -24,8 +23,11 @@ func TestContainerPsContext(t *testing.T) { call func() string }{ {types.Container{ID: containerID}, true, stringid.TruncateID(containerID), idHeader, ctx.ID}, + {types.Container{ID: containerID}, false, containerID, idHeader, ctx.ID}, {types.Container{Names: []string{"/foobar_baz"}}, true, "foobar_baz", namesHeader, ctx.Names}, {types.Container{Image: "ubuntu"}, true, "ubuntu", imageHeader, ctx.Image}, + {types.Container{Image: "verylongimagename"}, true, "verylongimag", imageHeader, ctx.Image}, + {types.Container{Image: "verylongimagename"}, false, "verylongimagename", imageHeader, ctx.Image}, {types.Container{Image: ""}, true, "", imageHeader, ctx.Image}, {types.Container{Command: "sh -c 'ls -la'"}, true, `"sh -c 'ls -la'"`, commandHeader, ctx.Command}, {types.Container{Created: unix}, true, time.Unix(unix, 0).String(), createdAtHeader, ctx.CreatedAt}, @@ -33,7 +35,9 @@ func TestContainerPsContext(t *testing.T) { {types.Container{Status: "RUNNING"}, true, "RUNNING", statusHeader, ctx.Status}, {types.Container{SizeRw: 10}, true, "10 B", sizeHeader, ctx.Size}, {types.Container{SizeRw: 10, SizeRootFs: 20}, true, "10 B (virtual 20 B)", sizeHeader, ctx.Size}, + {types.Container{}, true, "", labelsHeader, ctx.Labels}, {types.Container{Labels: map[string]string{"cpu": "6", "storage": "ssd"}}, true, "cpu=6,storage=ssd", labelsHeader, ctx.Labels}, + {types.Container{Created: unix}, true, "Less than a second", runningForHeader, ctx.RunningFor}, } for _, c := range cases { @@ -68,8 +72,8 @@ func TestContainerPsContext(t *testing.T) { } } - c := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}} - ctx = containerContext{c: c, trunc: true} + c1 := types.Container{Labels: map[string]string{"com.docker.swarm.swarm-id": "33", "com.docker.swarm.node_name": "ubuntu"}} + ctx = containerContext{c: c1, trunc: true} sid := ctx.Label("com.docker.swarm.swarm-id") node := ctx.Label("com.docker.swarm.node_name") @@ -86,17 +90,19 @@ func TestContainerPsContext(t *testing.T) { t.Fatalf("Expected %s, was %s\n", "SWARM ID\tNODE NAME", h) } -} -func TestContainerPsFormatError(t *testing.T) { - out := bytes.NewBufferString("") - ctx := Context{ - Format: "{{InvalidFunction}}", - Output: out, + c2 := types.Container{} + ctx = containerContext{c: c2, trunc: true} + + label := ctx.Label("anything.really") + if label != "" { + t.Fatalf("Expected an empty string, was %s", label) } - customFormat(ctx, make([]types.Container, 0)) - if out.String() != "Template parsing error: template: :1: function \"InvalidFunction\" not defined\n" { - t.Fatalf("Expected format error, got `%v`\n", out.String()) + ctx = containerContext{c: c2, trunc: true} + fullHeader := ctx.fullHeader() + if fullHeader != "" { + t.Fatalf("Expected fullHeader to be empty, was %s", fullHeader) } + } diff --git a/api/client/ps/formatter.go b/api/client/ps/formatter.go index 1a1323ac1c..2a45bfcf56 100644 --- a/api/client/ps/formatter.go +++ b/api/client/ps/formatter.go @@ -1,7 +1,12 @@ package ps import ( + "bytes" + "fmt" "io" + "strings" + "text/tabwriter" + "text/template" "github.com/docker/docker/api/types" ) @@ -71,3 +76,65 @@ func tableFormat(ctx Context, containers []types.Container) { customFormat(ctx, containers) } + +func customFormat(ctx Context, containers []types.Container) { + var ( + table bool + header string + format = ctx.Format + buffer = bytes.NewBufferString("") + ) + + if strings.HasPrefix(ctx.Format, tableKey) { + table = true + format = format[len(tableKey):] + } + + format = strings.Trim(format, " ") + r := strings.NewReplacer(`\t`, "\t", `\n`, "\n") + format = r.Replace(format) + + if table && ctx.Size { + format += "\t{{.Size}}" + } + + tmpl, err := template.New("").Parse(format) + if err != nil { + buffer.WriteString(fmt.Sprintf("Template parsing error: %v\n", err)) + buffer.WriteTo(ctx.Output) + return + } + + for _, container := range containers { + containerCtx := &containerContext{ + trunc: ctx.Trunc, + c: container, + } + if err := tmpl.Execute(buffer, containerCtx); err != nil { + buffer = bytes.NewBufferString(fmt.Sprintf("Template parsing error: %v\n", err)) + buffer.WriteTo(ctx.Output) + return + } + if table && len(header) == 0 { + header = containerCtx.fullHeader() + } + buffer.WriteString("\n") + } + + if table { + if len(header) == 0 { + // if we still don't have a header, we didn't have any containers so we need to fake it to get the right headers from the template + containerCtx := &containerContext{} + tmpl.Execute(bytes.NewBufferString(""), containerCtx) + header = containerCtx.fullHeader() + } + + t := tabwriter.NewWriter(ctx.Output, 20, 1, 3, ' ', 0) + t.Write([]byte(header)) + t.Write([]byte("\n")) + buffer.WriteTo(t) + t.Flush() + } else { + buffer.WriteTo(ctx.Output) + } +} diff --git a/api/client/ps/formatter_test.go b/api/client/ps/formatter_test.go new file mode 100644 index 0000000000..6e7304c367 --- /dev/null +++ b/api/client/ps/formatter_test.go @@ -0,0 +1,208 @@ +package ps + +import ( + "bytes" + "testing" + + "github.com/docker/docker/api/types" +) + +func TestFormat(t *testing.T) { + contexts := []struct { + context Context + expected string + }{ + // Errors + { + Context{ + Format: "{{InvalidFunction}}", + }, + `Template parsing error: template: :1: function "InvalidFunction" not defined +`, + }, + { + Context{ + Format: "{{nil}}", + }, + `Template parsing error: template: :1:2: executing "" at : nil is not a command +`, + }, + // Table Format + { + Context{ + Format: "table", + }, + `CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES +containerID1 ubuntu "" 45 years ago foobar_baz +containerID2 ubuntu "" 45 years ago foobar_bar +`, + }, + { + Context{ + Format: "table {{.Image}}", + }, + "IMAGE\nubuntu\nubuntu\n", + }, + { + Context{ + Format: "table {{.Image}}", + Size: true, + }, + "IMAGE SIZE\nubuntu 0 B\nubuntu 0 B\n", + }, + { + Context{ + Format: "table {{.Image}}", + Quiet: true, + }, + "IMAGE\nubuntu\nubuntu\n", + }, + { + Context{ + Format: "table", + Quiet: true, + }, + "containerID1\ncontainerID2\n", + }, + // Raw Format + { + Context{ + Format: "raw", + }, + `container_id: containerID1 +image: ubuntu +command: "" +created_at: 1970-01-01 00:00:00 +0000 UTC +status: +names: foobar_baz +labels: +ports: + +container_id: containerID2 +image: ubuntu +command: "" +created_at: 1970-01-01 00:00:00 +0000 UTC +status: +names: foobar_bar +labels: +ports: + +`, + }, + { + Context{ + Format: "raw", + Size: true, + }, + `container_id: containerID1 +image: ubuntu +command: "" +created_at: 1970-01-01 00:00:00 +0000 UTC +status: +names: foobar_baz +labels: +ports: +size: 0 B + +container_id: containerID2 +image: ubuntu +command: "" +created_at: 1970-01-01 00:00:00 +0000 UTC +status: +names: foobar_bar +labels: +ports: +size: 0 B + +`, + }, + { + Context{ + Format: "raw", + Quiet: true, + }, + "container_id: containerID1\ncontainer_id: containerID2\n", + }, + // Custom Format + { + Context{ + Format: "{{.Image}}", + }, + "ubuntu\nubuntu\n", + }, + { + Context{ + Format: "{{.Image}}", + Size: true, + }, + "ubuntu\nubuntu\n", + }, + } + + for _, context := range contexts { + containers := []types.Container{ + {ID: "containerID1", Names: []string{"/foobar_baz"}, Image: "ubuntu"}, + {ID: "containerID2", Names: []string{"/foobar_bar"}, Image: "ubuntu"}, + } + out := bytes.NewBufferString("") + context.context.Output = out + Format(context.context, containers) + actual := out.String() + if actual != context.expected { + t.Fatalf("Expected \n%s, got \n%s", context.expected, actual) + } + // Clean buffer + out.Reset() + } +} + +func TestCustomFormatNoContainers(t *testing.T) { + out := bytes.NewBufferString("") + containers := []types.Container{} + + contexts := []struct { + context Context + expected string + }{ + { + Context{ + Format: "{{.Image}}", + Output: out, + }, + "", + }, + { + Context{ + Format: "table {{.Image}}", + Output: out, + }, + "IMAGE\n", + }, + { + Context{ + Format: "{{.Image}}", + Output: out, + Size: true, + }, + "", + }, + { + Context{ + Format: "table {{.Image}}", + Output: out, + Size: true, + }, + "IMAGE SIZE\n", + }, + } + + for _, context := range contexts { + customFormat(context.context, containers) + actual := out.String() + if actual != context.expected { + t.Fatalf("Expected \n%s, got \n%s", context.expected, actual) + } + // Clean buffer + out.Reset() + } +}