first commit
This commit is contained in:
135
progress/progress_test.go
Normal file
135
progress/progress_test.go
Normal file
@@ -0,0 +1,135 @@
|
||||
package progress
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/hanbok/got/download"
|
||||
)
|
||||
|
||||
// reporterAt builds a Reporter wired to a fixed stat snapshot for rendering tests.
|
||||
func newReporter(human bool) *Reporter {
|
||||
return &Reporter{human: human, width: 200, win: map[string]*speedWindow{}}
|
||||
}
|
||||
|
||||
// TestSlidingWindowSpeed feeds samples across a ~10s window and checks the rate
|
||||
// is the windowed delta over its real duration, not the last-tick delta.
|
||||
func TestSlidingWindowSpeed(t *testing.T) {
|
||||
r := newReporter(false)
|
||||
base := time.Unix(1000, 0)
|
||||
// Five one-second ticks, +1000 bytes each: window holds 4s of deltas.
|
||||
w := &speedWindow{}
|
||||
r.win["x"] = w
|
||||
for i := 0; i < 5; i++ {
|
||||
w.add(base.Add(time.Duration(i)*time.Second), int64(i*1000), 0)
|
||||
}
|
||||
s := download.Stat{ID: "x", Completed: 4000}
|
||||
dl, ul := r.rates(s)
|
||||
if dl != 1000 {
|
||||
t.Errorf("dl = %d, want 1000", dl)
|
||||
}
|
||||
if ul != 0 {
|
||||
t.Errorf("ul = %d, want 0", ul)
|
||||
}
|
||||
}
|
||||
|
||||
// TestWindowDropsStale ensures samples older than windowTime are evicted so the
|
||||
// baseline of the delta stays inside the window.
|
||||
func TestWindowDropsStale(t *testing.T) {
|
||||
w := &speedWindow{}
|
||||
base := time.Unix(0, 0)
|
||||
// One old sample, then a fresh one 20s later.
|
||||
w.add(base, 0, 0)
|
||||
w.add(base.Add(20*time.Second), 5000, 0)
|
||||
if len(w.samples) != 1 {
|
||||
t.Fatalf("len(samples) = %d, want 1 (stale dropped)", len(w.samples))
|
||||
}
|
||||
}
|
||||
|
||||
// TestSingleSampleZero: with only one sample there is no span, so speed is 0.
|
||||
func TestSingleSampleZero(t *testing.T) {
|
||||
r := newReporter(false)
|
||||
r.win["x"] = &speedWindow{}
|
||||
r.win["x"].add(time.Unix(0, 0), 1<<20, 0)
|
||||
if dl, _ := r.rates(download.Stat{ID: "x"}); dl != 0 {
|
||||
t.Errorf("dl = %d, want 0 for single sample", dl)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRatesKeyedByID confirms two downloads sharing a Name don't cross-subtract
|
||||
// because the window is keyed on Stat.ID.
|
||||
func TestRatesKeyedByID(t *testing.T) {
|
||||
r := newReporter(false)
|
||||
base := time.Unix(0, 0)
|
||||
for _, id := range []string{"a", "b"} {
|
||||
r.win[id] = &speedWindow{}
|
||||
r.win[id].add(base, 0, 0)
|
||||
}
|
||||
r.win["a"].add(base.Add(time.Second), 1000, 0)
|
||||
r.win["b"].add(base.Add(time.Second), 2000, 0)
|
||||
if dl, _ := r.rates(download.Stat{ID: "a", Name: "dup"}); dl != 1000 {
|
||||
t.Errorf("a dl = %d, want 1000", dl)
|
||||
}
|
||||
if dl, _ := r.rates(download.Stat{ID: "b", Name: "dup"}); dl != 2000 {
|
||||
t.Errorf("b dl = %d, want 2000", dl)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLineOneSeeding: while seeding, render SEED(ratio), drop DL:, keep UL:.
|
||||
func TestLineOneSeeding(t *testing.T) {
|
||||
r := newReporter(true)
|
||||
s := download.Stat{
|
||||
ID: "h", Name: "f", IsBT: true, Status: download.Seeding,
|
||||
Total: 1000, Completed: 1000, Uploaded: 1500, Conns: 3, Seeders: 2,
|
||||
}
|
||||
line := r.lineOne(s)
|
||||
if !strings.Contains(line, "SEED(1.5)") {
|
||||
t.Errorf("missing SEED(1.5): %q", line)
|
||||
}
|
||||
if strings.Contains(line, "DL:") {
|
||||
t.Errorf("DL: should be dropped while seeding: %q", line)
|
||||
}
|
||||
if !strings.Contains(line, "UL:") {
|
||||
t.Errorf("UL: should be kept while seeding: %q", line)
|
||||
}
|
||||
}
|
||||
|
||||
// TestLineOneCNSD: CN always shown; SD shown for torrents (even with 0 seeders),
|
||||
// absent for HTTP.
|
||||
func TestLineOneCNSD(t *testing.T) {
|
||||
r := newReporter(true)
|
||||
bt := r.lineOne(download.Stat{ID: "h", Name: "f", IsBT: true, Total: 10, Completed: 1, Conns: 4, Seeders: 0})
|
||||
if !strings.Contains(bt, "CN:4") || !strings.Contains(bt, "SD:0") {
|
||||
t.Errorf("torrent line missing CN/SD: %q", bt)
|
||||
}
|
||||
http := r.lineOne(download.Stat{ID: "h2", Name: "f", IsBT: false, Total: 10, Completed: 1, Conns: 1})
|
||||
if !strings.Contains(http, "CN:1") {
|
||||
t.Errorf("http line missing CN: %q", http)
|
||||
}
|
||||
if strings.Contains(http, "SD:") {
|
||||
t.Errorf("http line should not show SD: %q", http)
|
||||
}
|
||||
}
|
||||
|
||||
// TestShortNameRune ensures CJK names are truncated on runes, not bytes.
|
||||
func TestShortNameRune(t *testing.T) {
|
||||
name := strings.Repeat("あ", 30) // 30 runes, 90 bytes
|
||||
got := shortName(name)
|
||||
runes := []rune(got)
|
||||
if len(runes) != 20 {
|
||||
t.Errorf("shortName rune count = %d, want 20", len(runes))
|
||||
}
|
||||
if runes[len(runes)-1] != '~' {
|
||||
t.Errorf("expected trailing ~, got %q", got)
|
||||
}
|
||||
}
|
||||
|
||||
// TestClampRunes clamps on runes so a multibyte glyph is never split.
|
||||
func TestClampRunes(t *testing.T) {
|
||||
s := strings.Repeat("あ", 10) // 10 runes
|
||||
got := clampRunes(s, 5)
|
||||
if r := []rune(got); len(r) != 5 {
|
||||
t.Errorf("clampRunes rune count = %d, want 5", len(r))
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user