flakey_spec_catcher 0.6.1 → 0.7.0

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: cfd29938b0542b31562ef717b6a5bc89f96dc78afa35daac74e408f93486309f
4
- data.tar.gz: 930d3b0b85d2bb214363c70dd3ea2f8959e16cba83650d3d804721113e824a5d
3
+ metadata.gz: 993c31c2baefd8e58b525db89b1ff79ea1d6220c2d515151fda4ce2c2bcd6df7
4
+ data.tar.gz: 4760b425917fc1284e46eaaa73e8cf48f4f1f6224fc423beb18f749df6756d0f
5
5
  SHA512:
6
- metadata.gz: 4d8133daadc9181017af45abfdda0200825cef3605459a1d4ca5a21a524b5b7d7ef9dfd8643b4545c2d2761aba103a71bbf4fbba1ee3adb1b10d60e5b6a51353
7
- data.tar.gz: 0d1d43b5ed4f93b1e21b871c03cf38e524c05c99a8f29a46998f78e867bc10d431143df64083a0b041c927e657120aac6007248b2a10955bcd581b0c583a0f4c
6
+ metadata.gz: 59be5545a0be53ffc435a299458d665893c35b7df44d9d3c8a8522792cd3ad68069b777d0ac9bd4bc8f10850ad38f679bcaac5fa198023836fe472b633daf401
7
+ data.tar.gz: aff2fe42b718049783361bf81d554182928963cc0e9bb0a4a27d9d0ceef7cc72dfbc155e24ad6a1a0c5effb5621ac35b3ae2c69f7c3ed8b16d87107eafecacc4
@@ -27,12 +27,14 @@ module FlakeySpecCatcher
27
27
  @change_capsules.map(&:file_name).uniq
28
28
  end
29
29
 
30
+ def sorted_change_contexts
31
+ @change_capsules.map(&:change_contexts).flatten.sort_by { |c| c.line_number.to_i }
32
+ end
33
+
30
34
  def condense_reruns
31
35
  # Don't re-run if the parent context of a change will be run
32
36
  reruns = []
33
- all_contexts = @change_capsules.map(&:change_contexts).flatten
34
- .sort_by { |c| c.line_number.to_i }
35
-
37
+ all_contexts = sorted_change_contexts
36
38
  all_contexts.each do |context|
37
39
  next if reruns.include?(context.file_name) || context.ancestor_present_in_reruns(reruns)
38
40
 
@@ -40,5 +42,26 @@ module FlakeySpecCatcher
40
42
  end
41
43
  reruns
42
44
  end
45
+
46
+ def tag_values_equal?(user_excluded_tag_value, rspec_tag_value)
47
+ return true if user_excluded_tag_value.nil? && rspec_tag_value.nil?
48
+
49
+ return false if user_excluded_tag_value.nil? || rspec_tag_value.nil?
50
+
51
+ (user_excluded_tag_value.tr('\'\"', '') == rspec_tag_value.tr('\'\" ', ''))
52
+ end
53
+
54
+ def find_reruns_by_tags(user_excluded_tags)
55
+ dropped_tests = []
56
+ sorted_change_contexts.each do |context|
57
+ context.tags.keys.each do |tag|
58
+ next unless user_excluded_tags.key?(tag) &&
59
+ tag_values_equal?(user_excluded_tags[tag], context.tags[tag])
60
+
61
+ dropped_tests << context.rerun_info
62
+ end
63
+ end
64
+ dropped_tests
65
+ end
43
66
  end
44
67
  end
@@ -15,15 +15,16 @@ module FlakeySpecCatcher
15
15
  # the 'describe' block only.
16
16
  class ChangeContext
17
17
  attr_reader :description, :line_number
18
- attr_reader :ancestor_contexts, :file_name
18
+ attr_reader :ancestor_contexts, :file_name, :tags
19
19
 
20
- def initialize(description:, line_number:, file_name:, ancestor_contexts: [])
20
+ def initialize(description:, line_number:, file_name:, ancestor_contexts: [], tags: {})
21
21
  @description = description
22
22
  @line_number = line_number
23
-
24
23
  @file_name = file_name
25
24
  @ancestor_contexts = ancestor_contexts
25
+ @tags = tags
26
26
  update_descriptions
27
+ identify_tags_and_values
27
28
  end
28
29
 
29
30
  def update_descriptions
@@ -33,6 +34,36 @@ module FlakeySpecCatcher
33
34
  @line_number = nil
34
35
  end
35
36
 
37
+ def example_group?
38
+ # If the description starts with 'it' or 'scenario' then the test
39
+ # can provide assertions/expectations and is not an example group
40
+ if @description =~ /^\s*(it|scenario)\s+/
41
+ false
42
+ else
43
+ true
44
+ end
45
+ end
46
+
47
+ # Get tag strings which optionally may continue tags with their values
48
+ # and return a hash
49
+ def identify_tags_and_values
50
+ tags_with_values = {}
51
+ tag_strings = identify_tag_strings
52
+ return if tag_strings.nil? || tag_strings.empty?
53
+
54
+ # Since tags can have a value represented as strings or symbols
55
+ # we'll only remove the hash rockets and not colons
56
+ # Example: ":tag => 'special'" => tags_with_values[:tag] = 'special'
57
+ # Example: ":tag => :special" => tags_with_values[:tag] = :special
58
+
59
+ tag_strings.each do |str|
60
+ tag_and_value = str.sub(/=>/, ' ').split(' ')
61
+ tags_with_values[tag_and_value[0]] = tag_and_value[1]
62
+ end
63
+
64
+ @tags = tags_with_values
65
+ end
66
+
36
67
  def ==(other)
37
68
  @description == other.description &&
38
69
  @line_number == other.line_number &&
@@ -54,5 +85,30 @@ module FlakeySpecCatcher
54
85
  end
55
86
  false
56
87
  end
88
+
89
+ private
90
+
91
+ # Since RSpec doesn't allow for tags to be excluded for runs when explicitly
92
+ # specifying an example group via `rspec test:line_number`, we have to
93
+ # do the filtering ourselves which requires us to also identify tags
94
+ # Assume here that tags are comma delimited following the initial description
95
+ def identify_tag_strings
96
+ return if @description.empty?
97
+
98
+ tags = @description.split(',')
99
+ return if tags.count == 1
100
+
101
+ # Remove the description
102
+ tags.shift
103
+ # Remove the trailing ' do' from the last tag
104
+ tags.last.sub!(/ do$/, '')
105
+
106
+ # Remove trailing and leading whitespace
107
+ tags.map!(&:strip)
108
+
109
+ # Grab tags which are represented as symbols
110
+ tags.select! { |tag| tag.start_with? ':' }
111
+ tags
112
+ end
57
113
  end
58
114
  end
@@ -7,10 +7,11 @@ module FlakeySpecCatcher
7
7
  #
8
8
  # Captures command line arguments for manual re-runs
9
9
  class CliOverride
10
- attr_reader :rerun_pattern, :rerun_usage, :repeat_factor, :enable_runs
10
+ attr_reader :rerun_pattern, :rerun_usage, :repeat_factor, :enable_runs, :excluded_tags
11
11
 
12
12
  def initialize
13
13
  @enable_runs = true
14
+ @excluded_tags = []
14
15
  parse_command_line_args
15
16
  end
16
17
 
@@ -40,6 +41,11 @@ module FlakeySpecCatcher
40
41
  @repeat_factor = parsed_repeat_factor if parsed_repeat_factor.positive?
41
42
  end
42
43
 
44
+ opts.on('-e', '--excluded-tags=EXCLUDED_TAGS',
45
+ 'Specify tags to exclude in a comma separated list') do |tags|
46
+ @excluded_tags = parse_tags(tags)
47
+ end
48
+
43
49
  opts.on('-v', '--version', 'Prints current flakey_spec_catcher_version') do
44
50
  puts "flakey_spec_catcher Version: #{FlakeySpecCatcher::VERSION}"
45
51
  @enable_runs = false
@@ -56,5 +62,28 @@ module FlakeySpecCatcher
56
62
  def remove_formatter_quotes(env_var)
57
63
  env_var.tr('\'\"', '')
58
64
  end
65
+
66
+ def parse_tags(tag_string)
67
+ tags = tag_string.tr('\'\" ', '').split(',').uniq
68
+ tags.map do |tag|
69
+ if tag[0] == ':'
70
+ tag
71
+ else
72
+ tag.prepend(':') unless tag[0] == ':'
73
+ end
74
+ end
75
+
76
+ # Since tags can have a value represented as strings or symbols
77
+ # we'll only remove the hash rockets and not colons
78
+ # Example: ":tag => 'special'" => tags_with_values[:tag] = 'special'
79
+ # Example: ":tag => :special" => tags_with_values[:tag] = :special
80
+ tags_with_values = {}
81
+ tags.each do |str|
82
+ tag_and_value = str.sub(/=>/, ' ').split(' ')
83
+ tags_with_values[tag_and_value[0]] = tag_and_value[1]
84
+ end
85
+
86
+ tags_with_values
87
+ end
59
88
  end
60
89
  end
@@ -32,12 +32,30 @@ module FlakeySpecCatcher
32
32
  def tests_for_rerun
33
33
  tests = if @user_config.rerun_file_only
34
34
  @git_controller.capsule_manager.changed_files
35
+ # If no tags are specified, condense re-runs else expand them
36
+ elsif @user_config.excluded_tags.empty?
37
+ condense_reruns - identify_tag_excluded_reruns
35
38
  else
36
- @git_controller.capsule_manager.condense_reruns
39
+ all_non_example_groups - identify_tag_excluded_reruns
37
40
  end
38
41
  filter_reruns_by_ignore_files(tests)
39
42
  end
40
43
 
44
+ def condense_reruns
45
+ @git_controller.capsule_manager.condense_reruns
46
+ end
47
+
48
+ # Get all change contexts that can invoke expectations/assertions
49
+ def all_non_example_groups
50
+ @git_controller.capsule_manager.sorted_change_contexts.reject(&:example_group?)
51
+ .map(&:rerun_info)
52
+ end
53
+
54
+ # Identifies all contexts that have a tag present
55
+ def identify_tag_excluded_reruns
56
+ @git_controller.capsule_manager.find_reruns_by_tags(@user_config.excluded_tags)
57
+ end
58
+
41
59
  def pair_reruns_with_usages
42
60
  reruns = tests_for_rerun
43
61
  configured_usage_patterns = @user_config.rspec_usage_patterns
@@ -73,7 +73,8 @@ module FlakeySpecCatcher
73
73
  def invoke_rspec_runner(test)
74
74
  configure_listener
75
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])
76
+ rspec_args = ['--out', '/dev/null', test]
77
+ return_status = RSpec::Core::Runner.run(rspec_args)
77
78
  RSpec.clear_examples
78
79
  return_status
79
80
  end
@@ -5,9 +5,11 @@ module FlakeySpecCatcher
5
5
  # UserConfig class
6
6
  #
7
7
  # Captures user-defined settings to configure RSpec re-run settings.
8
+
9
+ # rubocop:disable Metrics/ClassLength
8
10
  class UserConfig
9
11
  attr_reader :repeat_factor, :ignore_files, :ignore_branches, :silent_mode
10
- attr_reader :rerun_file_only, :rspec_usage_patterns
12
+ attr_reader :rerun_file_only, :rspec_usage_patterns, :excluded_tags
11
13
  attr_reader :manual_rerun_pattern, :manual_rerun_usage
12
14
  attr_reader :enable_runs
13
15
 
@@ -15,17 +17,24 @@ module FlakeySpecCatcher
15
17
  FSC_SILENT_MODE FSC_RERUN_FILE_ONLY FSC_USAGE_PATTERNS].freeze
16
18
 
17
19
  def initialize(cli_override: CliOverride.new)
20
+ apply_env_var_settings
21
+ @cli_override = cli_override
22
+ override_settings
23
+ end
24
+
25
+ private
26
+
27
+ # rubocop:disable Metrics/AbcSize
28
+ def apply_env_var_settings
18
29
  @repeat_factor = initialize_repeat_factor(ENV['FSC_REPEAT_FACTOR'])
19
30
  @ignore_files = env_var_string_to_array(ENV['FSC_IGNORE_FILES'])
20
31
  @ignore_branches = env_var_string_to_array(ENV['FSC_IGNORE_BRANCHES'])
21
32
  @silent_mode = env_var_string_to_bool(ENV['FSC_SILENT_MODE'])
22
33
  @rerun_file_only = env_var_string_to_bool(ENV['FSC_RERUN_FILE_ONLY'])
23
34
  @rspec_usage_patterns = env_var_string_to_pairs(ENV['FSC_USAGE_PATTERNS'])
24
- @cli_override = cli_override
25
- override_settings
35
+ @excluded_tags = env_var_string_to_tags(ENV['FSC_EXCLUDED_TAGS'])
26
36
  end
27
-
28
- private
37
+ # rubocop:enable Metrics/AbcSize
29
38
 
30
39
  def override_settings
31
40
  apply_cli_override
@@ -39,6 +48,11 @@ module FlakeySpecCatcher
39
48
  @manual_rerun_usage = @cli_override.rerun_usage
40
49
  @repeat_factor = @cli_override.repeat_factor if @cli_override.repeat_factor.to_i.positive?
41
50
  @enable_runs = @cli_override.enable_runs
51
+ @excluded_tags = if @cli_override.excluded_tags.empty?
52
+ @excluded_tags
53
+ else
54
+ @cli_override.excluded_tags
55
+ end
42
56
  end
43
57
 
44
58
  def remove_formatter_quotes(env_var)
@@ -75,6 +89,28 @@ module FlakeySpecCatcher
75
89
  end
76
90
  end
77
91
 
92
+ def env_var_string_to_tags(tag_string)
93
+ if tag_string.nil? || tag_string.empty?
94
+ {}
95
+ else
96
+ tag_strings = tag_string.tr('\'\" ', '').split(',').uniq
97
+ tags_with_values = {}
98
+ return if tag_strings.nil? || tag_strings.empty?
99
+
100
+ # Since tags can have a value represented as strings or symbols
101
+ # we'll only remove the hash rockets and not colons
102
+ # Example: ":tag => 'special'" => tags_with_values[:tag] = 'special'
103
+ # Example: ":tag => :special" => tags_with_values[:tag] = :special
104
+
105
+ tag_strings.each do |str|
106
+ tag_and_value = str.sub(/=>/, ' ').split(' ')
107
+ tags_with_values[tag_and_value[0]] = tag_and_value[1]
108
+ end
109
+
110
+ tags_with_values
111
+ end
112
+ end
113
+
78
114
  def format_regex_scan_results(matches)
79
115
  # Scanning the commit message will result in an array of matches
80
116
  # based on the specified regex. If the pattern uses groups like ours does
@@ -118,4 +154,5 @@ module FlakeySpecCatcher
118
154
  end
119
155
  # rubocop:enable Metrics/CyclomaticComplexity
120
156
  end
157
+ # rubocop:enable Metrics/ClassLength
121
158
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module FlakeySpecCatcher
4
- VERSION = '0.6.1'
4
+ VERSION = '0.7.0'
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.6.1
4
+ version: 0.7.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Brian Watson
@@ -122,7 +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.3
125
+ rubyforge_project:
126
+ rubygems_version: 2.7.7
126
127
  signing_key:
127
128
  specification_version: 4
128
129
  summary: Run new or changed specs many times to prevent unreliable specs