github.com/dolthub/dolt/go@v0.40.5-0.20240520175717-68db7794bea6/store/nbs/reflog_ring_buffer_test.go (about)

     1  // Copyright 2023 Dolthub, Inc.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package nbs
    16  
    17  import (
    18  	"fmt"
    19  	"testing"
    20  	"time"
    21  
    22  	"github.com/stretchr/testify/assert"
    23  	"github.com/stretchr/testify/require"
    24  )
    25  
    26  // TestIteration asserts that we can iterate over the contents of a reflog ring buffer as the ring buffer grows.
    27  func TestIteration(t *testing.T) {
    28  	buffer := newReflogRingBuffer(5)
    29  
    30  	// Assert that Iterate returns the correct items in the correct order when the buffer
    31  	// contains fewer items than the requested buffer size.
    32  	insertTestRecord(buffer, "aaaa")
    33  	insertTestRecord(buffer, "bbbb")
    34  	insertTestRecord(buffer, "cccc")
    35  	assertExpectedIterationOrder(t, buffer, []string{"aaaa", "bbbb", "cccc"})
    36  
    37  	// Assert that Iterate returns the correct items in the correct order when the buffer
    38  	// contains the same number of items as the requested buffer size.
    39  	insertTestRecord(buffer, "dddd")
    40  	insertTestRecord(buffer, "eeee")
    41  	assertExpectedIterationOrder(t, buffer, []string{"aaaa", "bbbb", "cccc", "dddd", "eeee"})
    42  
    43  	// Insert two new records that cause the buffer to exclude the first two records
    44  	insertTestRecord(buffer, "ffff")
    45  	insertTestRecord(buffer, "gggg")
    46  	assertExpectedIterationOrder(t, buffer, []string{"cccc", "dddd", "eeee", "ffff", "gggg"})
    47  
    48  	// Insert three records to fill up the buffer's internal capacity
    49  	insertTestRecord(buffer, "hhhh")
    50  	insertTestRecord(buffer, "iiii")
    51  	insertTestRecord(buffer, "jjjj")
    52  	assertExpectedIterationOrder(t, buffer, []string{"ffff", "gggg", "hhhh", "iiii", "jjjj"})
    53  
    54  	// Insert four records to test the buffer wrapping around for the first time
    55  	insertTestRecord(buffer, "kkkk")
    56  	insertTestRecord(buffer, "llll")
    57  	insertTestRecord(buffer, "mmmm")
    58  	insertTestRecord(buffer, "nnnn")
    59  	assertExpectedIterationOrder(t, buffer, []string{"jjjj", "kkkk", "llll", "mmmm", "nnnn"})
    60  
    61  	// Insert 10 records to test the buffer wrapping around a second time
    62  	insertTestRecord(buffer, "oooo")
    63  	insertTestRecord(buffer, "pppp")
    64  	insertTestRecord(buffer, "qqqq")
    65  	insertTestRecord(buffer, "rrrr")
    66  	insertTestRecord(buffer, "ssss")
    67  	insertTestRecord(buffer, "tttt")
    68  	insertTestRecord(buffer, "uuuu")
    69  	insertTestRecord(buffer, "vvvv")
    70  	insertTestRecord(buffer, "wwww")
    71  	insertTestRecord(buffer, "xxxx")
    72  	assertExpectedIterationOrder(t, buffer, []string{"tttt", "uuuu", "vvvv", "wwww", "xxxx"})
    73  }
    74  
    75  // TestTruncate asserts that the Truncate works correctly regardless of how much data
    76  // is currently stored in the buffer.
    77  func TestTruncate(t *testing.T) {
    78  	buffer := newReflogRingBuffer(5)
    79  
    80  	// When the buffer is empty, Truncate is a no-op
    81  	buffer.Truncate()
    82  	assertExpectedIterationOrder(t, buffer, []string{})
    83  	buffer.Truncate()
    84  	assertExpectedIterationOrder(t, buffer, []string{})
    85  
    86  	// When the buffer contains a single item
    87  	insertTestRecord(buffer, "aaaa")
    88  	buffer.Truncate()
    89  	assertExpectedIterationOrder(t, buffer, []string{})
    90  	buffer.Truncate()
    91  	assertExpectedIterationOrder(t, buffer, []string{})
    92  
    93  	// When the buffer is not full, Truncate empties the buffer
    94  	insertTestRecord(buffer, "bbbb")
    95  	insertTestRecord(buffer, "cccc")
    96  	insertTestRecord(buffer, "dddd")
    97  	buffer.Truncate()
    98  	assertExpectedIterationOrder(t, buffer, []string{})
    99  
   100  	// When the buffer is full, Truncate empties the buffer
   101  	insertTestRecord(buffer, "aaaa")
   102  	insertTestRecord(buffer, "bbbb")
   103  	insertTestRecord(buffer, "cccc")
   104  	insertTestRecord(buffer, "dddd")
   105  	insertTestRecord(buffer, "eeee")
   106  	insertTestRecord(buffer, "ffff")
   107  	insertTestRecord(buffer, "gggg")
   108  	insertTestRecord(buffer, "hhhh")
   109  	insertTestRecord(buffer, "iiii")
   110  	insertTestRecord(buffer, "jjjj")
   111  	insertTestRecord(buffer, "kkkk")
   112  	insertTestRecord(buffer, "llll")
   113  	insertTestRecord(buffer, "mmmm")
   114  	buffer.Truncate()
   115  	assertExpectedIterationOrder(t, buffer, []string{})
   116  }
   117  
   118  // TestIterationConflict asserts that when iterating through a reflog ring buffer and new items are written to the
   119  // buffer and wrap around into the iteration range, that iteration stops early and an error is returned.
   120  func TestIterationConflict(t *testing.T) {
   121  	buffer := newReflogRingBuffer(5)
   122  	buffer.Push(reflogRootHashEntry{"aaaa", time.Now()})
   123  	buffer.Push(reflogRootHashEntry{"bbbb", time.Now()})
   124  	buffer.Push(reflogRootHashEntry{"cccc", time.Now()})
   125  	buffer.Push(reflogRootHashEntry{"dddd", time.Now()})
   126  	buffer.Push(reflogRootHashEntry{"eeee", time.Now()})
   127  
   128  	iterationCount := 0
   129  	err := buffer.Iterate(func(item reflogRootHashEntry) error {
   130  		for i := 0; i < 100; i++ {
   131  			buffer.Push(reflogRootHashEntry{fmt.Sprintf("i-%d", i), time.Now()})
   132  		}
   133  		iterationCount++
   134  		return nil
   135  	})
   136  	require.Error(t, err)
   137  	require.Equal(t, errUnsafeIteration, err)
   138  	require.True(t, iterationCount < 5)
   139  }
   140  
   141  func insertTestRecord(buffer *reflogRingBuffer, root string) {
   142  	buffer.Push(reflogRootHashEntry{
   143  		root:      root,
   144  		timestamp: time.Now(),
   145  	})
   146  }
   147  
   148  func assertExpectedIterationOrder(t *testing.T, buffer *reflogRingBuffer, expectedRoots []string) {
   149  	i := 0
   150  	err := buffer.Iterate(func(item reflogRootHashEntry) error {
   151  		assert.Equal(t, expectedRoots[i], item.root)
   152  		assert.False(t, time.Time.IsZero(item.timestamp))
   153  		i++
   154  		return nil
   155  	})
   156  	assert.NoError(t, err)
   157  	assert.Equal(t, len(expectedRoots), i)
   158  }