diff --git a/command/formatter/stats.go b/command/formatter/stats.go index cc2588c392..b2c972251f 100644 --- a/command/formatter/stats.go +++ b/command/formatter/stats.go @@ -167,7 +167,7 @@ func (c *containerStatsContext) MemUsage() string { func (c *containerStatsContext) MemPerc() string { header := memPercHeader c.AddHeader(header) - if c.s.IsInvalid { + if c.s.IsInvalid || c.s.OSType == winOSType { return fmt.Sprintf("--") } return fmt.Sprintf("%.2f%%", c.s.MemoryPercentage) diff --git a/command/formatter/stats_test.go b/command/formatter/stats_test.go new file mode 100644 index 0000000000..f1f449e71a --- /dev/null +++ b/command/formatter/stats_test.go @@ -0,0 +1,228 @@ +package formatter + +import ( + "bytes" + "testing" + + "github.com/docker/docker/pkg/stringid" + "github.com/docker/docker/pkg/testutil/assert" +) + +func TestContainerStatsContext(t *testing.T) { + containerID := stringid.GenerateRandomID() + + var ctx containerStatsContext + tt := []struct { + stats StatsEntry + expValue string + expHeader string + call func() string + }{ + {StatsEntry{Name: containerID}, containerID, containerHeader, ctx.Container}, + {StatsEntry{CPUPercentage: 5.5}, "5.50%", cpuPercHeader, ctx.CPUPerc}, + {StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "--", cpuPercHeader, ctx.CPUPerc}, + {StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "0.31 B / 12.3 B", netIOHeader, ctx.NetIO}, + {StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "--", netIOHeader, ctx.NetIO}, + {StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "0.1 B / 2.3 B", blockIOHeader, ctx.BlockIO}, + {StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "--", blockIOHeader, ctx.BlockIO}, + {StatsEntry{MemoryPercentage: 10.2}, "10.20%", memPercHeader, ctx.MemPerc}, + {StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "--", memPercHeader, ctx.MemPerc}, + {StatsEntry{MemoryPercentage: 10.2, OSType: "windows"}, "--", memPercHeader, ctx.MemPerc}, + {StatsEntry{Memory: 24, MemoryLimit: 30}, "24 B / 30 B", memUseHeader, ctx.MemUsage}, + {StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "-- / --", memUseHeader, ctx.MemUsage}, + {StatsEntry{Memory: 24, MemoryLimit: 30, OSType: "windows"}, "24 B", winMemUseHeader, ctx.MemUsage}, + {StatsEntry{PidsCurrent: 10}, "10", pidsHeader, ctx.PIDs}, + {StatsEntry{PidsCurrent: 10, IsInvalid: true}, "--", pidsHeader, ctx.PIDs}, + {StatsEntry{PidsCurrent: 10, OSType: "windows"}, "--", pidsHeader, ctx.PIDs}, + } + + for _, te := range tt { + ctx = containerStatsContext{s: te.stats} + if v := te.call(); v != te.expValue { + t.Fatalf("Expected %q, got %q", te.expValue, v) + } + + h := ctx.FullHeader() + if h != te.expHeader { + t.Fatalf("Expected %q, got %q", te.expHeader, h) + } + } +} + +func TestContainerStatsContextWrite(t *testing.T) { + tt := []struct { + context Context + expected string + }{ + { + 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 +`, + }, + { + Context{Format: "table {{.MemUsage}}"}, + `MEM USAGE / LIMIT +20 B / 20 B +-- / -- +`, + }, + { + Context{Format: "{{.Container}} {{.CPUPerc}}"}, + `container1 20.00% +container2 -- +`, + }, + } + + for _, te := range tt { + stats := []StatsEntry{ + { + Name: "container1", + CPUPercentage: 20, + Memory: 20, + MemoryLimit: 20, + MemoryPercentage: 20, + NetworkRx: 20, + NetworkTx: 20, + BlockRead: 20, + BlockWrite: 20, + PidsCurrent: 2, + IsInvalid: false, + OSType: "linux", + }, + { + Name: "container2", + CPUPercentage: 30, + Memory: 30, + MemoryLimit: 30, + MemoryPercentage: 30, + NetworkRx: 30, + NetworkTx: 30, + BlockRead: 30, + BlockWrite: 30, + PidsCurrent: 3, + IsInvalid: true, + OSType: "linux", + }, + } + var out bytes.Buffer + te.context.Output = &out + err := ContainerStatsWrite(te.context, stats) + if err != nil { + assert.Error(t, err, te.expected) + } else { + assert.Equal(t, out.String(), te.expected) + } + } +} + +func TestContainerStatsContextWriteWindows(t *testing.T) { + tt := []struct { + context Context + expected string + }{ + { + Context{Format: "table {{.MemUsage}}"}, + `PRIV WORKING SET +20 B +-- / -- +`, + }, + { + Context{Format: "{{.Container}} {{.CPUPerc}}"}, + `container1 20.00% +container2 -- +`, + }, + { + Context{Format: "{{.Container}} {{.MemPerc}} {{.PIDs}}"}, + `container1 -- -- +container2 -- -- +`, + }, + } + + for _, te := range tt { + stats := []StatsEntry{ + { + Name: "container1", + CPUPercentage: 20, + Memory: 20, + MemoryLimit: 20, + MemoryPercentage: 20, + NetworkRx: 20, + NetworkTx: 20, + BlockRead: 20, + BlockWrite: 20, + PidsCurrent: 2, + IsInvalid: false, + OSType: "windows", + }, + { + Name: "container2", + CPUPercentage: 30, + Memory: 30, + MemoryLimit: 30, + MemoryPercentage: 30, + NetworkRx: 30, + NetworkTx: 30, + BlockRead: 30, + BlockWrite: 30, + PidsCurrent: 3, + IsInvalid: true, + OSType: "windows", + }, + } + var out bytes.Buffer + te.context.Output = &out + err := ContainerStatsWrite(te.context, stats) + if err != nil { + assert.Error(t, err, te.expected) + } else { + assert.Equal(t, out.String(), te.expected) + } + } +} + +func TestContainerStatsContextWriteWithNoStats(t *testing.T) { + var out bytes.Buffer + + contexts := []struct { + context Context + expected string + }{ + { + Context{ + Format: "{{.Container}}", + Output: &out, + }, + "", + }, + { + Context{ + Format: "table {{.Container}}", + Output: &out, + }, + "CONTAINER\n", + }, + { + Context{ + Format: "table {{.Container}}\t{{.CPUPerc}}", + Output: &out, + }, + "CONTAINER CPU %\n", + }, + } + + for _, context := range contexts { + ContainerStatsWrite(context.context, []StatsEntry{}) + assert.Equal(t, context.expected, out.String()) + // Clean buffer + out.Reset() + } +}