flakey_spec_catcher 0.9.2 → 0.9.8

Sign up to get free protection for your applications and to get access to all the features.
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: []