spec/utility/error_monitor_spec.rb (93 lines of code) (raw):

# # Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one # or more contributor license agreements. Licensed under the Elastic License; # you may not use this file except in compliance with the Elastic License. # # frozen_string_literal: true require 'utility/error_monitor' describe Utility::ErrorMonitor do it 'has expected default values' do monitor = Utility::ErrorMonitor.new expect(monitor.instance_variable_get('@max_errors')).to eq(1000) expect(monitor.instance_variable_get('@max_consecutive_errors')).to eq(10) expect(monitor.instance_variable_get('@max_error_ratio')).to eq(0.15) expect(monitor.instance_variable_get('@window_size')).to eq(100) end it 'raises an error after too many errors in a window' do monitor = Utility::ErrorMonitor.new(:max_error_ratio => 0.15, window_size: 100) 10.times do monitor.note_error(StandardError.new) end 84.times do monitor.note_success end 5.times do monitor.note_error(StandardError.new) end expect { monitor.note_error(StandardError.new) }.to raise_error(Utility::ErrorMonitor::MaxErrorsInWindowExceededError) end context 'when successes and failures were reported before' do # Regression test. # Problem fixed was that monitor incorrectly calculates max_error_ratio - it never # actually considered either ratio or window size - it was always raising an error if # window_size * max_error_ratio errors happened, which is 15 in case of this setup. # What it should do is really consider the window and error ratio, e.g: # 85 documents were correctly ingested, 15 failed. Window will be: 85 x success, 15 x failure, # error_ratio = 0.15, but condition to raise is max_error_ratio < error_ratio - it's false, so # no error is raised. # # Then 90 documents were ingested correctly, window moves and will be: 10 x failure, 90 x success, # error_ratio = 0.1; Then 10 errors happen, but error_ratio will stay the same because window will be # 90 x success, 10 x failure. let(:monitor) { Utility::ErrorMonitor.new(:max_error_ratio => 0.15, window_size: 100, max_consecutive_errors: 100) } before(:each) do # Setup is 100 triggers: # 5 x failure; 40 x success; 5 x failure; 40 x success, error_ratio = 0.1 2.times do 5.times do monitor.note_error(StandardError.new) end 40.times do monitor.note_success end end end it 'raises an error after too many errors in a window' do expect { # Before: # 5 x failure; 40 x success; 5 x failure; 40 x success, real error_ratio = 0.1 # After: # 40 x success; 5 x failure; 40 x success; 5 x failure, real error_ratio = 0.1 5.times do monitor.note_error(StandardError.new) end # Before: # 40 x success; 5 x failure; 40 x success; 5 x failure, real error_ratio = 0.1 # After: # 1 x success; 5x failure; 94 x success, real_error_ratio = 0.05 94.times do monitor.note_success end # Before: # 1 x success; 5 x failure; 94 x success, real_error_ratio = 0.05 # After: # 85 x success, 15 x failure, real_error_ratio = 0.15. # Any error within next 85 documents should trigger the MaxErrorsInWindowExceededError 15.times do monitor.note_error(StandardError.new) end }.to_not raise_error expect { monitor.note_error(StandardError.new) }.to raise_error(Utility::ErrorMonitor::MaxErrorsInWindowExceededError) end end it 'raises an error after too many failures in a row' do monitor = Utility::ErrorMonitor.new(:max_consecutive_errors => 3) expect { add_errors(monitor, 4) }.to raise_error do |e| expect(e).to be_a(Utility::ErrorMonitor::MaxSuccessiveErrorsExceededError) expect(e.cause.message).to eq('Error 4') end end it 'raises an error after too many total failures' do monitor = Utility::ErrorMonitor.new(:max_errors => 5, :max_consecutive_errors => 2) expect { add_errors(monitor, 6, :alternate_success => true) }.to raise_error do |e| expect(e).to be_a(Utility::ErrorMonitor::MaxErrorsExceededError) expect(e.cause.message).to eq('Error 6') end end it 'raises an error after too many failures in a window' do monitor = Utility::ErrorMonitor.new(:max_consecutive_errors => 15) expect { add_errors(monitor, 15) }.to_not raise_error monitor.note_success expect { monitor.note_error(StandardError.new("The hair that broke the camel's back")) }.to raise_error do |e| expect(e).to be_a(Utility::ErrorMonitor::MaxErrorsInWindowExceededError) expect(e.cause.message).to eq("The hair that broke the camel's back") end end it 'raises an error when finalized even if the window is not full' do monitor = Utility::ErrorMonitor.new expect { add_errors(monitor, 10, :alternate_success => true) }.to_not raise_error expect { monitor.finalize }.to raise_error do |e| expect(e).to be_a(Utility::ErrorMonitor::MaxErrorsInWindowExceededError) expect(e.cause.message).to eq('Error 10') end end it 'stores up to :error_buffer_size last errors' do monitor = Utility::ErrorMonitor.new(:max_consecutive_errors => 15, :error_queue_size => 10) expect { add_errors(monitor, 15) }.to change { monitor.error_queue.size }.from(0).to(10) expect(monitor.error_queue.first.error_message).to match(/Error 6/) expect(monitor.error_queue.last.error_message).to match(/Error 15/) end def add_errors(monitor, num_errors, alternate_success: false) num_errors.times do |i| monitor.note_error(StandardError.new("Error #{i + 1}")) monitor.note_success if alternate_success end end end