rspec-core 3.2.3 → 3.3.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.
Files changed (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +75 -0
  5. data/README.md +137 -20
  6. data/lib/rspec/autorun.rb +1 -0
  7. data/lib/rspec/core.rb +8 -16
  8. data/lib/rspec/core/backtrace_formatter.rb +1 -3
  9. data/lib/rspec/core/bisect/coordinator.rb +66 -0
  10. data/lib/rspec/core/bisect/example_minimizer.rb +130 -0
  11. data/lib/rspec/core/bisect/runner.rb +139 -0
  12. data/lib/rspec/core/bisect/server.rb +61 -0
  13. data/lib/rspec/core/bisect/subset_enumerator.rb +39 -0
  14. data/lib/rspec/core/configuration.rb +134 -5
  15. data/lib/rspec/core/configuration_options.rb +21 -10
  16. data/lib/rspec/core/example.rb +84 -50
  17. data/lib/rspec/core/example_group.rb +46 -18
  18. data/lib/rspec/core/example_status_persister.rb +235 -0
  19. data/lib/rspec/core/filter_manager.rb +43 -28
  20. data/lib/rspec/core/flat_map.rb +2 -0
  21. data/lib/rspec/core/formatters.rb +30 -20
  22. data/lib/rspec/core/formatters/base_text_formatter.rb +1 -0
  23. data/lib/rspec/core/formatters/bisect_formatter.rb +68 -0
  24. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +115 -0
  25. data/lib/rspec/core/formatters/deprecation_formatter.rb +0 -1
  26. data/lib/rspec/core/formatters/documentation_formatter.rb +0 -4
  27. data/lib/rspec/core/formatters/exception_presenter.rb +389 -0
  28. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  29. data/lib/rspec/core/formatters/helpers.rb +22 -2
  30. data/lib/rspec/core/formatters/html_formatter.rb +1 -4
  31. data/lib/rspec/core/formatters/html_printer.rb +2 -6
  32. data/lib/rspec/core/formatters/json_formatter.rb +6 -4
  33. data/lib/rspec/core/formatters/snippet_extractor.rb +12 -7
  34. data/lib/rspec/core/hooks.rb +8 -2
  35. data/lib/rspec/core/memoized_helpers.rb +77 -17
  36. data/lib/rspec/core/metadata.rb +24 -10
  37. data/lib/rspec/core/metadata_filter.rb +16 -3
  38. data/lib/rspec/core/mutex.rb +63 -0
  39. data/lib/rspec/core/notifications.rb +84 -189
  40. data/lib/rspec/core/option_parser.rb +105 -32
  41. data/lib/rspec/core/ordering.rb +28 -25
  42. data/lib/rspec/core/profiler.rb +32 -0
  43. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +6 -1
  44. data/lib/rspec/core/rake_task.rb +6 -20
  45. data/lib/rspec/core/reentrant_mutex.rb +52 -0
  46. data/lib/rspec/core/reporter.rb +65 -17
  47. data/lib/rspec/core/runner.rb +38 -14
  48. data/lib/rspec/core/set.rb +49 -0
  49. data/lib/rspec/core/shared_example_group.rb +3 -1
  50. data/lib/rspec/core/shell_escape.rb +49 -0
  51. data/lib/rspec/core/version.rb +1 -1
  52. data/lib/rspec/core/world.rb +31 -20
  53. metadata +35 -7
  54. metadata.gz.sig +0 -0
  55. data/lib/rspec/core/backport_random.rb +0 -339
@@ -17,6 +17,7 @@ module RSpec
17
17
  silence_metadata_example_group_deprecations do
18
18
  return filter_applies_to_any_value?(key, value, metadata) if Array === metadata[key] && !(Proc === value)
19
19
  return location_filter_applies?(value, metadata) if key == :locations
20
+ return id_filter_applies?(value, metadata) if key == :ids
20
21
  return filters_apply?(key, value, metadata) if Hash === value
21
22
 
22
23
  return false unless metadata.key?(key)
@@ -42,9 +43,17 @@ module RSpec
42
43
  metadata[key].any? { |v| filter_applies?(key, v, key => value) }
43
44
  end
44
45
 
46
+ def id_filter_applies?(rerun_paths_to_scoped_ids, metadata)
47
+ scoped_ids = rerun_paths_to_scoped_ids.fetch(metadata[:rerun_file_path]) { return false }
48
+
49
+ Metadata.ascend(metadata).any? do |meta|
50
+ scoped_ids.include?(meta[:scoped_id])
51
+ end
52
+ end
53
+
45
54
  def location_filter_applies?(locations, metadata)
46
55
  line_numbers = example_group_declaration_lines(locations, metadata)
47
- line_numbers.empty? || line_number_filter_applies?(line_numbers, metadata)
56
+ line_number_filter_applies?(line_numbers, metadata)
48
57
  end
49
58
 
50
59
  def line_number_filter_applies?(line_numbers, metadata)
@@ -69,10 +78,10 @@ module RSpec
69
78
  end
70
79
 
71
80
  def silence_metadata_example_group_deprecations
72
- RSpec.thread_local_metadata[:silence_metadata_example_group_deprecations] = true
81
+ RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations] = true
73
82
  yield
74
83
  ensure
75
- RSpec.thread_local_metadata.delete(:silence_metadata_example_group_deprecations)
84
+ RSpec::Support.thread_local_data.delete(:silence_metadata_example_group_deprecations)
76
85
  end
77
86
  end
78
87
  end
@@ -118,6 +127,7 @@ module RSpec
118
127
  end
119
128
 
120
129
  unless [].respond_to?(:each_with_object) # For 1.8.7
130
+ # :nocov:
121
131
  undef items_for
122
132
  def items_for(request_meta)
123
133
  @items_and_filters.inject([]) do |to_return, (item, item_meta)|
@@ -126,6 +136,7 @@ module RSpec
126
136
  to_return
127
137
  end
128
138
  end
139
+ # :nocov:
129
140
  end
130
141
  end
131
142
 
@@ -208,6 +219,7 @@ module RSpec
208
219
  end
209
220
 
210
221
  unless [].respond_to?(:each_with_object) # For 1.8.7
222
+ # :nocov:
211
223
  undef proc_keys_from
212
224
  def proc_keys_from(metadata)
213
225
  metadata.inject([]) do |to_return, (key, value)|
@@ -215,6 +227,7 @@ module RSpec
215
227
  to_return
216
228
  end
217
229
  end
230
+ # :nocov:
218
231
  end
219
232
  end
220
233
  end
@@ -0,0 +1,63 @@
1
+ module RSpec
2
+ module Core
3
+ # On 1.8.7, it's in the stdlib.
4
+ # We don't want to load the stdlib, b/c this is a test tool, and can affect the test environment,
5
+ # causing tests to pass where they should fail.
6
+ #
7
+ # So we're transcribing/modifying it from https://github.com/ruby/ruby/blob/v1_8_7_374/lib/thread.rb#L56
8
+ # Some methods we don't need are deleted.
9
+ # Anything I don't understand (there's quite a bit, actually) is left in.
10
+ # Some formating changes are made to appease the robot overlord:
11
+ # https://travis-ci.org/rspec/rspec-core/jobs/54410874
12
+ # @private
13
+ class Mutex
14
+ def initialize
15
+ @waiting = []
16
+ @locked = false
17
+ @waiting.taint
18
+ taint
19
+ end
20
+
21
+ # @private
22
+ def lock
23
+ while Thread.critical = true && @locked
24
+ @waiting.push Thread.current
25
+ Thread.stop
26
+ end
27
+ @locked = true
28
+ Thread.critical = false
29
+ self
30
+ end
31
+
32
+ # @private
33
+ def unlock
34
+ return unless @locked
35
+ Thread.critical = true
36
+ @locked = false
37
+ begin
38
+ t = @waiting.shift
39
+ t.wakeup if t
40
+ rescue ThreadError
41
+ retry
42
+ end
43
+ Thread.critical = false
44
+ begin
45
+ t.run if t
46
+ rescue ThreadError
47
+ :noop
48
+ end
49
+ self
50
+ end
51
+
52
+ # @private
53
+ def synchronize
54
+ lock
55
+ begin
56
+ yield
57
+ ensure
58
+ unlock
59
+ end
60
+ end
61
+ end unless defined?(::RSpec::Core::Mutex) # Avoid warnings for library wide checks spec
62
+ end
63
+ end
@@ -1,4 +1,6 @@
1
+ RSpec::Support.require_rspec_core "formatters/exception_presenter"
1
2
  RSpec::Support.require_rspec_core "formatters/helpers"
3
+ RSpec::Support.require_rspec_core "shell_escape"
2
4
  RSpec::Support.require_rspec_support "encoded_string"
3
5
 
4
6
  module RSpec::Core
@@ -38,17 +40,19 @@ module RSpec::Core
38
40
  def self.for(example)
39
41
  execution_result = example.execution_result
40
42
 
41
- if execution_result.pending_fixed?
42
- PendingExampleFixedNotification.new(example)
43
- elsif execution_result.example_skipped?
44
- SkippedExampleNotification.new(example)
45
- elsif execution_result.status == :pending
46
- PendingExampleFailedAsExpectedNotification.new(example)
47
- elsif execution_result.status == :failed
48
- FailedExampleNotification.new(example)
49
- else
50
- new(example)
51
- end
43
+ return SkippedExampleNotification.new(example) if execution_result.example_skipped?
44
+ return new(example) unless execution_result.status == :pending || execution_result.status == :failed
45
+
46
+ klass = if execution_result.pending_fixed?
47
+ PendingExampleFixedNotification
48
+ elsif execution_result.status == :pending
49
+ PendingExampleFailedAsExpectedNotification
50
+ else
51
+ FailedExampleNotification
52
+ end
53
+
54
+ exception_presenter = Formatters::ExceptionPresenter::Factory.new(example).build
55
+ klass.new(example, exception_presenter)
52
56
  end
53
57
 
54
58
  private_class_method :new
@@ -135,7 +139,8 @@ module RSpec::Core
135
139
  end
136
140
 
137
141
  # The `FailedExampleNotification` extends `ExampleNotification` with
138
- # things useful for failed specs.
142
+ # things useful for examples that have failure info -- typically a
143
+ # failed or pending spec.
139
144
  #
140
145
  # @example
141
146
  # def example_failed(notification)
@@ -151,19 +156,19 @@ module RSpec::Core
151
156
 
152
157
  # @return [Exception] The example failure
153
158
  def exception
154
- example.execution_result.exception
159
+ @exception_presenter.exception
155
160
  end
156
161
 
157
162
  # @return [String] The example description
158
163
  def description
159
- example.full_description
164
+ @exception_presenter.description
160
165
  end
161
166
 
162
167
  # Returns the message generated for this failure line by line.
163
168
  #
164
169
  # @return [Array<String>] The example failure message
165
170
  def message_lines
166
- add_shared_group_lines(failure_lines, NullColorizer)
171
+ @exception_presenter.message_lines
167
172
  end
168
173
 
169
174
  # Returns the message generated for this failure colorized line by line.
@@ -171,16 +176,14 @@ module RSpec::Core
171
176
  # @param colorizer [#wrap] An object to colorize the message_lines by
172
177
  # @return [Array<String>] The example failure message colorized
173
178
  def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
174
- add_shared_group_lines(failure_lines, colorizer).map do |line|
175
- colorizer.wrap line, message_color
176
- end
179
+ @exception_presenter.colorized_message_lines(colorizer)
177
180
  end
178
181
 
179
182
  # Returns the failures formatted backtrace.
180
183
  #
181
184
  # @return [Array<String>] the examples backtrace lines
182
185
  def formatted_backtrace
183
- backtrace_formatter.format_backtrace(exception.backtrace, example.metadata)
186
+ @exception_presenter.formatted_backtrace
184
187
  end
185
188
 
186
189
  # Returns the failures colorized formatted backtrace.
@@ -188,170 +191,28 @@ module RSpec::Core
188
191
  # @param colorizer [#wrap] An object to colorize the message_lines by
189
192
  # @return [Array<String>] the examples colorized backtrace lines
190
193
  def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
191
- formatted_backtrace.map do |backtrace_info|
192
- colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color
193
- end
194
+ @exception_presenter.colorized_formatted_backtrace(colorizer)
194
195
  end
195
196
 
196
197
  # @return [String] The failure information fully formatted in the way that
197
198
  # RSpec's built-in formatters emit.
198
199
  def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
199
- "\n #{failure_number}) #{description}\n#{formatted_message_and_backtrace(colorizer)}"
200
- end
201
-
202
- private
203
-
204
- if String.method_defined?(:encoding)
205
- def encoding_of(string)
206
- string.encoding
207
- end
208
- else
209
- def encoding_of(_string)
210
- end
211
- end
212
-
213
- def backtrace_formatter
214
- RSpec.configuration.backtrace_formatter
215
- end
216
-
217
- def exception_class_name
218
- name = exception.class.name.to_s
219
- name = "(anonymous error class)" if name == ''
220
- name
221
- end
222
-
223
- def failure_lines
224
- @failure_lines ||=
225
- begin
226
- lines = ["Failure/Error: #{read_failed_line.strip}"]
227
- lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/
228
- exception.message.to_s.split("\n").each do |line|
229
- lines << " #{line}" if exception.message
230
- end
231
- lines
232
- end
233
- end
234
-
235
- def add_shared_group_lines(lines, colorizer)
236
- example.metadata[:shared_group_inclusion_backtrace].each do |frame|
237
- lines << colorizer.wrap(frame.description, RSpec.configuration.default_color)
238
- end
239
-
240
- lines
241
- end
242
-
243
- def read_failed_line
244
- matching_line = find_failed_line
245
- unless matching_line
246
- return "Unable to find matching line from backtrace"
247
- end
248
-
249
- file_path, line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)[1..2]
250
-
251
- if File.exist?(file_path)
252
- File.readlines(file_path)[line_number.to_i - 1] ||
253
- "Unable to find matching line in #{file_path}"
254
- else
255
- "Unable to find #{file_path} to read failed line"
256
- end
257
- rescue SecurityError
258
- "Unable to read failed line"
259
- end
260
-
261
- def find_failed_line
262
- example_path = example.metadata[:absolute_file_path].downcase
263
- exception.backtrace.find do |line|
264
- next unless (line_path = line[/(.+?):(\d+)(|:\d+)/, 1])
265
- File.expand_path(line_path).downcase == example_path
266
- end
267
- end
268
-
269
- def formatted_message_and_backtrace(colorizer)
270
- formatted = ""
271
-
272
- colorized_message_lines(colorizer).each do |line|
273
- formatted << RSpec::Support::EncodedString.new(" #{line}\n", encoding_of(formatted))
274
- end
275
-
276
- colorized_formatted_backtrace(colorizer).each do |line|
277
- formatted << RSpec::Support::EncodedString.new(" #{line}\n", encoding_of(formatted))
278
- end
279
-
280
- formatted
281
- end
282
-
283
- def message_color
284
- RSpec.configuration.failure_color
285
- end
286
- end
287
-
288
- # The `PendingExampleFixedNotification` extends `ExampleNotification` with
289
- # things useful for specs that pass when they are expected to fail.
290
- #
291
- # @attr [RSpec::Core::Example] example the current example
292
- # @see ExampleNotification
293
- class PendingExampleFixedNotification < FailedExampleNotification
294
- public_class_method :new
295
-
296
- # Returns the examples description.
297
- #
298
- # @return [String] The example description
299
- def description
300
- "#{example.full_description} FIXED"
301
- end
302
-
303
- # Returns the message generated for this failure line by line.
304
- #
305
- # @return [Array<String>] The example failure message
306
- def message_lines
307
- ["Expected pending '#{example.execution_result.pending_message}' to fail. No Error was raised."]
200
+ @exception_presenter.fully_formatted(failure_number, colorizer)
308
201
  end
309
202
 
310
- # Returns the message generated for this failure colorized line by line.
311
- #
312
- # @param colorizer [#wrap] An object to colorize the message_lines by
313
- # @return [Array<String>] The example failure message colorized
314
- def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
315
- message_lines.map { |line| colorizer.wrap(line, RSpec.configuration.fixed_color) }
316
- end
317
- end
318
-
319
- # @private
320
- module PendingExampleNotificationMethods
321
203
  private
322
204
 
323
- def fully_formatted_header(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
324
- colorizer.wrap("\n #{pending_number}) #{example.full_description}\n", :pending) <<
325
- colorizer.wrap(" # #{example.execution_result.pending_message}\n", :detail)
205
+ def initialize(example, exception_presenter=Formatters::ExceptionPresenter.new(example.execution_result.exception, example))
206
+ @exception_presenter = exception_presenter
207
+ super(example)
326
208
  end
327
209
  end
328
210
 
329
- # The `PendingExampleFailedAsExpectedNotification` extends `FailedExampleNotification` with
330
- # things useful for pending specs that fail as expected.
331
- #
332
- # @attr [RSpec::Core::Example] example the current example
333
- # @see ExampleNotification
334
- class PendingExampleFailedAsExpectedNotification < FailedExampleNotification
335
- include PendingExampleNotificationMethods
336
- public_class_method :new
337
-
338
- # @return [Exception] The exception that occurred while the pending example was executed
339
- def exception
340
- example.execution_result.pending_exception
341
- end
342
-
343
- # @return [String] The pending detail fully formatted in the way that
344
- # RSpec's built-in formatters emit.
345
- def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
346
- fully_formatted_header(pending_number, colorizer) << formatted_message_and_backtrace(colorizer)
347
- end
348
-
349
- private
211
+ # @deprecated Use {FailedExampleNotification} instead.
212
+ class PendingExampleFixedNotification < FailedExampleNotification; end
350
213
 
351
- def message_color
352
- RSpec.configuration.pending_color
353
- end
354
- end
214
+ # @deprecated Use {FailedExampleNotification} instead.
215
+ class PendingExampleFailedAsExpectedNotification < FailedExampleNotification; end
355
216
 
356
217
  # The `SkippedExampleNotification` extends `ExampleNotification` with
357
218
  # things useful for specs that are skipped.
@@ -359,14 +220,15 @@ module RSpec::Core
359
220
  # @attr [RSpec::Core::Example] example the current example
360
221
  # @see ExampleNotification
361
222
  class SkippedExampleNotification < ExampleNotification
362
- include PendingExampleNotificationMethods
363
223
  public_class_method :new
364
224
 
365
225
  # @return [String] The pending detail fully formatted in the way that
366
226
  # RSpec's built-in formatters emit.
367
227
  def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
368
228
  formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location)
369
- fully_formatted_header(pending_number, colorizer) << colorizer.wrap(" # #{formatted_caller}\n", :detail)
229
+ colorizer.wrap("\n #{pending_number}) #{example.full_description}", :pending) << "\n " <<
230
+ Formatters::ExceptionPresenter::PENDING_DETAIL_FORMATTER.call(example, colorizer) <<
231
+ "\n" << colorizer.wrap(" # #{formatted_caller}\n", :detail)
370
232
  end
371
233
  end
372
234
 
@@ -477,7 +339,7 @@ module RSpec::Core
477
339
  def colorized_rerun_commands(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
478
340
  "\nFailed examples:\n\n" +
479
341
  failed_examples.map do |example|
480
- colorizer.wrap("rspec #{example.rerun_argument}", RSpec.configuration.failure_color) + " " +
342
+ colorizer.wrap("rspec #{rerun_argument_for(example)}", RSpec.configuration.failure_color) + " " +
481
343
  colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color)
482
344
  end.join("\n")
483
345
  end
@@ -507,6 +369,28 @@ module RSpec::Core
507
369
 
508
370
  formatted
509
371
  end
372
+
373
+ private
374
+
375
+ include RSpec::Core::ShellEscape
376
+
377
+ def rerun_argument_for(example)
378
+ location = example.location_rerun_argument
379
+ return location unless duplicate_rerun_locations.include?(location)
380
+ conditionally_quote(example.id)
381
+ end
382
+
383
+ def duplicate_rerun_locations
384
+ @duplicate_rerun_locations ||= begin
385
+ locations = RSpec.world.all_examples.map(&:location_rerun_argument)
386
+
387
+ Set.new.tap do |s|
388
+ locations.group_by { |l| l }.each do |l, ls|
389
+ s << l if ls.count > 1
390
+ end
391
+ end
392
+ end
393
+ end
510
394
  end
511
395
 
512
396
  # The `ProfileNotification` holds information about the results of running a
@@ -516,8 +400,16 @@ module RSpec::Core
516
400
  # @attr duration [Float] the time taken (in seconds) to run the suite
517
401
  # @attr examples [Array<RSpec::Core::Example>] the examples run
518
402
  # @attr number_of_examples [Fixnum] the number of examples to profile
519
- ProfileNotification = Struct.new(:duration, :examples, :number_of_examples)
403
+ # @attr example_groups [Array<RSpec::Core::Profiler>] example groups run
520
404
  class ProfileNotification
405
+ def initialize(duration, examples, number_of_examples, example_groups)
406
+ @duration = duration
407
+ @examples = examples
408
+ @number_of_examples = number_of_examples
409
+ @example_groups = example_groups
410
+ end
411
+ attr_reader :duration, :examples, :number_of_examples
412
+
521
413
  # @return [Array<RSpec::Core::Example>] the slowest examples
522
414
  def slowest_examples
523
415
  @slowest_examples ||=
@@ -551,26 +443,15 @@ module RSpec::Core
551
443
  private
552
444
 
553
445
  def calculate_slowest_groups
554
- example_groups = {}
555
-
556
- examples.each do |example|
557
- location = example.example_group.parent_groups.last.metadata[:location]
558
-
559
- location_hash = example_groups[location] ||= Hash.new(0)
560
- location_hash[:total_time] += example.execution_result.run_time
561
- location_hash[:count] += 1
562
- next if location_hash.key?(:description)
563
- location_hash[:description] = example.example_group.top_level_description
564
- end
565
-
566
446
  # stop if we've only one example group
567
- return {} if example_groups.keys.length <= 1
447
+ return {} if @example_groups.keys.length <= 1
568
448
 
569
- example_groups.each_value do |hash|
449
+ @example_groups.each_value do |hash|
570
450
  hash[:average] = hash[:total_time].to_f / hash[:count]
571
451
  end
572
452
 
573
- example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples)
453
+ groups = @example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples)
454
+ groups.map { |group, data| [group.location, data] }
574
455
  end
575
456
  end
576
457
 
@@ -599,5 +480,19 @@ module RSpec::Core
599
480
  # currently require no information, but we may wish to extend in future.
600
481
  class NullNotification
601
482
  end
483
+
484
+ # `CustomNotification` is used when sending custom events to formatters /
485
+ # other registered listeners, it creates attributes based on supplied hash
486
+ # of options.
487
+ class CustomNotification < Struct
488
+ # @param options [Hash] A hash of method / value pairs to create on this notification
489
+ # @return [CustomNotification]
490
+ #
491
+ # Build a custom notification based on the supplied option key / values.
492
+ def self.for(options={})
493
+ return NullNotification if options.keys.empty?
494
+ new(*options.keys).new(*options.values)
495
+ end
496
+ end
602
497
  end
603
498
  end