1# frozen_string_literal: true
2
3module Sometimes
4  def run_with_retries(example_to_run, retries)
5    example = RSpec.current_example
6    example.metadata[:retries] ||= retries
7
8    retries.times do |t|
9      example.metadata[:retried] = t + 1
10      example.instance_variable_set(:@exception, nil)
11      example_to_run.run
12      break unless example.exception
13    end
14
15    if e = example.exception
16      new_exception = e.exception(e.message + "[Retried #{retries} times]")
17      new_exception.set_backtrace e.backtrace
18      example.instance_variable_set(:@exception, new_exception)
19    end
20  end
21end
22
23RSpec.configure do |config|
24  config.include Sometimes
25  config.alias_example_to :sometimes, :sometimes => true
26  config.add_setting :sometimes_retry_count, :default => 5
27
28  config.around(:each, :sometimes => true) do |example|
29    retries = example.metadata[:retries] || RSpec.configuration.sometimes_retry_count
30    run_with_retries(example, retries)
31  end
32
33  config.after(:suite) do
34    message = proc do |color, text|
35      colored = RSpec::Core::Formatters::ConsoleCodes.wrap(text, color)
36      notification = RSpec::Core::Notifications::MessageNotification.new(colored)
37      formatter = RSpec.configuration.formatters.first
38      formatter.message(notification) if formatter.respond_to?(:message)
39    end
40
41    retried_examples = RSpec.world.example_groups.map do |g|
42      g.descendants.map do |d|
43        d.filtered_examples.select do |e|
44          e.metadata[:sometimes] && e.metadata.fetch(:retried, 1) > 1
45        end
46      end
47    end.flatten
48
49    message.call(retried_examples.empty? ? :green : :yellow, "\n\nRetried examples: #{retried_examples.count}")
50
51    retried_examples.each do |e|
52      message.call(:cyan, "  #{e.full_description}")
53      path = RSpec::Core::Metadata.relative_path(e.location)
54      message.call(:cyan, "  [#{e.metadata[:retried]}/#{e.metadata[:retries]}] " + path)
55    end
56  end
57end
58