github.com/anth0d/nomad@v0.0.0-20221214183521-ae3a0a2cad06/nomad/stream/event_buffer_test.go (about)

     1  package stream
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"math/rand"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/hashicorp/nomad/ci"
    11  	"github.com/hashicorp/nomad/nomad/structs"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/require"
    14  )
    15  
    16  func TestEventBufferFuzz(t *testing.T) {
    17  	ci.Parallel(t)
    18  
    19  	nReaders := 1000
    20  	nMessages := 1000
    21  
    22  	b := newEventBuffer(1000)
    23  
    24  	// Start a write goroutine that will publish 10000 messages with sequential
    25  	// indexes and some jitter in timing (to allow clients to "catch up" and block
    26  	// waiting for updates).
    27  	go func() {
    28  		seed := time.Now().UnixNano()
    29  		t.Logf("Using seed %d", seed)
    30  		// z is a Zipfian distribution that gives us a number of milliseconds to
    31  		// sleep which are mostly low - near zero but occasionally spike up to near
    32  		// 100.
    33  		z := rand.NewZipf(rand.New(rand.NewSource(seed)), 1.5, 1.5, 50)
    34  
    35  		for i := 0; i < nMessages; i++ {
    36  			// Event content is arbitrary and not valid for our use of buffers in
    37  			// streaming - here we only care about the semantics of the buffer.
    38  			e := structs.Event{
    39  				Index: uint64(i), // Indexes should be contiguous
    40  			}
    41  			b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}})
    42  			// Sleep sometimes for a while to let some subscribers catch up
    43  			wait := time.Duration(z.Uint64()) * time.Millisecond
    44  			time.Sleep(wait)
    45  		}
    46  	}()
    47  
    48  	// Run n subscribers following and verifying
    49  	errCh := make(chan error, nReaders)
    50  
    51  	// Load head here so all subscribers start from the same point or they might
    52  	// not run until several appends have already happened.
    53  	head := b.Head()
    54  
    55  	for i := 0; i < nReaders; i++ {
    56  		go func(i int) {
    57  			expect := uint64(0)
    58  			item := head
    59  			var err error
    60  			for {
    61  				item, err = item.Next(context.Background(), nil)
    62  				if err != nil {
    63  					errCh <- fmt.Errorf("subscriber %05d failed getting next %d: %s", i,
    64  						expect, err)
    65  					return
    66  				}
    67  				if item.Events.Events[0].Index != expect {
    68  					errCh <- fmt.Errorf("subscriber %05d got bad event want=%d, got=%d", i,
    69  						expect, item.Events.Events[0].Index)
    70  					return
    71  				}
    72  				expect++
    73  				if expect == uint64(nMessages) {
    74  					// Succeeded
    75  					errCh <- nil
    76  					return
    77  				}
    78  			}
    79  		}(i)
    80  	}
    81  
    82  	// Wait for all readers to finish one way or other
    83  	for i := 0; i < nReaders; i++ {
    84  		err := <-errCh
    85  		assert.NoError(t, err)
    86  	}
    87  }
    88  
    89  func TestEventBuffer_Slow_Reader(t *testing.T) {
    90  	ci.Parallel(t)
    91  
    92  	b := newEventBuffer(10)
    93  
    94  	for i := 1; i < 11; i++ {
    95  		e := structs.Event{
    96  			Index: uint64(i), // Indexes should be contiguous
    97  		}
    98  		b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}})
    99  	}
   100  
   101  	require.Equal(t, 10, b.Len())
   102  
   103  	head := b.Head()
   104  
   105  	for i := 10; i < 15; i++ {
   106  		e := structs.Event{
   107  			Index: uint64(i), // Indexes should be contiguous
   108  		}
   109  		b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}})
   110  	}
   111  
   112  	// Ensure the slow reader errors to handle dropped events and
   113  	// fetch latest head
   114  	ev, err := head.Next(context.Background(), nil)
   115  	require.Error(t, err)
   116  	require.Nil(t, ev)
   117  
   118  	newHead := b.Head()
   119  	require.Equal(t, 5, int(newHead.Events.Index))
   120  }
   121  
   122  func TestEventBuffer_Size(t *testing.T) {
   123  	ci.Parallel(t)
   124  
   125  	b := newEventBuffer(100)
   126  
   127  	for i := 0; i < 10; i++ {
   128  		e := structs.Event{
   129  			Index: uint64(i), // Indexes should be contiguous
   130  		}
   131  		b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}})
   132  	}
   133  
   134  	require.Equal(t, 10, b.Len())
   135  }
   136  
   137  func TestEventBuffer_MaxSize(t *testing.T) {
   138  	ci.Parallel(t)
   139  
   140  	b := newEventBuffer(10)
   141  
   142  	var events []structs.Event
   143  	for i := 0; i < 100; i++ {
   144  		events = append(events, structs.Event{})
   145  	}
   146  
   147  	b.Append(&structs.Events{Index: uint64(1), Events: events})
   148  	require.Equal(t, 1, b.Len())
   149  }
   150  
   151  // TestEventBuffer_Emptying_Buffer tests the behavior when all items
   152  // are removed, the event buffer should advance its head down to the last message
   153  // and insert a placeholder sentinel value.
   154  func TestEventBuffer_Emptying_Buffer(t *testing.T) {
   155  	ci.Parallel(t)
   156  
   157  	b := newEventBuffer(10)
   158  
   159  	for i := 0; i < 10; i++ {
   160  		e := structs.Event{
   161  			Index: uint64(i), // Indexes should be contiguous
   162  		}
   163  		b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}})
   164  	}
   165  
   166  	require.Equal(t, 10, int(b.Len()))
   167  
   168  	// empty the buffer, which will bring the event buffer down
   169  	// to a single sentinel value
   170  	for i := 0; i < 16; i++ {
   171  		b.advanceHead()
   172  	}
   173  
   174  	// head and tail are now a sentinel value
   175  	head := b.Head()
   176  	tail := b.Tail()
   177  	require.Equal(t, 0, int(head.Events.Index))
   178  	require.Equal(t, 0, b.Len())
   179  	require.Equal(t, head, tail)
   180  
   181  	e := structs.Event{
   182  		Index: uint64(100),
   183  	}
   184  	b.Append(&structs.Events{Index: uint64(100), Events: []structs.Event{e}})
   185  
   186  	ctx, cancel := context.WithDeadline(context.Background(), time.Now().Add(1*time.Second))
   187  	defer cancel()
   188  
   189  	next, err := head.Next(ctx, make(chan struct{}))
   190  	require.NoError(t, err)
   191  	require.NotNil(t, next)
   192  	require.Equal(t, uint64(100), next.Events.Index)
   193  
   194  }
   195  
   196  func TestEventBuffer_StartAt_CurrentIdx_Past_Start(t *testing.T) {
   197  	ci.Parallel(t)
   198  
   199  	cases := []struct {
   200  		desc     string
   201  		req      uint64
   202  		expected uint64
   203  		offset   int
   204  	}{
   205  		{
   206  			desc:     "requested index less than head receives head",
   207  			req:      10,
   208  			expected: 11,
   209  			offset:   1,
   210  		},
   211  		{
   212  			desc:     "requested exact match head",
   213  			req:      11,
   214  			expected: 11,
   215  			offset:   0,
   216  		},
   217  		{
   218  			desc:     "requested exact match",
   219  			req:      42,
   220  			expected: 42,
   221  			offset:   0,
   222  		},
   223  		{
   224  			desc:     "requested index greater than tail receives tail",
   225  			req:      500,
   226  			expected: 100,
   227  			offset:   400,
   228  		},
   229  	}
   230  
   231  	// buffer starts at index 11 goes to 100
   232  	b := newEventBuffer(100)
   233  
   234  	for i := 11; i <= 100; i++ {
   235  		e := structs.Event{
   236  			Index: uint64(i), // Indexes should be contiguous
   237  		}
   238  		b.Append(&structs.Events{Index: uint64(i), Events: []structs.Event{e}})
   239  	}
   240  
   241  	for _, tc := range cases {
   242  		t.Run(tc.desc, func(t *testing.T) {
   243  			got, offset := b.StartAtClosest(tc.req)
   244  			require.Equal(t, int(tc.expected), int(got.Events.Index))
   245  			require.Equal(t, tc.offset, offset)
   246  		})
   247  	}
   248  }