rspec-core 3.0.4 → 3.12.2

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +2 -1
  5. data/Changelog.md +888 -2
  6. data/{License.txt → LICENSE.md} +6 -5
  7. data/README.md +165 -24
  8. data/lib/rspec/autorun.rb +1 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +19 -20
  10. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  11. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  12. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  13. data/lib/rspec/core/bisect/server.rb +61 -0
  14. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  15. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  16. data/lib/rspec/core/bisect/utilities.rb +69 -0
  17. data/lib/rspec/core/configuration.rb +1287 -246
  18. data/lib/rspec/core/configuration_options.rb +95 -35
  19. data/lib/rspec/core/did_you_mean.rb +46 -0
  20. data/lib/rspec/core/drb.rb +21 -12
  21. data/lib/rspec/core/dsl.rb +10 -6
  22. data/lib/rspec/core/example.rb +305 -113
  23. data/lib/rspec/core/example_group.rb +431 -223
  24. data/lib/rspec/core/example_status_persister.rb +235 -0
  25. data/lib/rspec/core/filter_manager.rb +86 -115
  26. data/lib/rspec/core/flat_map.rb +6 -4
  27. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  28. data/lib/rspec/core/formatters/base_formatter.rb +14 -116
  29. data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
  30. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  31. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  32. data/lib/rspec/core/formatters/console_codes.rb +29 -18
  33. data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
  34. data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
  35. data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
  36. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  37. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  38. data/lib/rspec/core/formatters/helpers.rb +45 -15
  39. data/lib/rspec/core/formatters/html_formatter.rb +33 -28
  40. data/lib/rspec/core/formatters/html_printer.rb +30 -20
  41. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  42. data/lib/rspec/core/formatters/json_formatter.rb +18 -9
  43. data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
  44. data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
  45. data/lib/rspec/core/formatters/protocol.rb +182 -0
  46. data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
  47. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  48. data/lib/rspec/core/formatters.rb +81 -41
  49. data/lib/rspec/core/hooks.rb +314 -244
  50. data/lib/rspec/core/invocations.rb +87 -0
  51. data/lib/rspec/core/memoized_helpers.rb +161 -51
  52. data/lib/rspec/core/metadata.rb +132 -61
  53. data/lib/rspec/core/metadata_filter.rb +224 -64
  54. data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
  55. data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
  56. data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
  57. data/lib/rspec/core/mocking_adapters/null.rb +2 -0
  58. data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
  59. data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
  60. data/lib/rspec/core/notifications.rb +192 -206
  61. data/lib/rspec/core/option_parser.rb +174 -69
  62. data/lib/rspec/core/ordering.rb +48 -35
  63. data/lib/rspec/core/output_wrapper.rb +29 -0
  64. data/lib/rspec/core/pending.rb +25 -33
  65. data/lib/rspec/core/profiler.rb +34 -0
  66. data/lib/rspec/core/project_initializer/.rspec +0 -2
  67. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
  68. data/lib/rspec/core/project_initializer.rb +5 -3
  69. data/lib/rspec/core/rake_task.rb +99 -55
  70. data/lib/rspec/core/reporter.rb +128 -15
  71. data/lib/rspec/core/ruby_project.rb +14 -6
  72. data/lib/rspec/core/runner.rb +96 -45
  73. data/lib/rspec/core/sandbox.rb +37 -0
  74. data/lib/rspec/core/set.rb +54 -0
  75. data/lib/rspec/core/shared_example_group.rb +133 -43
  76. data/lib/rspec/core/shell_escape.rb +49 -0
  77. data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
  78. data/lib/rspec/core/version.rb +1 -1
  79. data/lib/rspec/core/warnings.rb +6 -6
  80. data/lib/rspec/core/world.rb +172 -68
  81. data/lib/rspec/core.rb +66 -21
  82. data.tar.gz.sig +0 -0
  83. metadata +93 -69
  84. metadata.gz.sig +0 -0
  85. data/lib/rspec/core/backport_random.rb +0 -336
@@ -0,0 +1,235 @@
1
+ RSpec::Support.require_rspec_support "directory_maker"
2
+
3
+ module RSpec
4
+ module Core
5
+ # Persists example ids and their statuses so that we can filter
6
+ # to just the ones that failed the last time they ran.
7
+ # @private
8
+ class ExampleStatusPersister
9
+ def self.load_from(file_name)
10
+ return [] unless File.exist?(file_name)
11
+ ExampleStatusParser.parse(File.read(file_name))
12
+ end
13
+
14
+ def self.persist(examples, file_name)
15
+ new(examples, file_name).persist
16
+ end
17
+
18
+ def initialize(examples, file_name)
19
+ @examples = examples
20
+ @file_name = file_name
21
+ end
22
+
23
+ def persist
24
+ RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(@file_name))
25
+ File.open(@file_name, File::RDWR | File::CREAT) do |f|
26
+ # lock the file while reading / persisting to avoid a race
27
+ # condition where parallel or unrelated spec runs race to
28
+ # update the same file
29
+ f.flock(File::LOCK_EX)
30
+ unparsed_previous_runs = f.read
31
+ f.rewind
32
+ f.write(dump_statuses(unparsed_previous_runs))
33
+ f.flush
34
+ f.truncate(f.pos)
35
+ end
36
+ end
37
+
38
+ private
39
+
40
+ def dump_statuses(unparsed_previous_runs)
41
+ statuses_from_previous_runs = ExampleStatusParser.parse(unparsed_previous_runs)
42
+ merged_statuses = ExampleStatusMerger.merge(statuses_from_this_run, statuses_from_previous_runs)
43
+ ExampleStatusDumper.dump(merged_statuses)
44
+ end
45
+
46
+ def statuses_from_this_run
47
+ @examples.map do |ex|
48
+ result = ex.execution_result
49
+
50
+ {
51
+ :example_id => ex.id,
52
+ :status => result.status ? result.status.to_s : Configuration::UNKNOWN_STATUS,
53
+ :run_time => result.run_time ? Formatters::Helpers.format_duration(result.run_time) : ""
54
+ }
55
+ end
56
+ end
57
+ end
58
+
59
+ # Merges together a list of example statuses from this run
60
+ # and a list from previous runs (presumably loaded from disk).
61
+ # Each example status object is expected to be a hash with
62
+ # at least an `:example_id` and a `:status` key. Examples that
63
+ # were loaded but not executed (due to filtering, `--fail-fast`
64
+ # or whatever) should have a `:status` of `UNKNOWN_STATUS`.
65
+ #
66
+ # This will produce a new list that:
67
+ # - Will be missing examples from previous runs that we know for sure
68
+ # no longer exist.
69
+ # - Will have the latest known status for any examples that either
70
+ # definitively do exist or may still exist.
71
+ # - Is sorted by file name and example definition order, so that
72
+ # the saved file is easily scannable if users want to inspect it.
73
+ # @private
74
+ class ExampleStatusMerger
75
+ def self.merge(this_run, from_previous_runs)
76
+ new(this_run, from_previous_runs).merge
77
+ end
78
+
79
+ def initialize(this_run, from_previous_runs)
80
+ @this_run = hash_from(this_run)
81
+ @from_previous_runs = hash_from(from_previous_runs)
82
+ @file_exists_cache = Hash.new { |hash, file| hash[file] = File.exist?(file) }
83
+ end
84
+
85
+ def merge
86
+ delete_previous_examples_that_no_longer_exist
87
+
88
+ @this_run.merge(@from_previous_runs) do |_ex_id, new, old|
89
+ new.fetch(:status) == Configuration::UNKNOWN_STATUS ? old : new
90
+ end.values.sort_by(&method(:sort_value_from))
91
+ end
92
+
93
+ private
94
+
95
+ def hash_from(example_list)
96
+ example_list.inject({}) do |hash, example|
97
+ hash[example.fetch(:example_id)] = example
98
+ hash
99
+ end
100
+ end
101
+
102
+ def delete_previous_examples_that_no_longer_exist
103
+ @from_previous_runs.delete_if do |ex_id, _|
104
+ example_must_no_longer_exist?(ex_id)
105
+ end
106
+ end
107
+
108
+ def example_must_no_longer_exist?(ex_id)
109
+ # Obviously, it exists if it was loaded for this spec run...
110
+ return false if @this_run.key?(ex_id)
111
+
112
+ spec_file = spec_file_from(ex_id)
113
+
114
+ # `this_run` includes examples that were loaded but not executed.
115
+ # Given that, if the spec file for this example was loaded,
116
+ # but the id does not still exist, it's safe to assume that
117
+ # the example must no longer exist.
118
+ return true if loaded_spec_files.include?(spec_file)
119
+
120
+ # The example may still exist as long as the file exists...
121
+ !@file_exists_cache[spec_file]
122
+ end
123
+
124
+ def loaded_spec_files
125
+ @loaded_spec_files ||= Set.new(@this_run.keys.map(&method(:spec_file_from)))
126
+ end
127
+
128
+ def spec_file_from(ex_id)
129
+ ex_id.split("[").first
130
+ end
131
+
132
+ def sort_value_from(example)
133
+ file, scoped_id = Example.parse_id(example.fetch(:example_id))
134
+ [file, *scoped_id.split(":").map(&method(:Integer))]
135
+ end
136
+ end
137
+
138
+ # Dumps a list of hashes in a pretty, human readable format
139
+ # for later parsing. The hashes are expected to have symbol
140
+ # keys and string values, and each hash should have the same
141
+ # set of keys.
142
+ # @private
143
+ class ExampleStatusDumper
144
+ def self.dump(examples)
145
+ new(examples).dump
146
+ end
147
+
148
+ def initialize(examples)
149
+ @examples = examples
150
+ end
151
+
152
+ def dump
153
+ return nil if @examples.empty?
154
+ (formatted_header_rows + formatted_value_rows).join("\n") << "\n"
155
+ end
156
+
157
+ private
158
+
159
+ def formatted_header_rows
160
+ @formatted_header_rows ||= begin
161
+ dividers = column_widths.map { |w| "-" * w }
162
+ [formatted_row_from(headers.map(&:to_s)), formatted_row_from(dividers)]
163
+ end
164
+ end
165
+
166
+ def formatted_value_rows
167
+ @formatted_value_rows ||= rows.map do |row|
168
+ formatted_row_from(row)
169
+ end
170
+ end
171
+
172
+ def rows
173
+ @rows ||= @examples.map { |ex| ex.values_at(*headers) }
174
+ end
175
+
176
+ def formatted_row_from(row_values)
177
+ padded_values = row_values.each_with_index.map do |value, index|
178
+ value.ljust(column_widths[index])
179
+ end
180
+
181
+ padded_values.join(" | ") << " |"
182
+ end
183
+
184
+ def headers
185
+ @headers ||= @examples.first.keys
186
+ end
187
+
188
+ def column_widths
189
+ @column_widths ||= begin
190
+ value_sets = rows.transpose
191
+
192
+ headers.each_with_index.map do |header, index|
193
+ values = value_sets[index] << header.to_s
194
+ values.map(&:length).max
195
+ end
196
+ end
197
+ end
198
+ end
199
+
200
+ # Parses a string that has been previously dumped by ExampleStatusDumper.
201
+ # Note that this parser is a bit naive in that it does a simple split on
202
+ # "\n" and " | ", with no concern for handling escaping. For now, that's
203
+ # OK because the values we plan to persist (example id, status, and perhaps
204
+ # example duration) are highly unlikely to contain "\n" or " | " -- after
205
+ # all, who puts those in file names?
206
+ # @private
207
+ class ExampleStatusParser
208
+ def self.parse(string)
209
+ new(string).parse
210
+ end
211
+
212
+ def initialize(string)
213
+ @header_line, _, *@row_lines = string.lines.to_a
214
+ end
215
+
216
+ def parse
217
+ @row_lines.map { |line| parse_row(line) }
218
+ end
219
+
220
+ private
221
+
222
+ def parse_row(line)
223
+ Hash[headers.zip(split_line(line))]
224
+ end
225
+
226
+ def headers
227
+ @headers ||= split_line(@header_line).grep(/\S/).map(&:to_sym)
228
+ end
229
+
230
+ def split_line(line)
231
+ line.split(/\s+\|\s+?/, -1)
232
+ end
233
+ end
234
+ end
235
+ end
@@ -1,71 +1,6 @@
1
1
  module RSpec
2
2
  module Core
3
3
  # @private
4
- # Manages the filtering of examples and groups by matching tags declared on
5
- # the command line or options files, or filters declared via
6
- # `RSpec.configure`, with hash key/values submitted within example group
7
- # and/or example declarations. For example, given this declaration:
8
- #
9
- # describe Thing, :awesome => true do
10
- # it "does something" do
11
- # # ...
12
- # end
13
- # end
14
- #
15
- # That group (or any other with `:awesome => true`) would be filtered in
16
- # with any of the following commands:
17
- #
18
- # rspec --tag awesome:true
19
- # rspec --tag awesome
20
- # rspec -t awesome:true
21
- # rspec -t awesome
22
- #
23
- # Prefixing the tag names with `~` negates the tags, thus excluding this group with
24
- # any of:
25
- #
26
- # rspec --tag ~awesome:true
27
- # rspec --tag ~awesome
28
- # rspec -t ~awesome:true
29
- # rspec -t ~awesome
30
- #
31
- # ## Options files and command line overrides
32
- #
33
- # Tag declarations can be stored in `.rspec`, `~/.rspec`, or a custom
34
- # options file. This is useful for storing defaults. For example, let's
35
- # say you've got some slow specs that you want to suppress most of the
36
- # time. You can tag them like this:
37
- #
38
- # describe Something, :slow => true do
39
- #
40
- # And then store this in `.rspec`:
41
- #
42
- # --tag ~slow:true
43
- #
44
- # Now when you run `rspec`, that group will be excluded.
45
- #
46
- # ## Overriding
47
- #
48
- # Of course, you probably want to run them sometimes, so you can override
49
- # this tag on the command line like this:
50
- #
51
- # rspec --tag slow:true
52
- #
53
- # ## RSpec.configure
54
- #
55
- # You can also store default tags with `RSpec.configure`. We use `tag` on
56
- # the command line (and in options files like `.rspec`), but for historical
57
- # reasons we use the term `filter` in `RSpec.configure:
58
- #
59
- # RSpec.configure do |c|
60
- # c.filter_run_including :foo => :bar
61
- # c.filter_run_excluding :foo => :bar
62
- # end
63
- #
64
- # These declarations can also be overridden from the command line.
65
- #
66
- # @see RSpec.configure
67
- # @see Configuration#filter_run_including
68
- # @see Configuration#filter_run_excluding
69
4
  class FilterManager
70
5
  attr_reader :exclusions, :inclusions
71
6
 
@@ -81,9 +16,15 @@ module RSpec
81
16
  # locations is a hash of expanded paths to arrays of line
82
17
  # numbers to match against. e.g.
83
18
  # { "path/to/file.rb" => [37, 42] }
84
- locations = inclusions.delete(:locations) || Hash.new { |h,k| h[k] = [] }
85
- locations[File.expand_path(file_path)].push(*line_numbers)
86
- inclusions.add_location(locations)
19
+ add_path_to_arrays_filter(:locations, File.expand_path(file_path), line_numbers)
20
+ end
21
+
22
+ def add_ids(rerun_path, scoped_ids)
23
+ # ids is a hash of relative paths to arrays of ids
24
+ # to match against. e.g.
25
+ # { "./path/to/file.rb" => ["1:1", "2:4"] }
26
+ rerun_path = Metadata.relative_path(File.expand_path rerun_path)
27
+ add_path_to_arrays_filter(:ids, rerun_path, scoped_ids)
87
28
  end
88
29
 
89
30
  def empty?
@@ -91,11 +32,25 @@ module RSpec
91
32
  end
92
33
 
93
34
  def prune(examples)
35
+ # Semantically, this is unnecessary (the filtering below will return the empty
36
+ # array unmodified), but for perf reasons it's worth exiting early here. Users
37
+ # commonly have top-level examples groups that do not have any direct examples
38
+ # and instead have nested groups with examples. In that kind of situation,
39
+ # `examples` will be empty.
40
+ return examples if examples.empty?
41
+
42
+ examples = prune_conditionally_filtered_examples(examples)
43
+
94
44
  if inclusions.standalone?
95
- base_exclusions = ExclusionRules.new
96
- examples.select {|e| !base_exclusions.include_example?(e) && include?(e) }
45
+ examples.select { |e| inclusions.include_example?(e) }
97
46
  else
98
- examples.select {|e| !exclude?(e) && include?(e)}
47
+ locations, ids, non_scoped_inclusions = inclusions.split_file_scoped_rules
48
+
49
+ examples.select do |ex|
50
+ file_scoped_include?(ex.metadata, ids, locations) do
51
+ !exclusions.include_example?(ex) && non_scoped_inclusions.include_example?(ex)
52
+ end
53
+ end
99
54
  end
100
55
  end
101
56
 
@@ -111,10 +66,6 @@ module RSpec
111
66
  exclusions.add_with_low_priority(args.last)
112
67
  end
113
68
 
114
- def exclude?(example)
115
- exclusions.include_example?(example)
116
- end
117
-
118
69
  def include(*args)
119
70
  inclusions.add(args.last)
120
71
  end
@@ -127,14 +78,42 @@ module RSpec
127
78
  inclusions.add_with_low_priority(args.last)
128
79
  end
129
80
 
130
- def include?(example)
131
- inclusions.include_example?(example)
81
+ private
82
+
83
+ def add_path_to_arrays_filter(filter_key, path, values)
84
+ filter = inclusions.delete(filter_key) || Hash.new { |h, k| h[k] = [] }
85
+ filter[path].concat(values)
86
+ inclusions.add(filter_key => filter)
87
+ end
88
+
89
+ def prune_conditionally_filtered_examples(examples)
90
+ examples.reject do |ex|
91
+ meta = ex.metadata
92
+ !meta.fetch(:if, true) || meta[:unless]
93
+ end
94
+ end
95
+
96
+ # When a user specifies a particular spec location, that takes priority
97
+ # over any exclusion filters (such as if the spec is tagged with `:slow`
98
+ # and there is a `:slow => true` exclusion filter), but only for specs
99
+ # defined in the same file as the location filters. Excluded specs in
100
+ # other files should still be excluded.
101
+ def file_scoped_include?(ex_metadata, ids, locations)
102
+ no_id_filters = ids[ex_metadata[:rerun_file_path]].empty?
103
+ no_location_filters = locations[
104
+ File.expand_path(ex_metadata[:rerun_file_path])
105
+ ].empty?
106
+
107
+ return yield if no_location_filters && no_id_filters
108
+
109
+ MetadataFilter.filter_applies?(:ids, ids, ex_metadata) ||
110
+ MetadataFilter.filter_applies?(:locations, locations, ex_metadata)
132
111
  end
133
112
  end
134
113
 
135
114
  # @private
136
115
  class FilterRules
137
- PROC_HEX_NUMBER = /0x[0-9a-f]+@/
116
+ PROC_HEX_NUMBER = /0x[0-9a-f]+@?/
138
117
  PROJECT_DIR = File.expand_path('.')
139
118
 
140
119
  attr_accessor :opposite
@@ -148,17 +127,17 @@ module RSpec
148
127
  [exclusions, inclusions]
149
128
  end
150
129
 
151
- def initialize(*args, &block)
152
- @rules = Hash.new(*args, &block)
130
+ def initialize(rules={})
131
+ @rules = rules
153
132
  end
154
133
 
155
134
  def add(updated)
156
135
  @rules.merge!(updated).each_key { |k| opposite.delete(k) }
157
136
  end
158
137
 
159
- def add_with_low_priority(_updated)
160
- updated = _updated.merge(@rules)
161
- opposite.each_pair { |k,v| updated.delete(k) if updated[k] == v }
138
+ def add_with_low_priority(updated)
139
+ updated = updated.merge(@rules)
140
+ opposite.each_pair { |k, v| updated.delete(k) if updated[k] == v }
162
141
  @rules.replace(updated)
163
142
  end
164
143
 
@@ -192,47 +171,51 @@ module RSpec
192
171
  end
193
172
 
194
173
  def description
195
- rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)','')
174
+ rules.inspect.gsub(PROC_HEX_NUMBER, '').gsub(PROJECT_DIR, '.').gsub(' (lambda)', '')
175
+ end
176
+
177
+ def include_example?(example)
178
+ MetadataFilter.apply?(:any?, @rules, example.metadata)
196
179
  end
197
180
  end
198
181
 
199
182
  # @private
200
- class InclusionRules < FilterRules
201
- STANDALONE_FILTERS = [:locations, :full_description]
202
-
203
- def add_location(locations)
204
- replace_filters({ :locations => locations })
205
- end
183
+ ExclusionRules = FilterRules
206
184
 
185
+ # @private
186
+ class InclusionRules < FilterRules
207
187
  def add(*args)
208
- set_standalone_filter(*args) || super
188
+ apply_standalone_filter(*args) || super
209
189
  end
210
190
 
211
191
  def add_with_low_priority(*args)
212
- set_standalone_filter(*args) || super
213
- end
214
-
215
- def use(*args)
216
- set_standalone_filter(*args) || super
192
+ apply_standalone_filter(*args) || super
217
193
  end
218
194
 
219
195
  def include_example?(example)
220
- @rules.empty? ? true : example.any_apply?(@rules)
196
+ @rules.empty? || super
221
197
  end
222
198
 
223
199
  def standalone?
224
200
  is_standalone_filter?(@rules)
225
201
  end
226
202
 
203
+ def split_file_scoped_rules
204
+ rules_dup = @rules.dup
205
+ locations = rules_dup.delete(:locations) { Hash.new([]) }
206
+ ids = rules_dup.delete(:ids) { Hash.new([]) }
207
+
208
+ return locations, ids, self.class.new(rules_dup)
209
+ end
210
+
227
211
  private
228
212
 
229
- def set_standalone_filter(updated)
213
+ def apply_standalone_filter(updated)
230
214
  return true if standalone?
215
+ return nil unless is_standalone_filter?(updated)
231
216
 
232
- if is_standalone_filter?(updated)
233
- replace_filters(updated)
234
- true
235
- end
217
+ replace_filters(updated)
218
+ true
236
219
  end
237
220
 
238
221
  def replace_filters(new_rules)
@@ -241,19 +224,7 @@ module RSpec
241
224
  end
242
225
 
243
226
  def is_standalone_filter?(rules)
244
- STANDALONE_FILTERS.any? { |key| rules.has_key?(key) }
245
- end
246
- end
247
-
248
- # @private
249
- class ExclusionRules < FilterRules
250
- CONDITIONAL_FILTERS = {
251
- :if => lambda { |value| !value },
252
- :unless => lambda { |value| value }
253
- }.freeze
254
-
255
- def include_example?(example)
256
- example.any_apply?(@rules) || example.any_apply?(CONDITIONAL_FILTERS)
227
+ rules.key?(:full_description)
257
228
  end
258
229
  end
259
230
  end
@@ -3,13 +3,15 @@ module RSpec
3
3
  # @private
4
4
  module FlatMap
5
5
  if [].respond_to?(:flat_map)
6
- def flat_map(array)
7
- array.flat_map { |item| yield item }
6
+ def flat_map(array, &block)
7
+ array.flat_map(&block)
8
8
  end
9
9
  else # for 1.8.7
10
- def flat_map(array)
11
- array.map { |item| yield item }.flatten
10
+ # :nocov:
11
+ def flat_map(array, &block)
12
+ array.map(&block).flatten(1)
12
13
  end
14
+ # :nocov:
13
15
  end
14
16
 
15
17
  module_function :flat_map
@@ -0,0 +1,45 @@
1
+ RSpec::Support.require_rspec_core "bisect/utilities"
2
+
3
+ module RSpec
4
+ module Core
5
+ module Formatters
6
+ # Contains common logic for formatters used by `--bisect` to communicate results
7
+ # back to the bisect runner.
8
+ #
9
+ # Subclasses must define a `notify_results(all_example_ids, failed_example_ids)`
10
+ # method.
11
+ # @private
12
+ class BaseBisectFormatter
13
+ def self.inherited(formatter)
14
+ Formatters.register formatter, :start_dump, :example_failed, :example_finished
15
+ end
16
+
17
+ def initialize(expected_failures)
18
+ @all_example_ids = []
19
+ @failed_example_ids = []
20
+ @remaining_failures = expected_failures
21
+ end
22
+
23
+ def example_failed(notification)
24
+ @failed_example_ids << notification.example.id
25
+ end
26
+
27
+ def example_finished(notification)
28
+ @all_example_ids << notification.example.id
29
+ return unless @remaining_failures.include?(notification.example.id)
30
+ @remaining_failures.delete(notification.example.id)
31
+
32
+ status = notification.example.execution_result.status
33
+ return if status == :failed && !@remaining_failures.empty?
34
+ RSpec.world.wants_to_quit = true
35
+ end
36
+
37
+ def start_dump(_notification)
38
+ # `notify_results` is defined in the subclass
39
+ notify_results(Bisect::ExampleSetDescriptor.new(
40
+ @all_example_ids, @failed_example_ids))
41
+ end
42
+ end
43
+ end
44
+ end
45
+ end