flakey_spec_catcher 0.9.2 → 0.9.8

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: 924bcd5b7fe71399bc43663478b9b238731e9920e4fcc8c54ba8977a345ee065
4
- data.tar.gz: '080682849736c9d46acd6eeeb8ccc8e98115fb9558004e821f4beed33dfd455e'
3
+ metadata.gz: fce0bbfeef6c7ba89586f7d59b64084329d685108396037f77dec4276cf7a402
4
+ data.tar.gz: 015d979e822fb914c0eaecbe1a518eb2971fba7d1a1d41c13e5ffb83e184b0f3
5
5
  SHA512:
6
- metadata.gz: 68cf5c4b41ae6b158a501bab2a6134a40dd1d0ca3d7ce26452948b986ddf713d443458643d2a27a8ff565b9856c647867630015c78dc2f9e7784bf31b0d772e4
7
- data.tar.gz: cee42fcbf243eaa03e56745491515ae7580e2e7974d751faef5f2387c40f07cb20b9f225d188e04009950b7cfd339720e406b789f4e7fa2ec537c2c109b81c18
6
+ metadata.gz: fa94174f28662dc5d1e10be69879852c9b7f1947dfa7acfcafc462916a9fa405a1fd8807a79a81f9cbf83fd91bc930370bae54491f892da7f90e9b3c1b2440c5
7
+ data.tar.gz: 2ce6f81244240c34a64a1c8319d95e56a50d24f270642f77ad5edf1f33464e8b2df601c2e6d20ca6705eca94f683e30caa81138b8909f57e0b301b5a897a09c4
data/README.md CHANGED
@@ -152,11 +152,18 @@ FSC_USAGE_PATTERNS = '{ spec/ui => bundle exec rspec }, { spec/api => parallel_r
152
152
  ```
153
153
  -t, --test=TEST_NAME Specify one or more specs in comma separated list
154
154
  -u, --usage=USAGE Specify a re-run usage for the manual re-run
155
+ --use-parent Use the parent of commit for git diff instead of the Head of SCM
155
156
  -r, --repeat=REPEAT_FACTOR Specify a repeat factor for the manual re-run(s)
156
157
  -e, --excluded-tags=EXCLUDED Specify tags to exclude in a comma separated list
157
158
  -o, --output=PATH_TO_OUTPUT Direct all re-run output to a specific file
159
+ --node-total Specify now many nodes the run is being split into
160
+ --node-index Specify the index this node represents in a split run
161
+ -d, --dry-run Performs all setup but doesn't run any tests
162
+ --dry-run-quiet Prints list of tests to be run
163
+ --verbose Send all output from running tests to stdout
158
164
  -v, --version Prints current flakey_spec_catcher_version
159
165
  -h, --help Displays available flakey_spec_catcher cli overrides
166
+ --rspec-options '[OPTIONS]' Execute default usage with rspec options
160
167
  ```
161
168
 
162
169
  Examples:
@@ -195,6 +202,36 @@ flakey_spec_catcher --test='api/spec/*_spec.rb' --usage='bundle exec parallel_rs
195
202
  flakey_spec_catcher --test='api/spec/admin_spec.rb' --repeat='10' --usage='rspec'
196
203
  ```
197
204
 
205
+ ### Running on multiple nodes:
206
+
207
+ If there are a lot of tests required to be checked, then you may want to run the
208
+ tests on multiple nodes. this can be done with the combination of the `--node-total`
209
+ and `--node-index` arguments.
210
+
211
+ Usage Examples:
212
+ ```sh
213
+ # if you have 30 tests to run
214
+
215
+ # this will be tests 0-9
216
+ bundle exec flakey_spec_catcher --node-total=3 --node-index=0
217
+ # this will be tests 10-19
218
+ bundle exec flakey_spec_catcher --node-total=3 --node-index=1
219
+ # this will be tests 20-29
220
+ bundle exec flakey_spec_catcher --node-total=3 --node-index=2
221
+ ```
222
+
223
+ Note that the last node may have a different number of tests that it runs:
224
+ ```sh
225
+ # if you have 20 tests to run
226
+
227
+ # this will be tests 0-6 (7 tests)
228
+ bundle exec flakey_spec_catcher --node-total=3 --node-index=0
229
+ # this will be tests 7-13 (7 tests)
230
+ bundle exec flakey_spec_catcher --node-total=3 --node-index=1
231
+ # this will be tests 14-19 (6 tests)
232
+ bundle exec flakey_spec_catcher --node-total=3 --node-index=2
233
+ ```
234
+
198
235
  ### Additional Git Detection Examples
199
236
 
200
237
  ```sh
@@ -9,4 +9,6 @@ if app.user_config.enable_runs
9
9
  app.show_settings
10
10
  app.rerun_preview
11
11
  exit app.run_specs
12
+ elsif app.user_config.dry_run
13
+ app.show_test_list
12
14
  end
@@ -76,7 +76,7 @@ module FlakeySpecCatcher
76
76
  end
77
77
 
78
78
  def line_matches_method_or_block(line)
79
- return true if line =~ /\s*do(\s+|$)/ || line =~ /^\s*def\s+/
79
+ return true if line =~ /\s*do(\s+|$)/ || line =~ /^\s*def\s+/ || line =~ /^\s*if\s+/
80
80
 
81
81
  false
82
82
  end
@@ -7,14 +7,18 @@ module FlakeySpecCatcher
7
7
  #
8
8
  # Captures command line arguments for manual re-runs
9
9
  class CliOverride
10
- attr_reader :rerun_patterns, :rerun_usage, :repeat_factor, :enable_runs, :excluded_tags, :use_parent
11
- attr_reader :output_file
10
+ attr_reader :rerun_patterns, :rerun_usage, :repeat_factor, :enable_runs, :excluded_tags, :use_parent, :dry_run
11
+ attr_reader :output_file, :split_nodes, :split_index, :verbose, :test_options
12
12
 
13
13
  def initialize
14
+ @dry_run = false
14
15
  @enable_runs = true
15
16
  @excluded_tags = []
16
17
  @use_parent = false
18
+ @verbose = false
19
+ @test_options = []
17
20
  parse_command_line_args
21
+ validate_arguments
18
22
  end
19
23
 
20
24
  private
@@ -36,15 +40,20 @@ module FlakeySpecCatcher
36
40
  end
37
41
 
38
42
  opts.on('-u', '--usage=USAGE', 'Specify a re-run usage for the manual re-run') do |usage|
39
- raise ArgumentError if @rerun_patterns.nil?
40
-
41
43
  @rerun_usage = remove_formatter_quotes(usage)
42
44
  end
43
45
 
46
+ opts.on('--node-total=SPLIT_NODES', 'Specify now many nodes the run is being split into') do |nodes|
47
+ @split_nodes = remove_formatter_quotes(nodes).to_i
48
+ end
49
+
50
+ opts.on('--node-index=SPLIT_INDEX', 'Specify the index this node represents in a split run') do |index|
51
+ @split_index = remove_formatter_quotes(index).to_i
52
+ end
53
+
44
54
  opts.on('-r', '--repeat=REPEAT_FACTOR',
45
55
  'Specify a repeat factor for the manual re-run(s)') do |repeat|
46
- parsed_repeat_factor = remove_formatter_quotes(repeat).to_i
47
- @repeat_factor = parsed_repeat_factor if parsed_repeat_factor.positive?
56
+ @repeat_factor = remove_formatter_quotes(repeat).to_i
48
57
  end
49
58
 
50
59
  opts.on('-e', '--excluded-tags=EXCLUDED_TAGS',
@@ -62,12 +71,44 @@ module FlakeySpecCatcher
62
71
  @enable_runs = false
63
72
  end
64
73
 
74
+ opts.on('-d', '--dry-run', "Performs all setup but doesn't run any tests") do
75
+ @dry_run = true
76
+ end
77
+
78
+ opts.on('--dry-run-quiet', 'Prints list of tests to be run') do
79
+ @enable_runs = false
80
+ @dry_run = true
81
+ end
82
+
83
+ opts.on('--verbose', 'Send all output from running tests to stdout') do
84
+ @verbose = true
85
+ end
86
+
65
87
  opts.on('-h', '--help', 'Displays available flakey_spec_catcher cli overrides') do
66
88
  puts opts
67
89
  @enable_runs = false
68
90
  end
91
+
92
+ opts.on("--rspec-options '[OPTIONS]'", 'execute default usage with rspec options') do |arg|
93
+ @test_options = arg.split(/[ ](?=(?:[^"]*"[^"]*")*[^"]*$)(?=(?:[^']*'[^']*')*[^']*$)/)
94
+ end
69
95
  end.parse!
70
96
  end
97
+
98
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
99
+ def validate_arguments
100
+ if !@rerun_usage.nil? && @rerun_patterns.nil?
101
+ raise ArgumentError, 'rerun usage can only be specified with rerun patterns'
102
+ end
103
+ raise ArgumentError, 'repeat factor must be positive' if !@repeat_factor.nil? && @repeat_factor.negative?
104
+ raise ArgumentError, 'split index and split nodes must be specified together' if @split_nodes.nil? != @split_index.nil?
105
+
106
+ splitting = !@split_nodes.nil? && !@split_index.nil?
107
+ raise ArgumentError, 'split total must be positive' if splitting && @split_nodes.negative?
108
+ raise ArgumentError, 'split index must be positive' if splitting && @split_index.negative?
109
+ raise ArgumentError, 'split index must be less than split nodes' if splitting && @split_index >= @split_nodes
110
+ end
111
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
71
112
  # rubocop:enable Metrics/AbcSize, Metrics/MethodLength, Metrics/BlockLength
72
113
 
73
114
  def remove_formatter_quotes(env_var)
@@ -26,6 +26,9 @@ module FlakeySpecCatcher
26
26
  @remote = find_git_remote
27
27
  @branch = find_remote_branch
28
28
  @capsule_manager = capsule_manager
29
+ # we don't need to do any git comparisons if we have manually specified tests to run.
30
+ return unless @user_config.manual_rerun_patterns.nil? || @user_config.manual_rerun_patterns.empty?
31
+
29
32
  initialize_git_comparison(test_mode)
30
33
  parse_changes
31
34
  identify_change_contexts
@@ -105,10 +108,12 @@ module FlakeySpecCatcher
105
108
  @git_comparison = "#{@base_commit_sha}..#{@working_commit_sha}"
106
109
  end
107
110
 
111
+ # rubocop:disable Metrics/AbcSize
108
112
  def parse_changes
109
113
  # For each file, get the change block
110
114
  diff_files.each do |filename|
111
115
  next unless filename =~ /_spec.rb/
116
+ next unless File.file?(filename)
112
117
 
113
118
  # Get diff pairs [ /path/to/file, "@@ -19 +19,4 @@" ]
114
119
  diff = `git diff --unified=0 #{@git_comparison} -- #{filename}`
@@ -123,6 +128,7 @@ module FlakeySpecCatcher
123
128
  end
124
129
  end
125
130
  end
131
+ # rubocop:enable Metrics/AbcSize
126
132
 
127
133
  def identify_change_contexts
128
134
  @capsule_manager.change_capsules.each(&:fill_contexts)
@@ -17,6 +17,7 @@ module FlakeySpecCatcher
17
17
  @user_config = user_config
18
18
  @rerun_capsules = []
19
19
  determine_rerun_usage
20
+ split!
20
21
  end
21
22
 
22
23
  def determine_rerun_usage
@@ -110,6 +111,37 @@ module FlakeySpecCatcher
110
111
 
111
112
  private
112
113
 
114
+ # rubocop:disable Metrics/AbcSize
115
+ def split!
116
+ return unless !@user_config.split_nodes.nil? && !@user_config.split_index.nil?
117
+
118
+ # we create this restriction for simplicity. if we have multiple capsules then
119
+ # we have to split while being aware of splitting across capsules.
120
+ # if we absolutely need to split with different usage types, then we can implement that later..
121
+ raise ArgumentError('can only split on one usage type') if @rerun_capsules.size != 1
122
+
123
+ # ensure everything is sorted consistently before splitting
124
+ capsule = @rerun_capsules[0]
125
+ capsule.testcase.sort!
126
+
127
+ # compute the total tests that are changing
128
+ test_count = capsule.testcase.size
129
+
130
+ # the last node is going to be different from the previous nodes.
131
+ # for instance, if there are 13 test to run and you want 3 nodes:
132
+ # node1 -> 5
133
+ # node2 -> 5
134
+ # node3 -> 3
135
+ # it may be more efficient to split differently, but for now this will work.
136
+ node_test_count = (test_count / @user_config.split_nodes.to_f).ceil
137
+
138
+ skipping = @user_config.split_index * node_test_count
139
+ skipping_until = (@user_config.split_index + 1) * node_test_count
140
+ split_tests = capsule.testcase[skipping...skipping_until]
141
+ @rerun_capsules = [FlakeySpecCatcher::RerunCapsule.new(usage: capsule.usage, testcase: split_tests)]
142
+ end
143
+ # rubocop:enable Metrics/AbcSize
144
+
113
145
  def filter_reruns_by_ignore_files(reruns)
114
146
  return reruns if @user_config.ignore_files.count.zero?
115
147
 
@@ -10,7 +10,7 @@ require_relative './event_listener.rb'
10
10
 
11
11
  module FlakeySpecCatcher
12
12
  class Runner
13
- attr_reader :user_config, :rerun_manager, :git_controller
13
+ attr_reader :user_config, :rerun_manager, :git_controller, :test_run_count
14
14
 
15
15
  def initialize(test_mode: false,
16
16
  user_config: FlakeySpecCatcher::UserConfig.new,
@@ -23,6 +23,8 @@ module FlakeySpecCatcher
23
23
  @user_config = user_config
24
24
  @rerun_manager = rerun_manager
25
25
  @rspec_result_manager = result_manager
26
+ @test_run_count = 0
27
+ @temp_output_file = @user_config.output_file + 'temp' unless @user_config.output_file == File::NULL
26
28
  end
27
29
 
28
30
  # Debug Methods
@@ -34,8 +36,10 @@ module FlakeySpecCatcher
34
36
  puts " Current Sha: #{@git_controller.working_commit_sha}"
35
37
  puts " Base Sha: #{@git_controller.base_commit_sha}"
36
38
  puts " Repeat factor: #{@user_config.repeat_factor}"
39
+ puts " Node Total: #{@user_config.split_nodes}" if @user_config.split_nodes
40
+ puts " Node Index: #{@user_config.split_index}" if @user_config.split_index
37
41
  puts " Changed Specs Detected: #{@git_controller.changed_examples}"
38
- return if @user_config.output_file == '/dev/null'
42
+ return if @user_config.output_file == File::NULL
39
43
 
40
44
  puts " Verbose Output Path: #{@user_config.output_file}"
41
45
  end
@@ -55,7 +59,17 @@ module FlakeySpecCatcher
55
59
  end
56
60
  # end Debug methods
57
61
 
62
+ def show_test_list
63
+ @rerun_manager.rerun_capsules.each do |capsule|
64
+ capsule.testcase.each do |test|
65
+ puts test
66
+ end
67
+ end
68
+ end
69
+
58
70
  def run_specs
71
+ return 0 if @user_config.dry_run
72
+
59
73
  status = 0
60
74
  @rerun_manager.rerun_capsules.sort.each do |capsule|
61
75
  @user_config.repeat_factor.times do
@@ -65,6 +79,7 @@ module FlakeySpecCatcher
65
79
  end
66
80
 
67
81
  display_results(status)
82
+ copy_to_user_output unless @user_config.output_file == File::NULL
68
83
 
69
84
  # Always return 0 if silent_mode is enabled
70
85
  @user_config.silent_mode ? 0 : status
@@ -80,21 +95,29 @@ module FlakeySpecCatcher
80
95
  def invoke_rspec_runner(test)
81
96
  configure_listener
82
97
  # Pass in CLI options to suppress normal output, and only run the specified test
83
- rspec_args = ['--out', @user_config.output_file, test]
84
- return_status = RSpec::Core::Runner.run(rspec_args)
98
+ rspec_args = ['--format', 'documentation', '--out', @user_config.output_file, test]
99
+ # Rspec output sent to stdout if verbose option is true
100
+ rspec_args << '-fd' if @user_config.verbose
101
+ return_status = RSpec::Core::Runner.run(@user_config.test_options.concat(rspec_args))
85
102
  RSpec.clear_examples
103
+ copy_output_to_temp_file unless @user_config.output_file == File::NULL
86
104
  return_status
87
105
  end
88
106
 
89
107
  def invoke_custom_rspec_runner(usage, testcase)
90
- custom_usage_output = `#{usage} #{testcase.join(' ')}`
108
+ if @user_config.verbose
109
+ $stdout << custom_usage_output = `#{usage} #{testcase.join(' ')}`
110
+ else
111
+ custom_usage_output = `#{usage} #{testcase.join(' ')}`
112
+ end
91
113
 
92
- File.open(@user_config.output_file, 'a') { |f| f.puts custom_usage_output } if @user_config.output_file != '/dev/null'
114
+ File.open(@user_config.output_file, 'a') { |f| f.puts custom_usage_output } if @user_config.output_file != File::NULL
93
115
 
94
116
  $?.exitstatus # rubocop:disable Style/SpecialGlobalVars
95
117
  end
96
118
 
97
119
  def handle_capsule_rerun(capsule)
120
+ @test_run_count += 1
98
121
  if capsule.default_usage?
99
122
  invoke_rspec_runner(capsule.testcase)
100
123
  else
@@ -120,5 +143,18 @@ module FlakeySpecCatcher
120
143
  :example_failed, :example_passed
121
144
  end
122
145
  end
146
+
147
+ def copy_output_to_temp_file
148
+ # copy contents of output file, it will get overwritten in RSpec::Core::Runner
149
+ File.open(@temp_output_file, 'a') do |f|
150
+ f.puts IO.readlines(@user_config.output_file)
151
+ end
152
+ end
153
+
154
+ def copy_to_user_output
155
+ # copy all appended output to original output file, delete the temp output file
156
+ IO.copy_stream(@temp_output_file, @user_config.output_file) if File.exist?(@temp_output_file)
157
+ File.delete(@temp_output_file) if File.exist?(@temp_output_file)
158
+ end
123
159
  end
124
160
  end
@@ -10,7 +10,8 @@ module FlakeySpecCatcher
10
10
  attr_reader :repeat_factor, :ignore_files, :ignore_branches, :silent_mode
11
11
  attr_reader :rerun_file_only, :rspec_usage_patterns, :excluded_tags
12
12
  attr_reader :manual_rerun_patterns, :manual_rerun_usage
13
- attr_reader :enable_runs, :output_file, :use_parent
13
+ attr_reader :enable_runs, :output_file, :use_parent, :dry_run
14
+ attr_reader :split_nodes, :split_index, :verbose, :test_options
14
15
 
15
16
  USER_CONFIG_ENV_VARS = %w[FSC_REPEAT_FACTOR FSC_IGNORE_FILES FSC_IGNORE_BRANCHES
16
17
  FSC_SILENT_MODE FSC_RERUN_FILE_ONLY FSC_USAGE_PATTERNS
@@ -34,14 +35,8 @@ module FlakeySpecCatcher
34
35
  @rspec_usage_patterns = env_var_string_to_pairs(ENV['FSC_USAGE_PATTERNS'])
35
36
  @excluded_tags = env_var_string_to_tags(ENV['FSC_EXCLUDED_TAGS'])
36
37
  @output_file = ENV['FSC_OUTPUT_FILE']
37
- end
38
- # rubocop:enable Metrics/AbcSize
39
-
40
- def override_settings
41
- apply_cli_override
42
- return unless @manual_rerun_patterns.nil?
43
-
44
- parse_commit_message
38
+ @split_nodes = env_var_string_to_int_or_nil('FSC_NODE_TOTAL')
39
+ @split_index = env_var_string_to_int_or_nil('FSC_NODE_INDEX')
45
40
  end
46
41
 
47
42
  def apply_cli_override
@@ -50,19 +45,32 @@ module FlakeySpecCatcher
50
45
  @use_parent = @cli_override.use_parent
51
46
  @repeat_factor = @cli_override.repeat_factor if @cli_override.repeat_factor.to_i.positive?
52
47
  @enable_runs = @cli_override.enable_runs
48
+ @dry_run = @cli_override.dry_run
49
+ @split_nodes = @cli_override.split_nodes unless @cli_override.split_nodes.nil?
50
+ @split_index = @cli_override.split_index unless @cli_override.split_index.nil?
53
51
  @excluded_tags = if @cli_override.excluded_tags.empty?
54
52
  @excluded_tags
55
53
  else
56
54
  @cli_override.excluded_tags
57
55
  end
58
56
  @output_file = set_output_file
57
+ @verbose = @cli_override.verbose
58
+ @test_options = @cli_override.test_options
59
+ end
60
+ # rubocop:enable Metrics/AbcSize
61
+
62
+ def override_settings
63
+ apply_cli_override
64
+ return unless @manual_rerun_patterns.nil?
65
+
66
+ parse_commit_message
59
67
  end
60
68
 
61
69
  def set_output_file
62
70
  if !@cli_override.output_file.nil?
63
71
  @cli_override.output_file
64
72
  elsif @output_file.nil? || @output_file.strip.empty?
65
- '/dev/null'
73
+ File::NULL
66
74
  else
67
75
  @output_file
68
76
  end
@@ -76,6 +84,18 @@ module FlakeySpecCatcher
76
84
  env_var.to_i.positive? ? env_var.to_i : 20
77
85
  end
78
86
 
87
+ def env_var_string_to_int_or_nil(env_var_name)
88
+ env_var = ENV[env_var_name]
89
+ if env_var.nil? || env_var.empty?
90
+ nil
91
+ else
92
+ check = env_var.to_i
93
+ raise ArgumentError, "#{env_var_name} must be positive" if check.negative?
94
+
95
+ check
96
+ end
97
+ end
98
+
79
99
  def env_var_string_to_array(env_var)
80
100
  if env_var.nil? || env_var.empty?
81
101
  []
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlakeySpecCatcher
4
- VERSION = '0.9.2'
4
+ VERSION = '0.9.8'
5
5
  end
metadata CHANGED
@@ -1,16 +1,16 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: flakey_spec_catcher
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.9.2
4
+ version: 0.9.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Watson
8
8
  - Mikey Hargiss
9
9
  - Ben Nelson
10
- autorequire:
10
+ autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-02-18 00:00:00.000000000 Z
13
+ date: 2020-07-23 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -68,7 +68,7 @@ dependencies:
68
68
  - - "~>"
69
69
  - !ruby/object:Gem::Version
70
70
  version: '0.17'
71
- description:
71
+ description:
72
72
  email:
73
73
  - bwatson@instructure.com
74
74
  - mhargiss@instructure.com
@@ -102,12 +102,12 @@ files:
102
102
  - lib/flakey_spec_catcher/version.rb
103
103
  - lib/helpers/colorize.rb
104
104
  - lib/helpers/indent_string.rb
105
- homepage:
105
+ homepage:
106
106
  licenses:
107
107
  - MIT
108
108
  metadata:
109
109
  allowed_push_host: https://rubygems.org
110
- post_install_message:
110
+ post_install_message:
111
111
  rdoc_options: []
112
112
  require_paths:
113
113
  - lib
@@ -122,8 +122,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
122
122
  - !ruby/object:Gem::Version
123
123
  version: '0'
124
124
  requirements: []
125
- rubygems_version: 3.0.4
126
- signing_key:
125
+ rubygems_version: 3.0.3
126
+ signing_key:
127
127
  specification_version: 4
128
128
  summary: Run new or changed specs many times to prevent unreliable specs
129
129
  test_files: []