flakey_spec_catcher 0.3.1 → 0.5.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|