Skip to content

Commit e0efbb4

Browse files
committed
progress: support rendering trackers that haven't started yet
1 parent 62b2484 commit e0efbb4

File tree

6 files changed

+141
-111
lines changed

6 files changed

+141
-111
lines changed

cmd/demo-progress/demo.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var (
2323
flagShowSpeedOverall = flag.Bool("show-speed-overall", false, "Show the overall tracker speed?")
2424
flagShowPinned = flag.Bool("show-pinned", false, "Show a pinned message?")
2525
flagRandomFail = flag.Bool("rnd-fail", false, "Introduce random failures in tracking")
26+
flagRandomDefer = flag.Bool("rnd-defer", false, "Introduce random deferred starts")
2627
flagRandomLogs = flag.Bool("rnd-logs", false, "Output random logs in the middle of tracking")
2728

2829
messageColors = []text.Color{
@@ -71,13 +72,18 @@ func trackSomething(pw progress.Writer, idx int64, updateMessage bool) {
7172

7273
units := getUnits(idx)
7374
message := getMessage(idx, units)
74-
tracker := progress.Tracker{Message: message, Total: total, Units: *units}
75+
tracker := progress.Tracker{Message: message, Total: total, Units: *units, DeferStart: *flagRandomDefer && rand.Float64() < 0.5}
7576
if idx == int64(*flagNumTrackers) {
7677
tracker.Total = 0
7778
}
7879

7980
pw.AppendTracker(&tracker)
8081

82+
if tracker.DeferStart {
83+
time.Sleep(3 * time.Second)
84+
tracker.Start()
85+
}
86+
8187
ticker := time.Tick(time.Millisecond * 500)
8288
updateTicker := time.Tick(time.Millisecond * 250)
8389
for !tracker.IsDone() {

progress/progress.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,9 @@ const (
6767
// to a queue, which gets picked up by the Render logic in the next rendering
6868
// cycle.
6969
func (p *Progress) AppendTracker(t *Tracker) {
70-
t.start()
70+
if !t.DeferStart {
71+
t.start()
72+
}
7173
p.overallTrackerMutex.Lock()
7274
defer p.overallTrackerMutex.Unlock()
7375

progress/render.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ func (p *Progress) extractDoneAndActiveTrackers() ([]*Tracker, []*Tracker) {
9393

9494
func (p *Progress) generateTrackerStr(t *Tracker, maxLen int, hint renderHint) string {
9595
value, total := t.valueAndTotal()
96-
if !hint.isOverallTracker && (total == 0 || value > total) {
96+
if !hint.isOverallTracker && t.IsStarted() && (total == 0 || value > total) {
9797
return p.generateTrackerStrIndeterminate(maxLen)
9898
}
9999
return p.generateTrackerStrDeterminate(value, total, maxLen)
@@ -382,14 +382,16 @@ func (p *Progress) renderTrackerStatsSpeed(out *strings.Builder, t *Tracker, hin
382382

383383
p.trackersActiveMutex.RLock()
384384
for _, tracker := range p.trackersActive {
385-
speed += float64(tracker.Value()) / time.Since(tracker.timeStart).Round(speedPrecision).Seconds()
385+
if !tracker.timeStart.IsZero() {
386+
speed += float64(tracker.Value()) / time.Since(tracker.timeStart).Round(speedPrecision).Seconds()
387+
}
386388
}
387389
p.trackersActiveMutex.RUnlock()
388390

389391
if speed > 0 {
390392
p.renderTrackerStatsSpeedInternal(out, p.style.Options.SpeedOverallFormatter(int64(speed)))
391393
}
392-
} else {
394+
} else if !t.timeStart.IsZero() {
393395
timeTaken := time.Since(t.timeStart)
394396
if timeTakenRounded := timeTaken.Round(speedPrecision); timeTakenRounded > speedPrecision {
395397
p.renderTrackerStatsSpeedInternal(out, t.Units.Sprint(int64(float64(t.Value())/timeTakenRounded.Seconds())))
@@ -412,7 +414,7 @@ func (p *Progress) renderTrackerStatsTime(outStats *strings.Builder, t *Tracker,
412414
var td, tp time.Duration
413415
if t.IsDone() {
414416
td = t.timeStop.Sub(t.timeStart)
415-
} else {
417+
} else if !t.timeStart.IsZero() {
416418
td = time.Since(t.timeStart)
417419
}
418420
if hint.isOverallTracker {

progress/render_test.go

Lines changed: 74 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,28 @@ func trackSomething(pw Writer, tracker *Tracker) {
6666
}
6767
}
6868

69+
func trackSomethingDeferred(pw Writer, tracker *Tracker) {
70+
incrementPerCycle := tracker.Total / 3
71+
tracker.DeferStart = true
72+
73+
pw.AppendTracker(tracker)
74+
skip := true
75+
76+
c := time.Tick(trackerIncrementInterval)
77+
for !tracker.IsDone() {
78+
select {
79+
case <-c:
80+
if skip {
81+
skip = false
82+
} else if tracker.value+incrementPerCycle > tracker.Total {
83+
tracker.Increment(tracker.Total - tracker.value)
84+
} else {
85+
tracker.Increment(incrementPerCycle)
86+
}
87+
}
88+
}
89+
}
90+
6991
func trackSomethingErrored(pw Writer, tracker *Tracker) {
7092
incrementPerCycle := tracker.Total / 3
7193
total := tracker.Total
@@ -279,118 +301,37 @@ func TestProgress_generateTrackerStr_Indeterminate(t *testing.T) {
279301
}
280302

281303
expectedTrackerStrMap := map[int64]string{
282-
0: "<=>.......",
283-
1: ".<=>......",
284-
2: "..<=>.....",
285-
3: "...<=>....",
286-
4: "....<=>...",
287-
5: ".....<=>..",
288-
6: "......<=>.",
289-
7: ".......<=>",
290-
8: "......<=>.",
291-
9: ".....<=>..",
292-
10: "....<=>...",
293-
11: "...<=>....",
294-
12: "..<=>.....",
295-
13: ".<=>......",
296-
14: "<=>.......",
297-
15: ".<=>......",
298-
16: "..<=>.....",
299-
17: "...<=>....",
300-
18: "....<=>...",
301-
19: ".....<=>..",
302-
20: "......<=>.",
303-
21: ".......<=>",
304-
22: "......<=>.",
305-
23: ".....<=>..",
306-
24: "....<=>...",
307-
25: "...<=>....",
308-
26: "..<=>.....",
309-
27: ".<=>......",
310-
28: "<=>.......",
311-
29: ".<=>......",
312-
30: "..<=>.....",
313-
31: "...<=>....",
314-
32: "....<=>...",
315-
33: ".....<=>..",
316-
34: "......<=>.",
317-
35: ".......<=>",
318-
36: "......<=>.",
319-
37: ".....<=>..",
320-
38: "....<=>...",
321-
39: "...<=>....",
322-
40: "..<=>.....",
323-
41: ".<=>......",
324-
42: "<=>.......",
325-
43: ".<=>......",
326-
44: "..<=>.....",
327-
45: "...<=>....",
328-
46: "....<=>...",
329-
47: ".....<=>..",
330-
48: "......<=>.",
331-
49: ".......<=>",
332-
50: "......<=>.",
333-
51: ".....<=>..",
334-
52: "....<=>...",
335-
53: "...<=>....",
336-
54: "..<=>.....",
337-
55: ".<=>......",
338-
56: "<=>.......",
339-
57: ".<=>......",
340-
58: "..<=>.....",
341-
59: "...<=>....",
342-
60: "....<=>...",
343-
61: ".....<=>..",
344-
62: "......<=>.",
345-
63: ".......<=>",
346-
64: "......<=>.",
347-
65: ".....<=>..",
348-
66: "....<=>...",
349-
67: "...<=>....",
350-
68: "..<=>.....",
351-
69: ".<=>......",
352-
70: "<=>.......",
353-
71: ".<=>......",
354-
72: "..<=>.....",
355-
73: "...<=>....",
356-
74: "....<=>...",
357-
75: ".....<=>..",
358-
76: "......<=>.",
359-
77: ".......<=>",
360-
78: "......<=>.",
361-
79: ".....<=>..",
362-
80: "....<=>...",
363-
81: "...<=>....",
364-
82: "..<=>.....",
365-
83: ".<=>......",
366-
84: "<=>.......",
367-
85: ".<=>......",
368-
86: "..<=>.....",
369-
87: "...<=>....",
370-
88: "....<=>...",
371-
89: ".....<=>..",
372-
90: "......<=>.",
373-
91: ".......<=>",
374-
92: "......<=>.",
375-
93: ".....<=>..",
376-
94: "....<=>...",
377-
95: "...<=>....",
378-
96: "..<=>.....",
379-
97: ".<=>......",
380-
98: "<=>.......",
381-
99: ".<=>......",
382-
100: "..<=>.....",
304+
-1: "..........",
305+
0: "<=>.......",
306+
1: ".<=>......",
307+
2: "..<=>.....",
308+
3: "...<=>....",
309+
4: "....<=>...",
310+
5: ".....<=>..",
311+
6: "......<=>.",
312+
7: ".......<=>",
313+
8: "......<=>.",
314+
9: ".....<=>..",
315+
10: "....<=>...",
316+
11: "...<=>....",
317+
12: "..<=>.....",
318+
13: ".<=>......",
383319
}
384320

385321
finalOutput := strings.Builder{}
386322
tr := Tracker{Total: 0}
387-
for value := int64(0); value <= 100; value++ {
388-
tr.value = value
323+
for value := int64(-1); value <= 100; value++ {
324+
if value >= 0 {
325+
tr.value = value
326+
}
389327
actualStr := pw.generateTrackerStr(&tr, 10, renderHint{})
390-
if expectedStr, ok := expectedTrackerStrMap[value]; ok {
328+
if expectedStr, ok := expectedTrackerStrMap[value%14]; ok {
391329
assert.Equal(t, expectedStr, actualStr, "value=%d", value)
392330
}
393331
finalOutput.WriteString(fmt.Sprintf(" %d: \"%s\",\n", value, actualStr))
332+
if value < 0 {
333+
tr.timeStart = time.Now()
334+
}
394335
}
395336
if t.Failed() {
396337
fmt.Println(finalOutput.String())
@@ -496,6 +437,35 @@ func TestProgress_RenderSomeTrackers_WithAutoStop(t *testing.T) {
496437
showOutputOnFailure(t, out)
497438
}
498439

440+
func TestProgress_RenderSomeTrackers_DeferStart(t *testing.T) {
441+
renderOutput := outputWriter{}
442+
443+
pw := generateWriter()
444+
pw.Style().Visibility.Speed = true
445+
pw.SetOutputWriter(&renderOutput)
446+
go trackSomething(pw, &Tracker{Message: "Calculating Total # 1\r", Total: 1000, Units: UnitsDefault})
447+
go trackSomething(pw, &Tracker{Message: "Downloading File\t# 2", Total: 1000, Units: UnitsBytes})
448+
go trackSomethingDeferred(pw, &Tracker{Message: "Transferring Amount # 3", Total: 1000, Units: UnitsCurrencyDollar})
449+
renderAndWait(pw, false)
450+
451+
expectedOutPatterns := []*regexp.Regexp{
452+
regexp.MustCompile(`Transferring Amount # 3 \.\.\. +0.00% \[\.{23}] \[\$0 in 0s]`),
453+
regexp.MustCompile(`Calculating Total # 1 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+ in [\d.]+ms; \d+\.\d+\w+/s]`),
454+
regexp.MustCompile(`Downloading File # 2 \.\.\. \d+\.\d+% \[[#.]{23}] \[\d+B in [\d.]+ms; \d+\.\d+\w+/s]`),
455+
regexp.MustCompile(`Transferring Amount # 3 \.\.\. \d+\.\d+% \[[<#>.]{23}] \[\$\d+ in [\d.]+ms; \$\d+\.\d+\w+/s]`),
456+
regexp.MustCompile(`Calculating Total # 1 \.\.\. done! \[\d+\.\d+K in [\d.]+ms; \d+\.\d+\w+/s]`),
457+
regexp.MustCompile(`Downloading File # 2 \.\.\. done! \[\d+\.\d+KB in [\d.]+ms; \d+\.\d+\w+/s]`),
458+
regexp.MustCompile(`Transferring Amount # 3 \.\.\. done! \[\$\d+\.\d+K in [\d.]+ms; \$\d+\.\d+\w+/s]`),
459+
}
460+
out := renderOutput.String()
461+
for _, expectedOutPattern := range expectedOutPatterns {
462+
if !expectedOutPattern.MatchString(out) {
463+
assert.Fail(t, "Failed to find a pattern in the Output.", expectedOutPattern.String())
464+
}
465+
}
466+
showOutputOnFailure(t, out)
467+
}
468+
499469
func TestProgress_RenderSomeTrackers_WithError(t *testing.T) {
500470
renderOutput := outputWriter{}
501471

progress/tracker.go

Lines changed: 29 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ type Tracker struct {
2525
// Units defines the type of the "value" being tracked
2626
Units Units
2727

28+
DeferStart bool
29+
2830
done bool
2931
err bool
3032
mutex sync.RWMutex
@@ -39,6 +41,10 @@ func (t *Tracker) ETA() time.Duration {
3941
t.mutex.RLock()
4042
defer t.mutex.RUnlock()
4143

44+
if t.timeStart.IsZero() {
45+
return time.Duration(0)
46+
}
47+
4248
timeTaken := time.Since(t.timeStart)
4349
if t.ExpectedDuration > time.Duration(0) && t.ExpectedDuration > timeTaken {
4450
return t.ExpectedDuration - timeTaken
@@ -67,6 +73,15 @@ func (t *Tracker) IncrementWithError(value int64) {
6773
t.mutex.Unlock()
6874
}
6975

76+
// IsStarted true if the tracker has started, false when using DeferStart
77+
// prior to Start, Increment, IncrementWithError or SetValue being called.
78+
func (t *Tracker) IsStarted() bool {
79+
t.mutex.RLock()
80+
defer t.mutex.RUnlock()
81+
82+
return !t.timeStart.IsZero()
83+
}
84+
7085
// IsDone returns true if the tracker is done (value has reached the expected
7186
// Total set during initialization).
7287
func (t *Tracker) IsDone() bool {
@@ -191,22 +206,35 @@ func (t *Tracker) valueAndTotal() (int64, int64) {
191206

192207
func (t *Tracker) incrementWithoutLock(value int64) {
193208
if !t.done {
209+
if t.timeStart.IsZero() {
210+
t.startWithoutLock()
211+
}
194212
t.value += value
195213
if t.Total > 0 && t.value >= t.Total {
196214
t.stop()
197215
}
198216
}
199217
}
200218

219+
func (t *Tracker) Start() {
220+
if t.timeStart.IsZero() {
221+
t.start()
222+
}
223+
}
224+
201225
func (t *Tracker) start() {
202226
t.mutex.Lock()
227+
t.startWithoutLock()
228+
t.mutex.Unlock()
229+
}
230+
231+
func (t *Tracker) startWithoutLock() {
203232
if t.Total < 0 {
204233
t.Total = math.MaxInt64
205234
}
206235
t.done = false
207236
t.err = false
208237
t.timeStart = time.Now()
209-
t.mutex.Unlock()
210238
}
211239

212240
// this must be called with the mutex held with a write lock

progress/tracker_test.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,28 @@ func TestTracker_IncrementWithError(t *testing.T) {
6868
assert.True(t, tracker.IsDone())
6969
}
7070

71+
func TestTracker_IsStarted(t *testing.T) {
72+
tracker := Tracker{DeferStart: true}
73+
assert.False(t, tracker.IsStarted())
74+
tracker.Start()
75+
assert.True(t, tracker.IsStarted())
76+
77+
tracker = Tracker{DeferStart: true}
78+
assert.False(t, tracker.IsStarted())
79+
tracker.Increment(1)
80+
assert.True(t, tracker.IsStarted())
81+
82+
tracker = Tracker{DeferStart: true}
83+
assert.False(t, tracker.IsStarted())
84+
tracker.IncrementWithError(1)
85+
assert.True(t, tracker.IsStarted())
86+
87+
tracker = Tracker{DeferStart: true}
88+
assert.False(t, tracker.IsStarted())
89+
tracker.SetValue(1)
90+
assert.True(t, tracker.IsStarted())
91+
}
92+
7193
func TestTracker_IsDone(t *testing.T) {
7294
tracker := Tracker{Total: 10}
7395
assert.False(t, tracker.IsDone())

0 commit comments

Comments
 (0)