Files
docker-cli/cli/command/service/formatter_test.go
Sebastiaan van Stijn dace8fdc75 formatter: reduce minimum width for columns in table-view
The tabwriter was configured to have a min-width for columns of 20 positions.
This seemed quite wide, and caused smaller columns to be printed with a large
gap between.

Before:

    docker container stats

    CONTAINER ID        NAME                CPU %               MEM USAGE / LIMIT     MEM %               NET I/O             BLOCK I/O           PIDS
    29184b3ae391        amazing_shirley     0.00%               800KiB / 1.944GiB     0.04%               1.44kB / 0B         0B / 0B             1
    403c101bad56        agitated_swartz     0.15%               34.31MiB / 1.944GiB   1.72%               10.2MB / 206kB      0B / 0B             51
    0dc4b7f6c6be        container2          0.00%               1.012MiB / 1.944GiB   0.05%               12.9kB / 0B         0B / 0B             5
    2d99abcc6f62        container99         0.00%               972KiB / 1.944GiB     0.05%               13kB / 0B           0B / 0B             5
    9f9aa90173ac        foo                 0.00%               820KiB / 1.944GiB     0.04%               13kB / 0B           0B / 0B             5

    docker container ls

    CONTAINER ID        IMAGE               COMMAND                  CREATED             STATUS              PORTS               NAMES
    29184b3ae391        docker-cli-dev      "ash"                    4 hours ago         Up 4 hours                              amazing_shirley
    403c101bad56        docker-dev:master   "hack/dind bash"         3 days ago          Up 3 days                               agitated_swartz
    0dc4b7f6c6be        nginx:alpine        "/docker-entrypoint.…"   4 days ago          Up 4 days           80/tcp              container2
    2d99abcc6f62        nginx:alpine        "/docker-entrypoint.…"   4 days ago          Up 4 days           80/tcp              container99
    9f9aa90173ac        nginx:alpine        "/docker-entrypoint.…"   4 days ago          Up 4 days           80/tcp              foo

    docker image ls

    REPOSITORY          TAG                    IMAGE ID            CREATED             SIZE
    docker-cli-dev      latest                 5f603caa04aa        4 hours ago         610MB
    docker-cli-native   latest                 9dd29f8d387b        4 hours ago         519MB
    docker-dev          master                 8132bf7a199e        3 days ago          2.02GB
    docker-dev          improve-build-errors   69e208994b3f        11 days ago         2.01GB
    docker-dev          refactor-idtools       69e208994b3f        11 days ago         2.01GB

After:

    docker container stats

    CONTAINER ID   NAME              CPU %     MEM USAGE / LIMIT     MEM %     NET I/O          BLOCK I/O   PIDS
    29184b3ae391   amazing_shirley   0.14%     5.703MiB / 1.944GiB   0.29%     1.44kB / 0B      0B / 0B     10
    403c101bad56   agitated_swartz   0.15%     56.97MiB / 1.944GiB   2.86%     10.2MB / 206kB   0B / 0B     51
    0dc4b7f6c6be   container2        0.00%     1016KiB / 1.944GiB    0.05%     12.9kB / 0B      0B / 0B     5
    2d99abcc6f62   container99       0.00%     956KiB / 1.944GiB     0.05%     13kB / 0B        0B / 0B     5
    9f9aa90173ac   foo               0.00%     980KiB / 1.944GiB     0.05%     13kB / 0B        0B / 0B     5

    docker container ls

    CONTAINER ID   IMAGE               COMMAND                  CREATED          STATUS          PORTS     NAMES
    29184b3ae391   docker-cli-dev      "ash"                    12 minutes ago   Up 12 minutes             amazing_shirley
    403c101bad56   docker-dev:master   "hack/dind bash"         3 days ago       Up 3 days                 agitated_swartz
    0dc4b7f6c6be   nginx:alpine        "/docker-entrypoint.…"   4 days ago       Up 4 days       80/tcp    container2
    2d99abcc6f62   nginx:alpine        "/docker-entrypoint.…"   4 days ago       Up 4 days       80/tcp    container99
    9f9aa90173ac   nginx:alpine        "/docker-entrypoint.…"   4 days ago       Up 4 days       80/tcp    foo

    docker image ls

    REPOSITORY          TAG                    IMAGE ID       CREATED         SIZE
    docker-cli-dev      latest                 5f603caa04aa   4 hours ago     610MB
    docker-cli-native   latest                 9dd29f8d387b   4 hours ago     519MB
    docker-dev          master                 8132bf7a199e   3 days ago      2.02GB
    docker-dev          improve-build-errors   69e208994b3f   11 days ago     2.01GB
    docker-dev          refactor-idtools       69e208994b3f   11 days ago     2.01GB

Signed-off-by: Sebastiaan van Stijn <github@gone.nl>
2020-08-31 16:31:15 +02:00

481 lines
11 KiB
Go

package service
import (
"bytes"
"encoding/json"
"fmt"
"strings"
"testing"
"github.com/docker/cli/cli/command/formatter"
"github.com/docker/docker/api/types/swarm"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
"gotest.tools/v3/golden"
)
func TestServiceContextWrite(t *testing.T) {
var (
// we need a pair of variables for setting the job parameters, because
// those parameters take pointers to uint64, which we can't make as a
// literal
varThree uint64 = 3
varTen uint64 = 10
)
cases := []struct {
context formatter.Context
expected string
}{
// Errors
{
formatter.Context{Format: "{{InvalidFunction}}"},
`Template parsing error: template: :1: function "InvalidFunction" not defined
`,
},
{
formatter.Context{Format: "{{nil}}"},
`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
`,
},
// Table format
{
formatter.Context{Format: NewListFormat("table", false)},
`ID NAME MODE REPLICAS IMAGE PORTS
02_bar bar replicated 2/4 *:80->8090/udp
01_baz baz global 1/3 *:80->8080/tcp
04_qux2 qux2 replicated 3/3 (max 2 per node)
03_qux10 qux10 replicated 2/3 (max 1 per node)
05_job1 zarp1 replicated job 2/3 (5/10 completed)
06_job2 zarp2 global job 1/1 (3/4 completed)
`,
},
{
formatter.Context{Format: NewListFormat("table", true)},
`02_bar
01_baz
04_qux2
03_qux10
05_job1
06_job2
`,
},
{
formatter.Context{Format: NewListFormat("table {{.Name}}\t{{.Mode}}", false)},
`NAME MODE
bar replicated
baz global
qux2 replicated
qux10 replicated
zarp1 replicated job
zarp2 global job
`,
},
{
formatter.Context{Format: NewListFormat("table {{.Name}}", true)},
`NAME
bar
baz
qux2
qux10
zarp1
zarp2
`,
},
// Raw Format
{
formatter.Context{Format: NewListFormat("raw", false)},
string(golden.Get(t, "service-context-write-raw.golden")),
},
{
formatter.Context{Format: NewListFormat("raw", true)},
`id: 02_bar
id: 01_baz
id: 04_qux2
id: 03_qux10
id: 05_job1
id: 06_job2
`,
},
// Custom Format
{
formatter.Context{Format: NewListFormat("{{.Name}}", false)},
`bar
baz
qux2
qux10
zarp1
zarp2
`,
},
}
services := []swarm.Service{
{
ID: "01_baz",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "baz"},
Mode: swarm.ServiceMode{
Global: &swarm.GlobalService{},
},
},
Endpoint: swarm.Endpoint{
Ports: []swarm.PortConfig{
{
PublishMode: "ingress",
PublishedPort: 80,
TargetPort: 8080,
Protocol: "tcp",
},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 1,
DesiredTasks: 3,
},
},
{
ID: "02_bar",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "bar"},
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{},
},
},
Endpoint: swarm.Endpoint{
Ports: []swarm.PortConfig{
{
PublishMode: "ingress",
PublishedPort: 80,
TargetPort: 8090,
Protocol: "udp",
},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 2,
DesiredTasks: 4,
},
},
{
ID: "03_qux10",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "qux10"},
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{},
},
TaskTemplate: swarm.TaskSpec{
Placement: &swarm.Placement{MaxReplicas: 1},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 2,
DesiredTasks: 3,
},
},
{
ID: "04_qux2",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "qux2"},
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{},
},
TaskTemplate: swarm.TaskSpec{
Placement: &swarm.Placement{MaxReplicas: 2},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 3,
DesiredTasks: 3,
},
},
{
ID: "05_job1",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "zarp1"},
Mode: swarm.ServiceMode{
ReplicatedJob: &swarm.ReplicatedJob{
MaxConcurrent: &varThree,
TotalCompletions: &varTen,
},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 2,
DesiredTasks: 3,
CompletedTasks: 5,
},
},
{
ID: "06_job2",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "zarp2"},
Mode: swarm.ServiceMode{
GlobalJob: &swarm.GlobalJob{},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 1,
DesiredTasks: 1,
CompletedTasks: 3,
},
},
}
for _, tc := range cases {
tc := tc
t.Run(string(tc.context.Format), func(t *testing.T) {
var out bytes.Buffer
tc.context.Output = &out
if err := ListFormatWrite(tc.context, services); err != nil {
assert.Error(t, err, tc.expected)
} else {
assert.Equal(t, out.String(), tc.expected)
}
})
}
}
func TestServiceContextWriteJSON(t *testing.T) {
services := []swarm.Service{
{
ID: "01_baz",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "baz"},
Mode: swarm.ServiceMode{
Global: &swarm.GlobalService{},
},
},
Endpoint: swarm.Endpoint{
Ports: []swarm.PortConfig{
{
PublishMode: "ingress",
PublishedPort: 80,
TargetPort: 8080,
Protocol: "tcp",
},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 1,
DesiredTasks: 3,
},
},
{
ID: "02_bar",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "bar"},
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{},
},
},
Endpoint: swarm.Endpoint{
Ports: []swarm.PortConfig{
{
PublishMode: "ingress",
PublishedPort: 80,
TargetPort: 8080,
Protocol: "tcp",
},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 2,
DesiredTasks: 4,
},
},
}
expectedJSONs := []map[string]interface{}{
{"ID": "02_bar", "Name": "bar", "Mode": "replicated", "Replicas": "2/4", "Image": "", "Ports": "*:80->8080/tcp"},
{"ID": "01_baz", "Name": "baz", "Mode": "global", "Replicas": "1/3", "Image": "", "Ports": "*:80->8080/tcp"},
}
out := bytes.NewBufferString("")
err := ListFormatWrite(formatter.Context{Format: "{{json .}}", Output: out}, services)
if err != nil {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
var m map[string]interface{}
err := json.Unmarshal([]byte(line), &m)
assert.NilError(t, err, msg)
assert.Check(t, is.DeepEqual(expectedJSONs[i], m), msg)
}
}
func TestServiceContextWriteJSONField(t *testing.T) {
services := []swarm.Service{
{
ID: "01_baz",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "baz"},
Mode: swarm.ServiceMode{
Global: &swarm.GlobalService{},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 2,
DesiredTasks: 4,
},
},
{
ID: "24_bar",
Spec: swarm.ServiceSpec{
Annotations: swarm.Annotations{Name: "bar"},
Mode: swarm.ServiceMode{
Replicated: &swarm.ReplicatedService{},
},
},
ServiceStatus: &swarm.ServiceStatus{
RunningTasks: 2,
DesiredTasks: 4,
},
},
}
out := bytes.NewBufferString("")
err := ListFormatWrite(formatter.Context{Format: "{{json .Name}}", Output: out}, services)
if err != nil {
t.Fatal(err)
}
for i, line := range strings.Split(strings.TrimSpace(out.String()), "\n") {
msg := fmt.Sprintf("Output: line %d: %s", i, line)
var s string
err := json.Unmarshal([]byte(line), &s)
assert.NilError(t, err, msg)
assert.Check(t, is.Equal(services[i].Spec.Name, s), msg)
}
}
func TestServiceContext_Ports(t *testing.T) {
c := serviceContext{
service: swarm.Service{
Endpoint: swarm.Endpoint{
Ports: []swarm.PortConfig{
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 81,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 80,
PublishedPort: 80,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 95,
PublishedPort: 95,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 90,
PublishedPort: 90,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 91,
PublishedPort: 91,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 92,
PublishedPort: 92,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 93,
PublishedPort: 93,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 94,
PublishedPort: 94,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 95,
PublishedPort: 95,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 90,
PublishedPort: 90,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 96,
PublishedPort: 96,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 91,
PublishedPort: 91,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 92,
PublishedPort: 92,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 93,
PublishedPort: 93,
PublishMode: "ingress",
},
{
Protocol: "udp",
TargetPort: 94,
PublishedPort: 94,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 60,
PublishedPort: 60,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 61,
PublishedPort: 61,
PublishMode: "ingress",
},
{
Protocol: "tcp",
TargetPort: 61,
PublishedPort: 62,
PublishMode: "ingress",
},
{
Protocol: "sctp",
TargetPort: 97,
PublishedPort: 97,
PublishMode: "ingress",
},
{
Protocol: "sctp",
TargetPort: 98,
PublishedPort: 98,
PublishMode: "ingress",
},
},
},
},
}
assert.Check(t, is.Equal("*:97-98->97-98/sctp, *:60-61->60-61/tcp, *:62->61/tcp, *:80-81->80/tcp, *:90-95->90-95/tcp, *:90-96->90-96/udp", c.Ports()))
}