github.com/yandex/pandora@v0.5.32/core/aggregator/encoder_test.go (about)

     1  package aggregator
     2  
     3  import (
     4  	"context"
     5  	"fmt"
     6  	"io"
     7  	"testing"
     8  	"time"
     9  
    10  	multierror "github.com/hashicorp/go-multierror"
    11  	"github.com/pkg/errors"
    12  	"github.com/stretchr/testify/assert"
    13  	"github.com/stretchr/testify/mock"
    14  	"github.com/stretchr/testify/require"
    15  	"github.com/yandex/pandora/core"
    16  	aggregatemock "github.com/yandex/pandora/core/aggregator/mocks"
    17  	coremock "github.com/yandex/pandora/core/mocks"
    18  	iomock "github.com/yandex/pandora/lib/ioutil2/mocks"
    19  	"github.com/yandex/pandora/lib/testutil"
    20  	"go.uber.org/zap"
    21  )
    22  
    23  type EncoderAggregatorTester struct {
    24  	t          testutil.TestingT
    25  	wc         *iomock.WriteCloser
    26  	sink       *coremock.DataSink
    27  	enc        *aggregatemock.SampleEncoder
    28  	newEncoder NewSampleEncoder
    29  	conf       EncoderAggregatorConfig
    30  	ctx        context.Context
    31  	cancel     context.CancelFunc
    32  	deps       core.AggregatorDeps
    33  
    34  	flushCB func()
    35  }
    36  
    37  func (tr *EncoderAggregatorTester) Testee() core.Aggregator {
    38  	return NewEncoderAggregator(tr.newEncoder, tr.conf)
    39  }
    40  
    41  func (tr *EncoderAggregatorTester) AssertExpectations() {
    42  	t := tr.t
    43  	tr.enc.AssertExpectations(t)
    44  	tr.wc.AssertExpectations(t)
    45  	tr.sink.AssertExpectations(t)
    46  }
    47  
    48  func NewEncoderAggregatorTester(t testutil.TestingT) *EncoderAggregatorTester {
    49  	testutil.ReplaceGlobalLogger()
    50  	tr := &EncoderAggregatorTester{t: t}
    51  	tr.wc = &iomock.WriteCloser{}
    52  	tr.sink = &coremock.DataSink{}
    53  	tr.sink.On("OpenSink").Once().Return(tr.wc, nil)
    54  	tr.enc = &aggregatemock.SampleEncoder{}
    55  
    56  	tr.newEncoder = func(w io.Writer, flushCB func()) SampleEncoder {
    57  		assert.Equal(t, tr.wc, w)
    58  		tr.flushCB = flushCB
    59  		return tr.enc
    60  	}
    61  	tr.conf = EncoderAggregatorConfig{
    62  		Sink:           tr.sink,
    63  		FlushInterval:  time.Second,
    64  		ReporterConfig: ReporterConfig{100},
    65  	}
    66  	tr.ctx, tr.cancel = context.WithCancel(context.Background())
    67  	tr.deps = core.AggregatorDeps{Log: zap.L()}
    68  	return tr
    69  }
    70  
    71  func TestEncoderAggregator(t *testing.T) {
    72  	tr := NewEncoderAggregatorTester(t)
    73  	runErr := make(chan error, 1)
    74  
    75  	testee := tr.Testee()
    76  	go func() {
    77  		runErr <- testee.Run(tr.ctx, tr.deps)
    78  	}()
    79  
    80  	for i := 0; i < 10; i++ {
    81  		tr.enc.On("Encode", i).Once().Return(nil)
    82  		testee.Report(i)
    83  	}
    84  
    85  	tr.enc.On("Flush").Once().Return(func() error {
    86  		tr.wc.On("Close").Once().Return(nil)
    87  		return nil
    88  	})
    89  
    90  	tr.cancel()
    91  	err := <-runErr
    92  	require.NoError(t, err)
    93  
    94  	tr.AssertExpectations()
    95  
    96  	assert.NotPanics(t, func() {
    97  		testee.Report(100)
    98  	})
    99  }
   100  
   101  func TestEncoderAggregator_HandleQueueBeforeFinish(t *testing.T) {
   102  	tr := NewEncoderAggregatorTester(t)
   103  	testee := tr.Testee()
   104  
   105  	for i := 0; i < 10; i++ {
   106  		tr.enc.On("Encode", i).Once().Return(nil)
   107  		testee.Report(i)
   108  	}
   109  	tr.enc.On("Flush").Once().Return(func() error {
   110  		tr.wc.On("Close").Once().Return(nil)
   111  		return nil
   112  	})
   113  
   114  	tr.cancel()
   115  	err := testee.Run(tr.ctx, tr.deps)
   116  	require.NoError(t, err)
   117  
   118  	tr.AssertExpectations()
   119  }
   120  
   121  func TestEncoderAggregator_CloseSampleEncoder(t *testing.T) {
   122  	tr := NewEncoderAggregatorTester(t)
   123  	newWOCloseEncoder := tr.newEncoder
   124  	tr.newEncoder = func(w io.Writer, onFlush func()) SampleEncoder {
   125  		encoder := newWOCloseEncoder(w, onFlush).(*aggregatemock.SampleEncoder)
   126  		return MockSampleEncoderAddCloser{encoder}
   127  	}
   128  	testee := tr.Testee()
   129  
   130  	tr.enc.On("Encode", 0).Once().Return(nil)
   131  	testee.Report(0)
   132  
   133  	tr.enc.On("Close").Once().Return(func() error {
   134  		tr.wc.On("Close").Once().Return(nil)
   135  		return nil
   136  	})
   137  
   138  	tr.cancel()
   139  	err := testee.Run(tr.ctx, tr.deps)
   140  	require.NoError(t, err)
   141  	tr.AssertExpectations()
   142  }
   143  
   144  func TestEncoderAggregator_EverythingFailed(t *testing.T) {
   145  	tr := NewEncoderAggregatorTester(t)
   146  	tr.conf.ReporterConfig.SampleQueueSize = 1
   147  	testee := tr.Testee()
   148  
   149  	var (
   150  		encodeErr  = fmt.Errorf("encode")
   151  		flushErr   = fmt.Errorf("flush")
   152  		wcCloseErr = fmt.Errorf("wc close")
   153  	)
   154  	tr.enc.On("Encode", 0).Once().Return(encodeErr)
   155  	testee.Report(0)
   156  	testee.Report(1) // Dropped
   157  
   158  	tr.enc.On("Flush").Once().Return(func() error {
   159  		tr.wc.On("Close").Once().Return(wcCloseErr)
   160  		return flushErr
   161  	})
   162  
   163  	tr.cancel()
   164  	err := testee.Run(tr.ctx, tr.deps)
   165  	require.Error(t, err)
   166  
   167  	wrappedErrors := err.(*multierror.Error).WrappedErrors()
   168  	var causes []error
   169  	for _, err := range wrappedErrors {
   170  		causes = append(causes, errors.Cause(err))
   171  	}
   172  	expectedErrors := []error{encodeErr, flushErr, wcCloseErr, &SomeSamplesDropped{1}}
   173  	assert.Equal(t, expectedErrors, causes)
   174  
   175  	tr.AssertExpectations()
   176  }
   177  
   178  func TestEncoderAggregator_AutoFlush(t *testing.T) {
   179  	testutil.RunFlaky(t, func(t testutil.TestingT) {
   180  		tr := NewEncoderAggregatorTester(t)
   181  		const flushInterval = 20 * time.Millisecond
   182  		tr.conf.FlushInterval = flushInterval
   183  		testee := tr.Testee()
   184  
   185  		var flushes int
   186  		const expectedAutoFlushes = 2
   187  		time.AfterFunc(expectedAutoFlushes*flushInterval+flushInterval/2, tr.cancel)
   188  		tr.enc.On("Flush").Return(func() error {
   189  			flushes++
   190  			return nil
   191  		})
   192  		tr.wc.On("Close").Return(nil)
   193  		err := testee.Run(tr.ctx, tr.deps)
   194  		require.NoError(t, err)
   195  
   196  		assert.Equal(t, expectedAutoFlushes+1, flushes, "Expeced + one for finish")
   197  	})
   198  }
   199  
   200  func TestEncoderAggregator_ManualFlush(t *testing.T) {
   201  	testutil.RunFlaky(t, func(t testutil.TestingT) {
   202  		tr := NewEncoderAggregatorTester(t)
   203  		const autoFlushInterval = 15 * time.Millisecond
   204  		tr.conf.FlushInterval = autoFlushInterval
   205  		testee := tr.Testee()
   206  		var (
   207  			flushes int
   208  		)
   209  
   210  		tr.enc.On("Encode", mock.Anything).Return(func(core.Sample) error {
   211  			tr.flushCB()
   212  			return nil
   213  		})
   214  		tr.enc.On("Flush").Return(func() error {
   215  			flushes++
   216  			tr.flushCB()
   217  			return nil
   218  		})
   219  		tr.wc.On("Close").Return(nil)
   220  
   221  		time.AfterFunc(autoFlushInterval*3, tr.cancel)
   222  
   223  		runErr := make(chan error)
   224  		go func() {
   225  			runErr <- testee.Run(tr.ctx, tr.deps)
   226  		}()
   227  		writeTicker := time.NewTicker(autoFlushInterval / 3)
   228  		defer writeTicker.Stop()
   229  		for {
   230  			select {
   231  			case <-writeTicker.C:
   232  				testee.Report(0)
   233  			case <-tr.ctx.Done():
   234  				return
   235  			}
   236  		}
   237  	})
   238  }
   239  
   240  type MockSampleEncoderAddCloser struct {
   241  	*aggregatemock.SampleEncoder
   242  }
   243  
   244  // Close provides a mock function with given fields:
   245  func (_m MockSampleEncoderAddCloser) Close() error {
   246  	ret := _m.Called()
   247  
   248  	var r0 error
   249  	if rf, ok := ret.Get(0).(func() error); ok {
   250  		r0 = rf()
   251  	} else {
   252  		r0 = ret.Error(0)
   253  	}
   254  
   255  	return r0
   256  }