github.com/mongodb/grip@v0.0.0-20240213223901-f906268d82b9/send/buffered_async_test.go (about) 1 package send 2 3 import ( 4 "context" 5 "fmt" 6 "strings" 7 "testing" 8 "time" 9 10 "github.com/mongodb/grip/level" 11 "github.com/mongodb/grip/message" 12 "github.com/pkg/errors" 13 "github.com/stretchr/testify/assert" 14 "github.com/stretchr/testify/require" 15 ) 16 17 const ( 18 maxProcessingDuration = time.Second 19 pollingInterval = 10 * time.Millisecond 20 contextTimeout = 30 * time.Second 21 ) 22 23 func TestBufferedAsyncSend(t *testing.T) { 24 var s *InternalSender 25 ctx, cancel := context.WithTimeout(context.Background(), contextTimeout) 26 defer cancel() 27 28 newBufferedAsyncSender := func(interval time.Duration, size int) *bufferedAsyncSender { 29 bs := &bufferedAsyncSender{ 30 Sender: s, 31 ctx: ctx, 32 cancel: cancel, 33 opts: BufferedAsyncSenderOptions{BufferedSenderOptions: BufferedSenderOptions{FlushInterval: interval}}, 34 buffer: make([]message.Composer, 0, size), 35 needsFlush: make(chan bool, 1), 36 incoming: make(chan message.Composer, defaultIncomingBufferFactor*size), 37 } 38 return bs 39 } 40 41 for name, test := range map[string]func(*testing.T){ 42 "RespectsPriority": func(t *testing.T) { 43 bs := newBufferedAsyncSender(time.Minute, 1) 44 45 bs.Send(message.ConvertToComposer(level.Trace, "should not send")) 46 assert.False(t, checkMessageSent(bs, s)) 47 }, 48 "FlushesAtCapacity": func(t *testing.T) { 49 bufferSize := 10 50 bs := newBufferedAsyncSender(time.Minute, bufferSize) 51 52 for i := 0; i < bufferSize; i++ { 53 bs.Send(message.ConvertToComposer(level.Debug, fmt.Sprintf("message %d", i+1))) 54 } 55 56 require.True(t, checkMessageSent(bs, s)) 57 msg := s.GetMessage() 58 msgs := strings.Split(msg.Message.String(), "\n") 59 assert.Len(t, msgs, 10) 60 for i, msg := range msgs { 61 require.Equal(t, fmt.Sprintf("message %d", i+1), msg) 62 } 63 }, 64 "FlushesOnInterval": func(t *testing.T) { 65 interval := maxProcessingDuration / 2 66 bs := newBufferedAsyncSender(interval, 10) 67 68 bs.Send(message.ConvertToComposer(level.Debug, "should flush")) 69 require.True(t, checkMessageSent(bs, s)) 70 msg := s.GetMessage() 71 assert.Equal(t, "should flush", msg.Message.String()) 72 }, 73 "OverflowBuffer": func(t *testing.T) { 74 bs := newBufferedAsyncSender(time.Minute, 10) 75 var capturedErr error 76 assert.NoError(t, bs.SetErrorHandler(func(err error, _ message.Composer) { capturedErr = err })) 77 78 for x := 0; x < defaultIncomingBufferFactor*10; x++ { 79 bs.Send(message.ConvertToComposer(level.Debug, "message")) 80 } 81 82 bs.Send(message.ConvertToComposer(level.Debug, "over the limit")) 83 require.True(t, checkMessageSent(bs, s)) 84 msg := s.GetMessage() 85 msgString := msg.Message.String() 86 assert.NotContains(t, "over the limit", msgString) 87 88 require.Error(t, capturedErr) 89 assert.Equal(t, "the message was dropped because the buffer was full", capturedErr.Error()) 90 }, 91 "ReturnsWhenClosed": func(t *testing.T) { 92 bs := newBufferedAsyncSender(time.Minute, 10) 93 done := make(chan bool, 1) 94 95 go func() { 96 bs.processMessages() 97 done <- true 98 }() 99 assert.NoError(t, bs.Close()) 100 101 assert.Eventually(t, func() bool { 102 select { 103 case <-done: 104 return true 105 default: 106 return false 107 } 108 }, maxProcessingDuration, pollingInterval) 109 }, 110 "ForceFlush": func(t *testing.T) { 111 bs := newBufferedAsyncSender(time.Minute, 10) 112 113 bs.Send(message.ConvertToComposer(level.Debug, "message")) 114 require.NoError(t, bs.Flush(nil)) 115 require.True(t, checkMessageSent(bs, s)) 116 msg := s.GetMessage() 117 assert.Equal(t, "message", msg.Message.String()) 118 }, 119 "NonEmptyBuffer": func(t *testing.T) { 120 bs := newBufferedAsyncSender(time.Minute, 10) 121 122 for _, msg := range []message.Composer{ 123 message.ConvertToComposer(level.Debug, "message1"), 124 message.ConvertToComposer(level.Debug, "message2"), 125 message.ConvertToComposer(level.Debug, "message3"), 126 } { 127 bs.Send(msg) 128 } 129 130 assert.NoError(t, bs.Close()) 131 require.True(t, checkMessageSent(bs, s)) 132 msgs := s.GetMessage() 133 assert.Equal(t, "message1\nmessage2\nmessage3", msgs.Message.String()) 134 }, 135 "CloseIsIdempotent": func(t *testing.T) { 136 bs := newBufferedAsyncSender(time.Minute, 10) 137 138 assert.NoError(t, bs.Close()) 139 assert.NoError(t, bs.Close()) 140 }, 141 "SendErrorsAfterClose": func(t *testing.T) { 142 bs := newBufferedAsyncSender(time.Minute, 10) 143 var capturedErr error 144 assert.NoError(t, bs.SetErrorHandler(func(err error, _ message.Composer) { capturedErr = err })) 145 146 assert.NoError(t, bs.Close()) 147 bs.Send(message.ConvertToComposer(level.Debug, "message")) 148 assert.Error(t, capturedErr) 149 assert.True(t, errors.Cause(capturedErr) == context.Canceled) 150 }, 151 } { 152 var err error 153 s, err = NewInternalLogger("buffs", LevelInfo{level.Debug, level.Debug}) 154 require.NoError(t, err) 155 t.Run(name, test) 156 } 157 } 158 159 func checkMessageSent(bs *bufferedAsyncSender, s *InternalSender) bool { 160 done := make(chan bool) 161 go func() { 162 bs.processMessages() 163 done <- true 164 }() 165 166 begin := time.Now() 167 168 ticker := time.NewTicker(pollingInterval) 169 defer ticker.Stop() 170 171 FOR: 172 for { 173 select { 174 case <-done: 175 break FOR 176 case <-ticker.C: 177 if s.HasMessage() || time.Since(begin) > maxProcessingDuration { 178 bs.Close() 179 break FOR 180 } 181 } 182 } 183 184 return s.HasMessage() 185 }