Permalink
Please sign in to comment.
Showing
with
302 additions
and 0 deletions.
- +5 −0 Changelog.md
- +3 −0 Gemfile
- +22 −0 Gemfile.lock
- +21 −0 LICENSE
- +51 −0 README.md
- +11 −0 RakeFile
- +70 −0 lib/retry_upto.rb
- +23 −0 retry_upto.gemspec
- +92 −0 test/retry_upto_test.rb
- +4 −0 test/test_helper.rb
@@ -0,0 +1,5 @@ | ||
+# retry_upto changelog | ||
+ | ||
+## v1.0 | ||
+ | ||
+First version |
3
Gemfile
@@ -0,0 +1,3 @@ | ||
+source "http://rubygems.org" | ||
+ | ||
+gemspec |
22
Gemfile.lock
@@ -0,0 +1,22 @@ | ||
+PATH | ||
+ remote: . | ||
+ specs: | ||
+ retry_upto (1.0) | ||
+ | ||
+GEM | ||
+ remote: http://rubygems.org/ | ||
+ specs: | ||
+ metaclass (0.0.1) | ||
+ minitest (2.6.1) | ||
+ mocha (0.10.0) | ||
+ metaclass (~> 0.0.1) | ||
+ rake (0.9.2) | ||
+ | ||
+PLATFORMS | ||
+ ruby | ||
+ | ||
+DEPENDENCIES | ||
+ minitest (> 2.0) | ||
+ mocha (>= 0.10.0) | ||
+ rake (>= 0.9.2) | ||
+ retry_upto! |
21
LICENSE
@@ -0,0 +1,21 @@ | ||
+Copyright (c) 2011 Raul Murciano, Glenn Gillen, Pedro Belo, | ||
+Jaime Iniesta, Lleïr Borras and Aitor García Rey. | ||
+ | ||
+Permission is hereby granted, free of charge, to any person obtaining | ||
+a copy of this software and associated documentation files (the | ||
+"Software"), to deal in the Software without restriction, including | ||
+without limitation the rights to use, copy, modify, merge, publish, | ||
+distribute, sublicense, and/or sell copies of the Software, and to | ||
+permit persons to whom the Software is furnished to do so, subject to | ||
+the following conditions: | ||
+ | ||
+The above copyright notice and this permission notice shall be | ||
+included in all copies or substantial portions of the Software. | ||
+ | ||
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, | ||
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF | ||
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND | ||
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE | ||
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION | ||
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION | ||
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. |
51
README.md
@@ -0,0 +1,51 @@ | ||
+# retry_upto: retry with steroids | ||
+ | ||
+## Usage | ||
+ | ||
+Basic usage | ||
+ | ||
+- retries up to 5 times catching any exception, doesn't wait between attempts: | ||
+ | ||
+ retry_upto(5) do ... end | ||
+ | ||
+Waiting time between attempts | ||
+ | ||
+- retries up to 5 times, waits 2 seconds between attempts: | ||
+ | ||
+ retry_upto(5, :interval => 2) do ... end | ||
+ | ||
+Varying waiting time between attempts | ||
+ | ||
+- retries up to 5 times, waits 1 second after the first attempt and increases | ||
+ the time between the following attempts (2, 4, 8, ...): | ||
+ | ||
+ retry_upto(5, :interval => 1, :growth => 2) do ... end | ||
+ | ||
+- retries up to 5 times, waits 1 second after the first attempt and decreases | ||
+ the time between the following attempts (0.5, 0.25, 0.125, ...): | ||
+ | ||
+ retry_upto(5, :interval => 1, :growth => 0.5) do ... end | ||
+ | ||
+- retries up to 5 times, waits 1 second after the first attempt and increases | ||
+ randomly the time between the following attempts: | ||
+ | ||
+ retry_upto(5, :interval => 1, :growth => lambda{ |x| x + rand(3) } ) do ... end | ||
+ | ||
+Retrying only when certain Exceptions get raised | ||
+ | ||
+- retries up to 5 times only after a ZeroDivisionError, raising any other Exception: | ||
+ | ||
+ retry_upto(5, :rescue => ZeroDivisionError) do ... end | ||
+ | ||
+All the options described above can be combined together. | ||
+ | ||
+## License | ||
+ | ||
+See the LICENSE file included in the distribution. | ||
+ | ||
+## Authors | ||
+ | ||
+This gem was born from gists by Raul Murciano, Glenn Gillen, Pedro Belo, Jaime Iniesta, Lleïr Borras and ideas taken from Aitor García Rey. | ||
+ | ||
+Yes, so many brain cells and so few lines of code. Great, isn't it? | ||
+ |
11
RakeFile
@@ -0,0 +1,11 @@ | ||
+require 'rake' | ||
+require 'rake/testtask' | ||
+ | ||
+task :default => [:test] | ||
+ | ||
+desc 'Run tests' | ||
+Rake::TestTask.new('test') do |t| | ||
+ t.pattern = 'test/*_test.rb' | ||
+ t.verbose = true | ||
+ t.warning = true | ||
+end |
@@ -0,0 +1,70 @@ | ||
+# retry with steroids | ||
+# | ||
+# Basic usage: | ||
+# | ||
+# - retries up to 5 times catching any exception, doesn't wait between attempts | ||
+# | ||
+# retry_upto(5) do ... end | ||
+# | ||
+# | ||
+# Waiting time between attempts: | ||
+# | ||
+# - retries up to 5 times, waits 2 seconds between attempts | ||
+# | ||
+# retry_upto(5, :interval => 2) do ... end | ||
+# | ||
+# | ||
+# Varying waiting time between attempts: | ||
+# | ||
+# - retries up to 5 times, waits 1 second after the first attempt and increases | ||
+# the time between the following attempts (2, 4, 8, ...) | ||
+# | ||
+# retry_upto(5, :interval => 1, :growth => 2) do ... end | ||
+# | ||
+# - retries up to 5 times, waits 1 second after the first attempt and decreases | ||
+# the time between the following attempts (0.5, 0.25, 0.125, ...) | ||
+# | ||
+# retry_upto(5, :interval => 1, :growth => 0.5) do ... end | ||
+# | ||
+# - retries up to 5 times, waits 1 second after the first attempt and increases | ||
+# randomly the time between the following attempts | ||
+# | ||
+# retry_upto(5, :interval => 1, :growth => lambda{ |x| x + rand(3) } ) do ... end | ||
+# | ||
+# | ||
+# Retrying only when certain Exceptions get raised: | ||
+# | ||
+# - retries up to 5 times only after a ZeroDivisionError, raising any other Exception | ||
+# | ||
+# retry_upto(5, :rescue => ZeroDivisionError) do ... end | ||
+# | ||
+# | ||
+# All the described options can be combined together. | ||
+ | ||
+def retry_upto(max_retries = 1, options = {}) | ||
+ yield | ||
+rescue (options[:rescue] || Exception) | ||
+ raise if (max_retries -= 1) == 0 | ||
+ sleep(options[:interval] || 0) | ||
+ if options[:growth].respond_to?('*') | ||
+ options[:interval] = options[:interval] * options[:growth] | ||
+ elsif options[:growth].respond_to?(:call) | ||
+ options[:interval] = options[:growth].call(options[:interval]) | ||
+ end | ||
+ retry | ||
+end | ||
+ | ||
+ | ||
+ | ||
+# Extends enumerator to allow usage like: | ||
+# | ||
+# 5.times.retry do | ||
+# ... | ||
+# end | ||
+# | ||
+ | ||
+class Enumerator | ||
+ def retry(options = {}, &blk) | ||
+ retry_upto(self.count, options, &blk) | ||
+ end | ||
+end |
@@ -0,0 +1,23 @@ | ||
+$:.unshift Dir.pwd | ||
+require File.expand_path("./lib/retry_upto") | ||
+ | ||
+Gem::Specification.new do |s| | ||
+ s.name = "retry_upto" | ||
+ s.version = "1.0" | ||
+ s.authors = ["Raul Murciano", "Glenn Gillen", "Pedro Belo", "Jaime Iniesta", "Lleïr Borras", "Aitor García Rey"] | ||
+ s.email = ["raul@murciano.net"] | ||
+ s.homepage = "http://github.com/raul/retry_upto" | ||
+ s.summary = "retry with steroids" | ||
+ s.description = "adds some useful options to retry code blocks" | ||
+ | ||
+ | ||
+ s.files = Dir["{lib,test}/**/*", "[A-Z]*", "init.rb"] - ["Gemfile.lock"] | ||
+ s.require_path = 'lib' | ||
+ | ||
+ s.add_development_dependency 'minitest', '>2.0' | ||
+ s.add_development_dependency 'mocha', '>=0.10.0' | ||
+ s.add_development_dependency 'rake', '>=0.9.2' | ||
+ | ||
+ s.rubyforge_project = "retry_upto" | ||
+ s.platform = Gem::Platform::RUBY | ||
+end |
@@ -0,0 +1,92 @@ | ||
+require './test/test_helper' | ||
+ | ||
+class Retry_uptoTest < MiniTest::Unit::TestCase | ||
+ | ||
+ class FooError < Exception; end | ||
+ | ||
+ # raises ZeroDivisionError in the two first hits | ||
+ class Target | ||
+ attr_accessor :hits | ||
+ def initialize; @hits = 0; end | ||
+ | ||
+ def hit! | ||
+ @hits += 1 | ||
+ raise FooError if @hits < 3 | ||
+ end | ||
+ end | ||
+ | ||
+ def setup | ||
+ @target = Target.new | ||
+ end | ||
+ | ||
+ # number of attempts | ||
+ | ||
+ def test_retries_one_time_by_default_without_capturing_the_exception | ||
+ assert_raises(FooError) do | ||
+ retry_upto{ @target.hit! } | ||
+ end | ||
+ assert_equal 1, @target.hits | ||
+ end | ||
+ | ||
+ def test_retries_the_desired_number_of_attempts | ||
+ retry_upto(3){ @target.hit! } | ||
+ assert_equal 3, @target.hits | ||
+ end | ||
+ | ||
+ # interval between attempts | ||
+ | ||
+ def test_there_is_no_interval_between_attempts_by_default | ||
+ self.expects(:sleep).times(2).with(0).returns(nil) | ||
+ retry_upto(3){ @target.hit! } | ||
+ end | ||
+ | ||
+ def test_interval_between_attempts_can_be_customized | ||
+ self.expects(:sleep).times(2).with(5).returns(nil) | ||
+ retry_upto(3, :interval => 5){ @target.hit! } | ||
+ end | ||
+ | ||
+ # interval growth between attempts | ||
+ | ||
+ def test_inverval_can_be_multiplied_by_an_integer_growth | ||
+ self.expects(:sleep).times(1).with(5) | ||
+ self.expects(:sleep).times(1).with(15) | ||
+ retry_upto(3, :interval => 5, :growth => 3){ @target.hit! } | ||
+ end | ||
+ | ||
+ def test_grow_for_inverval_between_attempts_can_be_defined_with_a_lambda | ||
+ self.expects(:sleep).times(1).with(5) | ||
+ self.expects(:sleep).times(1).with(7) | ||
+ retry_upto(3, :interval => 5, :growth => lambda{ |t| t + 2 }){ @target.hit! } | ||
+ end | ||
+ | ||
+ # exceptions | ||
+ | ||
+ def test_by_default_any_exception_gets_captured_if_there_are_attempts_left | ||
+ retry_upto(3){ @target.hit! } | ||
+ assert true # if we reach this no exception was raised out of retry_upto | ||
+ end | ||
+ | ||
+ def test_the_last_attempt_does_not_capture_the_exception | ||
+ assert_raises(FooError) do | ||
+ retry_upto(2){ @target.hit! } | ||
+ end | ||
+ end | ||
+ | ||
+ def test_a_specified_exception_will_be_captured_between_attempts | ||
+ retry_upto(3, :rescue => FooError){ @target.hit! } | ||
+ assert true # if we reach this no exception was raised out of retry_upto | ||
+ end | ||
+ | ||
+ def test_the_last_attempt_does_not_capture_the_specified_exception | ||
+ assert_raises(FooError) do | ||
+ retry_upto(2, :rescue => FooError){ @target.hit! } | ||
+ end | ||
+ end | ||
+ | ||
+ def test_a_exception_different_from_the_specified_one_will_not_be_captured_between_attempts | ||
+ assert_raises(FooError) do | ||
+ retry_upto(3, :rescue => ZeroDivisionError){ @target.hit! } | ||
+ end | ||
+ end | ||
+ | ||
+end |
@@ -0,0 +1,4 @@ | ||
+require 'rubygems' | ||
+require 'minitest/autorun' | ||
+require 'mocha' | ||
+require './lib/retry_upto' |
0 comments on commit
c3e6e3c