github.com/badrootd/nibiru-cometbft@v0.37.5-0.20240307173500-2a75559eee9b/libs/autofile/group_test.go (about)

     1  package autofile
     2  
     3  import (
     4  	"io"
     5  	"os"
     6  	"path/filepath"
     7  	"testing"
     8  
     9  	"github.com/stretchr/testify/assert"
    10  	"github.com/stretchr/testify/require"
    11  
    12  	cmtos "github.com/badrootd/nibiru-cometbft/libs/os"
    13  	cmtrand "github.com/badrootd/nibiru-cometbft/libs/rand"
    14  )
    15  
    16  func createTestGroupWithHeadSizeLimit(t *testing.T, headSizeLimit int64) *Group {
    17  	testID := cmtrand.Str(12)
    18  	testDir := "_test_" + testID
    19  	err := cmtos.EnsureDir(testDir, 0700)
    20  	require.NoError(t, err, "Error creating dir")
    21  
    22  	headPath := testDir + "/myfile"
    23  	g, err := OpenGroup(headPath, GroupHeadSizeLimit(headSizeLimit))
    24  	require.NoError(t, err, "Error opening Group")
    25  	require.NotEqual(t, nil, g, "Failed to create Group")
    26  
    27  	return g
    28  }
    29  
    30  func destroyTestGroup(t *testing.T, g *Group) {
    31  	g.Close()
    32  
    33  	err := os.RemoveAll(g.Dir)
    34  	require.NoError(t, err, "Error removing test Group directory")
    35  }
    36  
    37  func assertGroupInfo(t *testing.T, gInfo GroupInfo, minIndex, maxIndex int, totalSize, headSize int64) {
    38  	assert.Equal(t, minIndex, gInfo.MinIndex)
    39  	assert.Equal(t, maxIndex, gInfo.MaxIndex)
    40  	assert.Equal(t, totalSize, gInfo.TotalSize)
    41  	assert.Equal(t, headSize, gInfo.HeadSize)
    42  }
    43  
    44  func TestCheckHeadSizeLimit(t *testing.T) {
    45  	g := createTestGroupWithHeadSizeLimit(t, 1000*1000)
    46  
    47  	// At first, there are no files.
    48  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 0, 0, 0)
    49  
    50  	// Write 1000 bytes 999 times.
    51  	for i := 0; i < 999; i++ {
    52  		err := g.WriteLine(cmtrand.Str(999))
    53  		require.NoError(t, err, "Error appending to head")
    54  	}
    55  	err := g.FlushAndSync()
    56  	require.NoError(t, err)
    57  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 0, 999000, 999000)
    58  
    59  	// Even calling checkHeadSizeLimit manually won't rotate it.
    60  	g.checkHeadSizeLimit()
    61  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 0, 999000, 999000)
    62  
    63  	// Write 1000 more bytes.
    64  	err = g.WriteLine(cmtrand.Str(999))
    65  	require.NoError(t, err, "Error appending to head")
    66  	err = g.FlushAndSync()
    67  	require.NoError(t, err)
    68  
    69  	// Calling checkHeadSizeLimit this time rolls it.
    70  	g.checkHeadSizeLimit()
    71  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 1, 1000000, 0)
    72  
    73  	// Write 1000 more bytes.
    74  	err = g.WriteLine(cmtrand.Str(999))
    75  	require.NoError(t, err, "Error appending to head")
    76  	err = g.FlushAndSync()
    77  	require.NoError(t, err)
    78  
    79  	// Calling checkHeadSizeLimit does nothing.
    80  	g.checkHeadSizeLimit()
    81  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 1, 1001000, 1000)
    82  
    83  	// Write 1000 bytes 999 times.
    84  	for i := 0; i < 999; i++ {
    85  		err = g.WriteLine(cmtrand.Str(999))
    86  		require.NoError(t, err, "Error appending to head")
    87  	}
    88  	err = g.FlushAndSync()
    89  	require.NoError(t, err)
    90  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 1, 2000000, 1000000)
    91  
    92  	// Calling checkHeadSizeLimit rolls it again.
    93  	g.checkHeadSizeLimit()
    94  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 2, 2000000, 0)
    95  
    96  	// Write 1000 more bytes.
    97  	_, err = g.Head.Write([]byte(cmtrand.Str(999) + "\n"))
    98  	require.NoError(t, err, "Error appending to head")
    99  	err = g.FlushAndSync()
   100  	require.NoError(t, err)
   101  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 2, 2001000, 1000)
   102  
   103  	// Calling checkHeadSizeLimit does nothing.
   104  	g.checkHeadSizeLimit()
   105  	assertGroupInfo(t, g.ReadGroupInfo(), 0, 2, 2001000, 1000)
   106  
   107  	// Cleanup
   108  	destroyTestGroup(t, g)
   109  }
   110  
   111  func TestRotateFile(t *testing.T) {
   112  	g := createTestGroupWithHeadSizeLimit(t, 0)
   113  
   114  	// Create a different temporary directory and move into it, to make sure
   115  	// relative paths are resolved at Group creation
   116  	origDir, err := os.Getwd()
   117  	require.NoError(t, err)
   118  	defer func() {
   119  		if err := os.Chdir(origDir); err != nil {
   120  			t.Error(err)
   121  		}
   122  	}()
   123  
   124  	dir, err := os.MkdirTemp("", "rotate_test")
   125  	require.NoError(t, err)
   126  	defer os.RemoveAll(dir)
   127  	err = os.Chdir(dir)
   128  	require.NoError(t, err)
   129  
   130  	require.True(t, filepath.IsAbs(g.Head.Path))
   131  	require.True(t, filepath.IsAbs(g.Dir))
   132  
   133  	// Create and rotate files
   134  	err = g.WriteLine("Line 1")
   135  	require.NoError(t, err)
   136  	err = g.WriteLine("Line 2")
   137  	require.NoError(t, err)
   138  	err = g.WriteLine("Line 3")
   139  	require.NoError(t, err)
   140  	err = g.FlushAndSync()
   141  	require.NoError(t, err)
   142  	g.RotateFile()
   143  	err = g.WriteLine("Line 4")
   144  	require.NoError(t, err)
   145  	err = g.WriteLine("Line 5")
   146  	require.NoError(t, err)
   147  	err = g.WriteLine("Line 6")
   148  	require.NoError(t, err)
   149  	err = g.FlushAndSync()
   150  	require.NoError(t, err)
   151  
   152  	// Read g.Head.Path+"000"
   153  	body1, err := os.ReadFile(g.Head.Path + ".000")
   154  	assert.NoError(t, err, "Failed to read first rolled file")
   155  	if string(body1) != "Line 1\nLine 2\nLine 3\n" {
   156  		t.Errorf("got unexpected contents: [%v]", string(body1))
   157  	}
   158  
   159  	// Read g.Head.Path
   160  	body2, err := os.ReadFile(g.Head.Path)
   161  	assert.NoError(t, err, "Failed to read first rolled file")
   162  	if string(body2) != "Line 4\nLine 5\nLine 6\n" {
   163  		t.Errorf("got unexpected contents: [%v]", string(body2))
   164  	}
   165  
   166  	// Make sure there are no files in the current, temporary directory
   167  	files, err := os.ReadDir(".")
   168  	require.NoError(t, err)
   169  	assert.Empty(t, files)
   170  
   171  	// Cleanup
   172  	destroyTestGroup(t, g)
   173  }
   174  
   175  func TestWrite(t *testing.T) {
   176  	g := createTestGroupWithHeadSizeLimit(t, 0)
   177  
   178  	written := []byte("Medusa")
   179  	_, err := g.Write(written)
   180  	require.NoError(t, err)
   181  	err = g.FlushAndSync()
   182  	require.NoError(t, err)
   183  
   184  	read := make([]byte, len(written))
   185  	gr, err := g.NewReader(0)
   186  	require.NoError(t, err, "failed to create reader")
   187  
   188  	_, err = gr.Read(read)
   189  	assert.NoError(t, err, "failed to read data")
   190  	assert.Equal(t, written, read)
   191  
   192  	// Cleanup
   193  	destroyTestGroup(t, g)
   194  }
   195  
   196  // test that Read reads the required amount of bytes from all the files in the
   197  // group and returns no error if n == size of the given slice.
   198  func TestGroupReaderRead(t *testing.T) {
   199  	g := createTestGroupWithHeadSizeLimit(t, 0)
   200  
   201  	professor := []byte("Professor Monster")
   202  	_, err := g.Write(professor)
   203  	require.NoError(t, err)
   204  	err = g.FlushAndSync()
   205  	require.NoError(t, err)
   206  	g.RotateFile()
   207  	frankenstein := []byte("Frankenstein's Monster")
   208  	_, err = g.Write(frankenstein)
   209  	require.NoError(t, err)
   210  	err = g.FlushAndSync()
   211  	require.NoError(t, err)
   212  
   213  	totalWrittenLength := len(professor) + len(frankenstein)
   214  	read := make([]byte, totalWrittenLength)
   215  	gr, err := g.NewReader(0)
   216  	require.NoError(t, err, "failed to create reader")
   217  
   218  	n, err := gr.Read(read)
   219  	assert.NoError(t, err, "failed to read data")
   220  	assert.Equal(t, totalWrittenLength, n, "not enough bytes read")
   221  	professorPlusFrankenstein := professor
   222  	professorPlusFrankenstein = append(professorPlusFrankenstein, frankenstein...)
   223  	assert.Equal(t, professorPlusFrankenstein, read)
   224  
   225  	// Cleanup
   226  	destroyTestGroup(t, g)
   227  }
   228  
   229  // test that Read returns an error if number of bytes read < size of
   230  // the given slice. Subsequent call should return 0, io.EOF.
   231  func TestGroupReaderRead2(t *testing.T) {
   232  	g := createTestGroupWithHeadSizeLimit(t, 0)
   233  
   234  	professor := []byte("Professor Monster")
   235  	_, err := g.Write(professor)
   236  	require.NoError(t, err)
   237  	err = g.FlushAndSync()
   238  	require.NoError(t, err)
   239  	g.RotateFile()
   240  	frankenstein := []byte("Frankenstein's Monster")
   241  	frankensteinPart := []byte("Frankenstein")
   242  	_, err = g.Write(frankensteinPart) // note writing only a part
   243  	require.NoError(t, err)
   244  	err = g.FlushAndSync()
   245  	require.NoError(t, err)
   246  
   247  	totalLength := len(professor) + len(frankenstein)
   248  	read := make([]byte, totalLength)
   249  	gr, err := g.NewReader(0)
   250  	require.NoError(t, err, "failed to create reader")
   251  
   252  	// 1) n < (size of the given slice), io.EOF
   253  	n, err := gr.Read(read)
   254  	assert.Equal(t, io.EOF, err)
   255  	assert.Equal(t, len(professor)+len(frankensteinPart), n, "Read more/less bytes than it is in the group")
   256  
   257  	// 2) 0, io.EOF
   258  	n, err = gr.Read([]byte("0"))
   259  	assert.Equal(t, io.EOF, err)
   260  	assert.Equal(t, 0, n)
   261  
   262  	// Cleanup
   263  	destroyTestGroup(t, g)
   264  }
   265  
   266  func TestMinIndex(t *testing.T) {
   267  	g := createTestGroupWithHeadSizeLimit(t, 0)
   268  
   269  	assert.Zero(t, g.MinIndex(), "MinIndex should be zero at the beginning")
   270  
   271  	// Cleanup
   272  	destroyTestGroup(t, g)
   273  }
   274  
   275  func TestMaxIndex(t *testing.T) {
   276  	g := createTestGroupWithHeadSizeLimit(t, 0)
   277  
   278  	assert.Zero(t, g.MaxIndex(), "MaxIndex should be zero at the beginning")
   279  
   280  	err := g.WriteLine("Line 1")
   281  	require.NoError(t, err)
   282  	err = g.FlushAndSync()
   283  	require.NoError(t, err)
   284  	g.RotateFile()
   285  
   286  	assert.Equal(t, 1, g.MaxIndex(), "MaxIndex should point to the last file")
   287  
   288  	// Cleanup
   289  	destroyTestGroup(t, g)
   290  }