mirror of
				https://github.com/moby/moby.git
				synced 2022-11-09 12:21:53 -05:00 
			
		
		
		
	Merge pull request #15931 from vdemeester/api-client-ps-coverage
Add more unit tests on api/client/ps package
This commit is contained in:
		
						commit
						7ce270d4ea
					
				
					 4 changed files with 293 additions and 77 deletions
				
			
		| 
						 | 
				
			
			@ -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:]
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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, "<no image>", 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)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										208
									
								
								api/client/ps/formatter_test.go
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										208
									
								
								api/client/ps/formatter_test.go
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -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>: 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()
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue