aboutsummaryrefslogtreecommitdiff
path: root/fanin/fanin_test.go
diff options
context:
space:
mode:
Diffstat (limited to 'fanin/fanin_test.go')
-rw-r--r--fanin/fanin_test.go281
1 files changed, 281 insertions, 0 deletions
diff --git a/fanin/fanin_test.go b/fanin/fanin_test.go
new file mode 100644
index 0000000..bf2a413
--- /dev/null
+++ b/fanin/fanin_test.go
@@ -0,0 +1,281 @@
1package fanin
2
3import (
4 "context"
5 "sync"
6 "testing"
7 "time"
8)
9
10type mockObserver struct {
11 name string
12 t *testing.T
13}
14
15func (o *mockObserver) OnInputAdded() {
16 o.t.Logf("%s: OnInputAdded", o.name)
17}
18
19func (o *mockObserver) OnInputClosed() {
20 o.t.Logf("%s: OnInputClosed", o.name)
21}
22
23func TestNewFanIn(t *testing.T) {
24 t.Parallel()
25
26 obs := new(mockObserver)
27
28 tests := []struct {
29 name string
30 cfg *FanInConfig
31 shouldErr bool
32 }{
33 {
34 name: "valid config",
35 cfg: &FanInConfig{
36 BufferSize: 1,
37 Observer: obs,
38 },
39 shouldErr: false,
40 },
41 {
42 name: "invalid buffer size",
43 cfg: &FanInConfig{
44 BufferSize: -1,
45 Observer: obs,
46 },
47 shouldErr: true,
48 },
49 }
50
51 for _, test := range tests {
52 t.Run(test.name, func(t *testing.T) {
53 t.Parallel()
54 _, err := NewFanIn[int](WithBufferSize(test.cfg.BufferSize), WithObserver(obs))
55
56 if (err != nil) != test.shouldErr {
57 t.Errorf("validation error = %v, want err %v", err, test.shouldErr)
58 }
59 })
60 }
61}
62
63func TestFanIn_Add(t *testing.T) {
64 t.Parallel()
65 fi, _ := NewFanIn[int]()
66
67 inputs := make([]chan int, 10)
68 for i := range inputs {
69 inputs[i] = make(chan int, 1)
70 inputs[i] <- i
71 close(inputs[i])
72 }
73
74 for _, ch := range inputs {
75 fi.Add(ch)
76 }
77
78 count := 0
79 for range fi.Run(t.Context()) {
80 count++
81 }
82
83 if count != 10 {
84 t.Fatalf("expected 10 items, got %d", count)
85 }
86}
87
88func TestFanIn_Run(t *testing.T) {
89 t.Parallel()
90 fi, _ := NewFanIn[int]()
91
92 ch := make(chan int)
93 go func() {
94 ch <- 1
95 ch <- 2
96 ch <- 3
97 close(ch)
98 }()
99
100 fi.Add(ch)
101
102 for v := range fi.Run(t.Context()) {
103 t.Logf("value received %d", v)
104 }
105
106 <-fi.Done()
107}
108
109func TestFanIn_ContextCancellation(t *testing.T) {
110 t.Parallel()
111 fi, _ := NewFanIn[int]()
112 ctx, cancel := context.WithTimeout(t.Context(), time.Millisecond)
113 defer cancel()
114
115 inputs := make([]chan int, 10)
116 for i := range inputs {
117 inputs[i] = make(chan int, 1)
118 inputs[i] <- i
119 close(inputs[i])
120 }
121
122 for _, ch := range inputs {
123 fi.Add(ch)
124 }
125
126 select {
127 case <-fi.Run(ctx):
128 cancel()
129 case <-fi.Done():
130 cancel()
131 }
132}
133
134func TestFanIn_ShouldPanicRunMultiple(t *testing.T) {
135 t.Parallel()
136
137 defer func() {
138 if r := recover(); r == nil {
139 t.Errorf("expected panic for multiple Run calls")
140 }
141 }()
142
143 ctx := t.Context()
144 fi, _ := NewFanIn[int]()
145
146 inputs := make([]chan int, 10)
147 for i := range inputs {
148 inputs[i] = make(chan int, 1)
149 inputs[i] <- i
150 close(inputs[i])
151 }
152
153 for _, ch := range inputs {
154 fi.Add(ch)
155 }
156
157 for v := range fi.Run(ctx) {
158 t.Logf("value received %d", v)
159 }
160
161 fi.Run(ctx)
162}
163
164func TestFanIn_ShouldPanicAddAfterRun(t *testing.T) {
165 t.Parallel()
166
167 defer func() {
168 if r := recover(); r == nil {
169 t.Errorf("expected panic for Add after Run")
170 }
171 }()
172
173 fi, _ := NewFanIn[int]()
174
175 inputs := make([]chan int, 10)
176 for i := range inputs {
177 inputs[i] = make(chan int, 1)
178 inputs[i] <- i
179 close(inputs[i])
180 }
181
182 for _, ch := range inputs {
183 fi.Add(ch)
184 }
185
186 for v := range fi.Run(t.Context()) {
187 t.Logf("value received %d", v)
188 }
189
190 fi.Add(make(chan int))
191}
192
193func TestFanIn_Observer(t *testing.T) {
194 t.Parallel()
195 obs := &mockObserver{t: t}
196
197 fi, _ := NewFanIn[int](WithObserver(obs))
198
199 inputs := make([]chan int, 10)
200 for i := range inputs {
201 inputs[i] = make(chan int, 1)
202 inputs[i] <- i
203 close(inputs[i])
204 }
205
206 for _, ch := range inputs {
207 fi.Add(ch)
208 }
209
210 for v := range fi.Run(t.Context()) {
211 t.Logf("value received %d", v)
212 }
213
214 <-fi.Done()
215}
216
217func TestFanIn_AddConcurrency(t *testing.T) {
218 t.Parallel()
219
220 fi, _ := NewFanIn[int]()
221 var wg sync.WaitGroup
222 numGoroutines := 10
223 channelPerGoroutine := 10
224
225 for range numGoroutines {
226 wg.Go(func() {
227 inputs := make([]chan int, channelPerGoroutine)
228 for i := range inputs {
229 inputs[i] = make(chan int, 1)
230 inputs[i] <- i
231 close(inputs[i])
232 fi.Add(inputs[i])
233 }
234 })
235 }
236 wg.Wait()
237
238 count := 0
239 for range fi.Run(t.Context()) {
240 count++
241 }
242
243 <-fi.Done()
244
245 expectedCount := numGoroutines * channelPerGoroutine
246 if count != expectedCount {
247 t.Errorf("expected %d items, got %d", expectedCount, count)
248 }
249}
250
251func TestFanIn_NilChannel(t *testing.T) {
252 t.Parallel()
253
254 fi, _ := NewFanIn[int]()
255
256 defer func() {
257 if r := recover(); r == nil {
258 t.Error("expected panic when adding nil channel")
259 }
260 }()
261
262 fi.Add(nil)
263}
264
265func TestFanIn_RunWithNoInputs(t *testing.T) {
266 t.Parallel()
267
268 fi, _ := NewFanIn[int]()
269 out := fi.Run(t.Context())
270
271 count := 0
272 for range out {
273 count++
274 }
275
276 if count != 0 {
277 t.Errorf("expected 0 values from empty fan-in, got %d", count)
278 }
279
280 <-fi.Done()
281}