flakey_spec_catcher 0.3.1 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 71dd2fe6a58fc01c2647d06f6aa20f9c904f6ced4d1a54278d96344c40534713
4
- data.tar.gz: cc060a637c1e14e04e7fd914b84c0b6c854912512a649fcf95696f57069444e1
3
+ metadata.gz: 1c2c74dc190189f31023ee816f41a7856aceee56ccca2de3f385b3559932cfde
4
+ data.tar.gz: 15b1cf40884ea015014a2d0b4b96ea223f46f9381751282d370e21641a0456da
5
5
  SHA512:
6
- metadata.gz: 28114d12131611ae6405bd0c3d066b44c16c3efece45633e46b1c8f5a263eec7189c45b46efbf112897e5e7cbeecf110f493ee499c79d76647f64e2da4a99abe
7
- data.tar.gz: be57cbc9ab2052f770507effd1b2e985124607bc5bfa11b0c659279726969aea9390df6b352501bdd31a1e179cb3c554fd448dc24c705f4974087772f502bd00
6
+ metadata.gz: f3eea41fa5d50fd29d292810a5b1fbf0c25452e57c85c81999fb257063a02f823657e15bd2cb9e23c63fe90923367d89b9098e5aef56142dcf51f9638412d86e
7
+ data.tar.gz: 7fbc79e679dfc5c4f38c995c377ab119eae0d4e890d1d5b7b97ee9516215f60e094439f246dfd7e97c2b4fa3f5c2dd5293979d230d26da7fc19174d1192ce327
data/README.md CHANGED
@@ -75,6 +75,20 @@ FSC_USAGE_PATTERNS = '{ spec/ui => bundle exec rspec }, { spec/api => parallel_r
75
75
 
76
76
  ```
77
77
 
78
+ ### Manually re-running a test:
79
+
80
+ Once the gem has been installed, you may manually specify a test to run along with a custom usage
81
+ (if none is provided, the specified test will run with RSpec::Core::Runner) and a repeat_factor.
82
+ If no repeat_factor is provided, FSC will check ENV['FSC_REPEAT_FACTOR'] to see if a value
83
+ has been configured. If none was provided, it will fall back on the default of 20 re-runs
84
+
85
+ Usage Examples:
86
+ ```
87
+ flakey_spec_catcher --test='api/spec/user_spec.rb:3' --repeat='15'
88
+ flakey_spec_catcher --test='api/spec/*_spec.rb' --usage='bundle exec parallel_rspec'
89
+ flakey_spec_catcher --test='api/spec/admin_spec.rb' --repeat='10' --usage='rspec'
90
+ ```
91
+
78
92
  ### How FSC works:
79
93
 
80
94
  1. GitController runs a git diff and creates a ChangeSummary object to
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'optparse'
4
+
5
+ module FlakeySpecCatcher
6
+ # CliOverride class
7
+ #
8
+ # Captures command line arguments for manual re-runs
9
+ class CliOverride
10
+ attr_reader :rerun_pattern, :rerun_usage, :repeat_factor
11
+
12
+ def initialize
13
+ parse_command_line_args
14
+ end
15
+
16
+ private
17
+
18
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
19
+ def parse_command_line_args
20
+ OptionParser.new do |opts|
21
+ opts.banner = 'Usage: flakey_spec_catcher [OPTIONS]'
22
+
23
+ opts.on('-t', '--test=TEST_NAME',
24
+ 'Specify a spec as `path/to/file:line_number` or `file/path` to run') do |test|
25
+ @rerun_pattern = remove_formatter_quotes(test)
26
+ end
27
+
28
+ opts.on('-u', '--usage=USAGE', 'Specify a re-run usage for the manual re-run') do |usage|
29
+ raise ArgumentError if @rerun_pattern.nil?
30
+
31
+ @rerun_usage = remove_formatter_quotes(usage)
32
+ end
33
+
34
+ opts.on('-r', '--repeat=REPEAT_FACTOR',
35
+ 'Specify a repeat factor for the manual re-run(s)') do |repeat|
36
+ raise ArgumentError if @rerun_pattern.nil?
37
+
38
+ parsed_repeat_factor = remove_formatter_quotes(repeat).to_i
39
+ @repeat_factor = parsed_repeat_factor if parsed_repeat_factor.positive?
40
+ end
41
+
42
+ opts.on('-v', '--version', 'Prints current flakey_spec_catcher_version') do
43
+ puts "flakey_spec_catcher Version: #{FlakeySpecCatcher::VERSION}"
44
+ end
45
+
46
+ opts.on('-h', '--help', 'Displays available flakey_spec_catcher cli overrides') do
47
+ puts opts
48
+ end
49
+ end.parse!
50
+ end
51
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
52
+
53
+ def remove_formatter_quotes(env_var)
54
+ env_var.tr('\'\"', '')
55
+ end
56
+ end
57
+ end
@@ -0,0 +1,40 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rspec'
4
+
5
+ module FlakeySpecCatcher
6
+ # EventListener class
7
+ #
8
+ # Listens for events from RSpec and processes data from those events.
9
+ #
10
+ # This event listener will receive example_failed and example_passed events
11
+ # from RSpec and will log those results into the provided result manager
12
+ class EventListener
13
+ def initialize(result_manager)
14
+ @result_manager = result_manager
15
+ end
16
+
17
+ def example_failed(notification)
18
+ description = notification.example.full_description.to_s.strip
19
+ @result_manager.add_result(description, format_message(notification))
20
+ end
21
+
22
+ def example_passed(notification)
23
+ description = notification.example.full_description.to_s.strip
24
+ @result_manager.add_result(description)
25
+ end
26
+
27
+ private
28
+
29
+ def format_message(notification)
30
+ exception = notification.example.execution_result.exception
31
+ message = if exception.is_a?(RSpec::Core::MultipleExceptionError)
32
+ exception.all_exceptions.join("\n").strip
33
+ else
34
+ exception.to_s.strip
35
+ end
36
+
37
+ message
38
+ end
39
+ end
40
+ end
@@ -16,7 +16,17 @@ module FlakeySpecCatcher
16
16
  @git_controller = git_controller
17
17
  @user_config = user_config
18
18
  @rerun_capsules = []
19
- pair_reruns_with_usages
19
+ determine_rerun_usage
20
+ end
21
+
22
+ def determine_rerun_usage
23
+ if @user_config.manual_rerun_pattern.nil?
24
+ pair_reruns_with_usages
25
+ else
26
+ @rerun_capsules.clear
27
+ inject_manual_reruns(@user_config.manual_rerun_pattern,
28
+ @user_config.manual_rerun_usage)
29
+ end
20
30
  end
21
31
 
22
32
  def tests_for_rerun
@@ -52,6 +62,37 @@ module FlakeySpecCatcher
52
62
  end
53
63
  end
54
64
 
65
+ # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
66
+ def inject_manual_reruns(pattern, usage)
67
+ # Check if file exists first and handle if user supplies testcase:line_number
68
+ file_name = pattern.split(':')[0]
69
+ line_number_present = pattern.split(':').count > 1
70
+ matching_files = Dir.glob(file_name)
71
+
72
+ # If no file matches are run, don't queue up re-runs
73
+ if matching_files.count.zero?
74
+ puts "Specified pattern #{pattern} did not match an existing file"
75
+ raise ArgumentError
76
+ end
77
+
78
+ # It won't make sense to have multiple files to run with one specific line number
79
+ if line_number_present
80
+ if matching_files.count > 1
81
+ puts "Specified pattern #{pattern} matched multiple files but a line number was specified"
82
+ raise ArgumentError
83
+ else
84
+ add_rerun_capsule(testcase: pattern, usage: usage)
85
+ end
86
+
87
+ # No line numbers, queue up all matching files
88
+ else
89
+ matching_files.each do |file|
90
+ add_rerun_capsule(testcase: file, usage: usage)
91
+ end
92
+ end
93
+ end
94
+ # rubocop:enable Metrics/AbcSize, Metrics/MethodLength
95
+
55
96
  private
56
97
 
57
98
  def filter_reruns_by_ignore_files(reruns)
@@ -1,5 +1,8 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../helpers/colorize'
4
+ require_relative '../helpers/indent_string'
5
+
3
6
  module FlakeySpecCatcher
4
7
  # RspecResult class
5
8
  #
@@ -9,116 +12,50 @@ module FlakeySpecCatcher
9
12
  # re-run of that spec, the results will be pushed to the RspecResult object.
10
13
  # This class will then organize and output the results accordingly.
11
14
  class RspecResult
12
- attr_reader :file_name, :total_examples_run, :total_failures, :failure_summary
15
+ attr_accessor :description, :total_times_run, :total_failures
13
16
 
14
- def initialize(file_name)
15
- @file_name = file_name
16
- @total_examples_run = 0
17
- @total_failures = 0
18
- @failure_summary = []
17
+ def initialize(description, exception_message = nil)
18
+ @description = description
19
+ @total_times_run = 1
20
+ @total_failures = exception_message ? 1 : 0
21
+ @failures = []
22
+ add_failure(exception_message) if exception_message
19
23
  end
20
24
 
21
- def info
22
- ratio = "#{@total_examples_run - @total_failures}/#{@total_examples_run}"
23
- puts "\nFile Name: #{@file_name} passed #{ratio} examples"
25
+ def add_run(exception_message = nil)
26
+ @total_times_run += 1
24
27
 
25
- return unless @total_failures.positive?
28
+ return unless exception_message
26
29
 
27
- puts ' Failure Summary:'
28
- @failure_summary.each do |example, _occurrences|
29
- example.info
30
- end
30
+ @total_failures += 1
31
+ add_failure(exception_message)
31
32
  end
32
33
 
33
- def add_run(results)
34
- parse_totals(results)
35
-
36
- # Parse the failed examples and add them into the failure_summary
37
- if @failure_summary.empty?
38
- # Add the initial runs
39
- @failure_summary = parse_failed_examples(results)
40
- else
41
- populate_failure_summary(parse_failed_examples(results))
42
- end
34
+ def add_failure(exception_message)
35
+ failure = @failures.find { |f| f.exception_message == exception_message }
36
+ failure ? failure.add_failure : @failures.push(RSpecFailure.new(exception_message))
43
37
  end
44
38
 
45
- private
46
-
47
- def parse_failed_examples(results)
48
- failures = []
49
- # Currently we're only going to return the name of the example that failed
50
- # Example:
51
- # 1) Flakey spec Passes some of the time
52
- # Failure/Error: expect(1).to eq(rand(3))
53
- #
54
- # expected: 0
55
- # got: 1
56
- #
57
- # (compared using ==)
58
- # ./spec/flakey_example_spec.rb:6:in `block (2 levels) in <top (required)>'
59
-
60
- # Would return 'Flakey spec Passes some of the time'
61
-
62
- # Get the line that contains the failed example - Denoted by '1)' in the examples
63
- # and strip the example ennumerators
64
- example_lines = results.scan(/^\s*\d\).*$/)
65
- failed_examples = example_lines.map { |example| example.strip.sub!(/\d\)\s*/, '') }
66
-
67
- # Use the first line of the failed_examples to get the actual failures
68
- failed_examples.each do |example|
69
- # Go from the spec example description until '#)' or 'Finished in'
70
- result = results[/#{Regexp.escape(example)}(.*?)(Finished in| \d\))/m, 1]
71
- failures << RSpecFailure.new(example, result.strip)
39
+ def print_results
40
+ puts "\n#{@description.yellow}\nFAILED #{total_failures} / #{total_times_run} times"
41
+ @failures.each do |f|
42
+ puts "#{f.count.to_s.indent(2)} times with exception message:"
43
+ puts f.exception_message.indent(4).red.to_s
72
44
  end
73
-
74
- # Return the array of RSpecFailures
75
- failures
76
45
  end
77
46
 
78
- def parse_totals(results)
79
- # Get summary line to determine number of run results.
80
- # Will appear as "3 examples, 0 failures"
81
- summary = results.split("\n").find { |line| line =~ /example.*failure/ }
82
- pass_count, fail_count = summary.split(' ').select { |x| x[/\d/] }
83
- @total_examples_run += pass_count.to_i
84
- @total_failures += fail_count.to_i
85
- end
47
+ # Simple class to contain failed example data
48
+ class RSpecFailure
49
+ attr_reader :exception_message, :count
86
50
 
87
- def populate_failure_summary(failures)
88
- # For each failure, if that example is in @failure_summary, add the results to it
89
- failures.each do |failure|
90
- match_found = false
91
- @failure_summary.each do |example|
92
- # If example is already in the summary, add the expect/actual to the example's failures
93
- if example.example_name == failure.example_name
94
- example.add_case(failure.failure_details.join(''))
95
- match_found = true
96
- end
97
- end
98
- # If example is not in the summary, add the whole object to the array of examples
99
- @failure_summary << failure unless match_found
51
+ def initialize(exception_message)
52
+ @exception_message = exception_message
53
+ @count = 1
100
54
  end
101
- end
102
- end
103
55
 
104
- # Simple class to contain failed example data
105
- class RSpecFailure
106
- attr_reader :example_name, :failure_details
107
-
108
- def initialize(example_name, results)
109
- @example_name = example_name
110
- @failure_count = 1
111
- @failure_details = [results]
112
- end
113
-
114
- def add_case(results)
115
- @failure_count += 1
116
- @failure_details << results
117
- end
118
-
119
- def info
120
- puts "\n '#{@example_name}' failed #{@failure_count} time(s)"
121
- @failure_details.each { |failure| puts "\n #{failure}" }
56
+ def add_failure
57
+ @count += 1
58
+ end
122
59
  end
123
60
  end
124
61
  end
@@ -0,0 +1,49 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../helpers/colorize'
4
+ require_relative './rspec_result'
5
+
6
+ module FlakeySpecCatcher
7
+ # RspecResultManager class
8
+ #
9
+ # Holds a collection of RSpec results and provides helper methods
10
+ #
11
+ # An RspecResultManager will hold a collection of results, one for each
12
+ # distinct example. It also provides helpers for adding new results,
13
+ # displaying aggregate results, and checking the state of the collection.
14
+ class RspecResultManager
15
+ def initialize(rspec_result_class)
16
+ @result_class = rspec_result_class
17
+ @results = []
18
+ end
19
+
20
+ def add_result(description, message = nil)
21
+ result = @results.find { |r| r.description == description }
22
+ result ? result.add_run(message) : @results.push(@result_class.new(description, message))
23
+ end
24
+
25
+ def print_results
26
+ puts "\n********** SUMMARY **********"
27
+ print_successes_count(successes)
28
+ failures.each(&:print_results) if failures.any?
29
+ end
30
+
31
+ def failures
32
+ @results.select { |r| r.total_failures.positive? }
33
+ end
34
+
35
+ def successes
36
+ @results.select { |r| r.total_failures.zero? }
37
+ end
38
+
39
+ def empty?
40
+ @results.empty?
41
+ end
42
+
43
+ private
44
+
45
+ def print_successes_count(successes)
46
+ puts "#{successes.count} example(s) ran without any failures".green if successes.any?
47
+ end
48
+ end
49
+ end
@@ -1,27 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'rspec'
4
- require_relative './rspec_result'
5
4
  require_relative './git_controller'
6
5
  require_relative './capsule_manager'
7
6
  require_relative './user_config'
8
7
  require_relative './rerun_manager'
8
+ require_relative './rspec_result_manager'
9
+ require_relative './event_listener.rb'
9
10
 
10
11
  module FlakeySpecCatcher
11
12
  class Runner
12
13
  attr_reader :user_config, :rerun_manager, :git_controller
13
- attr_reader :rspec_results
14
14
 
15
15
  def initialize(test_mode: false,
16
16
  git_controller: FlakeySpecCatcher::GitController.new(test_mode: test_mode),
17
17
  user_config: FlakeySpecCatcher::UserConfig.new,
18
+ result_manager: FlakeySpecCatcher::RspecResultManager.new(FlakeySpecCatcher::RspecResult),
18
19
  rerun_manager: FlakeySpecCatcher::RerunManager.new(git_controller: git_controller,
19
20
  user_config: user_config))
20
21
 
21
- @rspec_results = []
22
22
  @git_controller = git_controller
23
23
  @user_config = user_config
24
24
  @rerun_manager = rerun_manager
25
+ @rspec_result_manager = result_manager
25
26
  end
26
27
 
27
28
  # Debug Methods
@@ -51,47 +52,61 @@ module FlakeySpecCatcher
51
52
  status = 0
52
53
  @user_config.repeat_factor.times do
53
54
  @rerun_manager.rerun_capsules.sort.each do |capsule|
54
- iteration_status = 0
55
- if capsule.default_usage?
56
- iteration_status = invoke_rspec_runner(capsule.testcase)
57
- else
58
- `#{capsule.usage} #{capsule.testcase}`
59
- iteration_status = $?.exitstatus # rubocop:disable Style/SpecialGlobalVars
60
- end
55
+ iteration_status = handle_capsule_rerun(capsule)
61
56
  status = [status, iteration_status].max
62
57
  end
63
58
  end
64
59
 
60
+ display_results(status)
61
+
65
62
  # Always return 0 if silent_mode is enabled
66
- if @user_config.silent_mode
67
- 0
68
- else
69
- status
70
- end
63
+ @user_config.silent_mode ? 0 : status
71
64
  end
72
65
 
73
66
  private
74
67
 
68
+ def display_results(status)
69
+ @rspec_result_manager.print_results unless @rspec_result_manager.empty?
70
+ status.zero? ? print_no_flakey_specs_detected_message : print_flakey_specs_detected_message
71
+ end
72
+
75
73
  def invoke_rspec_runner(test)
76
- return_status = RSpec::Core::Runner.run([test])
74
+ configure_listener
75
+ # Pass in CLI options to suppress normal output, and only run the specified test
76
+ return_status = RSpec::Core::Runner.run(['--out', '/dev/null', test])
77
77
  RSpec.clear_examples
78
78
  return_status
79
79
  end
80
80
 
81
- def print_rspec_results
82
- @rspec_results.map(&:get_info)
81
+ def invoke_custom_rspec_runner(usage, testcase)
82
+ `#{usage} #{testcase}`
83
+ $?.exitstatus # rubocop:disable Style/SpecialGlobalVars
84
+ end
83
85
 
84
- # Determine the exit code based on the parsed results
85
- if @rspec_results.any? { |r| r.total_failures.positive? }
86
- puts "\n********************************************"
87
- puts 'Flakiness Detected! Exiting with status code 1'
88
- puts "********************************************\n"
89
- return 1
86
+ def handle_capsule_rerun(capsule)
87
+ if capsule.default_usage?
88
+ invoke_rspec_runner(capsule.testcase)
90
89
  else
91
- puts "\n***********************************************"
92
- puts 'No Flakiness Detected! Exiting with status code 0'
93
- puts "***********************************************\n"
94
- return 0
90
+ invoke_custom_rspec_runner(capsule.usage, capsule.testcase)
91
+ end
92
+ end
93
+
94
+ def print_flakey_specs_detected_message
95
+ puts "\n**********************************************".magenta
96
+ puts ' Flakiness Detected!'.magenta
97
+ puts "**********************************************\n".magenta
98
+ end
99
+
100
+ def print_no_flakey_specs_detected_message
101
+ puts "\n*************************************************".green
102
+ puts ' No Flakiness Detected!'.green
103
+ puts "*************************************************\n".green
104
+ end
105
+
106
+ def configure_listener
107
+ RSpec.configure do |c|
108
+ c.reporter.register_listener EventListener.new(@rspec_result_manager),
109
+ :example_failed, :example_passed
95
110
  end
96
111
  end
97
112
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative './cli_override'
3
4
  module FlakeySpecCatcher
4
5
  # UserConfig class
5
6
  #
@@ -7,21 +8,40 @@ module FlakeySpecCatcher
7
8
  class UserConfig
8
9
  attr_reader :repeat_factor, :ignore_files, :ignore_branches, :silent_mode
9
10
  attr_reader :rerun_file_only, :rspec_usage_patterns
11
+ attr_reader :manual_rerun_pattern, :manual_rerun_usage
10
12
  USER_CONFIG_ENV_VARS = %w[FSC_REPEAT_FACTOR FSC_IGNORE_FILES FSC_IGNORE_BRANCHES
11
13
  FSC_SILENT_MODE FSC_RERUN_FILE_ONLY FSC_USAGE_PATTERNS].freeze
12
14
 
13
- def initialize
15
+ def initialize(cli_override: CliOverride.new)
14
16
  @repeat_factor = initialize_repeat_factor(ENV['FSC_REPEAT_FACTOR'])
15
17
  @ignore_files = env_var_string_to_array(ENV['FSC_IGNORE_FILES'])
16
18
  @ignore_branches = env_var_string_to_array(ENV['FSC_IGNORE_BRANCHES'])
17
19
  @silent_mode = env_var_string_to_bool(ENV['FSC_SILENT_MODE'])
18
20
  @rerun_file_only = env_var_string_to_bool(ENV['FSC_RERUN_FILE_ONLY'])
19
21
  @rspec_usage_patterns = env_var_string_to_pairs(ENV['FSC_USAGE_PATTERNS'])
20
- parse_commit_message
22
+ @cli_override = cli_override
23
+ override_settings
21
24
  end
22
25
 
23
26
  private
24
27
 
28
+ def override_settings
29
+ apply_cli_override
30
+ return unless @manual_rerun_pattern.nil?
31
+
32
+ parse_commit_message
33
+ end
34
+
35
+ def apply_cli_override
36
+ @manual_rerun_pattern = @cli_override.rerun_pattern
37
+ @manual_rerun_usage = @cli_override.rerun_usage
38
+ @repeat_factor = @cli_override.repeat_factor if @cli_override.repeat_factor.to_i.positive?
39
+ end
40
+
41
+ def remove_formatter_quotes(env_var)
42
+ env_var.tr('\'\"', '')
43
+ end
44
+
25
45
  def initialize_repeat_factor(env_var)
26
46
  env_var.to_i.positive? ? env_var.to_i : 20
27
47
  end
@@ -60,7 +80,7 @@ module FlakeySpecCatcher
60
80
 
61
81
  # For each array of matches, flatten the individual match group sub-array
62
82
  # then remove the formatter quotes we require the values to be wrapped in
63
- matches.map { |m| m.join('').tr("'", '') }
83
+ matches.map { |m| remove_formatter_quotes(m.join('')) }
64
84
  end
65
85
 
66
86
  def parse_commit_message
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlakeySpecCatcher
4
- VERSION = '0.3.1'
4
+ VERSION = '0.5.0'
5
5
  end
@@ -0,0 +1,35 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This is an extension of the Ruby String class.
4
+ # Example usage: puts "This message is red".red
5
+ # From http://stackoverflow.com/a/11482430/3038677
6
+ class String
7
+ module Colors
8
+ RED = 31
9
+ GREEN = 32
10
+ YELLOW = 33
11
+ MAGENTA = 35
12
+ end
13
+
14
+ def red
15
+ colorize(Colors::RED)
16
+ end
17
+
18
+ def green
19
+ colorize(Colors::GREEN)
20
+ end
21
+
22
+ def yellow
23
+ colorize(Colors::YELLOW)
24
+ end
25
+
26
+ def magenta
27
+ colorize(Colors::MAGENTA)
28
+ end
29
+
30
+ private
31
+
32
+ def colorize(color_code)
33
+ "\e[#{color_code}m#{self}\e[0m"
34
+ end
35
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: false
2
+
3
+ String.class_eval do
4
+ def indent(count, char = ' ')
5
+ gsub(/([^\n]*)(\n|$)/) do |_match|
6
+ last_iteration = (Regexp.last_match(1) == '' && Regexp.last_match(2) == '')
7
+ line = ''
8
+ line << (char * count) unless last_iteration
9
+ line << Regexp.last_match(1)
10
+ line << Regexp.last_match(2)
11
+ line
12
+ end
13
+ end
14
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flakey_spec_catcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.1
4
+ version: 0.5.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Watson
@@ -90,13 +90,18 @@ files:
90
90
  - lib/flakey_spec_catcher/change_capsule.rb
91
91
  - lib/flakey_spec_catcher/change_context.rb
92
92
  - lib/flakey_spec_catcher/change_summary.rb
93
+ - lib/flakey_spec_catcher/cli_override.rb
94
+ - lib/flakey_spec_catcher/event_listener.rb
93
95
  - lib/flakey_spec_catcher/git_controller.rb
94
96
  - lib/flakey_spec_catcher/rerun_capsule.rb
95
97
  - lib/flakey_spec_catcher/rerun_manager.rb
96
98
  - lib/flakey_spec_catcher/rspec_result.rb
99
+ - lib/flakey_spec_catcher/rspec_result_manager.rb
97
100
  - lib/flakey_spec_catcher/runner.rb
98
101
  - lib/flakey_spec_catcher/user_config.rb
99
102
  - lib/flakey_spec_catcher/version.rb
103
+ - lib/helpers/colorize.rb
104
+ - lib/helpers/indent_string.rb
100
105
  homepage:
101
106
  licenses:
102
107
  - MIT
@@ -117,7 +122,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
117
122
  - !ruby/object:Gem::Version
118
123
  version: '0'
119
124
  requirements: []
120
- rubygems_version: 3.0.1
125
+ rubyforge_project:
126
+ rubygems_version: 2.7.6
121
127
  signing_key:
122
128
  specification_version: 4
123
129
  summary: Run new or changed specs many times to prevent unreliable specs