github.com/hernad/nomad@v1.6.112/nomad/stream/event_buffer_test.go (about)

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