github.com/rudderlabs/rudder-go-kit@v0.30.0/async/single_sender_test.go (about) 1 package async_test 2 3 import ( 4 "context" 5 "testing" 6 "time" 7 8 "github.com/stretchr/testify/require" 9 "go.uber.org/goleak" 10 "golang.org/x/sync/errgroup" 11 12 "github.com/rudderlabs/rudder-go-kit/async" 13 ) 14 15 func TestSingleSender(t *testing.T) { 16 type valueOrError struct { 17 value int 18 err error 19 } 20 21 send := func(ctx context.Context, s *async.SingleSender[valueOrError], times int) (sendCalls int) { 22 defer s.Close() 23 for i := 0; i < times; i++ { 24 if ctx.Err() != nil { 25 s.Send(valueOrError{err: ctx.Err()}) 26 return 27 } 28 s.Send(valueOrError{value: i}) 29 sendCalls++ 30 } 31 return sendCalls 32 } 33 34 receive := func(ch <-chan valueOrError, delay time.Duration) ([]int, []error) { 35 var receivedValues []int 36 var receivedErrors []error 37 for v := range ch { 38 time.Sleep(delay) 39 if v.err != nil { 40 receivedErrors = append(receivedErrors, v.err) 41 } else { 42 receivedValues = append(receivedValues, v.value) 43 } 44 } 45 return receivedValues, receivedErrors 46 } 47 48 t.Run("receive all values from sender", func(t *testing.T) { 49 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 50 s := &async.SingleSender[valueOrError]{} 51 ctx, ch, _ := s.Begin(context.Background()) 52 defer s.Close() 53 54 g := &errgroup.Group{} 55 56 var sendCalls int 57 g.Go(func() error { 58 sendCalls = send(ctx, s, 10) 59 return nil 60 }) 61 62 var receivedValues []int 63 var receivedErrors []error 64 g.Go(func() error { 65 receivedValues, receivedErrors = receive(ch, 0) 66 return nil 67 }) 68 69 _ = g.Wait() 70 71 require.Equal(t, 10, sendCalls) 72 require.Equal(t, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, receivedValues) 73 require.Empty(t, receivedErrors) 74 }) 75 76 t.Run("parent context is canceled", func(t *testing.T) { 77 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 78 parentCtx, parentCtxCancel := context.WithCancel(context.Background()) 79 parentCtxCancel() 80 s := &async.SingleSender[valueOrError]{} 81 ctx, ch, _ := s.Begin(parentCtx) 82 defer s.Close() 83 84 g := &errgroup.Group{} 85 86 var sendCalls int 87 g.Go(func() error { 88 sendCalls = send(ctx, s, 10) 89 return nil 90 }) 91 92 var receivedValues []int 93 var receivedErrors []error 94 95 g.Go(func() error { 96 receivedValues, receivedErrors = receive(ch, 0) 97 return nil 98 }) 99 _ = g.Wait() 100 101 require.Zero(t, sendCalls) 102 require.Nil(t, receivedValues, "no values should be received") 103 require.Equal(t, []error{context.Canceled}, receivedErrors) 104 }) 105 106 t.Run("parent context is canceled after interaction has started", func(t *testing.T) { 107 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 108 parentCtx, parentCtxCancel := context.WithCancel(context.Background()) 109 110 s := &async.SingleSender[valueOrError]{} 111 ctx, ch, _ := s.Begin(parentCtx) 112 defer s.Close() 113 114 g := &errgroup.Group{} 115 116 var sendCalls int 117 g.Go(func() error { 118 sendCalls = send(ctx, s, 1000) 119 return nil 120 }) 121 122 var receivedValues []int 123 var receivedErrors []error 124 125 g.Go(func() error { 126 receivedValues, receivedErrors = receive(ch, 10*time.Millisecond) 127 return nil 128 }) 129 time.Sleep(time.Millisecond * 100) 130 parentCtxCancel() 131 _ = g.Wait() 132 133 require.GreaterOrEqual(t, sendCalls, 1, "sender should have called send at least for 1 value") 134 require.GreaterOrEqual(t, len(receivedValues), 1, "receiver should have called received at least for 1 value") 135 require.Equal(t, []error{context.Canceled}, receivedErrors) 136 }) 137 138 t.Run("try to send another value after sender is closed", func(t *testing.T) { 139 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 140 s := async.SingleSender[valueOrError]{} 141 _, ch, _ := s.Begin(context.Background()) 142 defer s.Close() 143 144 g := &errgroup.Group{} 145 146 g.Go(func() error { 147 for i := 0; i < 10; i++ { 148 s.Send(valueOrError{value: i}) 149 } 150 s.Close() 151 s.Send(valueOrError{value: 10}) 152 return nil 153 }) 154 155 var receivedValues []int 156 var receivedErrors []error 157 158 g.Go(func() error { 159 receivedValues, receivedErrors = receive(ch, 0) 160 return nil 161 }) 162 _ = g.Wait() 163 164 require.Equal(t, []int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}, receivedValues) 165 require.Empty(t, receivedErrors) 166 }) 167 168 t.Run("receiver leaves before sender sends all values", func(t *testing.T) { 169 defer goleak.VerifyNone(t, goleak.IgnoreCurrent()) 170 s := async.SingleSender[valueOrError]{} 171 ctx, ch, leave := s.Begin(context.Background()) 172 defer s.Close() 173 174 g := &errgroup.Group{} 175 176 var sendCalls int 177 g.Go(func() error { 178 sendCalls = send(ctx, &s, 10) 179 return nil 180 }) 181 182 var receivedValues []int 183 var receivedErrors []error 184 185 g.Go(func() error { 186 for v := range ch { 187 if v.err != nil { 188 receivedErrors = append(receivedErrors, v.err) 189 } else { 190 receivedValues = append(receivedValues, v.value) 191 } 192 // leave after receiving 1 value 193 leave() 194 // make sure sender has time to try and send another value and figure out that context is canceled 195 time.Sleep(100 * time.Millisecond) 196 } 197 return nil 198 }) 199 _ = g.Wait() 200 201 require.GreaterOrEqual(t, len(receivedValues), 1, "receiver should have received at least 1 value") 202 require.LessOrEqual(t, len(receivedValues), 2, "receiver should have received at most 2 values") 203 require.GreaterOrEqual(t, sendCalls, 1, "sender should have called send at least for 1 value") 204 require.LessOrEqual(t, sendCalls, 2, "sender should have called send at most for 2 values, i.e. it should stop sending after receiver leaves") 205 }) 206 207 t.Run("sender closes then sends again", func(t *testing.T) { 208 s := async.SingleSender[valueOrError]{} 209 _, ch, _ := s.Begin(context.Background()) 210 go func() { 211 s.Close() 212 s.Send(valueOrError{value: 1}) 213 }() 214 215 var values []int 216 for range ch { 217 values = append(values, 1) 218 } 219 220 require.Empty(t, values) 221 }) 222 }