github.com/m3db/m3@v1.5.0/src/dbnode/storage/bootstrap/bootstrapper/commitlog/source_data_test.go (about)

     1  // Copyright (c) 2018 Uber Technologies, Inc.
     2  //
     3  // Permission is hereby granted, free of charge, to any person obtaining a copy
     4  // of this software and associated documentation files (the "Software"), to deal
     5  // in the Software without restriction, including without limitation the rights
     6  // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
     7  // copies of the Software, and to permit persons to whom the Software is
     8  // furnished to do so, subject to the following conditions:
     9  //
    10  // The above copyright notice and this permission notice shall be included in
    11  // all copies or substantial portions of the Software.
    12  //
    13  // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
    14  // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
    15  // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
    16  // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
    17  // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
    18  // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
    19  // THE SOFTWARE.
    20  
    21  package commitlog
    22  
    23  import (
    24  	"errors"
    25  	"io"
    26  	"strings"
    27  	"testing"
    28  	"time"
    29  
    30  	"github.com/m3db/m3/src/dbnode/digest"
    31  	"github.com/m3db/m3/src/dbnode/namespace"
    32  	"github.com/m3db/m3/src/dbnode/persist"
    33  	"github.com/m3db/m3/src/dbnode/persist/fs"
    34  	"github.com/m3db/m3/src/dbnode/persist/fs/commitlog"
    35  	"github.com/m3db/m3/src/dbnode/storage/bootstrap"
    36  	"github.com/m3db/m3/src/dbnode/storage/bootstrap/result"
    37  	"github.com/m3db/m3/src/dbnode/storage/series"
    38  	"github.com/m3db/m3/src/dbnode/topology"
    39  	"github.com/m3db/m3/src/dbnode/ts"
    40  	"github.com/m3db/m3/src/dbnode/x/xio"
    41  	"github.com/m3db/m3/src/x/checked"
    42  	"github.com/m3db/m3/src/x/context"
    43  	"github.com/m3db/m3/src/x/ident"
    44  	"github.com/m3db/m3/src/x/instrument"
    45  	"github.com/m3db/m3/src/x/pool"
    46  	xtime "github.com/m3db/m3/src/x/time"
    47  
    48  	"github.com/golang/mock/gomock"
    49  	"github.com/stretchr/testify/require"
    50  )
    51  
    52  var (
    53  	testNamespaceID    = ident.StringID("commitlog_test_ns")
    54  	testDefaultRunOpts = bootstrap.NewRunOptions().SetInitialTopologyState(&topology.StateSnapshot{})
    55  
    56  	shortAnnotation = ts.Annotation("annot")
    57  	longAnnotation  = ts.Annotation(strings.Repeat("x", ts.OptimizedAnnotationLen*3))
    58  )
    59  
    60  func testNsMetadata(t *testing.T) namespace.Metadata {
    61  	md, err := namespace.NewMetadata(testNamespaceID, namespace.NewOptions())
    62  	require.NoError(t, err)
    63  	return md
    64  }
    65  
    66  func testCache(t *testing.T) bootstrap.Cache {
    67  	cache, err := bootstrap.NewCache(bootstrap.NewCacheOptions().
    68  		SetFilesystemOptions(fs.NewOptions()).
    69  		SetInstrumentOptions(instrument.NewOptions()))
    70  	require.NoError(t, err)
    71  
    72  	return cache
    73  }
    74  
    75  func TestAvailableEmptyRangeError(t *testing.T) {
    76  	var (
    77  		opts     = testDefaultOpts
    78  		src      = newCommitLogSource(opts, fs.Inspection{})
    79  		res, err = src.AvailableData(testNsMetadata(t), result.NewShardTimeRanges(), testCache(t), testDefaultRunOpts)
    80  	)
    81  	require.NoError(t, err)
    82  	require.True(t, result.NewShardTimeRanges().Equal(res))
    83  }
    84  
    85  func TestReadEmpty(t *testing.T) {
    86  	opts := testDefaultOpts
    87  
    88  	src := newCommitLogSource(opts, fs.Inspection{})
    89  	md := testNsMetadata(t)
    90  	target := result.NewShardTimeRanges()
    91  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, target, md)
    92  	defer tester.Finish()
    93  
    94  	tester.TestReadWith(src)
    95  	tester.TestUnfulfilledForNamespaceIsEmpty(md)
    96  
    97  	values, err := tester.EnsureDumpAllForNamespace(md)
    98  	require.NoError(t, err)
    99  	require.Equal(t, 0, len(values))
   100  	tester.EnsureNoLoadedBlocks()
   101  	tester.EnsureNoWrites()
   102  }
   103  
   104  func TestReadOnlyOnce(t *testing.T) {
   105  	opts := testDefaultOpts
   106  	md := testNsMetadata(t)
   107  	nsCtx := namespace.NewContextFrom(md)
   108  	src := newCommitLogSource(opts, fs.Inspection{}).(*commitLogSource)
   109  
   110  	blockSize := md.Options().RetentionOptions().BlockSize()
   111  	now := xtime.Now()
   112  	start := now.Truncate(blockSize).Add(-blockSize)
   113  	end := now.Truncate(blockSize)
   114  
   115  	ranges := xtime.NewRanges(xtime.Range{Start: start, End: end})
   116  
   117  	foo := ts.Series{Namespace: nsCtx.ID, Shard: 0, ID: ident.StringID("foo")}
   118  	bar := ts.Series{Namespace: nsCtx.ID, Shard: 1, ID: ident.StringID("bar")}
   119  	baz := ts.Series{Namespace: nsCtx.ID, Shard: 2, ID: ident.StringID("baz")}
   120  
   121  	values := testValues{
   122  		{foo, start, 1.0, xtime.Second, shortAnnotation},
   123  		{foo, start.Add(1 * time.Minute), 2.0, xtime.Second, longAnnotation},
   124  		{bar, start.Add(2 * time.Minute), 1.0, xtime.Second, longAnnotation},
   125  		{bar, start.Add(3 * time.Minute), 2.0, xtime.Second, shortAnnotation},
   126  		// "baz" is in shard 2 and should not be returned
   127  		{baz, start.Add(4 * time.Minute), 1.0, xtime.Second, shortAnnotation},
   128  	}
   129  
   130  	var commitLogReads int
   131  	src.newIteratorFn = func(
   132  		_ commitlog.IteratorOpts,
   133  	) (commitlog.Iterator, []commitlog.ErrorWithPath, error) {
   134  		commitLogReads++
   135  		return newTestCommitLogIterator(values, nil), nil, nil
   136  	}
   137  
   138  	targetRanges := result.NewShardTimeRanges().Set(0, ranges).Set(1, ranges)
   139  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, targetRanges, md)
   140  	defer tester.Finish()
   141  
   142  	// simulate 2 passes over the commit log
   143  	for i := 0; i < 2; i++ {
   144  		tester.TestReadWith(src)
   145  		tester.TestUnfulfilledForNamespaceIsEmpty(md)
   146  
   147  		read := tester.EnsureDumpWritesForNamespace(md)
   148  		require.Equal(t, 2, len(read))
   149  		enforceValuesAreCorrect(t, values[:4], read)
   150  		tester.EnsureNoLoadedBlocks()
   151  	}
   152  
   153  	// commit log should only be iterated over once.
   154  	require.Equal(t, 1, commitLogReads)
   155  }
   156  
   157  func TestReadErrorOnNewIteratorError(t *testing.T) {
   158  	opts := testDefaultOpts
   159  	src := newCommitLogSource(opts, fs.Inspection{}).(*commitLogSource)
   160  
   161  	src.newIteratorFn = func(
   162  		_ commitlog.IteratorOpts,
   163  	) (commitlog.Iterator, []commitlog.ErrorWithPath, error) {
   164  		return nil, nil, errors.New("an error")
   165  	}
   166  
   167  	now := xtime.Now()
   168  	ranges := xtime.NewRanges(xtime.Range{Start: now, End: now.Add(time.Hour)})
   169  
   170  	md := testNsMetadata(t)
   171  	target := result.NewShardTimeRanges().Set(0, ranges)
   172  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, target, md)
   173  	defer tester.Finish()
   174  
   175  	ctx := context.NewBackground()
   176  	defer ctx.Close()
   177  
   178  	res, err := src.Read(ctx, tester.Namespaces, tester.Cache)
   179  	require.Error(t, err)
   180  	require.Nil(t, res.Results)
   181  	tester.EnsureNoLoadedBlocks()
   182  	tester.EnsureNoWrites()
   183  }
   184  
   185  func TestReadOrderedValues(t *testing.T) {
   186  	opts := testDefaultOpts
   187  	md := testNsMetadata(t)
   188  	testReadOrderedValues(t, opts, md, nil)
   189  }
   190  
   191  func testReadOrderedValues(t *testing.T, opts Options, md namespace.Metadata, setAnn setAnnotation) {
   192  	nsCtx := namespace.NewContextFrom(md)
   193  
   194  	src := newCommitLogSource(opts, fs.Inspection{}).(*commitLogSource)
   195  
   196  	blockSize := md.Options().RetentionOptions().BlockSize()
   197  	now := xtime.Now()
   198  	start := now.Truncate(blockSize).Add(-blockSize)
   199  	end := now.Truncate(blockSize)
   200  
   201  	ranges := xtime.NewRanges(xtime.Range{Start: start, End: end})
   202  
   203  	foo := ts.Series{Namespace: nsCtx.ID, Shard: 0, ID: ident.StringID("foo")}
   204  	bar := ts.Series{Namespace: nsCtx.ID, Shard: 1, ID: ident.StringID("bar")}
   205  	baz := ts.Series{Namespace: nsCtx.ID, Shard: 2, ID: ident.StringID("baz")}
   206  
   207  	values := testValues{
   208  		{foo, start, 1.0, xtime.Second, longAnnotation},
   209  		{foo, start.Add(1 * time.Minute), 2.0, xtime.Second, shortAnnotation},
   210  		{bar, start.Add(2 * time.Minute), 1.0, xtime.Second, nil},
   211  		{bar, start.Add(3 * time.Minute), 2.0, xtime.Second, nil},
   212  		// "baz" is in shard 2 and should not be returned
   213  		{baz, start.Add(4 * time.Minute), 1.0, xtime.Second, nil},
   214  	}
   215  	if setAnn != nil {
   216  		values = setAnn(values)
   217  	}
   218  
   219  	src.newIteratorFn = func(
   220  		_ commitlog.IteratorOpts,
   221  	) (commitlog.Iterator, []commitlog.ErrorWithPath, error) {
   222  		return newTestCommitLogIterator(values, nil), nil, nil
   223  	}
   224  
   225  	targetRanges := result.NewShardTimeRanges().Set(0, ranges).Set(1, ranges)
   226  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, targetRanges, md)
   227  	defer tester.Finish()
   228  
   229  	tester.TestReadWith(src)
   230  	tester.TestUnfulfilledForNamespaceIsEmpty(md)
   231  
   232  	read := tester.EnsureDumpWritesForNamespace(md)
   233  	require.Equal(t, 2, len(read))
   234  	enforceValuesAreCorrect(t, values[:4], read)
   235  	tester.EnsureNoLoadedBlocks()
   236  }
   237  
   238  func TestReadUnorderedValues(t *testing.T) {
   239  	opts := testDefaultOpts
   240  	md := testNsMetadata(t)
   241  	testReadUnorderedValues(t, opts, md, nil)
   242  }
   243  
   244  func testReadUnorderedValues(t *testing.T, opts Options, md namespace.Metadata, setAnn setAnnotation) {
   245  	nsCtx := namespace.NewContextFrom(md)
   246  	src := newCommitLogSource(opts, fs.Inspection{}).(*commitLogSource)
   247  
   248  	blockSize := md.Options().RetentionOptions().BlockSize()
   249  	now := xtime.Now()
   250  	start := now.Truncate(blockSize).Add(-blockSize)
   251  	end := now.Truncate(blockSize)
   252  
   253  	ranges := xtime.NewRanges(xtime.Range{Start: start, End: end})
   254  
   255  	foo := ts.Series{Namespace: nsCtx.ID, Shard: 0, ID: ident.StringID("foo")}
   256  
   257  	values := testValues{
   258  		{foo, start.Add(10 * time.Minute), 1.0, xtime.Second, shortAnnotation},
   259  		{foo, start.Add(1 * time.Minute), 2.0, xtime.Second, nil},
   260  		{foo, start.Add(2 * time.Minute), 3.0, xtime.Second, longAnnotation},
   261  		{foo, start.Add(3 * time.Minute), 4.0, xtime.Second, nil},
   262  		{foo, start, 5.0, xtime.Second, nil},
   263  	}
   264  	if setAnn != nil {
   265  		values = setAnn(values)
   266  	}
   267  
   268  	src.newIteratorFn = func(
   269  		_ commitlog.IteratorOpts,
   270  	) (commitlog.Iterator, []commitlog.ErrorWithPath, error) {
   271  		return newTestCommitLogIterator(values, nil), nil, nil
   272  	}
   273  
   274  	targetRanges := result.NewShardTimeRanges().Set(0, ranges).Set(1, ranges)
   275  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, targetRanges, md)
   276  	defer tester.Finish()
   277  
   278  	tester.TestReadWith(src)
   279  	tester.TestUnfulfilledForNamespaceIsEmpty(md)
   280  
   281  	read := tester.EnsureDumpWritesForNamespace(md)
   282  	require.Equal(t, 1, len(read))
   283  	enforceValuesAreCorrect(t, values, read)
   284  	tester.EnsureNoLoadedBlocks()
   285  }
   286  
   287  // TestReadHandlesDifferentSeriesWithIdenticalUniqueIndex was added as a
   288  // regression test to make sure that the commit log bootstrapper does not make
   289  // any assumptions about series having a unique index because that only holds
   290  // for the duration that an M3DB node is on, but commit log files can span
   291  // multiple M3DB processes which means that unique indexes could be re-used
   292  // for multiple different series.
   293  func TestReadHandlesDifferentSeriesWithIdenticalUniqueIndex(t *testing.T) {
   294  	opts := testDefaultOpts
   295  	md := testNsMetadata(t)
   296  
   297  	nsCtx := namespace.NewContextFrom(md)
   298  	src := newCommitLogSource(opts, fs.Inspection{}).(*commitLogSource)
   299  
   300  	blockSize := md.Options().RetentionOptions().BlockSize()
   301  	now := xtime.Now()
   302  	start := now.Truncate(blockSize).Add(-blockSize)
   303  	end := now.Truncate(blockSize)
   304  
   305  	ranges := xtime.NewRanges(xtime.Range{Start: start, End: end})
   306  
   307  	// All series need to be in the same shard to exercise the regression.
   308  	foo := ts.Series{
   309  		Namespace:   nsCtx.ID,
   310  		Shard:       0,
   311  		ID:          ident.StringID("foo"),
   312  		UniqueIndex: 0,
   313  	}
   314  	bar := ts.Series{
   315  		Namespace:   nsCtx.ID,
   316  		Shard:       0,
   317  		ID:          ident.StringID("bar"),
   318  		UniqueIndex: 0,
   319  	}
   320  
   321  	values := testValues{
   322  		{foo, start, 1.0, xtime.Second, nil},
   323  		{bar, start, 2.0, xtime.Second, nil},
   324  	}
   325  
   326  	src.newIteratorFn = func(
   327  		_ commitlog.IteratorOpts,
   328  	) (commitlog.Iterator, []commitlog.ErrorWithPath, error) {
   329  		return newTestCommitLogIterator(values, nil), nil, nil
   330  	}
   331  
   332  	targetRanges := result.NewShardTimeRanges().Set(0, ranges).Set(1, ranges)
   333  	tester := bootstrap.BuildNamespacesTester(t, testDefaultRunOpts, targetRanges, md)
   334  	defer tester.Finish()
   335  
   336  	tester.TestReadWith(src)
   337  	tester.TestUnfulfilledForNamespaceIsEmpty(md)
   338  
   339  	read := tester.EnsureDumpWritesForNamespace(md)
   340  	require.Equal(t, 2, len(read))
   341  	enforceValuesAreCorrect(t, values, read)
   342  	tester.EnsureNoLoadedBlocks()
   343  }
   344  
   345  func TestItMergesSnapshotsAndCommitLogs(t *testing.T) {
   346  	opts := testDefaultOpts
   347  	md := testNsMetadata(t)
   348  
   349  	testItMergesSnapshotsAndCommitLogs(t, opts, md, nil)
   350  }
   351  
   352  func testItMergesSnapshotsAndCommitLogs(t *testing.T, opts Options,
   353  	md namespace.Metadata, setAnn setAnnotation) {
   354  	ctrl := gomock.NewController(t)
   355  	defer ctrl.Finish()
   356  
   357  	var (
   358  		nsCtx     = namespace.NewContextFrom(md)
   359  		src       = newCommitLogSource(opts, fs.Inspection{}).(*commitLogSource)
   360  		blockSize = md.Options().RetentionOptions().BlockSize()
   361  		now       = xtime.Now()
   362  		start     = now.Truncate(blockSize).Add(-blockSize)
   363  		end       = now.Truncate(blockSize)
   364  		ranges    = xtime.NewRanges()
   365  
   366  		foo             = ts.Series{Namespace: nsCtx.ID, Shard: 0, ID: ident.StringID("foo")}
   367  		commitLogValues = testValues{
   368  			{foo, start.Add(2 * time.Minute), 1.0, xtime.Nanosecond, shortAnnotation},
   369  			{foo, start.Add(3 * time.Minute), 2.0, xtime.Nanosecond, nil},
   370  			{foo, start.Add(4 * time.Minute), 3.0, xtime.Nanosecond, longAnnotation},
   371  		}
   372  	)
   373  	if setAnn != nil {
   374  		commitLogValues = setAnn(commitLogValues)
   375  	}
   376  
   377  	ranges.AddRange(xtime.Range{
   378  		Start: start,
   379  		End:   end,
   380  	})
   381  
   382  	src.newIteratorFn = func(
   383  		_ commitlog.IteratorOpts,
   384  	) (commitlog.Iterator, []commitlog.ErrorWithPath, error) {
   385  		return newTestCommitLogIterator(commitLogValues, nil), nil, nil
   386  	}
   387  
   388  	src.snapshotFilesFn = func(
   389  		filePathPrefix string,
   390  		namespace ident.ID,
   391  		shard uint32,
   392  	) (fs.FileSetFilesSlice, error) {
   393  		return fs.FileSetFilesSlice{
   394  			fs.FileSetFile{
   395  				ID: fs.FileSetFileIdentifier{
   396  					Namespace:   namespace,
   397  					BlockStart:  start,
   398  					Shard:       shard,
   399  					VolumeIndex: 0,
   400  				},
   401  				// Make sure path passes the "is snapshot" check in SnapshotTimeAndID method.
   402  				AbsoluteFilePaths:               []string{"snapshots/checkpoint"},
   403  				CachedHasCompleteCheckpointFile: fs.EvalTrue,
   404  				CachedSnapshotTime:              start.Add(time.Minute),
   405  			},
   406  		}, nil
   407  	}
   408  
   409  	mockReader := fs.NewMockDataFileSetReader(ctrl)
   410  	mockReader.EXPECT().Open(fs.ReaderOpenOptionsMatcher{
   411  		ID: fs.FileSetFileIdentifier{
   412  			Namespace:   nsCtx.ID,
   413  			BlockStart:  start,
   414  			Shard:       0,
   415  			VolumeIndex: 0,
   416  		},
   417  		FileSetType: persist.FileSetSnapshotType,
   418  	}).Return(nil).AnyTimes()
   419  	mockReader.EXPECT().Entries().Return(1).AnyTimes()
   420  	mockReader.EXPECT().Close().Return(nil).AnyTimes()
   421  
   422  	snapshotValues := testValues{
   423  		{foo, start.Add(1 * time.Minute), 1.0, xtime.Nanosecond, nil},
   424  	}
   425  	if setAnn != nil {
   426  		snapshotValues = setAnn(snapshotValues)
   427  	}
   428  
   429  	encoderPool := opts.ResultOptions().DatabaseBlockOptions().EncoderPool()
   430  	encoder := encoderPool.Get()
   431  	encoder.Reset(snapshotValues[0].t, 10, nsCtx.Schema)
   432  	for _, value := range snapshotValues {
   433  		dp := ts.Datapoint{
   434  			TimestampNanos: value.t,
   435  			Value:          value.v,
   436  		}
   437  		encoder.Encode(dp, value.u, value.a)
   438  	}
   439  
   440  	ctx := context.NewBackground()
   441  	defer ctx.Close()
   442  
   443  	reader, ok := encoder.Stream(ctx)
   444  	require.True(t, ok)
   445  
   446  	seg, err := reader.Segment()
   447  	require.NoError(t, err)
   448  
   449  	bytes, err := xio.ToBytes(reader)
   450  	require.Equal(t, io.EOF, err)
   451  	require.Equal(t, seg.Len(), len(bytes))
   452  
   453  	mockReader.EXPECT().Read().Return(
   454  		foo.ID,
   455  		ident.EmptyTagIterator,
   456  		checked.NewBytes(bytes, nil),
   457  		digest.Checksum(bytes),
   458  		nil,
   459  	)
   460  	mockReader.EXPECT().Read().Return(nil, nil, nil, uint32(0), io.EOF)
   461  
   462  	src.newReaderFn = func(
   463  		bytesPool pool.CheckedBytesPool,
   464  		opts fs.Options,
   465  	) (fs.DataFileSetReader, error) {
   466  		return mockReader, nil
   467  	}
   468  
   469  	targetRanges := result.NewShardTimeRanges().Set(0, ranges)
   470  	tester := bootstrap.BuildNamespacesTesterWithReaderIteratorPool(
   471  		t,
   472  		testDefaultRunOpts,
   473  		targetRanges,
   474  		opts.ResultOptions().DatabaseBlockOptions().MultiReaderIteratorPool(),
   475  		fs.NewOptions(),
   476  		md,
   477  	)
   478  
   479  	defer tester.Finish()
   480  	tester.TestReadWith(src)
   481  	tester.TestUnfulfilledForNamespaceIsEmpty(md)
   482  
   483  	// NB: this case is a little tricky in that this test is combining writes
   484  	// that come through both the `LoadBlock()` methods (for snapshotted data),
   485  	// and the `Write()` method  (for data that is not snapshotted) into the
   486  	// namespace data accumulator. Thus writes into the accumulated series should
   487  	// be verified against both of these methods.
   488  	read := tester.EnsureDumpWritesForNamespace(md)
   489  	require.Equal(t, 1, len(read))
   490  	enforceValuesAreCorrect(t, commitLogValues[0:3], read)
   491  
   492  	read = tester.EnsureDumpLoadedBlocksForNamespace(md)
   493  	enforceValuesAreCorrect(t, snapshotValues, read)
   494  }
   495  
   496  type setAnnotation func(testValues) testValues
   497  type annotationEqual func([]byte, []byte) bool
   498  
   499  type testValue struct {
   500  	s ts.Series
   501  	t xtime.UnixNano
   502  	v float64
   503  	u xtime.Unit
   504  	a ts.Annotation
   505  }
   506  
   507  type testValues []testValue
   508  
   509  func (v testValues) toDecodedBlockMap() bootstrap.DecodedBlockMap {
   510  	blockMap := make(bootstrap.DecodedBlockMap, len(v))
   511  	for _, bl := range v {
   512  		id := bl.s.ID.String()
   513  		val := series.DecodedTestValue{
   514  			Timestamp:  bl.t,
   515  			Value:      bl.v,
   516  			Unit:       bl.u,
   517  			Annotation: bl.a,
   518  		}
   519  
   520  		if values, found := blockMap[id]; found {
   521  			blockMap[id] = append(values, val)
   522  		} else {
   523  			blockMap[id] = bootstrap.DecodedValues{val}
   524  		}
   525  	}
   526  
   527  	return blockMap
   528  }
   529  
   530  func enforceValuesAreCorrect(
   531  	t *testing.T,
   532  	values testValues,
   533  	actual bootstrap.DecodedBlockMap,
   534  ) {
   535  	require.NoError(t, verifyValuesAreCorrect(values, actual))
   536  }
   537  
   538  func verifyValuesAreCorrect(
   539  	values testValues,
   540  	actual bootstrap.DecodedBlockMap,
   541  ) error {
   542  	expected := values.toDecodedBlockMap()
   543  	return expected.VerifyEquals(actual)
   544  }
   545  
   546  type testCommitLogIterator struct {
   547  	values testValues
   548  	idx    int
   549  	err    error
   550  	closed bool
   551  }
   552  
   553  func newTestCommitLogIterator(values testValues, err error) *testCommitLogIterator {
   554  	return &testCommitLogIterator{values: values, idx: -1, err: err}
   555  }
   556  
   557  func (i *testCommitLogIterator) Next() bool {
   558  	i.idx++
   559  	return i.idx < len(i.values)
   560  }
   561  
   562  func (i *testCommitLogIterator) Current() commitlog.LogEntry {
   563  	idx := i.idx
   564  	if idx == -1 {
   565  		idx = 0
   566  	}
   567  	v := i.values[idx]
   568  	return commitlog.LogEntry{
   569  		Series:     v.s,
   570  		Datapoint:  ts.Datapoint{TimestampNanos: v.t, Value: v.v},
   571  		Unit:       v.u,
   572  		Annotation: v.a,
   573  		Metadata: commitlog.LogEntryMetadata{
   574  			FileReadID:        uint64(idx) + 1,
   575  			SeriesUniqueIndex: v.s.UniqueIndex,
   576  		},
   577  	}
   578  }
   579  
   580  func (i *testCommitLogIterator) Err() error {
   581  	return i.err
   582  }
   583  
   584  func (i *testCommitLogIterator) Close() {
   585  	i.closed = true
   586  }