1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191
|
require 'rspec/core'
require 'rspec/retry/version'
require 'rspec_ext/rspec_ext'
module RSpec
class Retry
def self.setup
RSpec.configure do |config|
config.add_setting :verbose_retry, :default => false
config.add_setting :default_retry_count, :default => 1
config.add_setting :default_sleep_interval, :default => 0
config.add_setting :exponential_backoff, :default => false
config.add_setting :clear_lets_on_failure, :default => true
config.add_setting :display_try_failure_messages, :default => false
# retry based on example metadata
config.add_setting :retry_count_condition, :default => ->(_) { nil }
# If a list of exceptions is provided and 'retry' > 1, we only retry if
# the exception that was raised by the example is NOT in that list. Otherwise
# we ignore the 'retry' value and fail immediately.
#
# If no list of exceptions is provided and 'retry' > 1, we always retry.
config.add_setting :exceptions_to_hard_fail, :default => []
# If a list of exceptions is provided and 'retry' > 1, we only retry if
# the exception that was raised by the example is in that list. Otherwise
# we ignore the 'retry' value and fail immediately.
#
# If no list of exceptions is provided and 'retry' > 1, we always retry.
config.add_setting :exceptions_to_retry, :default => []
# Callback between retries
config.add_setting :retry_callback, :default => nil
config.around(:each) do |ex|
ex.run_with_retry
end
end
end
attr_reader :context, :ex
def initialize(ex, opts = {})
@ex = ex
@ex.metadata.merge!(opts)
current_example.attempts ||= 0
end
def current_example
@current_example ||= RSpec.current_example
end
def retry_count
[
(
ENV['RSPEC_RETRY_RETRY_COUNT'] ||
ex.metadata[:retry] ||
RSpec.configuration.retry_count_condition.call(ex) ||
RSpec.configuration.default_retry_count
).to_i,
1
].max
end
def attempts
current_example.attempts ||= 0
end
def attempts=(val)
current_example.attempts = val
end
def clear_lets
!ex.metadata[:clear_lets_on_failure].nil? ?
ex.metadata[:clear_lets_on_failure] :
RSpec.configuration.clear_lets_on_failure
end
def sleep_interval
if ex.metadata[:exponential_backoff]
2**(current_example.attempts-1) * ex.metadata[:retry_wait]
else
ex.metadata[:retry_wait] ||
RSpec.configuration.default_sleep_interval
end
end
def exceptions_to_hard_fail
ex.metadata[:exceptions_to_hard_fail] ||
RSpec.configuration.exceptions_to_hard_fail
end
def exceptions_to_retry
ex.metadata[:exceptions_to_retry] ||
RSpec.configuration.exceptions_to_retry
end
def verbose_retry?
RSpec.configuration.verbose_retry?
end
def display_try_failure_messages?
RSpec.configuration.display_try_failure_messages?
end
def run
example = current_example
loop do
if attempts > 0
RSpec.configuration.formatters.each { |f| f.retry(example) if f.respond_to? :retry }
if verbose_retry?
message = "RSpec::Retry: #{ordinalize(attempts + 1)} try #{example.location}"
message = "\n" + message if attempts == 1
RSpec.configuration.reporter.message(message)
end
end
example.metadata[:retry_attempts] = self.attempts
example.metadata[:retry_exceptions] ||= []
example.clear_exception
ex.run
self.attempts += 1
break if example.exception.nil?
example.metadata[:retry_exceptions] << example.exception
break if attempts >= retry_count
if exceptions_to_hard_fail.any?
break if exception_exists_in?(exceptions_to_hard_fail, example.exception)
end
if exceptions_to_retry.any?
break unless exception_exists_in?(exceptions_to_retry, example.exception)
end
if verbose_retry? && display_try_failure_messages?
if attempts != retry_count
exception_strings =
if ::RSpec::Core::MultipleExceptionError::InterfaceTag === example.exception
example.exception.all_exceptions.map(&:to_s)
else
[example.exception.to_s]
end
try_message = "\n#{ordinalize(attempts)} Try error in #{example.location}:\n#{exception_strings.join "\n"}\n"
RSpec.configuration.reporter.message(try_message)
end
end
example.example_group_instance.clear_lets if clear_lets
# If the callback is defined, let's call it
if RSpec.configuration.retry_callback
example.example_group_instance.instance_exec(example, &RSpec.configuration.retry_callback)
end
sleep sleep_interval if sleep_interval.to_f > 0
end
end
private
# borrowed from ActiveSupport::Inflector
def ordinalize(number)
if (11..13).include?(number.to_i % 100)
"#{number}th"
else
case number.to_i % 10
when 1; "#{number}st"
when 2; "#{number}nd"
when 3; "#{number}rd"
else "#{number}th"
end
end
end
def exception_exists_in?(list, exception)
list.any? do |exception_klass|
exception.is_a?(exception_klass) || exception_klass === exception
end
end
end
end
RSpec::Retry.setup
|