You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

194 lines
4.7 KiB

package queue
import (
"context"
"testing"
"time"
)
func TestNew(t *testing.T) {
tests := []struct {
desc string
opts Options
want error
}{
{"empty options", Options{}, errHardLimit},
{"zero limit negative quota", Options{SoftQuota: -1}, errHardLimit},
{"zero limit and quota", Options{SoftQuota: 0}, errHardLimit},
{"zero limit", Options{SoftQuota: 1, HardLimit: 0}, errHardLimit},
{"limit less than quota", Options{SoftQuota: 5, HardLimit: 3}, errHardLimit},
{"negative credit", Options{SoftQuota: 1, HardLimit: 1, BurstCredit: -6}, errBurstCredit},
{"valid default credit", Options{SoftQuota: 1, HardLimit: 2, BurstCredit: 0}, nil},
{"valid explicit credit", Options{SoftQuota: 1, HardLimit: 5, BurstCredit: 10}, nil},
}
for _, test := range tests {
t.Run(test.desc, func(t *testing.T) {
got, err := New(test.opts)
if err != test.want {
t.Errorf("New(%+v): got (%+v, %v), want err=%v", test.opts, got, err, test.want)
}
})
}
}
type testQueue struct {
t *testing.T
*Queue
}
func (q testQueue) mustAdd(item string) {
q.t.Helper()
if err := q.Add(item); err != nil {
q.t.Errorf("Add(%q): unexpected error: %v", item, err)
}
}
func (q testQueue) mustRemove(want string) {
q.t.Helper()
got, ok := q.Remove()
if !ok {
q.t.Error("Remove: queue is empty")
} else if got.(string) != want {
q.t.Errorf("Remove: got %q, want %q", got, want)
}
}
func mustQueue(t *testing.T, opts Options) testQueue {
t.Helper()
q, err := New(opts)
if err != nil {
t.Fatalf("New(%+v): unexpected error: %v", opts, err)
}
return testQueue{t: t, Queue: q}
}
func TestHardLimit(t *testing.T) {
q := mustQueue(t, Options{SoftQuota: 1, HardLimit: 1})
q.mustAdd("foo")
if err := q.Add("bar"); err != ErrQueueFull {
t.Errorf("Add: got err=%v, want %v", err, ErrQueueFull)
}
}
func TestSoftQuota(t *testing.T) {
q := mustQueue(t, Options{SoftQuota: 1, HardLimit: 4})
q.mustAdd("foo")
q.mustAdd("bar")
if err := q.Add("baz"); err != ErrNoCredit {
t.Errorf("Add: got err=%v, want %v", err, ErrNoCredit)
}
}
func TestBurstCredit(t *testing.T) {
q := mustQueue(t, Options{SoftQuota: 2, HardLimit: 5})
q.mustAdd("foo")
q.mustAdd("bar")
// We should still have all our initial credit.
if q.credit < 2 {
t.Errorf("Wrong credit: got %f, want ≥ 2", q.credit)
}
// Removing an item below soft quota should increase our credit.
q.mustRemove("foo")
if q.credit <= 2 {
t.Errorf("wrong credit: got %f, want > 2", q.credit)
}
// Credit should be capped by the hard limit.
q.mustRemove("bar")
q.mustAdd("baz")
q.mustRemove("baz")
if cap := float64(q.hardLimit - q.softQuota); q.credit > cap {
t.Errorf("Wrong credit: got %f, want ≤ %f", q.credit, cap)
}
}
func TestClose(t *testing.T) {
q := mustQueue(t, Options{SoftQuota: 2, HardLimit: 10})
q.mustAdd("alpha")
q.mustAdd("bravo")
q.mustAdd("charlie")
q.Close()
// After closing the queue, subsequent writes should fail.
if err := q.Add("foxtrot"); err == nil {
t.Error("Add should have failed after Close")
}
// However, the remaining contents of the queue should still work.
q.mustRemove("alpha")
q.mustRemove("bravo")
q.mustRemove("charlie")
}
func TestWait(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
q := mustQueue(t, Options{SoftQuota: 2, HardLimit: 2})
// A wait on an empty queue should time out.
t.Run("WaitTimeout", func(t *testing.T) {
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
got, err := q.Wait(ctx)
if err == nil {
t.Errorf("Wait: got %v, want error", got)
} else {
t.Logf("Wait correctly failed: %v", err)
}
})
// A wait on a non-empty queue should report an item.
t.Run("WaitNonEmpty", func(t *testing.T) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
const input = "figgy pudding"
q.mustAdd(input)
got, err := q.Wait(ctx)
if err != nil {
t.Errorf("Wait: unexpected error: %v", err)
} else if got != input {
t.Errorf("Wait: got %q, want %q", got, input)
}
})
// Wait should block until an item arrives.
t.Run("WaitOnEmpty", func(t *testing.T) {
const input = "fleet footed kittens"
done := make(chan struct{})
go func() {
defer close(done)
got, err := q.Wait(ctx)
if err != nil {
t.Errorf("Wait: unexpected error: %w", err)
} else if got != input {
t.Errorf("Wait: got %q, want %q", got, input)
}
}()
q.mustAdd(input)
<-done
})
// Closing the queue unblocks a wait.
t.Run("UnblockOnClose", func(t *testing.T) {
done := make(chan struct{})
go func() {
defer close(done)
got, err := q.Wait(ctx)
if err != ErrQueueClosed {
t.Errorf("Wait: got (%v, %v), want %v", got, err, ErrQueueClosed)
}
}()
q.Close()
<-done
})
}