github.com/m3db/m3@v1.5.0/src/dbnode/encoding/m3tsz/encoder_test.go (about)

     1  // Copyright (c) 2016 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 m3tsz
    22  
    23  import (
    24  	"io"
    25  	"math/rand"
    26  	"testing"
    27  	"time"
    28  
    29  	"github.com/m3db/m3/src/dbnode/encoding"
    30  	"github.com/m3db/m3/src/dbnode/ts"
    31  	"github.com/m3db/m3/src/dbnode/x/xio"
    32  	"github.com/m3db/m3/src/x/checked"
    33  	"github.com/m3db/m3/src/x/context"
    34  	xtime "github.com/m3db/m3/src/x/time"
    35  
    36  	"github.com/stretchr/testify/assert"
    37  	"github.com/stretchr/testify/require"
    38  	"go.uber.org/atomic"
    39  )
    40  
    41  var (
    42  	testStartTime         = xtime.FromSeconds(1427162400)
    43  	testDeterministicSeed = int64(testStartTime)
    44  )
    45  
    46  func getTestEncoder(startTime xtime.UnixNano) *encoder {
    47  	return NewEncoder(startTime, nil, false, nil).(*encoder)
    48  }
    49  
    50  func getTestOptEncoder(startTime xtime.UnixNano) *encoder {
    51  	return NewEncoder(startTime, nil, true, nil).(*encoder)
    52  }
    53  
    54  func TestWriteDeltaOfDeltaTimeUnitUnchanged(t *testing.T) {
    55  	inputs := []struct {
    56  		delta         time.Duration
    57  		timeUnit      xtime.Unit
    58  		expectedBytes []byte
    59  		expectedPos   int
    60  	}{
    61  		{0, xtime.Second, []byte{0x0}, 1},
    62  		{32 * time.Second, xtime.Second, []byte{0x90, 0x0}, 1},
    63  		{-63 * time.Second, xtime.Second, []byte{0xa0, 0x80}, 1},
    64  		{-128 * time.Second, xtime.Second, []byte{0xd8, 0x0}, 4},
    65  		{255 * time.Second, xtime.Second, []byte{0xcf, 0xf0}, 4},
    66  		{-2048 * time.Second, xtime.Second, []byte{0xe8, 0x0}, 8},
    67  		{2047 * time.Second, xtime.Second, []byte{0xe7, 0xff}, 8},
    68  		{4096 * time.Second, xtime.Second, []byte{0xf0, 0x0, 0x1, 0x0, 0x0}, 4},
    69  		{-4096 * time.Second, xtime.Second, []byte{0xff, 0xff, 0xff, 0x0, 0x0}, 4},
    70  		{4096 * time.Second, xtime.Nanosecond, []byte{0xf0, 0x0, 0x0, 0x3b, 0x9a, 0xca, 0x0, 0x0, 0x0}, 4},
    71  		{-4096 * time.Second, xtime.Nanosecond, []byte{0xff, 0xff, 0xff, 0xc4, 0x65, 0x36, 0x0, 0x0, 0x0}, 4},
    72  	}
    73  	for _, input := range inputs {
    74  		stream := encoding.NewOStream(nil, false, nil)
    75  		tsEncoder := NewTimestampEncoder(testStartTime, input.timeUnit, encoding.NewOptions())
    76  		tsEncoder.writeDeltaOfDeltaTimeUnitUnchanged(stream, 0, input.delta, input.timeUnit)
    77  		b, p := stream.RawBytes()
    78  		require.Equal(t, input.expectedBytes, b)
    79  		require.Equal(t, input.expectedPos, p)
    80  	}
    81  }
    82  
    83  func TestWriteDeltaOfDeltaTimeUnitChanged(t *testing.T) {
    84  	inputs := []struct {
    85  		delta         time.Duration
    86  		expectedBytes []byte
    87  		expectedPos   int
    88  	}{
    89  		{0, []byte{0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0}, 8},
    90  		{32 * time.Millisecond, []byte{0x0, 0x0, 0x0, 0x0, 0x1, 0xe8, 0x48, 0x0}, 8},
    91  		{-63 * time.Microsecond, []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x9, 0xe8}, 8},
    92  	}
    93  	for _, input := range inputs {
    94  		stream := encoding.NewOStream(nil, false, nil)
    95  		tsEncoder := NewTimestampEncoder(testStartTime, xtime.Nanosecond, encoding.NewOptions())
    96  		tsEncoder.writeDeltaOfDeltaTimeUnitChanged(stream, 0, input.delta)
    97  		b, p := stream.RawBytes()
    98  		require.Equal(t, input.expectedBytes, b)
    99  		require.Equal(t, input.expectedPos, p)
   100  	}
   101  }
   102  
   103  func TestWriteValue(t *testing.T) {
   104  	encoder := getTestEncoder(testStartTime)
   105  	inputs := []struct {
   106  		previousXOR   uint64
   107  		currentXOR    uint64
   108  		expectedBytes []byte
   109  		expectedPos   int
   110  	}{
   111  		{0x4028000000000000, 0, []byte{0x0}, 1},
   112  		{0x4028000000000000, 0x0120000000000000, []byte{0x80, 0x90}, 6},
   113  		{0x0120000000000000, 0x4028000000000000, []byte{0xc1, 0x2e, 0x1, 0x40}, 2},
   114  	}
   115  	for _, input := range inputs {
   116  		encoder.Reset(testStartTime, 0, nil)
   117  		eit := FloatEncoderAndIterator{PrevXOR: input.previousXOR}
   118  		eit.writeXOR(encoder.os, input.currentXOR)
   119  		b, p := encoder.os.RawBytes()
   120  		require.Equal(t, input.expectedBytes, b)
   121  		require.Equal(t, input.expectedPos, p)
   122  	}
   123  }
   124  
   125  func TestWriteAnnotation(t *testing.T) {
   126  	inputs := []struct {
   127  		annotation    ts.Annotation
   128  		expectedBytes []byte
   129  		expectedPos   int
   130  	}{
   131  		{
   132  			annotation:  nil,
   133  			expectedPos: 0,
   134  		},
   135  		{
   136  			annotation:    []byte{0x1, 0x2},
   137  			expectedBytes: []byte{0x80, 0x20, 0x40, 0x20, 0x40},
   138  			expectedPos:   3,
   139  		},
   140  
   141  		{
   142  			annotation:    []byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff},
   143  			expectedBytes: []byte{0x80, 0x21, 0xdf, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xe0},
   144  			expectedPos:   3,
   145  		},
   146  	}
   147  	for _, input := range inputs {
   148  		stream := encoding.NewOStream(nil, false, nil)
   149  		tsEncoder := NewTimestampEncoder(0, xtime.Nanosecond, encoding.NewOptions())
   150  		tsEncoder.writeAnnotation(stream, input.annotation)
   151  		b, p := stream.RawBytes()
   152  		require.Equal(t, input.expectedBytes, b)
   153  		require.Equal(t, input.expectedPos, p)
   154  	}
   155  }
   156  
   157  func getBytes(t *testing.T, e encoding.Encoder) []byte {
   158  	ctx := context.NewBackground()
   159  	defer ctx.Close()
   160  
   161  	r, ok := e.Stream(ctx)
   162  	if !ok {
   163  		return nil
   164  	}
   165  
   166  	bytes, err := xio.ToBytes(r)
   167  	assert.Equal(t, io.EOF, err)
   168  
   169  	return bytes
   170  }
   171  
   172  func TestWriteTimeUnit(t *testing.T) {
   173  	inputs := []struct {
   174  		timeUnit       xtime.Unit
   175  		expectedResult bool
   176  		expectedBytes  []byte
   177  		expectedPos    int
   178  	}{
   179  		{
   180  			timeUnit:       xtime.None,
   181  			expectedResult: false,
   182  			expectedPos:    0,
   183  		},
   184  		{
   185  			timeUnit:       xtime.Second,
   186  			expectedResult: true,
   187  			expectedBytes:  []byte{0x80, 0x40, 0x20},
   188  			expectedPos:    3,
   189  		},
   190  		{
   191  			timeUnit:       xtime.Unit(255),
   192  			expectedResult: false,
   193  			expectedPos:    0,
   194  		},
   195  	}
   196  	for _, input := range inputs {
   197  		stream := encoding.NewOStream(nil, false, nil)
   198  		tsEncoder := NewTimestampEncoder(0, xtime.Nanosecond, encoding.NewOptions())
   199  		tsEncoder.TimeUnit = xtime.None
   200  		assert.Equal(t, input.expectedResult, tsEncoder.maybeWriteTimeUnitChange(stream, input.timeUnit))
   201  		b, p := stream.RawBytes()
   202  		assert.Equal(t, input.expectedBytes, b)
   203  		assert.Equal(t, input.expectedPos, p)
   204  	}
   205  }
   206  
   207  func TestEncodeNoAnnotation(t *testing.T) {
   208  	ctx := context.NewBackground()
   209  	defer ctx.Close()
   210  
   211  	encoder := getTestEncoder(testStartTime)
   212  	_, ok := encoder.Stream(ctx)
   213  	require.False(t, ok)
   214  
   215  	startTime := xtime.FromSeconds(1427162462)
   216  	inputs := []ts.Datapoint{
   217  		{TimestampNanos: startTime, Value: 12},
   218  		{TimestampNanos: startTime.Add(time.Second * 60), Value: 12},
   219  		{TimestampNanos: startTime.Add(time.Second * 120), Value: 24},
   220  		{TimestampNanos: startTime.Add(-time.Second * 76), Value: 24},
   221  		{TimestampNanos: startTime.Add(-time.Second * 16), Value: 24},
   222  		{TimestampNanos: startTime.Add(time.Second * 2092), Value: 15},
   223  		{TimestampNanos: startTime.Add(time.Second * 4200), Value: 12},
   224  	}
   225  	for _, input := range inputs {
   226  		encoder.Encode(input, xtime.Second, nil)
   227  	}
   228  
   229  	expectedBytes := []byte{
   230  		0x13, 0xce, 0x4c, 0xa4, 0x30, 0xcb, 0x40, 0x0, 0x9f, 0x20, 0x14, 0x0, 0x0,
   231  		0x0, 0x0, 0x0, 0x0, 0x5f, 0x8c, 0xb0, 0x3a, 0x0, 0xe1, 0x0, 0x78, 0x0, 0x0,
   232  		0x40, 0x6, 0x58, 0x76, 0x8e, 0x0, 0x0,
   233  	}
   234  	require.Equal(t, expectedBytes, getBytes(t, encoder))
   235  
   236  	expectedBuffer := []byte{
   237  		0x13, 0xce, 0x4c, 0xa4, 0x30, 0xcb, 0x40, 0x0, 0x9f, 0x20, 0x14, 0x0, 0x0,
   238  		0x0, 0x0, 0x0, 0x0, 0x5f, 0x8c, 0xb0, 0x3a, 0x0, 0xe1, 0x0, 0x78, 0x0, 0x0,
   239  		0x40, 0x6, 0x58, 0x76, 0x8c,
   240  	}
   241  
   242  	b, p := encoder.os.RawBytes()
   243  	require.Equal(t, expectedBuffer, b)
   244  	require.Equal(t, 6, p)
   245  }
   246  
   247  func TestEncodeWithAnnotation(t *testing.T) {
   248  	ctx := context.NewBackground()
   249  	defer ctx.Close()
   250  
   251  	encoder := getTestEncoder(testStartTime)
   252  	_, ok := encoder.Stream(ctx)
   253  	require.False(t, ok)
   254  
   255  	startTime := xtime.FromSeconds(1427162462)
   256  	inputs := []struct {
   257  		dp  ts.Datapoint
   258  		ant ts.Annotation
   259  	}{
   260  		{ts.Datapoint{TimestampNanos: startTime, Value: 12}, []byte{0xa}},
   261  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 60), Value: 12}, []byte{0xa}},
   262  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 120), Value: 24}, nil},
   263  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 76), Value: 24}, nil},
   264  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 16), Value: 24}, []byte{0x1, 0x2}},
   265  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 2092), Value: 15}, nil},
   266  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 4200), Value: 12}, nil},
   267  	}
   268  
   269  	for _, input := range inputs {
   270  		encoder.Encode(input.dp, xtime.Second, input.ant)
   271  	}
   272  
   273  	expectedBuffer := []byte{
   274  		0x13, 0xce, 0x4c, 0xa4, 0x30, 0xcb, 0x40, 0x0, 0x80, 0x20, 0x1, 0x53, 0xe4,
   275  		0x2, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xf1, 0x96, 0x7, 0x40, 0x10, 0x4,
   276  		0x8, 0x4, 0xb, 0x84, 0x1, 0xe0, 0x0, 0x1, 0x0, 0x19, 0x61, 0xda, 0x30,
   277  	}
   278  
   279  	b, p := encoder.os.RawBytes()
   280  	require.Equal(t, expectedBuffer, b)
   281  	require.Equal(t, 4, p)
   282  
   283  	expectedBytes := []byte{
   284  		0x13, 0xce, 0x4c, 0xa4, 0x30, 0xcb, 0x40, 0x0, 0x80, 0x20, 0x1, 0x53, 0xe4,
   285  		0x2, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xf1, 0x96, 0x7, 0x40, 0x10, 0x4,
   286  		0x8, 0x4, 0xb, 0x84, 0x1, 0xe0, 0x0, 0x1, 0x0, 0x19, 0x61, 0xda, 0x38, 0x0,
   287  	}
   288  	require.Equal(t, expectedBytes, getBytes(t, encoder))
   289  }
   290  
   291  func TestEncodeWithTimeUnit(t *testing.T) {
   292  	ctx := context.NewBackground()
   293  	defer ctx.Close()
   294  
   295  	encoder := getTestEncoder(testStartTime)
   296  	_, ok := encoder.Stream(ctx)
   297  	require.False(t, ok)
   298  
   299  	startTime := xtime.FromSeconds(1427162462)
   300  	inputs := []struct {
   301  		dp ts.Datapoint
   302  		tu xtime.Unit
   303  	}{
   304  		{ts.Datapoint{TimestampNanos: startTime, Value: 12}, xtime.Second},
   305  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 60), Value: 12}, xtime.Second},
   306  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 120), Value: 24}, xtime.Second},
   307  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 76), Value: 24}, xtime.Second},
   308  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 16), Value: 24}, xtime.Second},
   309  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Nanosecond * 15500000000), Value: 15}, xtime.Nanosecond},
   310  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Millisecond * 1400), Value: 12}, xtime.Millisecond},
   311  		{ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 10), Value: 12}, xtime.Second},
   312  		{ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 10), Value: 12}, xtime.Second},
   313  	}
   314  
   315  	for _, input := range inputs {
   316  		encoder.Encode(input.dp, input.tu, nil)
   317  	}
   318  
   319  	expectedBytes := []byte{
   320  		0x13, 0xce, 0x4c, 0xa4, 0x30, 0xcb, 0x40, 0x0, 0x9f, 0x20, 0x14, 0x0, 0x0,
   321  		0x0, 0x0, 0x0, 0x0, 0x5f, 0x8c, 0xb0, 0x3a, 0x0, 0xe1, 0x0, 0x40, 0x20,
   322  		0x4f, 0xff, 0xff, 0xff, 0x22, 0x58, 0x60, 0xd0, 0xc, 0xb0, 0xee, 0x1, 0x1,
   323  		0x0, 0x0, 0x0, 0x1, 0xa4, 0x36, 0x76, 0x80, 0x47, 0x0, 0x80, 0x7f, 0xff,
   324  		0xff, 0xff, 0x7f, 0xd9, 0x9a, 0x80, 0x11, 0x44, 0x0,
   325  	}
   326  	require.Equal(t, expectedBytes, getBytes(t, encoder))
   327  }
   328  
   329  func TestEncodeWithAnnotationAndTimeUnit(t *testing.T) {
   330  	ctx := context.NewBackground()
   331  	defer ctx.Close()
   332  
   333  	encoder := getTestEncoder(testStartTime)
   334  	_, ok := encoder.Stream(ctx)
   335  	require.False(t, ok)
   336  
   337  	startTime := xtime.FromSeconds(1427162462)
   338  	inputs := []struct {
   339  		dp  ts.Datapoint
   340  		ant ts.Annotation
   341  		tu  xtime.Unit
   342  	}{
   343  		{
   344  			dp:  ts.Datapoint{TimestampNanos: startTime, Value: 12},
   345  			ant: []byte{0xa},
   346  			tu:  xtime.Second,
   347  		},
   348  		{
   349  			dp:  ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 60), Value: 12},
   350  			ant: nil,
   351  			tu:  xtime.Second,
   352  		},
   353  		{
   354  			dp:  ts.Datapoint{TimestampNanos: startTime.Add(time.Second * 120), Value: 24},
   355  			ant: nil,
   356  			tu:  xtime.Second,
   357  		},
   358  		{
   359  			dp:  ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 76), Value: 24},
   360  			ant: []byte{0x1, 0x2},
   361  			tu:  xtime.Second,
   362  		},
   363  		{
   364  			dp:  ts.Datapoint{TimestampNanos: startTime.Add(-time.Second * 16), Value: 24},
   365  			ant: nil,
   366  			tu:  xtime.Millisecond,
   367  		},
   368  		{
   369  			dp:  ts.Datapoint{TimestampNanos: startTime.Add(-time.Millisecond * 15500), Value: 15},
   370  			ant: []byte{0x3, 0x4, 0x5},
   371  			tu:  xtime.Millisecond,
   372  		},
   373  		{
   374  			dp:  ts.Datapoint{TimestampNanos: startTime.Add(-time.Millisecond * 14000), Value: 12},
   375  			ant: nil,
   376  			tu:  xtime.Second,
   377  		},
   378  	}
   379  
   380  	for _, input := range inputs {
   381  		encoder.Encode(input.dp, input.tu, input.ant)
   382  	}
   383  
   384  	expectedBytes := []byte{
   385  		0x13, 0xce, 0x4c, 0xa4, 0x30, 0xcb, 0x40, 0x0, 0x80, 0x20, 0x1, 0x53, 0xe4,
   386  		0x2, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0xb, 0xf1, 0x96, 0x6, 0x0, 0x81, 0x0,
   387  		0x81, 0x68, 0x2, 0x1, 0x1, 0x0, 0x0, 0x0, 0x1d, 0xcd, 0x65, 0x0, 0x0, 0x20,
   388  		0x8, 0x20, 0x18, 0x20, 0x2f, 0xf, 0xa6, 0x58, 0x77, 0x0, 0x80, 0x40, 0x0,
   389  		0x0, 0x0, 0xe, 0xe6, 0xb2, 0x80, 0x23, 0x80, 0x0,
   390  	}
   391  
   392  	require.Equal(t, expectedBytes, getBytes(t, encoder))
   393  }
   394  
   395  func TestInitTimeUnit(t *testing.T) {
   396  	inputs := []struct {
   397  		start    time.Time
   398  		tu       xtime.Unit
   399  		expected xtime.Unit
   400  	}{
   401  		{time.Unix(1, 0), xtime.Second, xtime.Second},
   402  		{time.Unix(1, 1000), xtime.Second, xtime.None},
   403  		{time.Unix(1, 1000), xtime.Microsecond, xtime.Microsecond},
   404  		{time.Unix(1, 1000), xtime.None, xtime.None},
   405  		{time.Unix(1, 1000), xtime.Unit(9), xtime.None},
   406  	}
   407  	for _, input := range inputs {
   408  		require.Equal(t, input.expected, initialTimeUnit(xtime.ToUnixNano(input.start), input.tu))
   409  	}
   410  }
   411  
   412  func TestEncoderResets(t *testing.T) {
   413  	ctx := context.NewBackground()
   414  	defer ctx.Close()
   415  
   416  	enc := getTestOptEncoder(testStartTime)
   417  	defer enc.Close()
   418  
   419  	require.Equal(t, 0, enc.os.Len())
   420  	_, ok := enc.Stream(ctx)
   421  	require.False(t, ok)
   422  
   423  	err := enc.Encode(ts.Datapoint{TimestampNanos: testStartTime, Value: 12}, xtime.Second, nil)
   424  	require.NoError(t, err)
   425  	require.True(t, enc.os.Len() > 0)
   426  
   427  	now := xtime.Now()
   428  	enc.Reset(now, 0, nil)
   429  	require.Equal(t, 0, enc.os.Len())
   430  	_, ok = enc.Stream(ctx)
   431  	require.False(t, ok)
   432  	b, _ := enc.os.RawBytes()
   433  	require.Equal(t, []byte{}, b)
   434  
   435  	err = enc.Encode(ts.Datapoint{TimestampNanos: now, Value: 13}, xtime.Second, nil)
   436  	require.NoError(t, err)
   437  	require.True(t, enc.os.Len() > 0)
   438  
   439  	enc.DiscardReset(now, 0, nil)
   440  	require.Equal(t, 0, enc.os.Len())
   441  	_, ok = enc.Stream(ctx)
   442  	require.False(t, ok)
   443  	b, _ = enc.os.RawBytes()
   444  	require.Equal(t, []byte{}, b)
   445  }
   446  
   447  func TestEncoderNumEncoded(t *testing.T) {
   448  	testMultiplePasses(t, multiplePassesTest{
   449  		postEncodeAll: func(enc *encoder, numDatapointsEncoded int) {
   450  			assert.Equal(t, numDatapointsEncoded, enc.NumEncoded())
   451  		},
   452  	})
   453  }
   454  
   455  func TestEncoderLastEncoded(t *testing.T) {
   456  	testMultiplePasses(t, multiplePassesTest{
   457  		postEncodeDatapoint: func(enc *encoder, datapoint ts.Datapoint) {
   458  			last, err := enc.LastEncoded()
   459  			require.NoError(t, err)
   460  			assert.Equal(t, datapoint.TimestampNanos, last.TimestampNanos)
   461  			assert.Equal(t, datapoint.Value, datapoint.Value)
   462  		},
   463  	})
   464  }
   465  
   466  func TestEncoderLenReturnsFinalStreamLength(t *testing.T) {
   467  	testMultiplePasses(t, multiplePassesTest{
   468  		postEncodeAll: func(enc *encoder, numDatapointsEncoded int) {
   469  			ctx := context.NewBackground()
   470  			defer ctx.BlockingClose()
   471  
   472  			encLen := enc.Len()
   473  			stream, ok := enc.Stream(ctx)
   474  
   475  			var streamLen int
   476  			if ok {
   477  				segment, err := stream.Segment()
   478  				require.NoError(t, err)
   479  				streamLen = segment.Len()
   480  			}
   481  			require.Equal(t, streamLen, encLen)
   482  		},
   483  	})
   484  }
   485  
   486  func TestEncoderEmpty(t *testing.T) {
   487  	testMultiplePasses(t, multiplePassesTest{
   488  		postEncodeAll: func(enc *encoder, numDatapointsEncoded int) {
   489  			assert.Equal(t, numDatapointsEncoded == 0, enc.Empty())
   490  		},
   491  	})
   492  }
   493  
   494  type testFinalizer struct {
   495  	t                *testing.T
   496  	numActiveStreams *atomic.Int32
   497  }
   498  
   499  func (f *testFinalizer) FinalizeBytes(bytes checked.Bytes) {
   500  	require.Equal(f.t, int32(0), f.numActiveStreams.Load(),
   501  		"expect 0 active streams when finalizing the byte slice")
   502  }
   503  
   504  func TestEncoderCloseWaitForStream(t *testing.T) {
   505  	numActiveStreams := atomic.NewInt32(0)
   506  
   507  	finalizer := &testFinalizer{t: t, numActiveStreams: numActiveStreams}
   508  	bytesOptions := checked.NewBytesOptions().SetFinalizer(finalizer)
   509  	cb := checked.NewBytes(make([]byte, 16), bytesOptions)
   510  	enc := NewEncoder(testStartTime, cb, false, nil).(*encoder)
   511  	defer enc.Close()
   512  
   513  	numStreams := 8
   514  	for i := 0; i <= numStreams; i++ {
   515  		numActiveStreams.Inc()
   516  		ctx := context.NewBackground()
   517  		_, ok := enc.Stream(ctx)
   518  		require.True(t, ok)
   519  		go func(ctx context.Context, idx int) {
   520  			time.Sleep(time.Duration(idx*rand.Intn(100)) * time.Millisecond)
   521  			numActiveStreams.Dec()
   522  			ctx.Close()
   523  		}(ctx, i)
   524  	}
   525  }
   526  
   527  type multiplePassesTest struct {
   528  	preEncodeAll        func(enc *encoder, numDatapointsToEncode int)
   529  	preEncodeDatapoint  func(enc *encoder, datapoint ts.Datapoint)
   530  	postEncodeDatapoint func(enc *encoder, datapoint ts.Datapoint)
   531  	postEncodeAll       func(enc *encoder, numDatapointsEncoded int)
   532  }
   533  
   534  func testMultiplePasses(t *testing.T, test multiplePassesTest) {
   535  	src := rand.NewSource(testDeterministicSeed)
   536  	rng := rand.New(src)
   537  	maxValues := 512
   538  
   539  	for n := 0; n < 1024; n++ {
   540  		encoder := getTestEncoder(testStartTime)
   541  
   542  		numValues := int(rng.Int63()) % maxValues
   543  		// Check boundary cases
   544  		switch n {
   545  		case 0:
   546  			numValues = 0
   547  		case 1:
   548  			numValues = 1
   549  		}
   550  
   551  		now := testStartTime
   552  		if test.preEncodeAll != nil {
   553  			test.preEncodeAll(encoder, numValues)
   554  		}
   555  
   556  		for i := 0; i < numValues; i++ {
   557  			now = now.Add(time.Duration(rng.Int63()) % time.Minute)
   558  			value := ts.Datapoint{
   559  				TimestampNanos: now,
   560  				Value:          rng.NormFloat64(),
   561  			}
   562  
   563  			if test.preEncodeDatapoint != nil {
   564  				test.preEncodeDatapoint(encoder, value)
   565  			}
   566  
   567  			err := encoder.Encode(value, xtime.Nanosecond, nil)
   568  			require.NoError(t, err)
   569  
   570  			if test.postEncodeDatapoint != nil {
   571  				test.postEncodeDatapoint(encoder, value)
   572  			}
   573  		}
   574  
   575  		if test.postEncodeAll != nil {
   576  			test.postEncodeAll(encoder, numValues)
   577  		}
   578  	}
   579  }
   580  
   581  func TestEncoderFailOnDeltaOfDeltaOverflow(t *testing.T) {
   582  	tests := []struct {
   583  		name           string
   584  		delta          time.Duration
   585  		units          xtime.Unit
   586  		positiveErrMsg string
   587  		negativeErrMsg string
   588  	}{
   589  		{
   590  			name:  "seconds, short gap",
   591  			delta: time.Hour,
   592  			units: xtime.Second,
   593  		},
   594  		{
   595  			name:  "seconds, huge gap",
   596  			delta: 25 * 24 * time.Hour,
   597  			units: xtime.Second,
   598  		},
   599  		{
   600  			name:           "seconds, too big gap",
   601  			delta:          1000 * 25 * 24 * time.Hour, // more than 2^31 s
   602  			units:          xtime.Second,
   603  			positiveErrMsg: "deltaOfDelta value 2160000000 s overflows 32 bits",
   604  			negativeErrMsg: "deltaOfDelta value -2159999999 s overflows 32 bits",
   605  		},
   606  		{
   607  			name:  "milliseconds, short gap",
   608  			delta: time.Hour,
   609  			units: xtime.Millisecond,
   610  		},
   611  		{
   612  			name:  "milliseconds, almost too big gap",
   613  			delta: 24 * 24 * time.Hour, // slightly less than 2^31 ms
   614  			units: xtime.Millisecond,
   615  		},
   616  		{
   617  			name:           "milliseconds, too big gap",
   618  			delta:          25 * 24 * time.Hour, // more than 2^31 ms
   619  			units:          xtime.Millisecond,
   620  			positiveErrMsg: "deltaOfDelta value 2160000000 ms overflows 32 bits",
   621  			negativeErrMsg: "deltaOfDelta value -2159999999 ms overflows 32 bits",
   622  		},
   623  		{
   624  			name:  "microseconds, short gap",
   625  			delta: time.Hour,
   626  			units: xtime.Microsecond,
   627  		},
   628  		{
   629  			name:  "microseconds, huge gap",
   630  			delta: 25 * 24 * time.Hour,
   631  			units: xtime.Microsecond,
   632  		},
   633  		{
   634  			name:  "nanoseconds, short gap",
   635  			delta: time.Hour,
   636  			units: xtime.Nanosecond,
   637  		},
   638  		{
   639  			name:  "nanoseconds, huge gap",
   640  			delta: 25 * 24 * time.Hour,
   641  			units: xtime.Nanosecond,
   642  		},
   643  	}
   644  
   645  	for _, tt := range tests {
   646  		t.Run(tt.name, func(t *testing.T) {
   647  			testPositiveDeltaOfDelta(t, tt.delta, tt.units, tt.positiveErrMsg)
   648  			testNegativeDeltaOfDelta(t, tt.delta, tt.units, tt.negativeErrMsg)
   649  		})
   650  	}
   651  }
   652  
   653  func testPositiveDeltaOfDelta(t *testing.T, delta time.Duration, units xtime.Unit, expectedErrMsg string) {
   654  	t.Helper()
   655  
   656  	ctx := context.NewBackground()
   657  	defer ctx.Close()
   658  
   659  	enc := getTestEncoder(testStartTime)
   660  
   661  	dp1 := dp(testStartTime, 1)
   662  	dp2 := dp(testStartTime.Add(delta), 2)
   663  
   664  	err := enc.Encode(dp1, units, nil)
   665  	require.NoError(t, err)
   666  
   667  	err = enc.Encode(dp2, units, nil)
   668  	if expectedErrMsg != "" {
   669  		require.EqualError(t, err, expectedErrMsg)
   670  		return
   671  	}
   672  	require.NoError(t, err)
   673  
   674  	dec := NewDecoder(enc.intOptimized, enc.opts)
   675  	stream, ok := enc.Stream(ctx)
   676  	require.True(t, ok)
   677  
   678  	it := dec.Decode(stream)
   679  	defer it.Close()
   680  
   681  	decodeAndCheck(t, it, dp1, units)
   682  	decodeAndCheck(t, it, dp2, units)
   683  
   684  	require.False(t, it.Next())
   685  	require.NoError(t, it.Err())
   686  }
   687  
   688  func testNegativeDeltaOfDelta(
   689  	t *testing.T, delta time.Duration, units xtime.Unit, expectedErrMsg string,
   690  ) {
   691  	t.Helper()
   692  
   693  	ctx := context.NewBackground()
   694  	defer ctx.Close()
   695  
   696  	oneUnit, err := units.Value()
   697  	require.NoError(t, err)
   698  
   699  	enc := getTestEncoder(testStartTime)
   700  
   701  	dps := []ts.Datapoint{
   702  		dp(testStartTime, 1),
   703  		dp(testStartTime.Add(delta/2), 2),
   704  		dp(testStartTime.Add(delta/2+delta), 3),
   705  		dp(testStartTime.Add(delta/2+delta+oneUnit), 4), // DoD = oneUnit - delta
   706  	}
   707  
   708  	for i, dp := range dps {
   709  		err = enc.Encode(dp, units, nil)
   710  		if i == 3 && expectedErrMsg != "" {
   711  			require.EqualError(t, err, expectedErrMsg)
   712  			return
   713  		}
   714  		require.NoError(t, err)
   715  	}
   716  
   717  	dec := NewDecoder(enc.intOptimized, enc.opts)
   718  	stream, ok := enc.Stream(ctx)
   719  	require.True(t, ok)
   720  
   721  	it := dec.Decode(stream)
   722  	defer it.Close()
   723  
   724  	for _, dp := range dps {
   725  		decodeAndCheck(t, it, dp, units)
   726  	}
   727  
   728  	require.False(t, it.Next())
   729  	require.NoError(t, it.Err())
   730  }
   731  
   732  func dp(timestamp xtime.UnixNano, value float64) ts.Datapoint {
   733  	return ts.Datapoint{
   734  		TimestampNanos: timestamp,
   735  		Value:          value,
   736  	}
   737  }
   738  
   739  func decodeAndCheck(
   740  	t *testing.T, it encoding.ReaderIterator, dp ts.Datapoint, units xtime.Unit,
   741  ) {
   742  	t.Helper()
   743  
   744  	require.True(t, it.Next())
   745  	decodedDP, decodedUnits, _ := it.Current()
   746  	assert.Equal(t, dp, decodedDP)
   747  	assert.Equal(t, units, decodedUnits)
   748  }