mirror of
https://github.com/lunchcat/sif.git
synced 2026-06-12 11:01:24 -07:00
Merge pull request #115 from vmfunc/fix/progress-milestones
fix(output): dedupe non-tty progress milestones
This commit is contained in:
@@ -28,12 +28,13 @@ const (
|
||||
|
||||
// Progress displays a progress bar for operations with known counts
|
||||
type Progress struct {
|
||||
total int64
|
||||
current int64
|
||||
message string
|
||||
lastItem string
|
||||
mu sync.Mutex
|
||||
paused bool
|
||||
total int64
|
||||
current int64
|
||||
message string
|
||||
lastItem string
|
||||
mu sync.Mutex
|
||||
paused bool
|
||||
lastShown int // last printed milestone bucket in non-tty mode
|
||||
}
|
||||
|
||||
// NewProgress creates a new progress bar
|
||||
@@ -110,8 +111,30 @@ func (p *Progress) render() {
|
||||
}
|
||||
percent := int(current * 100 / total)
|
||||
|
||||
// Print at 0%, 25%, 50%, 75%, 100%
|
||||
if current == 1 || percent == 25 || percent == 50 || percent == 75 || current == total {
|
||||
// map current to a milestone bucket (0=none,1..5). concurrent workers
|
||||
// hammer the same bucket, so only print when the bucket advances.
|
||||
bucket := 0
|
||||
switch {
|
||||
case current >= total:
|
||||
bucket = 5
|
||||
case percent >= 75:
|
||||
bucket = 4
|
||||
case percent >= 50:
|
||||
bucket = 3
|
||||
case percent >= 25:
|
||||
bucket = 2
|
||||
case current >= 1:
|
||||
bucket = 1
|
||||
}
|
||||
|
||||
p.mu.Lock()
|
||||
advanced := bucket > p.lastShown
|
||||
if advanced {
|
||||
p.lastShown = bucket
|
||||
}
|
||||
p.mu.Unlock()
|
||||
|
||||
if advanced {
|
||||
fmt.Printf(" [%d%%] %d/%d\n", percent, current, total)
|
||||
}
|
||||
return
|
||||
|
||||
@@ -12,7 +12,12 @@
|
||||
|
||||
package output
|
||||
|
||||
import "testing"
|
||||
import (
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// the non-tty milestone path divides current*100/total, so a zero-total bar
|
||||
// used to panic with integer divide-by-zero when piped or redirected.
|
||||
@@ -32,3 +37,60 @@ func TestProgressCounts(t *testing.T) {
|
||||
t.Errorf("current = %d, want 4", p.current)
|
||||
}
|
||||
}
|
||||
|
||||
// many concurrent workers used to spam the same milestone bucket (e.g. ten
|
||||
// "[25%] .../1000" lines). each bucket must now print at most once.
|
||||
func TestProgressNonTTYDedupesMilestones(t *testing.T) {
|
||||
savedTTY, savedAPI := IsTTY, apiMode
|
||||
IsTTY, apiMode = false, false
|
||||
defer func() { IsTTY, apiMode = savedTTY, savedAPI }()
|
||||
|
||||
out := captureStdout(t, func() {
|
||||
p := NewProgress(1000, "scanning")
|
||||
var wg sync.WaitGroup
|
||||
for i := 0; i < 40; i++ {
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for j := 0; j < 25; j++ {
|
||||
p.Increment("x")
|
||||
}
|
||||
}()
|
||||
}
|
||||
wg.Wait()
|
||||
})
|
||||
|
||||
lines := strings.Count(out, "\n")
|
||||
if lines > 5 {
|
||||
t.Errorf("printed %d milestone lines, want <=5:\n%s", lines, out)
|
||||
}
|
||||
}
|
||||
|
||||
func captureStdout(t *testing.T, fn func()) string {
|
||||
t.Helper()
|
||||
r, w, err := os.Pipe()
|
||||
if err != nil {
|
||||
t.Fatalf("pipe: %v", err)
|
||||
}
|
||||
saved := os.Stdout
|
||||
os.Stdout = w
|
||||
|
||||
done := make(chan string, 1)
|
||||
go func() {
|
||||
buf := make([]byte, 0, 4096)
|
||||
tmp := make([]byte, 1024)
|
||||
for {
|
||||
n, rerr := r.Read(tmp)
|
||||
buf = append(buf, tmp[:n]...)
|
||||
if rerr != nil {
|
||||
break
|
||||
}
|
||||
}
|
||||
done <- string(buf)
|
||||
}()
|
||||
|
||||
fn()
|
||||
os.Stdout = saved
|
||||
w.Close()
|
||||
return <-done
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user