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  }