flakey_spec_catcher 0.9.8 → 0.11.2

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: 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