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  }