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  }