pool_test.go 3.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203
  1. // Copyright (c) Tailscale Inc & AUTHORS
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package pool
  4. import (
  5. "slices"
  6. "testing"
  7. )
  8. func TestPool(t *testing.T) {
  9. p := Pool[int]{}
  10. if got, want := p.Len(), 0; got != want {
  11. t.Errorf("got initial length %v; want %v", got, want)
  12. }
  13. h1 := p.Add(101)
  14. h2 := p.Add(102)
  15. h3 := p.Add(103)
  16. h4 := p.Add(104)
  17. if got, want := p.Len(), 4; got != want {
  18. t.Errorf("got length %v; want %v", got, want)
  19. }
  20. tests := []struct {
  21. h Handle[int]
  22. want int
  23. }{
  24. {h1, 101},
  25. {h2, 102},
  26. {h3, 103},
  27. {h4, 104},
  28. }
  29. for i, test := range tests {
  30. got, ok := p.Peek(test.h)
  31. if !ok {
  32. t.Errorf("test[%d]: did not find item", i)
  33. continue
  34. }
  35. if got != test.want {
  36. t.Errorf("test[%d]: got %v; want %v", i, got, test.want)
  37. }
  38. }
  39. if deleted := p.Delete(h2); !deleted {
  40. t.Errorf("h2 not deleted")
  41. }
  42. if deleted := p.Delete(h2); deleted {
  43. t.Errorf("h2 should not be deleted twice")
  44. }
  45. if got, want := p.Len(), 3; got != want {
  46. t.Errorf("got length %v; want %v", got, want)
  47. }
  48. if _, ok := p.Peek(h2); ok {
  49. t.Errorf("h2 still in pool")
  50. }
  51. // Remove an item by handle
  52. got, ok := p.Take(h4)
  53. if !ok {
  54. t.Errorf("h4 not found")
  55. }
  56. if got != 104 {
  57. t.Errorf("got %v; want 104", got)
  58. }
  59. // Take doesn't work on previously-taken or deleted items.
  60. if _, ok := p.Take(h4); ok {
  61. t.Errorf("h4 should not be taken twice")
  62. }
  63. if _, ok := p.Take(h2); ok {
  64. t.Errorf("h2 should not be taken after delete")
  65. }
  66. // Remove all items and return them
  67. items := p.AppendTakeAll(nil)
  68. want := []int{101, 103}
  69. if !slices.Equal(items, want) {
  70. t.Errorf("got items %v; want %v", items, want)
  71. }
  72. if got := p.Len(); got != 0 {
  73. t.Errorf("got length %v; want 0", got)
  74. }
  75. // Insert and then clear should result in no items.
  76. p.Add(105)
  77. p.Clear()
  78. if got := p.Len(); got != 0 {
  79. t.Errorf("got length %v; want 0", got)
  80. }
  81. }
  82. func TestTakeRandom(t *testing.T) {
  83. p := Pool[int]{}
  84. for i := 0; i < 10; i++ {
  85. p.Add(i + 100)
  86. }
  87. seen := make(map[int]bool)
  88. for i := 0; i < 10; i++ {
  89. item, ok := p.TakeRandom()
  90. if !ok {
  91. t.Errorf("unexpected empty pool")
  92. break
  93. }
  94. if seen[item] {
  95. t.Errorf("got duplicate item %v", item)
  96. }
  97. seen[item] = true
  98. }
  99. // Verify that the pool is empty
  100. if _, ok := p.TakeRandom(); ok {
  101. t.Errorf("expected empty pool")
  102. }
  103. for i := 0; i < 10; i++ {
  104. want := 100 + i
  105. if !seen[want] {
  106. t.Errorf("item %v not seen", want)
  107. }
  108. }
  109. if t.Failed() {
  110. t.Logf("seen: %+v", seen)
  111. }
  112. }
  113. func BenchmarkPool_AddDelete(b *testing.B) {
  114. b.Run("impl=Pool", func(b *testing.B) {
  115. p := Pool[int]{}
  116. // Warm up/force an initial allocation
  117. h := p.Add(0)
  118. p.Delete(h)
  119. b.ResetTimer()
  120. for i := 0; i < b.N; i++ {
  121. h := p.Add(i)
  122. p.Delete(h)
  123. }
  124. })
  125. b.Run("impl=map", func(b *testing.B) {
  126. p := make(map[int]bool)
  127. // Force initial allocation
  128. p[0] = true
  129. delete(p, 0)
  130. b.ResetTimer()
  131. for i := 0; i < b.N; i++ {
  132. p[i] = true
  133. delete(p, i)
  134. }
  135. })
  136. }
  137. func BenchmarkPool_TakeRandom(b *testing.B) {
  138. b.Run("impl=Pool", func(b *testing.B) {
  139. p := Pool[int]{}
  140. // Insert the number of items we'll be taking, then reset the timer.
  141. for i := 0; i < b.N; i++ {
  142. p.Add(i)
  143. }
  144. b.ResetTimer()
  145. // Now benchmark taking all the items.
  146. for i := 0; i < b.N; i++ {
  147. p.TakeRandom()
  148. }
  149. if p.Len() != 0 {
  150. b.Errorf("pool not empty")
  151. }
  152. })
  153. b.Run("impl=map", func(b *testing.B) {
  154. p := make(map[int]bool)
  155. // Insert the number of items we'll be taking, then reset the timer.
  156. for i := 0; i < b.N; i++ {
  157. p[i] = true
  158. }
  159. b.ResetTimer()
  160. // Now benchmark taking all the items.
  161. for i := 0; i < b.N; i++ {
  162. // Taking a random item is simulated by a single map iteration.
  163. for k := range p {
  164. delete(p, k) // "take" the item by removing it
  165. break
  166. }
  167. }
  168. if len(p) != 0 {
  169. b.Errorf("map not empty")
  170. }
  171. })
  172. }