1package formatter
2
3import (
4	"bytes"
5	"testing"
6
7	"github.com/docker/docker/pkg/stringid"
8	"gotest.tools/assert"
9	is "gotest.tools/assert/cmp"
10)
11
12func TestContainerStatsContext(t *testing.T) {
13	containerID := stringid.GenerateRandomID()
14
15	var ctx containerStatsContext
16	tt := []struct {
17		stats     StatsEntry
18		osType    string
19		expValue  string
20		expHeader string
21		call      func() string
22	}{
23		{StatsEntry{Container: containerID}, "", containerID, containerHeader, ctx.Container},
24		{StatsEntry{CPUPercentage: 5.5}, "", "5.50%", cpuPercHeader, ctx.CPUPerc},
25		{StatsEntry{CPUPercentage: 5.5, IsInvalid: true}, "", "--", cpuPercHeader, ctx.CPUPerc},
26		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3}, "", "0.31B / 12.3B", netIOHeader, ctx.NetIO},
27		{StatsEntry{NetworkRx: 0.31, NetworkTx: 12.3, IsInvalid: true}, "", "--", netIOHeader, ctx.NetIO},
28		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3}, "", "0.1B / 2.3B", blockIOHeader, ctx.BlockIO},
29		{StatsEntry{BlockRead: 0.1, BlockWrite: 2.3, IsInvalid: true}, "", "--", blockIOHeader, ctx.BlockIO},
30		{StatsEntry{MemoryPercentage: 10.2}, "", "10.20%", memPercHeader, ctx.MemPerc},
31		{StatsEntry{MemoryPercentage: 10.2, IsInvalid: true}, "", "--", memPercHeader, ctx.MemPerc},
32		{StatsEntry{MemoryPercentage: 10.2}, "windows", "--", memPercHeader, ctx.MemPerc},
33		{StatsEntry{Memory: 24, MemoryLimit: 30}, "", "24B / 30B", memUseHeader, ctx.MemUsage},
34		{StatsEntry{Memory: 24, MemoryLimit: 30, IsInvalid: true}, "", "-- / --", memUseHeader, ctx.MemUsage},
35		{StatsEntry{Memory: 24, MemoryLimit: 30}, "windows", "24B", winMemUseHeader, ctx.MemUsage},
36		{StatsEntry{PidsCurrent: 10}, "", "10", pidsHeader, ctx.PIDs},
37		{StatsEntry{PidsCurrent: 10, IsInvalid: true}, "", "--", pidsHeader, ctx.PIDs},
38		{StatsEntry{PidsCurrent: 10}, "windows", "--", pidsHeader, ctx.PIDs},
39	}
40
41	for _, te := range tt {
42		ctx = containerStatsContext{s: te.stats, os: te.osType}
43		if v := te.call(); v != te.expValue {
44			t.Fatalf("Expected %q, got %q", te.expValue, v)
45		}
46	}
47}
48
49func TestContainerStatsContextWrite(t *testing.T) {
50	tt := []struct {
51		context  Context
52		expected string
53	}{
54		{
55			Context{Format: "{{InvalidFunction}}"},
56			`Template parsing error: template: :1: function "InvalidFunction" not defined
57`,
58		},
59		{
60			Context{Format: "{{nil}}"},
61			`Template parsing error: template: :1:2: executing "" at <nil>: nil is not a command
62`,
63		},
64		{
65			Context{Format: "table {{.MemUsage}}"},
66			`MEM USAGE / LIMIT
6720B / 20B
68-- / --
69`,
70		},
71		{
72			Context{Format: "{{.Container}}  {{.ID}}  {{.Name}}"},
73			`container1  abcdef  foo
74container2    --
75`,
76		},
77		{
78			Context{Format: "{{.Container}}  {{.CPUPerc}}"},
79			`container1  20.00%
80container2  --
81`,
82		},
83	}
84
85	for _, te := range tt {
86		stats := []StatsEntry{
87			{
88				Container:        "container1",
89				ID:               "abcdef",
90				Name:             "/foo",
91				CPUPercentage:    20,
92				Memory:           20,
93				MemoryLimit:      20,
94				MemoryPercentage: 20,
95				NetworkRx:        20,
96				NetworkTx:        20,
97				BlockRead:        20,
98				BlockWrite:       20,
99				PidsCurrent:      2,
100				IsInvalid:        false,
101			},
102			{
103				Container:        "container2",
104				CPUPercentage:    30,
105				Memory:           30,
106				MemoryLimit:      30,
107				MemoryPercentage: 30,
108				NetworkRx:        30,
109				NetworkTx:        30,
110				BlockRead:        30,
111				BlockWrite:       30,
112				PidsCurrent:      3,
113				IsInvalid:        true,
114			},
115		}
116		var out bytes.Buffer
117		te.context.Output = &out
118		err := ContainerStatsWrite(te.context, stats, "linux", false)
119		if err != nil {
120			assert.Error(t, err, te.expected)
121		} else {
122			assert.Check(t, is.Equal(te.expected, out.String()))
123		}
124	}
125}
126
127func TestContainerStatsContextWriteWindows(t *testing.T) {
128	tt := []struct {
129		context  Context
130		expected string
131	}{
132		{
133			Context{Format: "table {{.MemUsage}}"},
134			`PRIV WORKING SET
13520B
136-- / --
137`,
138		},
139		{
140			Context{Format: "{{.Container}}  {{.CPUPerc}}"},
141			`container1  20.00%
142container2  --
143`,
144		},
145		{
146			Context{Format: "{{.Container}}  {{.MemPerc}}  {{.PIDs}}"},
147			`container1  --  --
148container2  --  --
149`,
150		},
151	}
152
153	for _, te := range tt {
154		stats := []StatsEntry{
155			{
156				Container:        "container1",
157				CPUPercentage:    20,
158				Memory:           20,
159				MemoryLimit:      20,
160				MemoryPercentage: 20,
161				NetworkRx:        20,
162				NetworkTx:        20,
163				BlockRead:        20,
164				BlockWrite:       20,
165				PidsCurrent:      2,
166				IsInvalid:        false,
167			},
168			{
169				Container:        "container2",
170				CPUPercentage:    30,
171				Memory:           30,
172				MemoryLimit:      30,
173				MemoryPercentage: 30,
174				NetworkRx:        30,
175				NetworkTx:        30,
176				BlockRead:        30,
177				BlockWrite:       30,
178				PidsCurrent:      3,
179				IsInvalid:        true,
180			},
181		}
182		var out bytes.Buffer
183		te.context.Output = &out
184		err := ContainerStatsWrite(te.context, stats, "windows", false)
185		if err != nil {
186			assert.Error(t, err, te.expected)
187		} else {
188			assert.Check(t, is.Equal(te.expected, out.String()))
189		}
190	}
191}
192
193func TestContainerStatsContextWriteWithNoStats(t *testing.T) {
194	var out bytes.Buffer
195
196	contexts := []struct {
197		context  Context
198		expected string
199	}{
200		{
201			Context{
202				Format: "{{.Container}}",
203				Output: &out,
204			},
205			"",
206		},
207		{
208			Context{
209				Format: "table {{.Container}}",
210				Output: &out,
211			},
212			"CONTAINER\n",
213		},
214		{
215			Context{
216				Format: "table {{.Container}}\t{{.CPUPerc}}",
217				Output: &out,
218			},
219			"CONTAINER           CPU %\n",
220		},
221	}
222
223	for _, context := range contexts {
224		ContainerStatsWrite(context.context, []StatsEntry{}, "linux", false)
225		assert.Check(t, is.Equal(context.expected, out.String()))
226		// Clean buffer
227		out.Reset()
228	}
229}
230
231func TestContainerStatsContextWriteWithNoStatsWindows(t *testing.T) {
232	var out bytes.Buffer
233
234	contexts := []struct {
235		context  Context
236		expected string
237	}{
238		{
239			Context{
240				Format: "{{.Container}}",
241				Output: &out,
242			},
243			"",
244		},
245		{
246			Context{
247				Format: "table {{.Container}}\t{{.MemUsage}}",
248				Output: &out,
249			},
250			"CONTAINER           PRIV WORKING SET\n",
251		},
252		{
253			Context{
254				Format: "table {{.Container}}\t{{.CPUPerc}}\t{{.MemUsage}}",
255				Output: &out,
256			},
257			"CONTAINER           CPU %               PRIV WORKING SET\n",
258		},
259	}
260
261	for _, context := range contexts {
262		ContainerStatsWrite(context.context, []StatsEntry{}, "windows", false)
263		assert.Check(t, is.Equal(context.expected, out.String()))
264		// Clean buffer
265		out.Reset()
266	}
267}
268
269func TestContainerStatsContextWriteTrunc(t *testing.T) {
270	var out bytes.Buffer
271
272	contexts := []struct {
273		context  Context
274		trunc    bool
275		expected string
276	}{
277		{
278			Context{
279				Format: "{{.ID}}",
280				Output: &out,
281			},
282			false,
283			"b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc\n",
284		},
285		{
286			Context{
287				Format: "{{.ID}}",
288				Output: &out,
289			},
290			true,
291			"b95a83497c91\n",
292		},
293	}
294
295	for _, context := range contexts {
296		ContainerStatsWrite(context.context, []StatsEntry{{ID: "b95a83497c9161c9b444e3d70e1a9dfba0c1840d41720e146a95a08ebf938afc"}}, "linux", context.trunc)
297		assert.Check(t, is.Equal(context.expected, out.String()))
298		// Clean buffer
299		out.Reset()
300	}
301}
302