Permalink
Browse files

First commit

  • Loading branch information...
raul committed Oct 8, 2011
0 parents commit c3e6e3c61814b42ee3e79f5a50440589c45830fc
Showing with 302 additions and 0 deletions.
  1. +5 −0 Changelog.md
  2. +3 −0 Gemfile
  3. +22 −0 Gemfile.lock
  4. +21 −0 LICENSE
  5. +51 −0 README.md
  6. +11 −0 RakeFile
  7. +70 −0 lib/retry_upto.rb
  8. +23 −0 retry_upto.gemspec
  9. +92 −0 test/retry_upto_test.rb
  10. +4 −0 test/test_helper.rb
@@ -0,0 +1,5 @@
+# retry_upto changelog
+
+## v1.0
+
+First version
@@ -0,0 +1,3 @@
+source "http://rubygems.org"
+
+gemspec
@@ -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.
@@ -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?
+
@@ -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

Please sign in to comment.