1package pb
2
3import (
4	"fmt"
5	"strings"
6	"testing"
7	"time"
8
9	"github.com/fatih/color"
10)
11
12func testState(total, value int64, maxWidth int, bools ...bool) (s *State) {
13	s = &State{
14		total:           total,
15		current:         value,
16		adaptiveElWidth: maxWidth,
17		ProgressBar:     new(ProgressBar),
18	}
19	if len(bools) > 0 {
20		s.Set(Bytes, bools[0])
21	}
22	if len(bools) > 1 && bools[1] {
23		s.adaptive = true
24	}
25	return
26}
27
28func testElementBarString(t *testing.T, state *State, el Element, want string, args ...string) {
29	if state.ProgressBar == nil {
30		state.ProgressBar = new(ProgressBar)
31	}
32	res := el.ProgressElement(state, args...)
33	if res != want {
34		t.Errorf("Unexpected result: '%s'; want: '%s'", res, want)
35	}
36	if state.IsAdaptiveWidth() && state.AdaptiveElWidth() != CellCount(res) {
37		t.Errorf("Unepected width: %d; want: %d", CellCount(res), state.AdaptiveElWidth())
38	}
39}
40
41func TestElementPercent(t *testing.T) {
42	testElementBarString(t, testState(100, 50, 0), ElementPercent, "50.00%")
43	testElementBarString(t, testState(100, 50, 0), ElementPercent, "50 percent", "%v percent")
44	testElementBarString(t, testState(0, 50, 0), ElementPercent, "?%")
45	testElementBarString(t, testState(0, 50, 0), ElementPercent, "unkn", "%v%%", "unkn")
46}
47
48func TestElementCounters(t *testing.T) {
49	testElementBarString(t, testState(100, 50, 0), ElementCounters, "50 / 100")
50	testElementBarString(t, testState(100, 50, 0), ElementCounters, "50 of 100", "%s of %s")
51	testElementBarString(t, testState(100, 50, 0, true), ElementCounters, "50 B of 100 B", "%s of %s")
52	testElementBarString(t, testState(100, 50, 0, true), ElementCounters, "50 B / 100 B")
53	testElementBarString(t, testState(0, 50, 0, true), ElementCounters, "50 B")
54	testElementBarString(t, testState(0, 50, 0, true), ElementCounters, "50 B / ?", "", "%[1]s / ?")
55}
56
57func TestElementBar(t *testing.T) {
58	// short
59	testElementBarString(t, testState(100, 50, 1, false, true), ElementBar, "[")
60	testElementBarString(t, testState(100, 50, 2, false, true), ElementBar, "[]")
61	testElementBarString(t, testState(100, 50, 3, false, true), ElementBar, "[>]")
62	testElementBarString(t, testState(100, 50, 4, false, true), ElementBar, "[>_]")
63	testElementBarString(t, testState(100, 50, 5, false, true), ElementBar, "[->_]")
64	// middle
65	testElementBarString(t, testState(100, 50, 10, false, true), ElementBar, "[--->____]")
66	testElementBarString(t, testState(100, 50, 10, false, true), ElementBar, "<--->____>", "<", "", "", "", ">")
67	// finished
68	st := testState(100, 100, 10, false, true)
69	st.finished = true
70	testElementBarString(t, st, ElementBar, "[--------]")
71	// empty color
72	st = testState(100, 50, 10, false, true)
73	st.Set(Terminal, true)
74	color.NoColor = false
75	testElementBarString(t, st, ElementBar, " --->____]", color.RedString("%s", ""))
76	// empty
77	testElementBarString(t, testState(0, 50, 10, false, true), ElementBar, "[________]")
78	// full
79	testElementBarString(t, testState(20, 20, 10, false, true), ElementBar, "[------->]")
80	// everflow
81	testElementBarString(t, testState(20, 50, 10, false, true), ElementBar, "[------->]")
82	// small width
83	testElementBarString(t, testState(20, 50, 2, false, true), ElementBar, "[]")
84	testElementBarString(t, testState(20, 50, 1, false, true), ElementBar, "[")
85	// negative counters
86	testElementBarString(t, testState(-50, -150, 10, false, true), ElementBar, "[------->]")
87	testElementBarString(t, testState(-150, -50, 10, false, true), ElementBar, "[-->_____]")
88	testElementBarString(t, testState(50, -150, 10, false, true), ElementBar, "[------->]")
89	testElementBarString(t, testState(-50, 150, 10, false, true), ElementBar, "[------->]")
90	// long entities / unicode
91	f1 := []string{"進捗|", "многобайт", "active", "пусто", "|end"}
92	testElementBarString(t, testState(100, 50, 1, false, true), ElementBar, " ", f1...)
93	testElementBarString(t, testState(100, 50, 3, false, true), ElementBar, "進 ", f1...)
94	testElementBarString(t, testState(100, 50, 4, false, true), ElementBar, "進捗", f1...)
95	testElementBarString(t, testState(100, 50, 29, false, true), ElementBar, "進捗|многactiveпустопусто|end", f1...)
96	testElementBarString(t, testState(100, 50, 11, false, true), ElementBar, "進捗|aп|end", f1...)
97
98	// unicode
99	f2 := []string{"⚑", ".", ">", "⟞", "⚐"}
100	testElementBarString(t, testState(100, 50, 8, false, true), ElementBar, "⚑..>⟞⟞⟞⚐", f2...)
101
102	// no adaptive
103	testElementBarString(t, testState(0, 50, 10), ElementBar, "[____________________________]")
104
105	var formats = [][]string{
106		[]string{},
107		f1, f2,
108	}
109
110	// all widths / extreme values
111	// check for panic and correct width
112	for _, f := range formats {
113		for tt := int64(-2); tt < 12; tt++ {
114			for v := int64(-2); v < 12; v++ {
115				state := testState(tt, v, 0, false, true)
116				for w := -2; w < 20; w++ {
117					state.adaptiveElWidth = w
118					res := ElementBar(state, f...)
119					var we = w
120					if we <= 0 {
121						we = 30
122					}
123					if CellCount(res) != we {
124						t.Errorf("Unexpected len(%d): '%s'", we, res)
125					}
126				}
127			}
128		}
129	}
130}
131
132func TestElementSpeed(t *testing.T) {
133	var state = testState(1000, 0, 0, false)
134	state.time = time.Now()
135	for i := int64(0); i < 10; i++ {
136		state.id = uint64(i) + 1
137		state.current += 42
138		state.time = state.time.Add(time.Second)
139		state.finished = i == 9
140		if state.finished {
141			state.current += 100
142		}
143		r := ElementSpeed(state)
144		r2 := ElementSpeed(state)
145		if r != r2 {
146			t.Errorf("Must be the same: '%s' vs '%s'", r, r2)
147		}
148		if i < 1 {
149			// do not calc first result
150			if w := "? p/s"; r != w {
151				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
152			}
153		} else if state.finished {
154			if w := "58 p/s"; r != w {
155				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
156			}
157			state.time = state.time.Add(-time.Hour)
158			r = ElementSpeed(state)
159			if w := "? p/s"; r != w {
160				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
161			}
162		} else {
163			if w := "42 p/s"; r != w {
164				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
165			}
166		}
167	}
168}
169
170func TestElementRemainingTime(t *testing.T) {
171	var state = testState(100, 0, 0, false)
172	state.time = time.Now()
173	state.startTime = state.time
174	for i := int64(0); i < 10; i++ {
175		state.id = uint64(i) + 1
176		state.time = state.time.Add(time.Second)
177		state.finished = i == 9
178		r := ElementRemainingTime(state)
179		if i < 1 {
180			// do not calc first two results
181			if w := "?"; r != w {
182				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
183			}
184		} else if state.finished {
185			// final elapsed time
186			if w := "10s"; r != w {
187				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
188			}
189		} else {
190			w := fmt.Sprintf("%ds", 10-i)
191			if r != w {
192				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
193			}
194		}
195		state.current += 10
196	}
197}
198
199func TestElementElapsedTime(t *testing.T) {
200	t.Run("default behavior", func(t *testing.T) {
201		var state = testState(1000, 0, 0, false)
202		state.startTime = time.Now()
203		state.time = state.startTime
204		for i := int64(0); i <= 12; i++ {
205			r := ElementElapsedTime(state)
206			w := fmt.Sprintf("%d.0s", i)
207			if i == 0 || i >= 10 {
208				w = fmt.Sprintf("%ds", i)
209			}
210			if r != w {
211				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
212			}
213			state.time = state.time.Add(time.Second)
214		}
215	})
216	t.Run("with round set", func(t *testing.T) {
217		var state = testState(1000, 0, 0, false)
218		state.Set(TimeRound, time.Second)
219		state.startTime = time.Now()
220		state.time = state.startTime
221		for i := int64(0); i <= 10; i++ {
222			r := ElementElapsedTime(state)
223			w := fmt.Sprintf("%ds", i)
224			if r != w {
225				t.Errorf("Unexpected result[%d]: '%s' vs '%s'", i, r, w)
226			}
227			state.time = state.time.Add(time.Second)
228		}
229	})
230}
231
232func TestElementString(t *testing.T) {
233	var state = testState(0, 0, 0, false)
234	testElementBarString(t, state, ElementString, "", "myKey")
235	state.Set("myKey", "my value")
236	testElementBarString(t, state, ElementString, "my value", "myKey")
237	state.Set("myKey", "my value1")
238	testElementBarString(t, state, ElementString, "my value1", "myKey")
239	testElementBarString(t, state, ElementString, "")
240}
241
242func TestElementCycle(t *testing.T) {
243	var state = testState(0, 0, 0, false)
244	testElementBarString(t, state, ElementCycle, "")
245	testElementBarString(t, state, ElementCycle, "1", "1", "2", "3")
246	testElementBarString(t, state, ElementCycle, "2", "1", "2", "3")
247	testElementBarString(t, state, ElementCycle, "3", "1", "2", "3")
248	testElementBarString(t, state, ElementCycle, "1", "1", "2", "3")
249	testElementBarString(t, state, ElementCycle, "2", "1", "2")
250	testElementBarString(t, state, ElementCycle, "1", "1", "2")
251}
252
253func TestAdaptiveWrap(t *testing.T) {
254	var state = testState(0, 0, 0, false)
255	state.id = 1
256	state.Set("myKey", "my value")
257	el := adaptiveWrap(ElementString)
258	testElementBarString(t, state, el, adElPlaceholder, "myKey")
259	if v := state.recalc[0].ProgressElement(state); v != "my value" {
260		t.Errorf("Unexpected result: %s", v)
261	}
262	state.id = 2
263	testElementBarString(t, state, el, adElPlaceholder, "myKey1")
264	state.Set("myKey", "my value1")
265	if v := state.recalc[0].ProgressElement(state); v != "my value1" {
266		t.Errorf("Unexpected result: %s", v)
267	}
268}
269
270func TestRegisterElement(t *testing.T) {
271	var testEl ElementFunc = func(state *State, args ...string) string {
272		return strings.Repeat("*", state.AdaptiveElWidth())
273	}
274	RegisterElement("testEl", testEl, true)
275	result := ProgressBarTemplate(`{{testEl . }}`).New(0).SetWidth(5).String()
276	if result != "*****" {
277		t.Errorf("Unexpected result: '%v'", result)
278	}
279}
280
281func BenchmarkBar(b *testing.B) {
282	var formats = map[string][]string{
283		"simple":      []string{".", ".", ".", ".", "."},
284		"unicode":     []string{"⚑", "⚒", "⚟", "⟞", "⚐"},
285		"color":       []string{color.RedString("%s", "."), color.RedString("%s", "."), color.RedString("%s", "."), color.RedString("%s", "."), color.RedString("%s", ".")},
286		"long":        []string{"..", "..", "..", "..", ".."},
287		"longunicode": []string{"⚑⚑", "⚒⚒", "⚟⚟", "⟞⟞", "⚐⚐"},
288	}
289	for name, args := range formats {
290		state := testState(100, 50, 100, false, true)
291		b.Run(name, func(b *testing.B) {
292			b.ReportAllocs()
293			for i := 0; i < b.N; i++ {
294				ElementBar(state, args...)
295			}
296		})
297	}
298}
299