slice_test.go 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. package reader
  2. import (
  3. "context"
  4. "errors"
  5. "io"
  6. "testing"
  7. )
  8. // TestSliceReader_FoldedTerminalSignal verifies that the read which exhausts the
  9. // slice returns io.EOF together with the final items, rather than deferring the
  10. // signal to a separate (0, io.EOF) call.
  11. func TestSliceReader_FoldedTerminalSignal(t *testing.T) {
  12. tests := []struct {
  13. name string
  14. items int
  15. bufSize int
  16. // wantReads is the expected (n, eof) result of each successive Read.
  17. wantReads []struct {
  18. n int
  19. eof bool
  20. }
  21. }{
  22. {
  23. name: "buffer larger than items",
  24. items: 3,
  25. bufSize: 10,
  26. wantReads: []struct {
  27. n int
  28. eof bool
  29. }{{3, true}},
  30. },
  31. {
  32. name: "buffer exactly fits items",
  33. items: 5,
  34. bufSize: 5,
  35. wantReads: []struct {
  36. n int
  37. eof bool
  38. }{{5, true}},
  39. },
  40. {
  41. name: "buffer evenly divides items",
  42. items: 6,
  43. bufSize: 3,
  44. wantReads: []struct {
  45. n int
  46. eof bool
  47. }{{3, false}, {3, true}},
  48. },
  49. {
  50. name: "buffer unevenly divides items",
  51. items: 7,
  52. bufSize: 3,
  53. wantReads: []struct {
  54. n int
  55. eof bool
  56. }{{3, false}, {3, false}, {1, true}},
  57. },
  58. {
  59. name: "single item",
  60. items: 1,
  61. bufSize: 1,
  62. wantReads: []struct {
  63. n int
  64. eof bool
  65. }{{1, true}},
  66. },
  67. }
  68. for _, tc := range tests {
  69. t.Run(tc.name, func(t *testing.T) {
  70. r := NewSliceReader(seq(tc.items))
  71. dst := make([]int, tc.bufSize)
  72. for i, want := range tc.wantReads {
  73. n, err := r.Read(context.Background(), dst)
  74. if n != want.n {
  75. t.Errorf("read %d: got n=%d, want %d", i, n, want.n)
  76. }
  77. gotEOF := errors.Is(err, io.EOF)
  78. if gotEOF != want.eof {
  79. t.Errorf("read %d: got eof=%v (err=%v), want eof=%v", i, gotEOF, err, want.eof)
  80. }
  81. if !want.eof && err != nil {
  82. t.Errorf("read %d: unexpected error: %v", i, err)
  83. }
  84. }
  85. })
  86. }
  87. }
  88. // TestSliceReader_EmptySlice verifies an exhausted-from-the-start reader returns
  89. // (0, io.EOF) immediately.
  90. func TestSliceReader_EmptySlice(t *testing.T) {
  91. for _, items := range [][]int{nil, {}} {
  92. r := NewSliceReader(items)
  93. n, err := r.Read(context.Background(), make([]int, 4))
  94. if n != 0 {
  95. t.Errorf("got n=%d, want 0", n)
  96. }
  97. if !errors.Is(err, io.EOF) {
  98. t.Errorf("got err=%v, want io.EOF", err)
  99. }
  100. }
  101. }
  102. // TestSliceReader_ReadAfterExhaustion verifies that reads following the folded
  103. // terminal signal continue to return (0, io.EOF).
  104. func TestSliceReader_ReadAfterExhaustion(t *testing.T) {
  105. r := NewSliceReader(seq(2))
  106. dst := make([]int, 4)
  107. // First read drains everything and folds in io.EOF.
  108. if n, err := r.Read(context.Background(), dst); n != 2 || !errors.Is(err, io.EOF) {
  109. t.Fatalf("first read: got (%d, %v), want (2, io.EOF)", n, err)
  110. }
  111. // Subsequent reads keep reporting io.EOF with no data.
  112. for i := 0; i < 3; i++ {
  113. n, err := r.Read(context.Background(), dst)
  114. if n != 0 || !errors.Is(err, io.EOF) {
  115. t.Errorf("read after exhaustion %d: got (%d, %v), want (0, io.EOF)", i, n, err)
  116. }
  117. }
  118. }
  119. // TestSliceReader_PreservesOrderAndValues drains the reader in small chunks and
  120. // verifies every item is returned exactly once, in order.
  121. func TestSliceReader_PreservesOrderAndValues(t *testing.T) {
  122. const items = 25
  123. r := NewSliceReader(seq(items))
  124. dst := make([]int, 4)
  125. var got []int
  126. for {
  127. n, err := r.Read(context.Background(), dst)
  128. got = append(got, dst[:n]...)
  129. if errors.Is(err, io.EOF) {
  130. break
  131. }
  132. if err != nil {
  133. t.Fatalf("unexpected error: %v", err)
  134. }
  135. }
  136. if len(got) != items {
  137. t.Fatalf("got %d items, want %d", len(got), items)
  138. }
  139. for i, v := range got {
  140. if v != i {
  141. t.Errorf("item %d: got %d, want %d", i, v, i)
  142. }
  143. }
  144. }
  145. // TestSliceReader_ContextCancellation verifies a cancelled context short-circuits
  146. // the read even when items remain.
  147. func TestSliceReader_ContextCancellation(t *testing.T) {
  148. r := NewSliceReader(seq(5))
  149. ctx, cancel := context.WithCancel(context.Background())
  150. cancel()
  151. n, err := r.Read(ctx, make([]int, 4))
  152. if n != 0 {
  153. t.Errorf("got n=%d, want 0", n)
  154. }
  155. if !errors.Is(err, context.Canceled) {
  156. t.Errorf("got err=%v, want context.Canceled", err)
  157. }
  158. // The reader was not advanced, so it still yields all items afterward.
  159. got := 0
  160. for {
  161. n, err := r.Read(context.Background(), make([]int, 2))
  162. got += n
  163. if errors.Is(err, io.EOF) {
  164. break
  165. }
  166. if err != nil {
  167. t.Fatalf("unexpected error draining: %v", err)
  168. }
  169. }
  170. if got != 5 {
  171. t.Errorf("drained %d items after cancellation, want 5", got)
  172. }
  173. }
  174. // TestSliceReader_ZeroLengthBuffer documents the io.Reader-style behavior: a
  175. // zero-length dst with items remaining reads nothing and returns no error.
  176. func TestSliceReader_ZeroLengthBuffer(t *testing.T) {
  177. r := NewSliceReader(seq(3))
  178. n, err := r.Read(context.Background(), []int{})
  179. if n != 0 {
  180. t.Errorf("got n=%d, want 0", n)
  181. }
  182. if err != nil {
  183. t.Errorf("got err=%v, want nil", err)
  184. }
  185. }
  186. // TestSliceReader_Close verifies Close is a no-op that reports no error and does
  187. // not disturb the read position.
  188. func TestSliceReader_Close(t *testing.T) {
  189. r := NewSliceReader(seq(2))
  190. if err := r.Close(); err != nil {
  191. t.Errorf("Close: got err=%v, want nil", err)
  192. }
  193. n, err := r.Read(context.Background(), make([]int, 4))
  194. if n != 2 || !errors.Is(err, io.EOF) {
  195. t.Errorf("read after Close: got (%d, %v), want (2, io.EOF)", n, err)
  196. }
  197. }
  198. // TestSliceReader_PointerElements exercises the generic reader with a pointer
  199. // element type, matching how the pricing readers use it.
  200. func TestSliceReader_PointerElements(t *testing.T) {
  201. items := []*int{ptr(1), ptr(2), ptr(3)}
  202. r := NewSliceReader(items)
  203. dst := make([]*int, 3)
  204. n, err := r.Read(context.Background(), dst)
  205. if n != 3 || !errors.Is(err, io.EOF) {
  206. t.Fatalf("got (%d, %v), want (3, io.EOF)", n, err)
  207. }
  208. for i, p := range dst {
  209. if p == nil || *p != i+1 {
  210. t.Errorf("item %d: got %v, want pointer to %d", i, p, i+1)
  211. }
  212. }
  213. }
  214. // seq returns []int{0, 1, ..., n-1}.
  215. func seq(n int) []int {
  216. s := make([]int, n)
  217. for i := range s {
  218. s[i] = i
  219. }
  220. return s
  221. }
  222. func ptr[T any](v T) *T { return &v }