flakey_spec_catcher 0.9.8 → 0.11.2

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: fce0bbfeef6c7ba89586f7d59b64084329d685108396037f77dec4276cf7a402
4
- data.tar.gz: 015d979e822fb914c0eaecbe1a518eb2971fba7d1a1d41c13e5ffb83e184b0f3
3
+ metadata.gz: adf31fd04e49611a2c10622b19350ab444224589815ac5f54948ec6aad53fc3a
4
+ data.tar.gz: 23526cc9694ac22402e6b47fbe956197af5be8b5648abba785e822884e1cd747
5
5
  SHA512:
6
- metadata.gz: fa94174f28662dc5d1e10be69879852c9b7f1947dfa7acfcafc462916a9fa405a1fd8807a79a81f9cbf83fd91bc930370bae54491f892da7f90e9b3c1b2440c5
7
- data.tar.gz: 2ce6f81244240c34a64a1c8319d95e56a50d24f270642f77ad5edf1f33464e8b2df601c2e6d20ca6705eca94f683e30caa81138b8909f57e0b301b5a897a09c4
6
+ metadata.gz: 9e15abca29cc1a0186ceadcb83f6238c3a847b4f59747e8c8bc0ef23387ed2a8b5cf3777675d32690bfb529b8e7aba6e6e8ba9970d3d9628628392cfa346506b
7
+ data.tar.gz: ce915b99014c9eee3210e55663dec289b20dcb39936a28ffd299fc7c0bb003561ef3d67e4a620bf28057e8ae4fe60aca1ed17f2e66436aa6d0f0fdae928e2629
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
+ /.byebug_history
1
2
  /coverage
2
3
  /Gemfile.lock
3
4
  /flakey_spec_catcher-*.gem
@@ -28,11 +28,13 @@ Gem::Specification.new do |gem|
28
28
  gem.require_paths = ['lib']
29
29
 
30
30
  gem.metadata['allowed_push_host'] = 'https://rubygems.org'
31
- gem.required_ruby_version = '>= 2.3'
31
+ gem.required_ruby_version = '>= 2.6'
32
32
 
33
- gem.add_dependency 'rspec', '~> 3.8'
33
+ gem.add_dependency 'rspec', '~> 3.10'
34
+ gem.add_development_dependency 'byebug', '~> 11.1'
34
35
  gem.add_development_dependency 'climate_control', '~> 0.2'
35
- gem.add_development_dependency 'rake', '~> 12.3'
36
- gem.add_development_dependency 'simplecov', '~> 0.17'
36
+ gem.add_development_dependency 'rake', '~> 13.0'
37
+ gem.add_development_dependency 'rubocop', '~> 0.93.1'
38
+ gem.add_development_dependency 'simplecov', '~> 0.19'
37
39
  end
38
40
  # rubocop:enable Layout/ExtraSpacing, Layout/SpaceAroundOperators
@@ -8,7 +8,7 @@ module FlakeySpecCatcher
8
8
  # A ChangeCapsule object will represent the changes made to a block of code. It
9
9
  # accomplishes this using ChangeContext and ChangeSummary objects.
10
10
  class ChangeCapsule
11
- attr_reader :file_name, :change_summary, :change_contexts
11
+ attr_reader :file_name, :change_summary, :change_contexts, :spec_tree
12
12
  SCOPE_SPECIFIERS = %w[it context describe scenario].freeze
13
13
  SHARED_EXAMPLES = %w[include_examples it_behaves_like it_should_behave_like matching].freeze
14
14
 
@@ -16,6 +16,7 @@ module FlakeySpecCatcher
16
16
  @file_name = file_name
17
17
  @change_summary = change_summary
18
18
  @change_contexts = []
19
+ @spec_tree = {}
19
20
  handle_initial_change_contexts(change_contexts)
20
21
  end
21
22
 
@@ -23,23 +24,30 @@ module FlakeySpecCatcher
23
24
  @change_contexts.map(&:rerun_info)
24
25
  end
25
26
 
27
+ # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
26
28
  def fill_contexts
27
29
  change_context_stack = []
28
30
  ignore_scope_closure = 0
29
31
  lines_in_file = File.read(@file_name).split("\n")
30
32
  lines_in_file.each_with_index do |line, index|
33
+ shared_example_identified = false
34
+
31
35
  # Check if line matches an rspec example or examplegroup format
32
36
  if line =~ spec_scope
33
37
  handle_change_context(line, index, change_context_stack)
34
38
  # Else, ignore other blocks that might pollute context stack
39
+ elsif line =~ shared_example_scope
40
+ handle_change_context(line, index, change_context_stack)
41
+ shared_example_identified = true
35
42
  elsif line_matches_method_or_block(line)
36
43
  ignore_scope_closure += 1
37
44
  end
38
45
 
39
46
  fill_context(line, index, change_context_stack)
40
47
 
41
- # Note - some things use do-like loops and we need to be able to ignore those
42
- if line =~ pop_scope
48
+ if shared_example_identified
49
+ change_context_stack.pop
50
+ elsif line =~ pop_scope
43
51
  if ignore_scope_closure.positive?
44
52
  ignore_scope_closure -= 1
45
53
  else
@@ -48,6 +56,7 @@ module FlakeySpecCatcher
48
56
  end
49
57
  end
50
58
  end
59
+ # rubocop:enable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
51
60
 
52
61
  private
53
62
 
@@ -69,14 +78,15 @@ module FlakeySpecCatcher
69
78
  end
70
79
 
71
80
  def spec_scope
72
- # Not sure if we need to check for description in quotes
73
- # spec_scope = /^\s*(#{SCOPE_SPECIFIERS.join("|")})\s*('.*'|".*").*do\s*$/
74
-
75
81
  /\s*(#{SCOPE_SPECIFIERS.join("|")}).*\s+do.*$/
76
82
  end
77
83
 
84
+ def shared_example_scope
85
+ /\s*(#{SHARED_EXAMPLES.join("|")}).*\s+.*$/
86
+ end
87
+
78
88
  def line_matches_method_or_block(line)
79
- return true if line =~ /\s*do(\s+|$)/ || line =~ /^\s*def\s+/ || line =~ /^\s*if\s+/
89
+ return true if line =~ /\s*do(\s+|$)/ || line =~ /^\s*def\s+/ || line =~ /^\s*if\s+/ || line =~ /^\s*class\s+/
80
90
 
81
91
  false
82
92
  end
@@ -112,6 +122,24 @@ module FlakeySpecCatcher
112
122
 
113
123
  change_context_stack.push(change_context) unless
114
124
  change_context_stack.any? { |c| c == change_context }
125
+
126
+ build_spec_tree(change_context_stack)
127
+ end
128
+
129
+ def build_spec_tree(change_contexts)
130
+ return unless change_contexts.count > 1
131
+
132
+ current_scope = change_contexts[-1].rerun_info
133
+ parent_scope = change_contexts[-2].rerun_info
134
+
135
+ # Don't build the file level context into spec tree
136
+ return unless parent_scope =~ /:/
137
+
138
+ @spec_tree[parent_scope] = if @spec_tree[parent_scope]
139
+ @spec_tree[parent_scope] << current_scope
140
+ else
141
+ [current_scope]
142
+ end
115
143
  end
116
144
  end
117
145
  end
@@ -8,7 +8,8 @@ module FlakeySpecCatcher
8
8
  # Captures command line arguments for manual re-runs
9
9
  class CliOverride
10
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
11
+ attr_reader :output_file, :split_nodes, :split_index, :verbose, :test_options, :break_on_first_failure
12
+ attr_reader :list_child_specs
12
13
 
13
14
  def initialize
14
15
  @dry_run = false
@@ -56,6 +57,14 @@ module FlakeySpecCatcher
56
57
  @repeat_factor = remove_formatter_quotes(repeat).to_i
57
58
  end
58
59
 
60
+ opts.on('--break-on-first-failure', 'Break on first failure') do |break_on_first_failure|
61
+ @break_on_first_failure = break_on_first_failure
62
+ end
63
+
64
+ opts.on('--list-child-specs', 'List Child Specs (Verbose Spec Listing') do |list_child_specs|
65
+ @list_child_specs = list_child_specs
66
+ end
67
+
59
68
  opts.on('-e', '--excluded-tags=EXCLUDED_TAGS',
60
69
  'Specify tags to exclude in a comma separated list') do |tags|
61
70
  @excluded_tags = parse_tags(tags)
@@ -56,45 +56,12 @@ module FlakeySpecCatcher
56
56
  return nil if @remote.nil?
57
57
 
58
58
  working_branch = `git branch | grep '*'`.delete('*').gsub(/\s+/, '')
59
- # `git remote show origin` will show us if our branch is configured to push
60
- # to a non-master branch. Note that our push destination is what we're interested in
61
- # Assume master if no matches
62
-
63
- # Example output
64
- # * remote origin
65
- # Fetch URL: gerrit:repo
66
- # Push URL: gerrit:repo
67
- # HEAD branch: master
68
- # Remote branches:
69
- # dev/reports tracked
70
- # edge tracked
71
- # master tracked
72
- # Local branch configured for 'git pull':
73
- # master merges with remote master
74
- # Local refs configured for 'git push':
75
- # dev/reports pushes to dev/reports (up to date)
76
- # master pushes to master (up to date)
77
-
78
- remote_branches = `git remote show #{@remote}`.split("\n")
79
-
80
- # Separate 'dev/reports pushes to dev/reports (up to date)' into
81
- # ['dev/reports', 'dev/reports'] or [<LOCAL BRANCH>, <REMOTE BRANCH>]
82
- remote_pairs = remote_branches.map { |r| r.scan(/(\S*)\s+pushes to\s+(\S*)\s+/).flatten }
83
-
84
- # check if the working branch (currently checked out branch) corresponds to a remote_pair
85
- # if so, use that remote pair for comparison, else use master
86
- match = remote_pairs.find do |pair|
87
- # working branch (pair[0]) pushes to remote (pair[1])
88
- pair[0] == working_branch
89
- end
59
+ branch_remote = `git config branch.#{working_branch}.remote`.strip
60
+ return 'master' unless @remote == branch_remote
90
61
 
91
- remote_branch = if match.nil?
92
- 'master'
93
- else
94
- # match is formatted as [working_branch, remote]
95
- match[1]
96
- end
97
- remote_branch
62
+ remote_branch = `git config branch.#{branch}.merge`.strip.sub(%r{^refs/heads/}, '')
63
+ remote_branch = nil if remote_branch.empty?
64
+ remote_branch || 'master'
98
65
  end
99
66
 
100
67
  def initialize_git_comparison(test_mode)
@@ -108,7 +75,7 @@ module FlakeySpecCatcher
108
75
  @git_comparison = "#{@base_commit_sha}..#{@working_commit_sha}"
109
76
  end
110
77
 
111
- # rubocop:disable Metrics/AbcSize
78
+ # rubocop:disable Metrics/CyclomaticComplexity
112
79
  def parse_changes
113
80
  # For each file, get the change block
114
81
  diff_files.each do |filename|
@@ -128,7 +95,7 @@ module FlakeySpecCatcher
128
95
  end
129
96
  end
130
97
  end
131
- # rubocop:enable Metrics/AbcSize
98
+ # rubocop:enable Metrics/CyclomaticComplexity
132
99
 
133
100
  def identify_change_contexts
134
101
  @capsule_manager.change_capsules.each(&:fill_contexts)
@@ -39,6 +39,7 @@ module FlakeySpecCatcher
39
39
  else
40
40
  all_non_example_groups - identify_tag_excluded_reruns
41
41
  end
42
+ tests = transform_parent_specs(tests).flatten if @user_config.list_child_specs
42
43
  filter_reruns_by_ignore_files(tests)
43
44
  end
44
45
 
@@ -171,5 +172,34 @@ module FlakeySpecCatcher
171
172
  @rerun_capsules.push(FlakeySpecCatcher::RerunCapsule.new(testcase: testcase, usage: usage))
172
173
  end
173
174
  end
175
+
176
+ def transform_file_level_change(test, tree)
177
+ # Map file level changes to the highest level Example Group in the tree
178
+ if test =~ /:/
179
+ test
180
+ else
181
+ tree.keys.first
182
+ end
183
+ end
184
+
185
+ def transform_parent_specs(tests)
186
+ transformed_tests = []
187
+
188
+ tests.each do |test|
189
+ capsule = @git_controller.capsule_manager.change_capsules.find { |c| c.file_name == test.split(':')[0] }
190
+ spec_tree = capsule.spec_tree
191
+ test = transform_file_level_change(test, spec_tree)
192
+ # If test is a key in the spec_tree, it's an example group, so we queue up its descendant examples
193
+ if spec_tree[test]
194
+ spec_tree[test].each do |child|
195
+ transformed_tests << transform_parent_specs([child]).flatten
196
+ end
197
+ # Else test is just an example
198
+ else
199
+ transformed_tests << test
200
+ end
201
+ end
202
+ transformed_tests
203
+ end
174
204
  end
175
205
  end
@@ -36,6 +36,7 @@ module FlakeySpecCatcher
36
36
  puts " Current Sha: #{@git_controller.working_commit_sha}"
37
37
  puts " Base Sha: #{@git_controller.base_commit_sha}"
38
38
  puts " Repeat factor: #{@user_config.repeat_factor}"
39
+ puts " Break on first failure: #{@user_config.break_on_first_failure}" if @user_config.break_on_first_failure
39
40
  puts " Node Total: #{@user_config.split_nodes}" if @user_config.split_nodes
40
41
  puts " Node Index: #{@user_config.split_index}" if @user_config.split_index
41
42
  puts " Changed Specs Detected: #{@git_controller.changed_examples}"
@@ -75,6 +76,7 @@ module FlakeySpecCatcher
75
76
  @user_config.repeat_factor.times do
76
77
  iteration_status = handle_capsule_rerun(capsule)
77
78
  status = [status, iteration_status].max
79
+ break if @user_config.break_on_first_failure && !status.zero?
78
80
  end
79
81
  end
80
82
 
@@ -12,10 +12,11 @@ module FlakeySpecCatcher
12
12
  attr_reader :manual_rerun_patterns, :manual_rerun_usage
13
13
  attr_reader :enable_runs, :output_file, :use_parent, :dry_run
14
14
  attr_reader :split_nodes, :split_index, :verbose, :test_options
15
+ attr_reader :break_on_first_failure, :list_child_specs
15
16
 
16
17
  USER_CONFIG_ENV_VARS = %w[FSC_REPEAT_FACTOR FSC_IGNORE_FILES FSC_IGNORE_BRANCHES
17
18
  FSC_SILENT_MODE FSC_RERUN_FILE_ONLY FSC_USAGE_PATTERNS
18
- FSC_EXCLUDED_TAGS FSC_OUTPUT_FILE].freeze
19
+ FSC_EXCLUDED_TAGS FSC_OUTPUT_FILE FSC_LIST_CHILD_SPECS].freeze
19
20
 
20
21
  def initialize(cli_override: CliOverride.new)
21
22
  apply_env_var_settings
@@ -32,6 +33,7 @@ module FlakeySpecCatcher
32
33
  @ignore_branches = env_var_string_to_array(ENV['FSC_IGNORE_BRANCHES'])
33
34
  @silent_mode = env_var_string_to_bool(ENV['FSC_SILENT_MODE'])
34
35
  @rerun_file_only = env_var_string_to_bool(ENV['FSC_RERUN_FILE_ONLY'])
36
+ @list_child_specs = env_var_string_to_bool(ENV['FSC_LIST_CHILD_SPECS'])
35
37
  @rspec_usage_patterns = env_var_string_to_pairs(ENV['FSC_USAGE_PATTERNS'])
36
38
  @excluded_tags = env_var_string_to_tags(ENV['FSC_EXCLUDED_TAGS'])
37
39
  @output_file = ENV['FSC_OUTPUT_FILE']
@@ -44,6 +46,8 @@ module FlakeySpecCatcher
44
46
  @manual_rerun_usage = @cli_override.rerun_usage
45
47
  @use_parent = @cli_override.use_parent
46
48
  @repeat_factor = @cli_override.repeat_factor if @cli_override.repeat_factor.to_i.positive?
49
+ @break_on_first_failure = @cli_override.break_on_first_failure
50
+ @list_child_specs = @cli_override.list_child_specs unless @cli_override.list_child_specs.nil?
47
51
  @enable_runs = @cli_override.enable_runs
48
52
  @dry_run = @cli_override.dry_run
49
53
  @split_nodes = @cli_override.split_nodes unless @cli_override.split_nodes.nil?
@@ -168,7 +172,7 @@ module FlakeySpecCatcher
168
172
  end
169
173
  end
170
174
 
171
- # rubocop:disable Metrics/CyclomaticComplexity
175
+ # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
172
176
  def override_user_config(env_var, env_value)
173
177
  case env_var
174
178
  when 'FSC_REPEAT_FACTOR'
@@ -181,6 +185,8 @@ module FlakeySpecCatcher
181
185
  @silent_mode = env_var_string_to_bool(env_value)
182
186
  when 'FSC_RERUN_FILE_ONLY'
183
187
  @rerun_file_only = env_var_string_to_bool(env_value)
188
+ when 'FSC_LIST_CHILD_SPECS'
189
+ @list_child_specs = env_var_string_to_bool(env_value)
184
190
  when 'FSC_USAGE_PATTERNS'
185
191
  @rspec_usage_patterns = env_var_string_to_pairs(env_value)
186
192
  when 'FSC_EXCLUDED_TAGS'
@@ -189,6 +195,6 @@ module FlakeySpecCatcher
189
195
  @output_file = env_value
190
196
  end
191
197
  end
192
- # rubocop:enable Metrics/CyclomaticComplexity
198
+ # rubocop:enable Metrics/CyclomaticComplexity, Metrics/MethodLength
193
199
  end
194
200
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlakeySpecCatcher
4
- VERSION = '0.9.8'
4
+ VERSION = '0.11.2'
5
5
  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.9.8
4
+ version: 0.11.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Watson
@@ -10,7 +10,7 @@ authors:
10
10
  autorequire:
11
11
  bindir: bin
12
12
  cert_chain: []
13
- date: 2020-07-23 00:00:00.000000000 Z
13
+ date: 2021-10-15 00:00:00.000000000 Z
14
14
  dependencies:
15
15
  - !ruby/object:Gem::Dependency
16
16
  name: rspec
@@ -18,14 +18,28 @@ dependencies:
18
18
  requirements:
19
19
  - - "~>"
20
20
  - !ruby/object:Gem::Version
21
- version: '3.8'
21
+ version: '3.10'
22
22
  type: :runtime
23
23
  prerelease: false
24
24
  version_requirements: !ruby/object:Gem::Requirement
25
25
  requirements:
26
26
  - - "~>"
27
27
  - !ruby/object:Gem::Version
28
- version: '3.8'
28
+ version: '3.10'
29
+ - !ruby/object:Gem::Dependency
30
+ name: byebug
31
+ requirement: !ruby/object:Gem::Requirement
32
+ requirements:
33
+ - - "~>"
34
+ - !ruby/object:Gem::Version
35
+ version: '11.1'
36
+ type: :development
37
+ prerelease: false
38
+ version_requirements: !ruby/object:Gem::Requirement
39
+ requirements:
40
+ - - "~>"
41
+ - !ruby/object:Gem::Version
42
+ version: '11.1'
29
43
  - !ruby/object:Gem::Dependency
30
44
  name: climate_control
31
45
  requirement: !ruby/object:Gem::Requirement
@@ -46,28 +60,42 @@ dependencies:
46
60
  requirements:
47
61
  - - "~>"
48
62
  - !ruby/object:Gem::Version
49
- version: '12.3'
63
+ version: '13.0'
64
+ type: :development
65
+ prerelease: false
66
+ version_requirements: !ruby/object:Gem::Requirement
67
+ requirements:
68
+ - - "~>"
69
+ - !ruby/object:Gem::Version
70
+ version: '13.0'
71
+ - !ruby/object:Gem::Dependency
72
+ name: rubocop
73
+ requirement: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - "~>"
76
+ - !ruby/object:Gem::Version
77
+ version: 0.93.1
50
78
  type: :development
51
79
  prerelease: false
52
80
  version_requirements: !ruby/object:Gem::Requirement
53
81
  requirements:
54
82
  - - "~>"
55
83
  - !ruby/object:Gem::Version
56
- version: '12.3'
84
+ version: 0.93.1
57
85
  - !ruby/object:Gem::Dependency
58
86
  name: simplecov
59
87
  requirement: !ruby/object:Gem::Requirement
60
88
  requirements:
61
89
  - - "~>"
62
90
  - !ruby/object:Gem::Version
63
- version: '0.17'
91
+ version: '0.19'
64
92
  type: :development
65
93
  prerelease: false
66
94
  version_requirements: !ruby/object:Gem::Requirement
67
95
  requirements:
68
96
  - - "~>"
69
97
  - !ruby/object:Gem::Version
70
- version: '0.17'
98
+ version: '0.19'
71
99
  description:
72
100
  email:
73
101
  - bwatson@instructure.com
@@ -115,14 +143,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
115
143
  requirements:
116
144
  - - ">="
117
145
  - !ruby/object:Gem::Version
118
- version: '2.3'
146
+ version: '2.6'
119
147
  required_rubygems_version: !ruby/object:Gem::Requirement
120
148
  requirements:
121
149
  - - ">="
122
150
  - !ruby/object:Gem::Version
123
151
  version: '0'
124
152
  requirements: []
125
- rubygems_version: 3.0.3
153
+ rubygems_version: 3.0.1
126
154
  signing_key:
127
155
  specification_version: 4
128
156
  summary: Run new or changed specs many times to prevent unreliable specs