Files
talk/internal/queue/laws_test.go
T

114 lines
4.0 KiB
Go
Raw Normal View History

package queue
import (
"strings"
"testing"
"time"
)
// TestTruncateText: cut to max runes at a word boundary (not in the first
// half), without dangling punctuation; short texts and max 0 stay untouched;
// runes count, not bytes.
func TestTruncateText(t *testing.T) {
cases := []struct {
name string
in string
max int
want string
truncated bool
}{
{"short stays", "Build done", 400, "Build done", false},
{"exactly max stays", "abcde", 5, "abcde", false},
{"unlimited", strings.Repeat("x", 1000), 0, strings.Repeat("x", 1000), false},
{"word boundary", "The build is done and ready", 14, "The build is", true},
{"punctuation dropped", "Done, but then there was more", 6, "Done", true},
{"no boundary in second half → hard cut", "abcdefghijklmnop qrs", 10, "abcdefghij", true},
{"umlauts count as one rune", "Übergröße ärgert", 9, "Übergröße", true},
}
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
got, tr := truncateText(c.in, c.max)
if got != c.want || tr != c.truncated {
t.Errorf("truncateText(%q, %d) = %q/%v, want %q/%v", c.in, c.max, got, tr, c.want, c.truncated)
}
if c.max > 0 && len([]rune(got)) > c.max {
t.Errorf("result %q longer than %d runes", got, c.max)
}
})
}
}
// TestEnqueueTruncatesText: the MaxText law applies on admission; the
// shortened version is spoken and Admission reports Truncated.
func TestEnqueueTruncatesText(t *testing.T) {
g := newSpeaker()
q := New(Laws{MaxText: 20}, g.speak, nil)
defer q.Close()
adm, err := q.Enqueue(Item{Text: "This text is clearly longer than twenty characters"})
if err != nil || adm.ID == "" || !adm.Truncated {
t.Fatalf("Enqueue = %+v, %v; want admitted + Truncated", adm, err)
}
items := g.waitUntil(t, 1)
if items[0].Text != "This text is clearly" {
t.Errorf("spoken = %q, want the shortened version", items[0].Text)
}
g.step <- struct{}{}
adm, _ = q.Enqueue(Item{Text: "short"})
if adm.Truncated {
t.Error("short text reported as truncated")
}
g.waitUntil(t, 2)
g.step <- struct{}{}
}
// TestDecideExpired: an item that waited longer than MaxAge is discarded,
// before quiet hours and rate limit but after now (now breaks out).
func TestDecideExpired(t *testing.T) {
laws := Laws{MaxAge: time.Minute, RateLimit: time.Hour}
now := time.Now()
old := Item{Text: "old", Priority: PriorityNormal, enqueued: now.Add(-2 * time.Minute)}
fresh := Item{Text: "fresh", Priority: PriorityNormal, enqueued: now.Add(-10 * time.Second)}
if v := laws.decide(old, now, true); v != verdictDiscardExpired {
t.Errorf("old item with open slot = %v, want verdictDiscardExpired", v)
}
if v := laws.decide(fresh, now, true); v != verdictPlay {
t.Errorf("fresh item with open slot = %v, want verdictPlay", v)
}
if v := laws.decide(fresh, now, false); v != verdictWait {
t.Errorf("fresh item without slot = %v, want verdictWait", v)
}
oldNow := old
oldNow.Now = true
if v := laws.decide(oldNow, now, false); v != verdictPlay {
t.Errorf("old now item = %v, want verdictPlay (now breaks out)", v)
}
if v := (Laws{}).decide(old, now, true); v != verdictPlay {
t.Errorf("MaxAge 0 (unlimited) discards old item: %v", v)
}
}
// TestParkedItemExpires: a normal item parks for the rate-limit slot; if it
// is older than MaxAge when it comes due, it is discarded (log names
// max_age) and never spoken.
func TestParkedItemExpires(t *testing.T) {
logb := &syncLog{}
g := newSpeaker()
q := New(Laws{RateLimit: 300 * time.Millisecond, MaxAge: 150 * time.Millisecond}, g.speak, logb)
defer q.Close()
q.Enqueue(Item{Text: "first"}) // books the window
g.waitUntil(t, 1)
g.step <- struct{}{}
adm, _ := q.Enqueue(Item{Text: "parks too long"}) // parks ~300 ms > MaxAge
if adm.ID == "" || adm.Discarded != "" {
t.Fatalf("nothing may expire on admission: %+v", adm)
}
waitFor(t, func() bool { return strings.Contains(logb.String(), "max_age") })
g.noFurther(t)
if !strings.Contains(logb.String(), adm.ID) {
t.Errorf("log does not name the expired announcement:\n%s", logb.String())
}
}