github.com/Jeffail/benthos/v3@v3.65.0/internal/checkpoint/capped_test.go (about) 1 package checkpoint 2 3 import ( 4 "context" 5 "math/rand" 6 "sync" 7 "testing" 8 "time" 9 10 "github.com/stretchr/testify/assert" 11 "github.com/stretchr/testify/require" 12 ) 13 14 func TestCappedSequential(t *testing.T) { 15 c, cancel := newCheckpointTester(t, 1000, 5*time.Second) 16 defer cancel() 17 18 c.AssertNoHighest() 19 20 c.AssertTrackAllowed(1, 1) 21 c.AssertTrackAllowed(2, 1) 22 c.AssertTrackAllowed(3, 1) 23 24 c.AssertNoHighest() 25 26 c.Resolve(1, 1) 27 c.AssertHighest(1) 28 29 c.Resolve(2, 2) 30 c.AssertHighest(2) 31 32 c.Resolve(3, 3) 33 c.AssertHighest(3) 34 35 c.AssertTrackAllowed(4, 1) 36 c.AssertHighest(3) 37 38 c.Resolve(4, 4) 39 c.AssertHighest(4) 40 } 41 42 func TestCappedBigJumps(t *testing.T) { 43 c, cancel := newCheckpointTester(t, 1, 5*time.Second) 44 defer cancel() 45 46 c.AssertNoHighest() 47 48 c.AssertTrackAllowed(1000, 1) 49 50 c.AssertNoHighest() 51 52 c.Resolve(1000, 1000) 53 54 c.AssertHighest(1000) 55 56 c.AssertTrackAllowed(2000, 1) 57 58 c.AssertHighest(1000) 59 60 c.Resolve(2000, 2000) 61 62 c.AssertHighest(2000) 63 } 64 65 func TestCappedBigJumpsMore(t *testing.T) { 66 c, cancel := newCheckpointTester(t, 2, 5*time.Second) 67 defer cancel() 68 69 c.AssertNoHighest() 70 71 c.AssertTrackAllowed(1000, 1) 72 73 c.AssertNoHighest() 74 75 c.AssertTrackAllowed(2000, 1) 76 77 c.AssertNoHighest() 78 79 c.Resolve(1000, 1000) 80 81 c.AssertHighest(1000) 82 83 c.Resolve(2000, 2000) 84 85 c.AssertHighest(2000) 86 } 87 88 func TestCappedStartsBig(t *testing.T) { 89 c, cancel := newCheckpointTester(t, 100, 5*time.Second) 90 defer cancel() 91 92 c.AssertNoHighest() 93 94 c.AssertTrackAllowed(500, 1) 95 c.AssertTrackAllowed(501, 1) 96 c.AssertTrackAllowed(502, 1) 97 98 c.AssertNoHighest() 99 100 c.Resolve(500, 500) 101 c.AssertHighest(500) 102 103 c.Resolve(501, 501) 104 c.AssertHighest(501) 105 106 c.Resolve(502, 502) 107 c.AssertHighest(502) 108 109 c.AssertTrackAllowed(503, 1) 110 111 c.AssertHighest(502) 112 113 c.Resolve(503, 503) 114 c.AssertHighest(503) 115 } 116 117 func TestCappedCapHappy(t *testing.T) { 118 c, cancel := newCheckpointTester(t, 100, 5*time.Second) 119 defer cancel() 120 121 c.AssertNoHighest() 122 123 c.AssertTrackAllowed(100, 1) 124 c.AssertNoHighest() 125 c.Resolve(100, 100) 126 c.AssertHighest(100) 127 128 for i := 101; i <= 200; i++ { 129 c.AssertTrackAllowed(int64(i), 1) 130 } 131 132 c.AssertTrackBlocked(201, 1) 133 134 time.Sleep(50 * time.Millisecond) 135 136 c.AssertNotPending(201) 137 138 c.Resolve(101, 101) 139 140 time.Sleep(50 * time.Millisecond) 141 142 c.AssertPending(201) 143 144 for i := int64(102); i <= 201; i++ { 145 c.Resolve(i, i) 146 c.AssertHighest(i) 147 } 148 } 149 150 func TestCappedOutOfSync(t *testing.T) { 151 c, cancel := newCheckpointTester(t, 1000, 5*time.Second) 152 defer cancel() 153 154 c.AssertNoHighest() 155 156 c.AssertTrackAllowed(1, 1) 157 c.AssertTrackAllowed(2, 1) 158 c.AssertTrackAllowed(3, 1) 159 c.AssertTrackAllowed(4, 1) 160 161 c.AssertNoHighest() 162 163 c.Resolve(2, -1) 164 c.AssertNoHighest() 165 166 c.Resolve(1, 2) 167 c.AssertHighest(2) 168 169 c.Resolve(3, 3) 170 c.AssertHighest(3) 171 172 c.Resolve(4, 4) 173 c.AssertHighest(4) 174 } 175 176 func TestCappedSequentialLarge(t *testing.T) { 177 c, cancel := newCheckpointTester(t, 1000, 5*time.Second) 178 defer cancel() 179 180 c.AssertNoHighest() 181 182 for i := int64(0); i < 1000; i++ { 183 c.AssertTrackAllowed(i, 1) 184 c.AssertNoHighest() 185 } 186 187 for i := int64(0); i < 1000; i++ { 188 c.Resolve(i, i) 189 c.AssertHighest(i) 190 } 191 } 192 193 func TestCappedSequentialChunks(t *testing.T) { 194 c, cancel := newCheckpointTester(t, 1000, 5*time.Second) 195 defer cancel() 196 197 chunkSize := int64(100) 198 for i := int64(0); i < 10; i++ { 199 for j := int64(0); j < chunkSize; j++ { 200 offset := i*chunkSize + j 201 c.AssertTrackAllowed(offset, 1) 202 } 203 for j := int64(0); j < chunkSize; j++ { 204 offset := i*chunkSize + j 205 c.Resolve(offset, offset) 206 c.AssertHighest(offset) 207 } 208 } 209 } 210 211 func TestCappedSequentialReverseLarge(t *testing.T) { 212 c, cancel := newCheckpointTester(t, 1000, 5*time.Second) 213 defer cancel() 214 215 for i := int64(0); i < 1000; i++ { 216 c.AssertTrackAllowed(i, 1) 217 } 218 for i := int64(999); i > 0; i-- { 219 c.Resolve(i, -1) 220 c.AssertNoHighest() 221 } 222 223 c.Resolve(0, 999) 224 c.AssertHighest(999) 225 } 226 227 func TestCappedSequentialRandomLarge(t *testing.T) { 228 c, cancel := newCheckpointTester(t, 1000, 5*time.Second) 229 defer cancel() 230 231 indexes := make([]int64, 1000) 232 for i := int64(0); i < 1000; i++ { 233 c.AssertTrackAllowed(i, 1) 234 indexes[int(i)] = i 235 } 236 237 rand.Shuffle(len(indexes), func(i, j int) { 238 indexes[i], indexes[j] = indexes[j], indexes[i] 239 }) 240 241 resolved := 0 242 for index, i := range indexes { 243 c.Resolve(i, -2) // -2 means don't check highest 244 resolved++ 245 246 highestI := c.checkpointer.Highest() 247 if highestI == nil { 248 highestI = int64(-1) 249 } 250 251 if resolved == len(indexes) { 252 assert.Equal(t, int64(999), highestI.(int64)) 253 } else { 254 // Assert that the remaining offsets are all higher 255 for _, k := range indexes[index+1:] { 256 assert.True(t, k > highestI.(int64)) 257 } 258 } 259 } 260 } 261 262 func BenchmarkCappedChunked100(b *testing.B) { 263 checkpointLimit := int64(1000) 264 chunkSize := int64(100) 265 resolvers := make([]func() interface{}, chunkSize) 266 267 b.ReportAllocs() 268 269 c := NewCapped(checkpointLimit) 270 ctx := context.Background() 271 272 N := int64(b.N) / chunkSize 273 batchSize := checkpointLimit / chunkSize 274 275 for i := int64(0); i < N; i++ { 276 for j := int64(0); j < chunkSize; j++ { 277 offset := i*chunkSize + j 278 resolver, err := c.Track(ctx, offset, batchSize) 279 if err != nil { 280 b.Fatal(err) 281 } 282 resolvers[j] = resolver 283 } 284 for j := int64(0); j < chunkSize; j++ { 285 resolver := resolvers[j] 286 resolvers[j] = nil 287 v, ok := resolver().(int64) 288 if !ok { 289 b.Fatal("should always resolve with a maximum") 290 } 291 292 offset := i*chunkSize + j 293 if offset != v { 294 b.Errorf("Wrong value: %v != %v", offset, v) 295 } 296 } 297 } 298 } 299 300 func BenchmarkCappedChunkedReverse100(b *testing.B) { 301 checkpointLimit := int64(1000) 302 chunkSize := int64(100) 303 resolvers := make([]func() interface{}, chunkSize) 304 305 b.ReportAllocs() 306 307 c := NewCapped(checkpointLimit) 308 ctx := context.Background() 309 310 N := int64(b.N) / chunkSize 311 batchSize := checkpointLimit / chunkSize 312 313 for i := int64(0); i < N; i++ { 314 for j := int64(0); j < chunkSize; j++ { 315 offset := i*chunkSize + j 316 resolver, err := c.Track(ctx, offset, batchSize) 317 if err != nil { 318 b.Fatal(err) 319 } 320 resolvers[j] = resolver 321 } 322 for j := chunkSize - 1; j >= 0; j-- { 323 resolver := resolvers[j] 324 resolvers[j] = nil 325 v, ok := resolver().(int64) 326 327 exp := int64(-1) 328 329 if i > 0 { 330 exp = (i * chunkSize) - 1 331 } 332 333 if j == 0 { 334 exp = ((i + 1) * chunkSize) - 1 335 } 336 337 if exp >= 0 { 338 if !ok { 339 b.Fatal("should resolve with a maximum") 340 } else if exp != v { 341 b.Errorf("Wrong value: %v != %v", exp, v) 342 } 343 } 344 } 345 } 346 } 347 348 func BenchmarkCappedChunkedReverse1000(b *testing.B) { 349 checkpointLimit := int64(1000) 350 chunkSize := int64(1000) 351 resolvers := make([]func() interface{}, chunkSize) 352 353 b.ReportAllocs() 354 355 c := NewCapped(checkpointLimit) 356 ctx := context.Background() 357 358 N := int64(b.N) / chunkSize 359 batchSize := checkpointLimit / chunkSize 360 361 for i := int64(0); i < N; i++ { 362 for j := int64(0); j < chunkSize; j++ { 363 offset := i*chunkSize + j 364 resolver, err := c.Track(ctx, offset, batchSize) 365 if err != nil { 366 b.Fatal(err) 367 } 368 resolvers[j] = resolver 369 } 370 for j := chunkSize - 1; j >= 0; j-- { 371 resolver := resolvers[j] 372 resolvers[j] = nil 373 v, ok := resolver().(int64) 374 375 exp := int64(-1) 376 377 if i > 0 { 378 exp = (i * chunkSize) - 1 379 } 380 381 if j == 0 { 382 exp = ((i + 1) * chunkSize) - 1 383 } 384 385 if exp >= 0 { 386 if !ok { 387 b.Fatal("should resolve with a maximum") 388 } else if exp != v { 389 b.Errorf("Wrong value: %v != %v", exp, v) 390 } 391 } 392 } 393 } 394 } 395 396 func BenchmarkCappedSequential(b *testing.B) { 397 resolvers := make([]func() interface{}, b.N) 398 399 b.ReportAllocs() 400 401 c := NewCapped(int64(b.N)) 402 ctx := context.Background() 403 404 var err error 405 for i := int64(0); i < int64(b.N); i++ { 406 resolvers[i], err = c.Track(ctx, i, 1) 407 if err != nil { 408 b.Fatal(err) 409 } 410 } 411 412 for i := 0; i < b.N; i++ { 413 v, ok := resolvers[i]().(int64) 414 if !ok { 415 b.Fatal("should resolve with a maximum") 416 } 417 if int64(i) != v { 418 b.Errorf("Wrong value: %v != %v", i, v) 419 } 420 } 421 } 422 423 type checkpointTester struct { 424 mu sync.Mutex 425 ctx context.Context 426 t *testing.T 427 checkpointer *Capped 428 resolvers map[int64]func() interface{} 429 } 430 431 // nolint:gocritic // Ignore unnamedResult false positive 432 func newCheckpointTester(t *testing.T, capacity int64, timeout time.Duration) (*checkpointTester, func()) { 433 ctx, cancel := context.WithTimeout(context.Background(), timeout) 434 435 return &checkpointTester{ 436 ctx: ctx, 437 t: t, 438 checkpointer: NewCapped(capacity), 439 resolvers: map[int64]func() interface{}{}, 440 }, cancel 441 } 442 443 func (c *checkpointTester) AssertNoHighest() { 444 c.t.Helper() 445 actual := c.checkpointer.Highest() 446 require.Nil(c.t, actual, "should not have a highest offset") 447 } 448 449 func (c *checkpointTester) AssertHighest(expected int64) { 450 c.t.Helper() 451 actual := c.checkpointer.Highest() 452 require.NotNil(c.t, actual, "should have a highest offset") 453 assert.Equal(c.t, expected, actual, "highest offset should match expected") 454 } 455 456 func (c *checkpointTester) AssertPending(offset int64) { 457 c.t.Helper() 458 459 c.mu.Lock() 460 _, ok := c.resolvers[offset] 461 c.mu.Unlock() 462 463 require.True(c.t, ok, "offset should be pending") 464 } 465 466 func (c *checkpointTester) AssertNotPending(offset int64) { 467 c.t.Helper() 468 469 c.mu.Lock() 470 _, ok := c.resolvers[offset] 471 c.mu.Unlock() 472 473 require.False(c.t, ok, "offset should not be pending") 474 } 475 476 func (c *checkpointTester) AssertTrackAllowed(offset, batchSize int64) { 477 c.t.Helper() 478 479 c.track(offset, batchSize) 480 } 481 482 func (c *checkpointTester) AssertTrackBlocked(offset, batchSize int64) { 483 c.t.Helper() 484 485 go c.track(offset, batchSize) 486 487 time.Sleep(50 * time.Millisecond) 488 489 c.mu.Lock() 490 _, ok := c.resolvers[offset] 491 c.mu.Unlock() 492 493 assert.False(c.t, ok, "Track call should be blocked") 494 } 495 496 func (c *checkpointTester) Resolve(offset, expectedHighest int64) { 497 c.t.Helper() 498 499 c.mu.Lock() 500 resolve, ok := c.resolvers[offset] 501 delete(c.resolvers, offset) 502 c.mu.Unlock() 503 504 require.True(c.t, ok) 505 506 actualHighest := resolve() 507 if expectedHighest == -1 { 508 assert.Nil(c.t, actualHighest, "should not yet have a highest") 509 } else if expectedHighest >= 0 { 510 require.NotNil(c.t, actualHighest, "should have a highest at this point") 511 assert.Equal(c.t, expectedHighest, actualHighest) 512 } 513 } 514 515 func (c *checkpointTester) track(offset, batchSize int64) { 516 c.t.Helper() 517 518 resolve, err := c.checkpointer.Track(c.ctx, offset, batchSize) 519 require.NoError(c.t, err, "Track should succeed") 520 c.mu.Lock() 521 c.resolvers[offset] = resolve 522 c.mu.Unlock() 523 }