spec/retriable_spec.rb in retriable-3.1.1 vs spec/retriable_spec.rb in retriable-3.1.2
- old
+ new
@@ -1,434 +1,265 @@
-require_relative "spec_helper"
+describe Retriable do
+ let(:time_table_handler) do
+ ->(_exception, try, _elapsed_time, next_interval) { @next_interval_table[try] = next_interval }
+ end
-class TestError < Exception; end
+ before(:each) do
+ described_class.configure { |c| c.sleep_disabled = true }
+ @tries = 0
+ @next_interval_table = {}
+ end
-describe Retriable do
- subject do
- Retriable
+ def increment_tries
+ @tries += 1
end
- before do
- srand 0
+ def increment_tries_with_exception(exception_class = nil)
+ exception_class ||= StandardError
+ increment_tries
+ raise exception_class, "#{exception_class} occurred"
end
- describe "with sleep disabled" do
- before do
- Retriable.configure do |c|
- c.sleep_disabled = true
- end
+ context "global scope extension" do
+ it "cannot be called in the global scope without requiring the core_ext/kernel" do
+ expect { retriable { puts "should raise NoMethodError" } }.to raise_error(NoMethodError)
end
- it "stops at first try if the block does not raise an exception" do
- tries = 0
- subject.retriable do
- tries += 1
- end
+ it "can be called once the kernel extension is required" do
+ require_relative "../lib/retriable/core_ext/kernel"
- expect(tries).must_equal 1
+ expect { retriable { increment_tries_with_exception } }.to raise_error(StandardError)
+ expect(@tries).to eq(3)
end
+ end
- it "raises a LocalJumpError if #retriable is not given a block" do
- expect do
- subject.retriable on: StandardError
- end.must_raise LocalJumpError
+ context "#retriable" do
+ it "raises a LocalJumpError if not given a block" do
+ expect { described_class.retriable }.to raise_error(LocalJumpError)
+ expect { described_class.retriable(timeout: 2) }.to raise_error(LocalJumpError)
+ end
- expect do
- subject.retriable on: StandardError, timeout: 2
- end.must_raise LocalJumpError
+ it "stops at first try if the block does not raise an exception" do
+ described_class.retriable { increment_tries }
+ expect(@tries).to eq(1)
end
it "makes 3 tries when retrying block of code raising StandardError with no arguments" do
- tries = 0
-
- expect do
- subject.retriable do
- tries += 1
- raise StandardError.new, "StandardError occurred"
- end
- end.must_raise StandardError
-
- expect(tries).must_equal 3
+ expect { described_class.retriable { increment_tries_with_exception } }.to raise_error(StandardError)
+ expect(@tries).to eq(3)
end
- it "makes only 1 try when exception raised is not ancestor of StandardError" do
- tries = 0
-
+ it "makes only 1 try when exception raised is not descendent of StandardError" do
expect do
- subject.retriable do
- tries += 1
- raise TestError.new, "TestError occurred"
- end
- end.must_raise TestError
+ described_class.retriable { increment_tries_with_exception(NonStandardError) }
+ end.to raise_error(NonStandardError)
- expect(tries).must_equal 1
+ expect(@tries).to eq(1)
end
- it "#retriable with custom exception tries 3 times and re-raises the exception" do
- tries = 0
-
+ it "with custom exception tries 3 times and re-raises the exception" do
expect do
- subject.retriable on: TestError do
- tries += 1
- raise TestError.new, "TestError occurred"
- end
- end.must_raise TestError
+ described_class.retriable(on: NonStandardError) { increment_tries_with_exception(NonStandardError) }
+ end.to raise_error(NonStandardError)
- expect(tries).must_equal 3
+ expect(@tries).to eq(3)
end
- it "#retriable tries 10 times" do
- tries = 0
-
- expect do
- subject.retriable(tries: 10) do
- tries += 1
- raise StandardError.new, "StandardError occurred"
- end
- end.must_raise StandardError
-
- expect(tries).must_equal 10
+ it "tries 10 times when specified" do
+ expect { described_class.retriable(tries: 10) { increment_tries_with_exception } }.to raise_error(StandardError)
+ expect(@tries).to eq(10)
end
- it "#retriable will timeout after 1 second" do
- expect do
- subject.retriable timeout: 1 do
- sleep 1.1
- end
- end.must_raise Timeout::Error
+ it "will timeout after 1 second" do
+ expect { described_class.retriable(timeout: 1) { sleep(1.1) } }.to raise_error(Timeout::Error)
end
it "applies a randomized exponential backoff to each try" do
- tries = 0
- time_table = []
-
- handler = lambda do |exception, _try, _elapsed_time, next_interval|
- expect(exception.class).must_equal ArgumentError
- time_table << next_interval
- end
-
expect do
- Retriable.retriable(
- on: [EOFError, ArgumentError],
- on_retry: handler,
- tries: 10,
- ) do
- tries += 1
- raise ArgumentError.new, "ArgumentError occurred"
- end
- end.must_raise ArgumentError
+ described_class.retriable(on_retry: time_table_handler, tries: 10) { increment_tries_with_exception }
+ end.to raise_error(StandardError)
- expect(time_table).must_equal([
- 0.5244067512211441,
- 0.9113920238761231,
- 1.2406087918999114,
- 1.7632403621664823,
- 2.338001204738311,
- 4.350816718580626,
- 5.339852157217869,
- 11.889873261212443,
- 18.756037881636484,
- nil,
- ])
+ expect(@next_interval_table).to eq(
+ 1 => 0.5244067512211441,
+ 2 => 0.9113920238761231,
+ 3 => 1.2406087918999114,
+ 4 => 1.7632403621664823,
+ 5 => 2.338001204738311,
+ 6 => 4.350816718580626,
+ 7 => 5.339852157217869,
+ 8 => 11.889873261212443,
+ 9 => 18.756037881636484,
+ 10 => nil,
+ )
- expect(tries).must_equal(10)
+ expect(@tries).to eq(10)
end
- describe "retries with an on_#retriable handler, 6 max retries, and a 0.0 rand_factor" do
- before do
- tries = 6
- @try_count = 0
- @time_table = {}
+ context "with rand_factor 0.0 and an on_retry handler" do
+ let(:tries) { 6 }
+ let(:no_rand_timetable) { { 1 => 0.5, 2 => 0.75, 3 => 1.125 } }
+ let(:args) { { on_retry: time_table_handler, rand_factor: 0.0, tries: tries } }
- handler = lambda do |exception, try, _elapsed_time, next_interval|
- expect(exception.class).must_equal ArgumentError
- @time_table[try] = next_interval
+ it "applies a non-randomized exponential backoff to each try" do
+ described_class.retriable(args) do
+ increment_tries
+ raise StandardError if @tries < tries
end
- Retriable.retriable(
- on: [EOFError, ArgumentError],
- on_retry: handler,
- rand_factor: 0.0,
- tries: tries,
- ) do
- @try_count += 1
- raise ArgumentError.new, "ArgumentError occurred" if @try_count < tries
- end
+ expect(@tries).to eq(tries)
+ expect(@next_interval_table).to eq(no_rand_timetable.merge(4 => 1.6875, 5 => 2.53125))
end
- it "makes 6 tries" do
- expect(@try_count).must_equal 6
- end
+ it "obeys a max interval of 1.5 seconds" do
+ expect do
+ described_class.retriable(args.merge(max_interval: 1.5)) { increment_tries_with_exception }
+ end.to raise_error(StandardError)
- it "applies a non-randomized exponential backoff to each try" do
- expect(@time_table).must_equal(
- 1 => 0.5,
- 2 => 0.75,
- 3 => 1.125,
- 4 => 1.6875,
- 5 => 2.53125,
- )
+ expect(@next_interval_table).to eq(no_rand_timetable.merge(4 => 1.5, 5 => 1.5, 6 => nil))
end
- end
- it "#retriable has a max interval of 1.5 seconds" do
- tries = 0
- time_table = {}
+ it "obeys custom defined intervals" do
+ interval_hash = no_rand_timetable.merge(4 => 1.5, 5 => 1.5, 6 => nil)
+ intervals = interval_hash.values.compact.sort
- handler = lambda do |_exception, try, _elapsed_time, next_interval|
- time_table[try] = next_interval
- end
+ expect do
+ described_class.retriable(on_retry: time_table_handler, intervals: intervals) do
+ increment_tries_with_exception
+ end
+ end.to raise_error(StandardError)
- expect do
- subject.retriable(
- on: StandardError,
- on_retry: handler,
- rand_factor: 0.0,
- tries: 5,
- max_interval: 1.5,
- ) do
- tries += 1
- raise StandardError.new, "StandardError occurred"
- end
- end.must_raise StandardError
-
- expect(time_table).must_equal(
- 1 => 0.5,
- 2 => 0.75,
- 3 => 1.125,
- 4 => 1.5,
- 5 => nil,
- )
+ expect(@next_interval_table).to eq(interval_hash)
+ expect(@tries).to eq(intervals.size + 1)
+ end
end
- it "#retriable with custom defined intervals" do
- intervals = [
- 0.5,
- 0.75,
- 1.125,
- 1.5,
- 1.5,
- ]
- time_table = {}
+ context "with an array :on parameter" do
+ it "handles both kinds of exceptions" do
+ described_class.retriable(on: [StandardError, NonStandardError]) do
+ increment_tries
- handler = lambda do |_exception, try, _elapsed_time, next_interval|
- time_table[try] = next_interval
- end
-
- try_count = 0
-
- expect do
- subject.retriable(
- on_retry: handler,
- intervals: intervals,
- ) do
- try_count += 1
- raise StandardError.new, "StandardError occurred"
+ raise StandardError if @tries == 1
+ raise NonStandardError if @tries == 2
end
- end.must_raise StandardError
- expect(time_table).must_equal(
- 1 => 0.5,
- 2 => 0.75,
- 3 => 1.125,
- 4 => 1.5,
- 5 => 1.5,
- 6 => nil,
- )
-
- expect(try_count).must_equal(6)
+ expect(@tries).to eq(3)
+ end
end
- it "#retriable with a hash exception where the value is an exception message pattern" do
- e = expect do
- subject.retriable on: { TestError => /something went wrong/ } do
- raise TestError, "something went wrong"
- end
- end.must_raise TestError
+ context "with a hash :on parameter" do
+ let(:on_hash) { { NonStandardError => /NonStandardError occurred/ } }
- expect(e.message).must_equal "something went wrong"
- end
+ it "where the value is an exception message pattern" do
+ expect do
+ described_class.retriable(on: on_hash) { increment_tries_with_exception(NonStandardError) }
+ end.to raise_error(NonStandardError, /NonStandardError occurred/)
- it "#retriable with a hash exception list matches exception subclasses" do
- class SecondTestError < TestError; end
- class DifferentTestError < Exception; end
+ expect(@tries).to eq(3)
+ end
- tries = 0
- e = expect do
- subject.retriable on: {
- DifferentTestError => /should never happen/,
- TestError => /something went wrong/,
- DifferentTestError => /also should never happen/,
- }, tries: 4 do
- tries += 1
- raise SecondTestError, "something went wrong"
- end
- end.must_raise SecondTestError
+ it "matches exception subclasses when message matches pattern" do
+ expect do
+ described_class.retriable(on: on_hash.merge(DifferentError => [/shouldn't happen/, /also not/])) do
+ increment_tries_with_exception(SecondNonStandardError)
+ end
+ end.to raise_error(SecondNonStandardError, /SecondNonStandardError occurred/)
- expect(e.message).must_equal "something went wrong"
- expect(tries).must_equal 4
- end
+ expect(@tries).to eq(3)
+ end
- it "#retriable with a hash exception list does not retry matching exception subclass but not message" do
- class SecondTestError < TestError; end
+ it "does not retry matching exception subclass but not message" do
+ expect do
+ described_class.retriable(on: on_hash) do
+ increment_tries
+ raise SecondNonStandardError, "not a match"
+ end
+ end.to raise_error(SecondNonStandardError, /not a match/)
- tries = 0
- expect do
- subject.retriable on: { TestError => /something went wrong/ }, tries: 4 do
- tries += 1
- raise SecondTestError, "not a match"
- end
- end.must_raise SecondTestError
+ expect(@tries).to eq(1)
+ end
- expect(tries).must_equal 1
- end
+ it "successfully retries when the values are arrays of exception message patterns" do
+ exceptions = []
+ handler = ->(exception, try, _elapsed_time, _next_interval) { exceptions[try] = exception }
+ on_hash = { StandardError => nil, NonStandardError => [/foo/, /bar/] }
- it "#retriable with a hash exception list where the values are exception message patterns" do
- tries = 0
- exceptions = []
- handler = lambda do |exception, try, _elapsed_time, _next_interval|
- exceptions[try] = exception
- end
+ expect do
+ described_class.retriable(tries: 4, on: on_hash, on_retry: handler) do
+ increment_tries
- e = expect do
- subject.retriable tries: 4, on: { StandardError => nil, TestError => [/foo/, /bar/] }, on_retry: handler do
- tries += 1
- case tries
- when 1
- raise TestError, "foo"
- when 2
- raise TestError, "bar"
- when 3
- raise StandardError
- else
- raise TestError, "crash"
+ case @tries
+ when 1
+ raise NonStandardError, "foo"
+ when 2
+ raise NonStandardError, "bar"
+ when 3
+ raise StandardError
+ else
+ raise NonStandardError, "crash"
+ end
end
- end
- end.must_raise TestError
+ end.to raise_error(NonStandardError, /crash/)
- expect(e.message).must_equal "crash"
- expect(exceptions[1].class).must_equal TestError
- expect(exceptions[1].message).must_equal "foo"
- expect(exceptions[2].class).must_equal TestError
- expect(exceptions[2].message).must_equal "bar"
- expect(exceptions[3].class).must_equal StandardError
+ expect(exceptions[1]).to be_a(NonStandardError)
+ expect(exceptions[1].message).to eq("foo")
+ expect(exceptions[2]).to be_a(NonStandardError)
+ expect(exceptions[2].message).to eq("bar")
+ expect(exceptions[3]).to be_a(StandardError)
+ end
end
- it "#retriable can be called in the global scope" do
- expect do
- retriable do
- puts "should raise NoMethodError"
- end
- end.must_raise NoMethodError
+ it "runs for a max elapsed time of 2 seconds" do
+ described_class.configure { |c| c.sleep_disabled = false }
- require_relative "../lib/retriable/core_ext/kernel"
-
- tries = 0
-
expect do
- retriable do
- tries += 1
- raise StandardError
+ described_class.retriable(base_interval: 1.0, multiplier: 1.0, rand_factor: 0.0, max_elapsed_time: 2.0) do
+ increment_tries_with_exception
end
- end.must_raise StandardError
+ end.to raise_error(StandardError)
- expect(tries).must_equal 3
+ expect(@tries).to eq(2)
end
- end
- it "#retriable runs for a max elapsed time of 2 seconds" do
- subject.configure do |c|
- c.sleep_disabled = false
+ it "raises ArgumentError on invalid options" do
+ expect { described_class.retriable(does_not_exist: 123) { increment_tries } }.to raise_error(ArgumentError)
end
-
- expect(subject.config.sleep_disabled).must_equal false
-
- tries = 0
- time_table = {}
-
- handler = lambda do |_exception, try, elapsed_time, _next_interval|
- time_table[try] = elapsed_time
- end
-
- expect do
- subject.retriable(
- base_interval: 1.0,
- multiplier: 1.0,
- rand_factor: 0.0,
- max_elapsed_time: 2.0,
- on_retry: handler,
- ) do
- tries += 1
- raise EOFError
- end
- end.must_raise EOFError
-
- expect(tries).must_equal 2
end
- it "raises NoMethodError on invalid configuration" do
- assert_raises NoMethodError do
- Retriable.configure { |c| c.does_not_exist = 123 }
+ context "#configure" do
+ it "raises NoMethodError on invalid configuration" do
+ expect { described_class.configure { |c| c.does_not_exist = 123 } }.to raise_error(NoMethodError)
end
end
- it "raises ArgumentError on invalid option on #retriable" do
- assert_raises ArgumentError do
- Retriable.retriable(does_not_exist: 123)
- end
- end
+ context "#with_context" do
+ let(:api_tries) { 4 }
- describe "#with_context" do
before do
- Retriable.configure do |c|
- c.sleep_disabled = true
+ described_class.configure do |c|
c.contexts[:sql] = { tries: 1 }
- c.contexts[:api] = { tries: 3 }
+ c.contexts[:api] = { tries: api_tries }
end
end
- it "sql context stops at first try if the block does not raise an exception" do
- tries = 0
- subject.with_context(:sql) do
- tries += 1
- end
-
- expect(tries).must_equal 1
+ it "stops at first try if the block does not raise an exception" do
+ described_class.with_context(:sql) { increment_tries }
+ expect(@tries).to eq(1)
end
- it "with_context respects the context options" do
- tries = 0
-
- expect do
- subject.with_context(:api) do
- tries += 1
- raise StandardError.new, "StandardError occurred"
- end
- end.must_raise StandardError
-
- expect(tries).must_equal 3
+ it "respects the context options" do
+ expect { described_class.with_context(:api) { increment_tries_with_exception } }.to raise_error(StandardError)
+ expect(@tries).to eq(api_tries)
end
- it "with_context allows override options" do
- tries = 0
-
+ it "allows override options" do
expect do
- subject.with_context(:sql, tries: 5) do
- tries += 1
- raise StandardError.new, "StandardError occurred"
- end
- end.must_raise StandardError
+ described_class.with_context(:sql, tries: 5) { increment_tries_with_exception }
+ end.to raise_error(StandardError)
- expect(tries).must_equal 5
+ expect(@tries).to eq(5)
end
it "raises an ArgumentError when the context isn't found" do
- tries = 0
-
- expect do
- subject.with_context(:wtf) do
- tries += 1
- end
- end.must_raise ArgumentError
+ expect { described_class.with_context(:wtf) { increment_tries } }.to raise_error(ArgumentError, /wtf not found/)
end
end
end