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 }