From 3dbe02d88585491670166cd6a9e4256ed51277dd Mon Sep 17 00:00:00 2001 From: Nikita Fedyashev Date: Fri, 30 Jan 2015 16:48:12 +0600 Subject: [PATCH] Cleanup and add regression spec --- lib/retryable.rb | 182 +++++++++++++-------------------- lib/retryable/config.rb | 16 --- lib/retryable/configuration.rb | 65 ++++++++++++ spec/lib/config_spec.rb | 23 ----- spec/lib/configuration_spec.rb | 39 +++++++ spec/spec_helper.rb | 10 ++ 6 files changed, 184 insertions(+), 151 deletions(-) delete mode 100644 lib/retryable/config.rb create mode 100644 lib/retryable/configuration.rb delete mode 100644 spec/lib/config_spec.rb create mode 100644 spec/lib/configuration_spec.rb diff --git a/lib/retryable.rb b/lib/retryable.rb index bc34a81..0e3366a 100644 --- a/lib/retryable.rb +++ b/lib/retryable.rb @@ -1,135 +1,93 @@ require 'retryable/version' -require 'retryable/config' +require 'retryable/configuration' module Retryable - # Used to set up and modify settings for the retryable. - class Configuration - OPTIONS = [ - :ensure, - :exception_cb, - :matching, - :on, - :sleep, - :tries - ].freeze - - attr_accessor :ensure - attr_accessor :exception_cb - attr_accessor :matching - attr_accessor :on - attr_accessor :sleep - attr_accessor :tries - - #alias_method :test_mode?, :test_mode - - def initialize - @ensure = Proc.new {} - @exception_cb = Proc.new {} - @matching = /.*/ - @on = StandardError - @sleep = 1 - @tries = 2 - end + class << self + # A Retryable configuration object. Must act like a hash and return sensible + # values for all Retryable configuration options. See Retryable::Configuration. + attr_writer :configuration - # Allows config options to be read like a hash + # Call this method to modify defaults in your initializers. # - # @param [Symbol] option Key for a given attribute - def [](option) - send(option) + # @example + # Retryable.configure do |config| + # config.ensure = Proc.new {} + # config.exception_cb = Proc.new {} + # config.matching = /.*/ + # config.on = StandardError + # config.sleep = 1 + # config.tries = 2 + # end + def configure + yield(configuration) end - # Returns a hash of all configurable options - def to_hash - OPTIONS.inject({}) do |hash, option| - hash[option.to_sym] = self.send(option) - hash - end + # The configuration object. + # @see Retryable.configure + def configuration + @configuration ||= Configuration.new end - # Returns a hash of all configurable options merged with +hash+ - # - # @param [Hash] hash A set of configuration options that will take precedence over the defaults - def merge(hash) - to_hash.merge(hash) + def enabled? + configuration.enabled? end - end -end -module Retryable - # Call this method to modify defaults in your initializers. - # - # @example - # Retryable.configure do |config| - # config.ensure = Proc.new {} - # config.exception_cb = Proc.new {} - # config.matching = /.*/ - # config.on = StandardError - # config.sleep = 1 - # config.tries = 2 - # end - def self.configure - yield(configuration) - end + def enable + configuration.enable + end - # The configuration object. - # @see Retryable.configure - def self.configuration - @configuration ||= Configuration.new - end + def disable + configuration.disable + end - class << self - # A Retryable configuration object. Must act like a hash and return sensible - # values for all Retryable configuration options. See Retryable::Configuration. - attr_writer :configuration - end + def retryable(options = {}, &block) + opts = { + :tries => self.configuration.tries, + :sleep => self.configuration.sleep, + :on => self.configuration.on, + :matching => self.configuration.matching, + :ensure => self.configuration.ensure, + :exception_cb => self.configuration.exception_cb + } + + check_for_invalid_options(options, opts) + opts.merge!(options) + + return if opts[:tries] == 0 + + on_exception, tries = [ opts[:on] ].flatten, opts[:tries] + retries = 0 + retry_exception = nil - def self.retryable(options = {}, &block) - opts = { - :tries => self.configuration.tries, - :sleep => self.configuration.sleep, - :on => self.configuration.on, - :matching => self.configuration.matching, - :ensure => self.configuration.ensure, - :exception_cb => self.configuration.exception_cb - } - - check_for_invalid_options(options, opts) - opts.merge!(options) - - return if opts[:tries] == 0 - - on_exception, tries = [ opts[:on] ].flatten, opts[:tries] - retries = 0 - retry_exception = nil - - begin - return yield retries, retry_exception - rescue *on_exception => exception - raise unless Retryable.enabled? - raise unless exception.message =~ opts[:matching] - raise if retries+1 >= tries - - # Interrupt Exception could be raised while sleeping begin - Kernel.sleep opts[:sleep].respond_to?(:call) ? opts[:sleep].call(retries) : opts[:sleep] - rescue *on_exception + return yield retries, retry_exception + rescue *on_exception => exception + raise unless configuration.enabled? + raise unless exception.message =~ opts[:matching] + raise if retries+1 >= tries + + # Interrupt Exception could be raised while sleeping + begin + Kernel.sleep opts[:sleep].respond_to?(:call) ? opts[:sleep].call(retries) : opts[:sleep] + rescue *on_exception + end + + retries += 1 + retry_exception = exception + opts[:exception_cb].call(retry_exception) + retry + ensure + opts[:ensure].call(retries) end - - retries += 1 - retry_exception = exception - opts[:exception_cb].call(retry_exception) - retry - ensure - opts[:ensure].call(retries) end - end - private + private - def self.check_for_invalid_options(custom_options, default_options) - invalid_options = default_options.merge(custom_options).keys - default_options.keys + def check_for_invalid_options(custom_options, default_options) + invalid_options = default_options.merge(custom_options).keys - default_options.keys - raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty? + raise ArgumentError.new("[Retryable] Invalid options: #{invalid_options.join(", ")}") unless invalid_options.empty? + end end end diff --git a/lib/retryable/config.rb b/lib/retryable/config.rb deleted file mode 100644 index 72edbd4..0000000 --- a/lib/retryable/config.rb +++ /dev/null @@ -1,16 +0,0 @@ -module Retryable - @enabled = true - class << self; attr_accessor :enabled; end - - def self.enable - @enabled = true - end - - def self.disable - @enabled = false - end - - def self.enabled? - !!@enabled - end -end diff --git a/lib/retryable/configuration.rb b/lib/retryable/configuration.rb new file mode 100644 index 0000000..939dca1 --- /dev/null +++ b/lib/retryable/configuration.rb @@ -0,0 +1,65 @@ +module Retryable + # Used to set up and modify settings for the retryable. + class Configuration + OPTIONS = [ + :ensure, + :exception_cb, + :matching, + :on, + :sleep, + :tries + ].freeze + + attr_accessor :ensure + attr_accessor :exception_cb + attr_accessor :matching + attr_accessor :on + attr_accessor :sleep + attr_accessor :tries + + attr_accessor :enabled + + alias_method :enabled?, :enabled + + def initialize + @ensure = Proc.new {} + @exception_cb = Proc.new {} + @matching = /.*/ + @on = StandardError + @sleep = 1 + @tries = 2 + + @enabled = true + end + + def enable + @enabled = true + end + + def disable + @enabled = false + end + + # Allows config options to be read like a hash + # + # @param [Symbol] option Key for a given attribute + def [](option) + send(option) + end + + # Returns a hash of all configurable options + def to_hash + OPTIONS.inject({}) do |hash, option| + hash[option.to_sym] = self.send(option) + hash + end + end + + # Returns a hash of all configurable options merged with +hash+ + # + # @param [Hash] hash A set of configuration options that will take precedence over the defaults + def merge(hash) + to_hash.merge(hash) + end + end +end diff --git a/spec/lib/config_spec.rb b/spec/lib/config_spec.rb deleted file mode 100644 index 9387ec3..0000000 --- a/spec/lib/config_spec.rb +++ /dev/null @@ -1,23 +0,0 @@ -require 'spec_helper' - -RSpec.describe Retryable do - it 'is enabled by default' do - expect(Retryable).to be_enabled - end - - it 'could be disabled' do - Retryable.disable - expect(Retryable).not_to be_enabled - end - - context 'when disabled' do - before do - Retryable.disable - end - - it 'could be re-enabled' do - Retryable.enable - expect(Retryable).to be_enabled - end - end -end diff --git a/spec/lib/configuration_spec.rb b/spec/lib/configuration_spec.rb new file mode 100644 index 0000000..a806365 --- /dev/null +++ b/spec/lib/configuration_spec.rb @@ -0,0 +1,39 @@ +require 'spec_helper' + +RSpec.describe Retryable do + it 'is enabled by default' do + expect(Retryable).to be_enabled + end + + it 'could be disabled' do + Retryable.disable + expect(Retryable).not_to be_enabled + end + + context 'when disabled' do + before do + Retryable.disable + end + + it 'could be re-enabled' do + Retryable.enable + expect(Retryable).to be_enabled + end + end + + context 'when configured globally with custom sleep parameter' do + it 'passes retry count and exception on retry' do + expect(Kernel).to receive(:sleep).once.with(3) + + Retryable.configure do |config| + config.sleep = 3 + end + + count_retryable(:tries => 2) do |tries, ex| + expect(ex.class).to eq(StandardError) if tries > 0 + raise StandardError if tries < 1 + end + expect(@try_count).to eq(2) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index b92178d..ce71960 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -4,6 +4,10 @@ RSpec.configure do |config| config.disable_monkey_patching! + config.before(:each) do + reset_config + end + def count_retryable(*opts) @try_count = 0 return Retryable.retryable(*opts) do |*args| @@ -11,4 +15,10 @@ def count_retryable(*opts) yield *args end end + + private + + def reset_config + Retryable.configuration = nil + end end