rspec-core 3.2.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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