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 +4 -4
- data/README.md +14 -0
- data/lib/flakey_spec_catcher/cli_override.rb +57 -0
- data/lib/flakey_spec_catcher/event_listener.rb +40 -0
- data/lib/flakey_spec_catcher/rerun_manager.rb +42 -1
- data/lib/flakey_spec_catcher/rspec_result.rb +32 -95
- data/lib/flakey_spec_catcher/rspec_result_manager.rb +49 -0
- data/lib/flakey_spec_catcher/runner.rb +43 -28
- data/lib/flakey_spec_catcher/user_config.rb +23 -3
- data/lib/flakey_spec_catcher/version.rb +1 -1
- data/lib/helpers/colorize.rb +35 -0
- data/lib/helpers/indent_string.rb +14 -0
- metadata +8 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1c2c74dc190189f31023ee816f41a7856aceee56ccca2de3f385b3559932cfde
|
4
|
+
data.tar.gz: 15b1cf40884ea015014a2d0b4b96ea223f46f9381751282d370e21641a0456da
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
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
|
-
|
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
|
-
|
15
|
+
attr_accessor :description, :total_times_run, :total_failures
|
13
16
|
|
14
|
-
def initialize(
|
15
|
-
@
|
16
|
-
@
|
17
|
-
@total_failures = 0
|
18
|
-
@
|
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
|
22
|
-
|
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
|
28
|
+
return unless exception_message
|
26
29
|
|
27
|
-
|
28
|
-
|
29
|
-
example.info
|
30
|
-
end
|
30
|
+
@total_failures += 1
|
31
|
+
add_failure(exception_message)
|
31
32
|
end
|
32
33
|
|
33
|
-
def
|
34
|
-
|
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
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
|
79
|
-
|
80
|
-
|
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
|
-
|
88
|
-
|
89
|
-
|
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
|
-
|
105
|
-
|
106
|
-
|
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 =
|
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
|
-
|
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
|
-
|
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
|
82
|
-
|
81
|
+
def invoke_custom_rspec_runner(usage, testcase)
|
82
|
+
`#{usage} #{testcase}`
|
83
|
+
$?.exitstatus # rubocop:disable Style/SpecialGlobalVars
|
84
|
+
end
|
83
85
|
|
84
|
-
|
85
|
-
if
|
86
|
-
|
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
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
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
|
-
|
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('')
|
83
|
+
matches.map { |m| remove_formatter_quotes(m.join('')) }
|
64
84
|
end
|
65
85
|
|
66
86
|
def parse_commit_message
|
@@ -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.
|
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
|
-
|
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
|