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