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

  1. package queue
  2. import (
  3. "context"
  4. "testing"
  5. "time"
  6. )
  7. func TestNew(t *testing.T) {
  8. tests := []struct {
  9. desc string
  10. opts Options
  11. want error
  12. }{
  13. {"empty options", Options{}, errHardLimit},
  14. {"zero limit negative quota", Options{SoftQuota: -1}, errHardLimit},
  15. {"zero limit and quota", Options{SoftQuota: 0}, errHardLimit},
  16. {"zero limit", Options{SoftQuota: 1, HardLimit: 0}, errHardLimit},
  17. {"limit less than quota", Options{SoftQuota: 5, HardLimit: 3}, errHardLimit},
  18. {"negative credit", Options{SoftQuota: 1, HardLimit: 1, BurstCredit: -6}, errBurstCredit},
  19. {"valid default credit", Options{SoftQuota: 1, HardLimit: 2, BurstCredit: 0}, nil},
  20. {"valid explicit credit", Options{SoftQuota: 1, HardLimit: 5, BurstCredit: 10}, nil},
  21. }
  22. for _, test := range tests {
  23. t.Run(test.desc, func(t *testing.T) {
  24. got, err := New(test.opts)
  25. if err != test.want {
  26. t.Errorf("New(%+v): got (%+v, %v), want err=%v", test.opts, got, err, test.want)
  27. }
  28. })
  29. }
  30. }
  31. type testQueue struct {
  32. t *testing.T
  33. *Queue
  34. }
  35. func (q testQueue) mustAdd(item string) {
  36. q.t.Helper()
  37. if err := q.Add(item); err != nil {
  38. q.t.Errorf("Add(%q): unexpected error: %v", item, err)
  39. }
  40. }
  41. func (q testQueue) mustRemove(want string) {
  42. q.t.Helper()
  43. got, ok := q.Remove()
  44. if !ok {
  45. q.t.Error("Remove: queue is empty")
  46. } else if got.(string) != want {
  47. q.t.Errorf("Remove: got %q, want %q", got, want)
  48. }
  49. }
  50. func mustQueue(t *testing.T, opts Options) testQueue {
  51. t.Helper()
  52. q, err := New(opts)
  53. if err != nil {
  54. t.Fatalf("New(%+v): unexpected error: %v", opts, err)
  55. }
  56. return testQueue{t: t, Queue: q}
  57. }
  58. func TestHardLimit(t *testing.T) {
  59. q := mustQueue(t, Options{SoftQuota: 1, HardLimit: 1})
  60. q.mustAdd("foo")
  61. if err := q.Add("bar"); err != ErrQueueFull {
  62. t.Errorf("Add: got err=%v, want %v", err, ErrQueueFull)
  63. }
  64. }
  65. func TestSoftQuota(t *testing.T) {
  66. q := mustQueue(t, Options{SoftQuota: 1, HardLimit: 4})
  67. q.mustAdd("foo")
  68. q.mustAdd("bar")
  69. if err := q.Add("baz"); err != ErrNoCredit {
  70. t.Errorf("Add: got err=%v, want %v", err, ErrNoCredit)
  71. }
  72. }
  73. func TestBurstCredit(t *testing.T) {
  74. q := mustQueue(t, Options{SoftQuota: 2, HardLimit: 5})
  75. q.mustAdd("foo")
  76. q.mustAdd("bar")
  77. // We should still have all our initial credit.
  78. if q.credit < 2 {
  79. t.Errorf("Wrong credit: got %f, want ≥ 2", q.credit)
  80. }
  81. // Removing an item below soft quota should increase our credit.
  82. q.mustRemove("foo")
  83. if q.credit <= 2 {
  84. t.Errorf("wrong credit: got %f, want > 2", q.credit)
  85. }
  86. // Credit should be capped by the hard limit.
  87. q.mustRemove("bar")
  88. q.mustAdd("baz")
  89. q.mustRemove("baz")
  90. if cap := float64(q.hardLimit - q.softQuota); q.credit > cap {
  91. t.Errorf("Wrong credit: got %f, want ≤ %f", q.credit, cap)
  92. }
  93. }
  94. func TestClose(t *testing.T) {
  95. q := mustQueue(t, Options{SoftQuota: 2, HardLimit: 10})
  96. q.mustAdd("alpha")
  97. q.mustAdd("bravo")
  98. q.mustAdd("charlie")
  99. q.Close()
  100. // After closing the queue, subsequent writes should fail.
  101. if err := q.Add("foxtrot"); err == nil {
  102. t.Error("Add should have failed after Close")
  103. }
  104. // However, the remaining contents of the queue should still work.
  105. q.mustRemove("alpha")
  106. q.mustRemove("bravo")
  107. q.mustRemove("charlie")
  108. }
  109. func TestWait(t *testing.T) {
  110. ctx, cancel := context.WithCancel(context.Background())
  111. defer cancel()
  112. q := mustQueue(t, Options{SoftQuota: 2, HardLimit: 2})
  113. // A wait on an empty queue should time out.
  114. t.Run("WaitTimeout", func(t *testing.T) {
  115. ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
  116. defer cancel()
  117. got, err := q.Wait(ctx)
  118. if err == nil {
  119. t.Errorf("Wait: got %v, want error", got)
  120. } else {
  121. t.Logf("Wait correctly failed: %v", err)
  122. }
  123. })
  124. // A wait on a non-empty queue should report an item.
  125. t.Run("WaitNonEmpty", func(t *testing.T) {
  126. ctx, cancel := context.WithCancel(context.Background())
  127. defer cancel()
  128. const input = "figgy pudding"
  129. q.mustAdd(input)
  130. got, err := q.Wait(ctx)
  131. if err != nil {
  132. t.Errorf("Wait: unexpected error: %v", err)
  133. } else if got != input {
  134. t.Errorf("Wait: got %q, want %q", got, input)
  135. }
  136. })
  137. // Wait should block until an item arrives.
  138. t.Run("WaitOnEmpty", func(t *testing.T) {
  139. const input = "fleet footed kittens"
  140. done := make(chan struct{})
  141. go func() {
  142. defer close(done)
  143. got, err := q.Wait(ctx)
  144. if err != nil {
  145. t.Errorf("Wait: unexpected error: %w", err)
  146. } else if got != input {
  147. t.Errorf("Wait: got %q, want %q", got, input)
  148. }
  149. }()
  150. q.mustAdd(input)
  151. <-done
  152. })
  153. // Closing the queue unblocks a wait.
  154. t.Run("UnblockOnClose", func(t *testing.T) {
  155. done := make(chan struct{})
  156. go func() {
  157. defer close(done)
  158. got, err := q.Wait(ctx)
  159. if err != ErrQueueClosed {
  160. t.Errorf("Wait: got (%v, %v), want %v", got, err, ErrQueueClosed)
  161. }
  162. }()
  163. q.Close()
  164. <-done
  165. })
  166. }