From 665e9f725eb48e68d6aeb714a9422acd3b264997 Mon Sep 17 00:00:00 2001 From: Mike Vastola Date: Sun, 24 Jan 2021 15:19:00 -0500 Subject: [PATCH] Miscelaneous code cleanup - Rubocop - Moved rubocop checks into travis and disabled hound - Hound did not support rubocop 0.50.x, which is the last version to support Ruby v2 - Condensed/simplified .rubocop.yml - Corrected easily fixed cops - Gemfile - Added extra constraints due to encountered bugs - Added pry enhancements - TODO: add to gemspec - Rake - Added Rakefile with tasks to simplify testing (both locally and on travis) - Travis - Upgraded OS to use Ubuntu xenial rather than trusty (end of life) - Script now runs bundle exec rake rather than rspec directly --- .hound.yml | 3 +- .rubocop.yml | 50 ++++++++---- .rubocop_todo.yml | 21 ----- .travis.yml | 8 +- Gemfile | 20 +++-- Gemfile.lock | 40 ++++++++-- Rakefile | 38 +++++++++ lib/retriable.rb | 18 ++--- lib/retriable/config.rb | 20 ++--- lib/retriable/core_ext/kernel.rb | 2 +- lib/retriable/exponential_backoff.rb | 16 ++-- lib/retriable/version.rb | 2 +- retriable.gemspec | 28 +++---- spec/config_spec.rb | 26 +++---- spec/exponential_backoff_spec.rb | 24 +++--- spec/retriable_spec.rb | 110 +++++++++++++++------------ spec/spec_helper.rb | 8 +- 17 files changed, 262 insertions(+), 172 deletions(-) delete mode 100644 .rubocop_todo.yml create mode 100644 Rakefile diff --git a/.hound.yml b/.hound.yml index 5d0ff60..ab75abd 100644 --- a/.hound.yml +++ b/.hound.yml @@ -1,2 +1,3 @@ ruby: - config_file: .rubocop.yml +# config_file: .rubocop.yml + enabled: false \ No newline at end of file diff --git a/.rubocop.yml b/.rubocop.yml index 4a30f76..85439ee 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,29 +1,31 @@ -inherit_from: .rubocop_todo.yml AllCops: TargetRubyVersion: 2.0 -Style/StringLiterals: - EnforcedStyle: double_quotes +Layout/IndentArray: + EnforcedStyle: consistent -Style/Documentation: - Enabled: false +Layout/SpaceAroundOperators: + AllowForAlignment: true -Style/TrailingCommaInArguments: - EnforcedStyleForMultiline: comma Lint/InheritException: + Exclude: + - 'spec/support/exceptions.rb' + + +Metrics/AbcSize: Enabled: false -Layout/IndentArray: +Metrics/BlockLength: Enabled: false -Layout/IndentHash: +Metrics/ClassLength: Enabled: false -Style/NegatedIf: +Metrics/CyclomaticComplexity: Enabled: false -Metrics/ClassLength: +Metrics/MethodLength: Enabled: false Metrics/ModuleLength: @@ -32,11 +34,31 @@ Metrics/ModuleLength: Metrics/LineLength: Max: 120 -Metrics/MethodLength: +Metrics/PerceivedComplexity: Enabled: false -Metrics/BlockLength: + +RSpec/ExampleLength: Enabled: false -Metrics/AbcSize: +RSpec/FilePath: + # rspec thinks these should be in spec/retriable/ + # There is a SpecSuffixOnly option that should fix this + # but it's being silently ignored + Exclude: + - 'spec/config_spec.rb' + - 'spec/exponential_backoff_spec.rb' + +RSpec/InstanceVariable: + Exclude: + - 'spec/retriable_spec.rb' + + +Style/Documentation: + Enabled: false + +Style/NegatedIf: Enabled: false + +Style/TrailingCommaInArguments: + EnforcedStyleForMultiline: comma diff --git a/.rubocop_todo.yml b/.rubocop_todo.yml deleted file mode 100644 index 85a2ae6..0000000 --- a/.rubocop_todo.yml +++ /dev/null @@ -1,21 +0,0 @@ -# This configuration was generated by -# `rubocop --auto-gen-config` -# on 2020-12-28 23:59:17 -0500 using RuboCop version 0.50.0. -# The point is for the user to remove these configuration records -# one by one as the offenses are removed from the code base. -# Note that changes in the inspected code, or installation of new -# versions of RuboCop, may require this file to be generated again. - -# Offense count: 1 -Metrics/CyclomaticComplexity: - Max: 14 - -# Offense count: 2 -# Configuration parameters: AllowHeredoc, AllowURI, URISchemes, IgnoreCopDirectives, IgnoredPatterns. -# URISchemes: http, https -Metrics/LineLength: - Max: 202 - -# Offense count: 1 -Metrics/PerceivedComplexity: - Max: 15 diff --git a/.travis.yml b/.travis.yml index 860d452..167fde9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,6 @@ # Send builds to container-based infrastructure # http://docs.travis-ci.com/user/workers/container-based-infrastructure/ -dist: trusty +dist: xenial language: ruby rvm: - 2.0.0 @@ -10,6 +10,8 @@ rvm: - 2.4.6 - 2.5.5 - 2.6.2 + - 2.7 + - 3.0 - ruby-head - jruby-9.2.9.0 - jruby-head @@ -24,8 +26,8 @@ env: - CC_TEST_REPORTER_ID=20a1139ef1830b4f813a10a03d90e8aa179b5226f75e75c5a949b25756ebf558 before_install: - - gem install rubygems-update -v '<3' --no-document && update_rubygems - gem install bundler -v 1.17.3 + - bundle install before_script: - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter @@ -33,7 +35,7 @@ before_script: - ./cc-test-reporter before-build script: - - bundle exec rspec + - bundle exec rake test:ci after_script: - ./cc-test-reporter after-build --exit-code $TRAVIS_TEST_RESULT diff --git a/Gemfile b/Gemfile index 75ad103..efbce00 100644 --- a/Gemfile +++ b/Gemfile @@ -1,19 +1,25 @@ # frozen_string_literal: true -source "https://rubygems.org" +source 'https://rubygems.org' gemspec group :test do - gem "rspec" - gem "simplecov", require: false + gem 'rspec' + gem 'simplecov', require: false end -group :development do - gem "rubocop", ">= 0.50", "< 0.51", require: false - gem "rubocop-rspec", require: false +group :development, :test do + gem 'rubocop', '>= 0.50', '< 0.51', require: false + gem 'rubocop-rspec', require: false end group :development, :test do - gem "pry" + gem 'jazz_fingers' + gem 'rake' + # byebug constraint due to lack of support for binding.local_variables + # in ruby 2.0 + gem 'byebug', '< 9.0.0' + # pry constraints fix https://github.com/pry/pry/issues/2121 + gem 'pry', '!= 0.13.0', '!= 0.13.1' end diff --git a/Gemfile.lock b/Gemfile.lock index b98b7e5..bfc444a 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -7,18 +7,42 @@ GEM remote: https://rubygems.org/ specs: ast (2.4.2) + awesome_print (1.8.0) + byebug (8.2.5) coderay (1.1.3) + coolline (0.5.0) + unicode_utils (~> 1.4) diff-lcs (1.4.4) docile (1.3.5) + hirb (0.7.3) + jazz_fingers (3.0.1) + awesome_print (~> 1.6) + hirb (~> 0.7) + pry (~> 0.10) + pry-byebug (~> 3.1) + pry-coolline (~> 0.2) + pry-doc (~> 0.6) + pry-remote (>= 0.1.7) json (2.5.1) - method_source (1.0.0) + method_source (0.9.2) parallel (1.13.0) parser (2.7.2.0) ast (~> 2.4.1) powerpack (0.1.3) - pry (0.13.1) - coderay (~> 1.1) - method_source (~> 1.0) + pry (0.12.2) + coderay (~> 1.1.0) + method_source (~> 0.9.0) + pry-byebug (3.3.0) + byebug (~> 8.0) + pry (~> 0.10) + pry-coolline (0.2.5) + coolline (~> 0.5) + pry-doc (0.13.5) + pry (~> 0.11) + yard (~> 0.9.11) + pry-remote (0.1.8) + pry (~> 0.9) + slop (~> 3.0) rainbow (2.2.2) rake rake (12.3.3) @@ -50,14 +74,20 @@ GEM json (>= 1.8, < 3) simplecov-html (~> 0.10.0) simplecov-html (0.10.2) + slop (3.6.0) unicode-display_width (1.7.0) + unicode_utils (1.4.0) + yard (0.9.26) PLATFORMS ruby DEPENDENCIES bundler - pry + byebug (< 9.0.0) + jazz_fingers + pry (!= 0.13.1, != 0.13.0) + rake retriable! rspec rubocop (>= 0.50, < 0.51) diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..377b18a --- /dev/null +++ b/Rakefile @@ -0,0 +1,38 @@ +require 'bundler/setup' +Bundler.require(:test, :development) + +begin + require 'rspec/core/rake_task' + require 'rubocop/rake_task' +rescue LoadError => e + require 'shellwords' + raise "Required gem #{e.path.inspect} not found. " \ + 'Be sure you ran `bundle install` and are launching rake via ' \ + "`bundle exec rake #{ARGV.shelljoin}`." +end + +namespace :test do + RSpec::Core::RakeTask.new(:rspec) do |t| + t.rspec_opts = '-w --backtrace --no-fail-fast' + end + + RuboCop::RakeTask.new('rubocop') do |t, _task_args| + t.options << '--require' << 'rubocop-rspec' + t.options << '--fail-level' << 'convention' + t.options << '--display-cop-names' + t.options << '--extra-details' + t.options << '--display-style-guide' + # Can't enable parallel because it will break rubocop:autocorrect + # t.options << '--parallel' + end + + desc 'Run all tests' + task all: %w[test:rubocop test:rspec] + + task ci: %w[test:all] +end + +desc 'Alias for test:all' +task test: %w[test:all] + +task default: %w[test] diff --git a/lib/retriable.rb b/lib/retriable.rb index c31b965..69716fe 100644 --- a/lib/retriable.rb +++ b/lib/retriable.rb @@ -1,7 +1,7 @@ -require "timeout" -require_relative "retriable/config" -require_relative "retriable/exponential_backoff" -require_relative "retriable/version" +require 'timeout' +require_relative 'retriable/config' +require_relative 'retriable/exponential_backoff' +require_relative 'retriable/version' module Retriable module_function @@ -15,8 +15,9 @@ def config end def with_context(context_key, options = {}, &block) - if !config.contexts.key?(context_key) - raise ArgumentError, "#{context_key} not found in Retriable.config.contexts. Available contexts: #{config.contexts.keys}" + unless config.contexts.key?(context_key) + raise ArgumentError, "#{context_key} not found in Retriable.config.contexts. "\ + "Available contexts: #{config.contexts.keys}" end retriable(config.contexts[context_key].merge(options), &block) if block @@ -56,7 +57,7 @@ def retriable(opts = {}) # TODO: Ideally this would be it's own function, but would probably require # a separate class to efficiently pass on the processed config - run_try = Proc.new do |interval, try| + run_try = proc do |interval, try| begin return Timeout.timeout(timeout) { return yield(try) } if timeout return yield(try) @@ -68,7 +69,6 @@ def retriable(opts = {}) end on_retry.call(exception, try, elapsed_time.call, interval) if on_retry - # Note: Tries can't always be calculated if a custom Enumerator is given # for the intervals argument. (Enumerator#size will return nil, per docs) # So we'll just let the enumerator run out, and then invoke run_try once more. @@ -88,7 +88,7 @@ def retriable(opts = {}) result = run_try.call(interval, try) return result end - break if tries and try + 1 >= tries + break if tries && (try + 1 >= tries) end run_try.call(nil, try + 1) end diff --git a/lib/retriable/config.rb b/lib/retriable/config.rb index 22a585c..b224366 100644 --- a/lib/retriable/config.rb +++ b/lib/retriable/config.rb @@ -1,16 +1,16 @@ -require_relative "exponential_backoff" +require_relative 'exponential_backoff' module Retriable class Config ATTRIBUTES = (ExponentialBackoff::ATTRIBUTES + %i[ -sleep_disabled -max_elapsed_time -intervals -timeout -on -on_retry -contexts -]).freeze + sleep_disabled + max_elapsed_time + intervals + timeout + on + on_retry + contexts + ]).freeze attr_accessor(*ATTRIBUTES) @@ -31,7 +31,7 @@ def initialize(opts = {}) @contexts = {} opts.each do |k, v| - raise ArgumentError, "#{k} is not a valid option" if !ATTRIBUTES.include?(k) + raise ArgumentError, "#{k} is not a valid option" unless ATTRIBUTES.include?(k) instance_variable_set(:"@#{k}", v) end end diff --git a/lib/retriable/core_ext/kernel.rb b/lib/retriable/core_ext/kernel.rb index a95c9f2..d80d6de 100644 --- a/lib/retriable/core_ext/kernel.rb +++ b/lib/retriable/core_ext/kernel.rb @@ -1,4 +1,4 @@ -require_relative "../../retriable" +require_relative '../../retriable' module Kernel def retriable(opts = {}, &block) diff --git a/lib/retriable/exponential_backoff.rb b/lib/retriable/exponential_backoff.rb index 4ec6041..21b7846 100644 --- a/lib/retriable/exponential_backoff.rb +++ b/lib/retriable/exponential_backoff.rb @@ -1,12 +1,12 @@ module Retriable class ExponentialBackoff ATTRIBUTES = %i[ -tries -base_interval -multiplier -max_interval -rand_factor -].freeze + tries + base_interval + multiplier + max_interval + rand_factor + ].freeze attr_accessor(*ATTRIBUTES) @@ -18,7 +18,7 @@ def initialize(opts = {}) @multiplier = 1.5 opts.each do |k, v| - raise ArgumentError, "#{k} is not a valid option" if !ATTRIBUTES.include?(k) + raise ArgumentError, "#{k} is not a valid option" unless ATTRIBUTES.include?(k) instance_variable_set(:"@#{k}", v) end end @@ -32,7 +32,7 @@ def intervals try += 1 raise StopIteration if tries && try >= tries end - end.lazy + end end private diff --git a/lib/retriable/version.rb b/lib/retriable/version.rb index 62479cb..3d9b14b 100644 --- a/lib/retriable/version.rb +++ b/lib/retriable/version.rb @@ -1,3 +1,3 @@ module Retriable - VERSION = "3.1.2".freeze + VERSION = '3.1.2'.freeze end diff --git a/retriable.gemspec b/retriable.gemspec index 04119be..f8ff005 100644 --- a/retriable.gemspec +++ b/retriable.gemspec @@ -1,26 +1,26 @@ -lib = File.expand_path("../lib", __FILE__) +lib = File.expand_path('../lib', __FILE__) $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) -require "retriable/version" +require 'retriable/version' Gem::Specification.new do |spec| - spec.name = "retriable" + spec.name = 'retriable' spec.version = Retriable::VERSION - spec.authors = ["Jack Chu"] - spec.email = ["jack@jackchu.com"] - spec.summary = "Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff" - spec.description = "Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff. This is especially useful when interacting external api/services or file system calls." - spec.homepage = "https://github.com/kamui/retriable" - spec.license = "MIT" + spec.authors = ['Jack Chu'] + spec.email = ['jack@jackchu.com'] + spec.summary = 'Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff' + spec.description = 'Retriable is a simple DSL to retry failed code blocks with randomized exponential backoff. ' \ + 'This is especially useful when interacting external api/services or file system calls.' + spec.homepage = 'https://github.com/kamui/retriable' + spec.license = 'MIT' spec.files = `git ls-files -z`.split("\x0") spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) - spec.require_paths = ["lib"] + spec.require_paths = ['lib'] - spec.required_ruby_version = ">= 2.0.0" - - spec.add_development_dependency "bundler" - spec.add_development_dependency "rspec", "~> 3" + spec.required_ruby_version = '>= 2.0.0' + spec.add_development_dependency 'bundler' + spec.add_development_dependency 'rspec', '~> 3' end diff --git a/spec/config_spec.rb b/spec/config_spec.rb index 53fcb48..4cb14df 100644 --- a/spec/config_spec.rb +++ b/spec/config_spec.rb @@ -1,53 +1,53 @@ describe Retriable::Config do let(:default_config) { described_class.new } - context "defaults" do - it "sleep defaults to enabled" do + context 'defaults' do + it 'sleep defaults to enabled' do expect(default_config.sleep_disabled).to be_falsey end - it "tries defaults to 3" do + it 'tries defaults to 3' do expect(default_config.tries).to eq(3) end - it "max interval defaults to 60" do + it 'max interval defaults to 60' do expect(default_config.max_interval).to eq(60) end - it "randomization factor defaults to 0.5" do + it 'randomization factor defaults to 0.5' do expect(default_config.base_interval).to eq(0.5) end - it "multiplier defaults to 1.5" do + it 'multiplier defaults to 1.5' do expect(default_config.multiplier).to eq(1.5) end - it "max elapsed time defaults to 900" do + it 'max elapsed time defaults to 900' do expect(default_config.max_elapsed_time).to eq(900) end - it "intervals defaults to nil" do + it 'intervals defaults to nil' do expect(default_config.intervals).to be_nil end - it "timeout defaults to nil" do + it 'timeout defaults to nil' do expect(default_config.timeout).to be_nil end - it "on defaults to [StandardError]" do + it 'on defaults to [StandardError]' do expect(default_config.on).to eq([StandardError]) end - it "on_retry handler defaults to nil" do + it 'on_retry handler defaults to nil' do expect(default_config.on_retry).to be_nil end - it "contexts defaults to {}" do + it 'contexts defaults to {}' do expect(default_config.contexts).to eq({}) end end - it "raises errors on invalid configuration" do + it 'raises errors on invalid configuration' do expect { described_class.new(does_not_exist: 123) }.to raise_error(ArgumentError, /not a valid option/) end end diff --git a/spec/exponential_backoff_spec.rb b/spec/exponential_backoff_spec.rb index 1bb6f53..d211981 100644 --- a/spec/exponential_backoff_spec.rb +++ b/spec/exponential_backoff_spec.rb @@ -1,25 +1,25 @@ describe Retriable::ExponentialBackoff do - context "defaults" do + context 'defaults' do let(:backoff_config) { described_class.new } - it "tries defaults to 3" do + it 'tries defaults to 3' do expect(backoff_config.tries).to eq(3) end - it "max interval defaults to 60" do + it 'max interval defaults to 60' do expect(backoff_config.max_interval).to eq(60) end - it "randomization factor defaults to 0.5" do + it 'randomization factor defaults to 0.5' do expect(backoff_config.base_interval).to eq(0.5) end - it "multiplier defaults to 1.5" do + it 'multiplier defaults to 1.5' do expect(backoff_config.multiplier).to eq(1.5) end end - it "generates 10 randomized intervals" do + it 'generates 10 randomized intervals' do expect(described_class.new(tries: 9).intervals.to_a).to eq([ 0.5244067512211441, 0.9113920238761231, @@ -33,11 +33,11 @@ ]) end - it "generates defined number of intervals" do + it 'generates defined number of intervals' do expect(described_class.new(tries: 5).intervals.size).to eq(5) end - it "generates intervals with a defined base interval" do + it 'generates intervals with a defined base interval' do expect(described_class.new(base_interval: 1).intervals.to_a).to eq([ 1.0488135024422882, 1.8227840477522461, @@ -45,7 +45,7 @@ ]) end - it "generates intervals with a defined multiplier" do + it 'generates intervals with a defined multiplier' do expect(described_class.new(multiplier: 1).intervals.to_a).to eq([ 0.5244067512211441, 0.607594682584082, @@ -53,11 +53,11 @@ ]) end - it "generates intervals with a defined max interval" do + it 'generates intervals with a defined max interval' do expect(described_class.new(max_interval: 1.0, rand_factor: 0.0).intervals.to_a).to eq([0.5, 0.75, 1.0]) end - it "generates intervals with a defined rand_factor" do + it 'generates intervals with a defined rand_factor' do expect(described_class.new(rand_factor: 0.2).intervals.to_a).to eq([ 0.5097627004884576, 0.8145568095504492, @@ -65,7 +65,7 @@ ]) end - it "generates 10 non-randomized intervals" do + it 'generates 10 non-randomized intervals' do non_random_intervals = 9.times.inject([0.5]) { |memo, _i| memo + [memo.last * 1.5] } expect(described_class.new(tries: 10, rand_factor: 0.0).intervals.to_a).to eq(non_random_intervals) end diff --git a/spec/retriable_spec.rb b/spec/retriable_spec.rb index c005920..4317892 100644 --- a/spec/retriable_spec.rb +++ b/spec/retriable_spec.rb @@ -19,36 +19,36 @@ def increment_tries_with_exception(exception_class = nil) raise exception_class, "#{exception_class} occurred" 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) + 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 "can be called once the kernel extension is required" do - require_relative "../lib/retriable/core_ext/kernel" + it 'can be called once the kernel extension is required' do + require_relative '../lib/retriable/core_ext/kernel' expect { retriable { increment_tries_with_exception } }.to raise_error(StandardError) expect(@tries).to eq(3) end end - context "#retriable" do - it "raises a LocalJumpError if not given a block" do + 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 - it "stops at first try if the block does not raise an exception" do + 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 + it 'makes 3 tries when retrying block of code raising StandardError with no arguments' do 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 descendent of StandardError" do + it 'makes only 1 try when exception raised is not descendent of StandardError' do expect do described_class.retriable { increment_tries_with_exception(NonStandardError) } end.to raise_error(NonStandardError) @@ -56,7 +56,7 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to eq(1) end - it "with custom exception tries 3 times and re-raises the exception" do + it 'with custom exception tries 3 times and re-raises the exception' do expect do described_class.retriable(on: NonStandardError) { increment_tries_with_exception(NonStandardError) } end.to raise_error(NonStandardError) @@ -64,16 +64,16 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to eq(3) end - it "tries 10 times when specified" do + 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 "will timeout after 1 second" do + 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 + it 'applies a randomized exponential backoff to each try' do expect do described_class.retriable(on_retry: time_table_handler, tries: 10) { increment_tries_with_exception } end.to raise_error(StandardError) @@ -94,12 +94,12 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to eq(10) end - context "with rand_factor 0.0 and an on_retry handler" do + 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 } } - it "applies a non-randomized exponential backoff to each try" do + it 'applies a non-randomized exponential backoff to each try' do described_class.retriable(args) do increment_tries raise StandardError if @tries < tries @@ -109,7 +109,7 @@ def increment_tries_with_exception(exception_class = nil) expect(@next_interval_table).to eq(no_rand_timetable.merge(4 => 1.6875, 5 => 2.53125)) end - it "obeys a max interval of 1.5 seconds" do + 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) @@ -117,7 +117,7 @@ def increment_tries_with_exception(exception_class = nil) expect(@next_interval_table).to eq(no_rand_timetable.merge(4 => 1.5, 5 => 1.5, 6 => nil)) end - it "obeys custom defined intervals" do + 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 @@ -132,35 +132,47 @@ def increment_tries_with_exception(exception_class = nil) end end - context "with interval enumerator" do + context 'with interval enumerator' do let(:intervals_with_size) do Enumerator.new(4) { |result| loop { result << 0.00001 } } end let(:intervals_with_one_result) do - Enumerator.new(4) { |result| loop { result << 0.00001; raise StopIteration } } + Enumerator.new(4) do |result| + loop do + result << 0.00001 + raise StopIteration + end + end end - it "uses size of Enumerator if it can be determined without iterating" do + it 'uses size of Enumerator if it can be determined without iterating' do expect do - described_class.retriable(on: StandardError, tries: 6, intervals: intervals_with_size, max_elapsed_time: 1.2) do + described_class.retriable(on: StandardError, tries: 6, + intervals: intervals_with_size, max_elapsed_time: 1.2) do increment_tries_with_exception end end.to raise_error(StandardError) expect(@tries).to eq(5) end - it "recognizes if Enumerator stops iterating" do + it 'recognizes if Enumerator stops iterating' do expect do - described_class.retriable(on: StandardError, tries: 6, intervals: intervals_with_one_result, max_elapsed_time: 1.2) do + described_class.retriable(on: StandardError, tries: 6, + intervals: intervals_with_one_result, max_elapsed_time: 1.2) do increment_tries_with_exception end end.to raise_error(StandardError) expect(@tries).to eq(2) end - it "lazily iterates through the enumerator" do + it 'lazily iterates through the enumerator' do start_time = Time.now - intervals = Enumerator.new { |result| loop { result << 0.00001; sleep 0.2 } } + intervals = Enumerator.new do |result| + loop do + result << 0.00001 + sleep 0.2 + end + end expect do described_class.retriable(on: StandardError, tries: 6, intervals: intervals, max_elapsed_time: 1.2) do increment_tries_with_exception @@ -171,10 +183,10 @@ def increment_tries_with_exception(exception_class = nil) end end - context "with unlimited tries" do + context 'with unlimited tries' do let(:args) { { on: StandardError, tries: nil, rand_factor: 0.0, multiplier: 1, base_interval: 0.00001 } } - it "keeps going indefinitely" do + it 'keeps going indefinitely' do start_time = Time.now.to_i expect do described_class.retriable(args) do @@ -185,8 +197,8 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to be > 100 end end - context "with an array :on parameter" do - it "handles both kinds of exceptions" do + context 'with an array :on parameter' do + it 'handles both kinds of exceptions' do described_class.retriable(on: [StandardError, NonStandardError]) do increment_tries @@ -198,10 +210,10 @@ def increment_tries_with_exception(exception_class = nil) end end - context "with a hash :on parameter" do + context 'with a hash :on parameter' do let(:on_hash) { { NonStandardError => /NonStandardError occurred/ } } - it "where the value is an exception message pattern" do + 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/) @@ -209,7 +221,7 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to eq(3) end - it "matches exception subclasses when message matches pattern" do + 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) @@ -219,18 +231,18 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to eq(3) end - it "does not retry matching exception subclass but not message" do + 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" + raise SecondNonStandardError, 'not a match' end end.to raise_error(SecondNonStandardError, /not a match/) expect(@tries).to eq(1) end - it "successfully retries when the values are arrays of exception message patterns" do + 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/] } @@ -241,26 +253,26 @@ def increment_tries_with_exception(exception_class = nil) case @tries when 1 - raise NonStandardError, "foo" + raise NonStandardError, 'foo' when 2 - raise NonStandardError, "bar" + raise NonStandardError, 'bar' when 3 raise StandardError else - raise NonStandardError, "crash" + raise NonStandardError, 'crash' end end end.to raise_error(NonStandardError, /crash/) expect(exceptions[1]).to be_a(NonStandardError) - expect(exceptions[1].message).to eq("foo") + expect(exceptions[1].message).to eq('foo') expect(exceptions[2]).to be_a(NonStandardError) - expect(exceptions[2].message).to eq("bar") + expect(exceptions[2].message).to eq('bar') expect(exceptions[3]).to be_a(StandardError) end end - it "runs for a max elapsed time of 2 seconds" do + it 'runs for a max elapsed time of 2 seconds' do described_class.configure { |c| c.sleep_disabled = false } expect do @@ -272,18 +284,18 @@ def increment_tries_with_exception(exception_class = nil) expect(@tries).to eq(2) end - it "raises ArgumentError on invalid options" do + it 'raises ArgumentError on invalid options' do expect { described_class.retriable(does_not_exist: 123) { increment_tries } }.to raise_error(ArgumentError) end end - context "#configure" do - it "raises NoMethodError on invalid configuration" do + 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 - context "#with_context" do + context '#with_context' do let(:api_tries) { 4 } before do @@ -293,17 +305,17 @@ def increment_tries_with_exception(exception_class = nil) end end - it "stops at first try if the block does not raise an exception" do + 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 "respects the context options" do + 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 "allows override options" do + it 'allows override options' do expect do described_class.with_context(:sql, tries: 5) { increment_tries_with_exception } end.to raise_error(StandardError) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 335d435..1dfbe7a 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,9 +1,9 @@ -require "simplecov" +require 'simplecov' SimpleCov.start -require "pry" -require_relative "../lib/retriable" -require_relative "support/exceptions.rb" +require 'pry' +require_relative '../lib/retriable' +require_relative 'support/exceptions.rb' RSpec.configure do |config| config.before(:each) do