mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00

This fix is an attempt to address https://github.com/docker/docker/pull/28213#issuecomment-273840405 Currently when specify table format with table `--format "table {{.ID}}..."`, the delimiter in the header section of the table is always `"\t"`. That is actually different from the content of the table as the delimiter could be anything (or even contatenated with `.`, for example): ``` $ docker service ps web --format 'table {{.Name}}.{{.ID}}' --no-trunc NAME ID web.1.inyhxhvjcijl0hdbu8lgrwwh7 \_ web.1.p9m4kx2srjqmfms4igam0uqlb ``` This fix is an attampt to address the skewness of the table when delimiter is not `"\t"`. The basic idea is that, when header consists of `table` key, the header section will be redendered the same way as content section. A map mapping each placeholder name to the HEADER entry name is used for the context of the header. Unit tests have been updated and added to cover the changes. This fix is related to #28313. Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
266 lines
6.2 KiB
Go
266 lines
6.2 KiB
Go
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
|
|
osType string
|
|
expValue string
|
|
expHeader string
|
|
call func() string
|
|
}{
|
|
{StatsEntry{Container: 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.31B / 12.3B", netIOHeader, ctx.NetIO},
|
|
{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
|
|
{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", 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}, "windows", "--", memPercHeader, ctx.MemPerc},
|
|
{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
|
|
{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
|
|
{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
|
|
{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
|
|
{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
|
|
{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
|
|
}
|
|
|
|
for _, te := range tt {
|
|
ctx = containerStatsContext{s: te.stats, os: te.osType}
|
|
if v := te.call(); v != te.expValue {
|
|
t.Fatalf("Expected %q, got %q", te.expValue, v)
|
|
}
|
|
}
|
|
}
|
|
|
|
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>: nil is not a command
|
|
`,
|
|
},
|
|
{
|
|
Context{Format: "table {{.MemUsage}}"},
|
|
`MEM USAGE / LIMIT
|
|
20B / 20B
|
|
-- / --
|
|
`,
|
|
},
|
|
{
|
|
Context{Format: "{{.Container}} {{.ID}} {{.Name}}"},
|
|
`container1 abcdef foo
|
|
container2 --
|
|
`,
|
|
},
|
|
{
|
|
Context{Format: "{{.Container}} {{.CPUPerc}}"},
|
|
`container1 20.00%
|
|
container2 --
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, te := range tt {
|
|
stats := []StatsEntry{
|
|
{
|
|
Container: "container1",
|
|
ID: "abcdef",
|
|
Name: "/foo",
|
|
CPUPercentage: 20,
|
|
Memory: 20,
|
|
MemoryLimit: 20,
|
|
MemoryPercentage: 20,
|
|
NetworkRx: 20,
|
|
NetworkTx: 20,
|
|
BlockRead: 20,
|
|
BlockWrite: 20,
|
|
PidsCurrent: 2,
|
|
IsInvalid: false,
|
|
},
|
|
{
|
|
Container: "container2",
|
|
CPUPercentage: 30,
|
|
Memory: 30,
|
|
MemoryLimit: 30,
|
|
MemoryPercentage: 30,
|
|
NetworkRx: 30,
|
|
NetworkTx: 30,
|
|
BlockRead: 30,
|
|
BlockWrite: 30,
|
|
PidsCurrent: 3,
|
|
IsInvalid: true,
|
|
},
|
|
}
|
|
var out bytes.Buffer
|
|
te.context.Output = &out
|
|
err := ContainerStatsWrite(te.context, stats, "linux")
|
|
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
|
|
20B
|
|
-- / --
|
|
`,
|
|
},
|
|
{
|
|
Context{Format: "{{.Container}} {{.CPUPerc}}"},
|
|
`container1 20.00%
|
|
container2 --
|
|
`,
|
|
},
|
|
{
|
|
Context{Format: "{{.Container}} {{.MemPerc}} {{.PIDs}}"},
|
|
`container1 -- --
|
|
container2 -- --
|
|
`,
|
|
},
|
|
}
|
|
|
|
for _, te := range tt {
|
|
stats := []StatsEntry{
|
|
{
|
|
Container: "container1",
|
|
CPUPercentage: 20,
|
|
Memory: 20,
|
|
MemoryLimit: 20,
|
|
MemoryPercentage: 20,
|
|
NetworkRx: 20,
|
|
NetworkTx: 20,
|
|
BlockRead: 20,
|
|
BlockWrite: 20,
|
|
PidsCurrent: 2,
|
|
IsInvalid: false,
|
|
},
|
|
{
|
|
Container: "container2",
|
|
CPUPercentage: 30,
|
|
Memory: 30,
|
|
MemoryLimit: 30,
|
|
MemoryPercentage: 30,
|
|
NetworkRx: 30,
|
|
NetworkTx: 30,
|
|
BlockRead: 30,
|
|
BlockWrite: 30,
|
|
PidsCurrent: 3,
|
|
IsInvalid: true,
|
|
},
|
|
}
|
|
var out bytes.Buffer
|
|
te.context.Output = &out
|
|
err := ContainerStatsWrite(te.context, stats, "windows")
|
|
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{}, "linux")
|
|
assert.Equal(t, context.expected, out.String())
|
|
// Clean buffer
|
|
out.Reset()
|
|
}
|
|
}
|
|
|
|
func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
|
|
var out bytes.Buffer
|
|
|
|
contexts := []struct {
|
|
context Context
|
|
expected string
|
|
}{
|
|
{
|
|
Context{
|
|
Format: "{{.Container}}",
|
|
Output: &out,
|
|
},
|
|
"",
|
|
},
|
|
{
|
|
Context{
|
|
Format: "table {{.Container}}\t{{.MemUsage}}",
|
|
Output: &out,
|
|
},
|
|
"CONTAINER PRIV WORKING SET\n",
|
|
},
|
|
{
|
|
Context{
|
|
Format: "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}",
|
|
Output: &out,
|
|
},
|
|
"CONTAINER CPU % PRIV WORKING SET\n",
|
|
},
|
|
}
|
|
|
|
for _, context := range contexts {
|
|
ContainerStatsWrite(context.context, []StatsEntry{}, "windows")
|
|
assert.Equal(t, out.String(), context.expected)
|
|
// Clean buffer
|
|
out.Reset()
|
|
}
|
|
}
|