rspec-core 3.5.4 → 3.9.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 (64) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +165 -2
  5. data/README.md +16 -16
  6. data/lib/rspec/core.rb +2 -1
  7. data/lib/rspec/core/bisect/coordinator.rb +26 -30
  8. data/lib/rspec/core/bisect/example_minimizer.rb +12 -8
  9. data/lib/rspec/core/bisect/fork_runner.rb +134 -0
  10. data/lib/rspec/core/bisect/server.rb +10 -14
  11. data/lib/rspec/core/bisect/{runner.rb → shell_command.rb} +27 -70
  12. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  13. data/lib/rspec/core/bisect/utilities.rb +58 -0
  14. data/lib/rspec/core/configuration.rb +315 -79
  15. data/lib/rspec/core/configuration_options.rb +43 -4
  16. data/lib/rspec/core/did_you_mean.rb +46 -0
  17. data/lib/rspec/core/drb.rb +3 -1
  18. data/lib/rspec/core/example.rb +19 -12
  19. data/lib/rspec/core/example_group.rb +17 -7
  20. data/lib/rspec/core/formatters.rb +28 -11
  21. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  22. data/lib/rspec/core/formatters/base_formatter.rb +1 -1
  23. data/lib/rspec/core/formatters/base_text_formatter.rb +3 -5
  24. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  25. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +29 -16
  26. data/lib/rspec/core/formatters/console_codes.rb +7 -4
  27. data/lib/rspec/core/formatters/deprecation_formatter.rb +9 -9
  28. data/lib/rspec/core/formatters/documentation_formatter.rb +37 -4
  29. data/lib/rspec/core/formatters/exception_presenter.rb +21 -4
  30. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  31. data/lib/rspec/core/formatters/html_formatter.rb +4 -2
  32. data/lib/rspec/core/formatters/html_printer.rb +3 -3
  33. data/lib/rspec/core/formatters/html_snippet_extractor.rb +4 -0
  34. data/lib/rspec/core/formatters/json_formatter.rb +9 -3
  35. data/lib/rspec/core/formatters/progress_formatter.rb +1 -0
  36. data/lib/rspec/core/formatters/protocol.rb +43 -42
  37. data/lib/rspec/core/formatters/snippet_extractor.rb +1 -3
  38. data/lib/rspec/core/{source → formatters}/syntax_highlighter.rb +21 -1
  39. data/lib/rspec/core/hooks.rb +18 -10
  40. data/lib/rspec/core/invocations.rb +30 -10
  41. data/lib/rspec/core/memoized_helpers.rb +36 -14
  42. data/lib/rspec/core/metadata.rb +2 -3
  43. data/lib/rspec/core/metadata_filter.rb +29 -17
  44. data/lib/rspec/core/notifications.rb +34 -11
  45. data/lib/rspec/core/option_parser.rb +32 -4
  46. data/lib/rspec/core/output_wrapper.rb +29 -0
  47. data/lib/rspec/core/profiler.rb +3 -1
  48. data/lib/rspec/core/project_initializer/.rspec +0 -1
  49. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +1 -4
  50. data/lib/rspec/core/rake_task.rb +21 -1
  51. data/lib/rspec/core/reporter.rb +33 -16
  52. data/lib/rspec/core/runner.rb +31 -15
  53. data/lib/rspec/core/set.rb +5 -0
  54. data/lib/rspec/core/shared_example_group.rb +41 -19
  55. data/lib/rspec/core/shell_escape.rb +2 -2
  56. data/lib/rspec/core/version.rb +1 -1
  57. data/lib/rspec/core/world.rb +24 -5
  58. metadata +26 -20
  59. metadata.gz.sig +0 -0
  60. data/lib/rspec/core/formatters/bisect_formatter.rb +0 -69
  61. data/lib/rspec/core/source.rb +0 -86
  62. data/lib/rspec/core/source/location.rb +0 -13
  63. data/lib/rspec/core/source/node.rb +0 -93
  64. data/lib/rspec/core/source/token.rb +0 -87
@@ -10,7 +10,7 @@ module RSpec
10
10
  # @note `subject` was contributed by Joe Ferris to support the one-liner
11
11
  # syntax embraced by shoulda matchers:
12
12
  #
13
- # describe Widget do
13
+ # RSpec.describe Widget do
14
14
  # it { is_expected.to validate_presence_of(:name) }
15
15
  # # or
16
16
  # it { should validate_presence_of(:name) }
@@ -23,7 +23,7 @@ module RSpec
23
23
  # @example
24
24
  #
25
25
  # # Explicit declaration of subject.
26
- # describe Person do
26
+ # RSpec.describe Person do
27
27
  # subject { Person.new(:birthdate => 19.years.ago) }
28
28
  # it "should be eligible to vote" do
29
29
  # subject.should be_eligible_to_vote
@@ -32,7 +32,7 @@ module RSpec
32
32
  # end
33
33
  #
34
34
  # # Implicit subject => { Person.new }.
35
- # describe Person do
35
+ # RSpec.describe Person do
36
36
  # it "should be eligible to vote" do
37
37
  # subject.should be_eligible_to_vote
38
38
  # # ^ ^ explicit reference to subject not recommended
@@ -40,7 +40,7 @@ module RSpec
40
40
  # end
41
41
  #
42
42
  # # One-liner syntax - expectation is set on the subject.
43
- # describe Person do
43
+ # RSpec.describe Person do
44
44
  # it { is_expected.to be_eligible_to_vote }
45
45
  # # or
46
46
  # it { should be_eligible_to_vote }
@@ -67,7 +67,7 @@ module RSpec
67
67
  #
68
68
  # @example
69
69
  #
70
- # describe Person do
70
+ # RSpec.describe Person do
71
71
  # it { should be_eligible_to_vote }
72
72
  # end
73
73
  #
@@ -86,7 +86,7 @@ module RSpec
86
86
  #
87
87
  # @example
88
88
  #
89
- # describe Person do
89
+ # RSpec.describe Person do
90
90
  # it { should_not be_eligible_to_vote }
91
91
  # end
92
92
  #
@@ -270,7 +270,7 @@ EOS
270
270
  #
271
271
  # @example
272
272
  #
273
- # describe Thing do
273
+ # RSpec.describe Thing do
274
274
  # let(:thing) { Thing.new }
275
275
  #
276
276
  # it "does something" do
@@ -285,7 +285,29 @@ EOS
285
285
  # We have to pass the block directly to `define_method` to
286
286
  # allow it to use method constructs like `super` and `return`.
287
287
  raise "#let or #subject called without a block" if block.nil?
288
- MemoizedHelpers.module_for(self).__send__(:define_method, name, &block)
288
+ raise(
289
+ "#let or #subject called with a reserved name #initialize"
290
+ ) if :initialize == name
291
+ our_module = MemoizedHelpers.module_for(self)
292
+
293
+ # If we have a module clash in our helper module
294
+ # then we need to remove it to prevent a warning.
295
+ #
296
+ # Note we do not check ancestor modules (see: `instance_methods(false)`)
297
+ # as we can override them.
298
+ if our_module.instance_methods(false).include?(name)
299
+ our_module.__send__(:remove_method, name)
300
+ end
301
+ our_module.__send__(:define_method, name, &block)
302
+
303
+ # If we have a module clash in the example module
304
+ # then we need to remove it to prevent a warning.
305
+ #
306
+ # Note we do not check ancestor modules (see: `instance_methods(false)`)
307
+ # as we can override them.
308
+ if instance_methods(false).include?(name)
309
+ remove_method(name)
310
+ end
289
311
 
290
312
  # Apply the memoization. The method has been defined in an ancestor
291
313
  # module so we can use `super` here to get the value.
@@ -320,7 +342,7 @@ EOS
320
342
  # end
321
343
  # end
322
344
  #
323
- # describe Thing do
345
+ # RSpec.describe Thing do
324
346
  # after(:example) { Thing.reset_count }
325
347
  #
326
348
  # context "using let" do
@@ -376,13 +398,13 @@ EOS
376
398
  #
377
399
  # @example
378
400
  #
379
- # describe CheckingAccount, "with $50" do
401
+ # RSpec.describe CheckingAccount, "with $50" do
380
402
  # subject { CheckingAccount.new(Money.new(50, :USD)) }
381
403
  # it { is_expected.to have_a_balance_of(Money.new(50, :USD)) }
382
404
  # it { is_expected.not_to be_overdrawn }
383
405
  # end
384
406
  #
385
- # describe CheckingAccount, "with a non-zero starting balance" do
407
+ # RSpec.describe CheckingAccount, "with a non-zero starting balance" do
386
408
  # subject(:account) { CheckingAccount.new(Money.new(50, :USD)) }
387
409
  # it { is_expected.not_to be_overdrawn }
388
410
  # it "has a balance equal to the starting balance" do
@@ -430,7 +452,7 @@ EOS
430
452
  # end
431
453
  # end
432
454
  #
433
- # describe Thing do
455
+ # RSpec.describe Thing do
434
456
  # after(:example) { Thing.reset_count }
435
457
  #
436
458
  # context "using subject" do
@@ -480,9 +502,9 @@ EOS
480
502
  def self.module_for(example_group)
481
503
  get_constant_or_yield(example_group, :LetDefinitions) do
482
504
  mod = Module.new do
483
- include Module.new {
505
+ include(Module.new {
484
506
  example_group.const_set(:NamedSubjectPreventSuper, self)
485
- }
507
+ })
486
508
  end
487
509
 
488
510
  example_group.const_set(:LetDefinitions, mod)
@@ -7,7 +7,7 @@ module RSpec
7
7
  # In addition to metadata that is used internally, this also stores
8
8
  # user-supplied metadata, e.g.
9
9
  #
10
- # describe Something, :type => :ui do
10
+ # RSpec.describe Something, :type => :ui do
11
11
  # it "does something", :slow => true do
12
12
  # # ...
13
13
  # end
@@ -136,7 +136,6 @@ module RSpec
136
136
 
137
137
  populate_location_attributes
138
138
  metadata.update(user_metadata)
139
- RSpec.configuration.apply_derived_metadata_to(metadata)
140
139
  end
141
140
 
142
141
  private
@@ -169,7 +168,7 @@ module RSpec
169
168
  end
170
169
 
171
170
  def description_separator(parent_part, child_part)
172
- if parent_part.is_a?(Module) && child_part =~ /^(#|::|\.)/
171
+ if parent_part.is_a?(Module) && /^(?:#|::|\.)/.match(child_part.to_s)
173
172
  ''.freeze
174
173
  else
175
174
  ' '.freeze
@@ -13,24 +13,19 @@ module RSpec
13
13
  end
14
14
 
15
15
  # @private
16
- def filter_applies?(key, value, metadata)
16
+ def filter_applies?(key, filter_value, metadata)
17
17
  silence_metadata_example_group_deprecations do
18
- return location_filter_applies?(value, metadata) if key == :locations
19
- return id_filter_applies?(value, metadata) if key == :ids
20
- return filters_apply?(key, value, metadata) if Hash === value
21
-
22
- return false unless metadata.key?(key)
23
- return true if TrueClass === value && !!metadata[key]
24
- return filter_applies_to_any_value?(key, value, metadata) if Array === metadata[key] && !(Proc === value)
25
-
26
- case value
27
- when Regexp
28
- metadata[key] =~ value
29
- when Proc
30
- proc_filter_applies?(key, value, metadata)
31
- else
32
- metadata[key].to_s == value.to_s
33
- end
18
+ return location_filter_applies?(filter_value, metadata) if key == :locations
19
+ return id_filter_applies?(filter_value, metadata) if key == :ids
20
+ return filters_apply?(key, filter_value, metadata) if Hash === filter_value
21
+
22
+ meta_value = metadata.fetch(key) { return false }
23
+
24
+ return true if TrueClass === filter_value && meta_value
25
+ return proc_filter_applies?(key, filter_value, metadata) if Proc === filter_value
26
+ return filter_applies_to_any_value?(key, filter_value, metadata) if Array === meta_value
27
+
28
+ filter_value === meta_value || filter_value.to_s == meta_value.to_s
34
29
  end
35
30
  end
36
31
 
@@ -116,6 +111,10 @@ module RSpec
116
111
  @items_and_filters.unshift [item, metadata]
117
112
  end
118
113
 
114
+ def delete(item, metadata)
115
+ @items_and_filters.delete [item, metadata]
116
+ end
117
+
119
118
  def items_for(request_meta)
120
119
  @items_and_filters.each_with_object([]) do |(item, item_meta), to_return|
121
120
  to_return << item if item_meta.empty? ||
@@ -172,6 +171,11 @@ module RSpec
172
171
  handle_mutation(metadata)
173
172
  end
174
173
 
174
+ def delete(item, metadata)
175
+ super
176
+ reconstruct_caches
177
+ end
178
+
175
179
  def items_for(metadata)
176
180
  # The filtering of `metadata` to `applicable_metadata` is the key thing
177
181
  # that makes the memoization actually useful in practice, since each
@@ -196,6 +200,14 @@ module RSpec
196
200
 
197
201
  private
198
202
 
203
+ def reconstruct_caches
204
+ @applicable_keys.clear
205
+ @proc_keys.clear
206
+ @items_and_filters.each do |_item, metadata|
207
+ handle_mutation(metadata)
208
+ end
209
+ end
210
+
199
211
  def handle_mutation(metadata)
200
212
  @applicable_keys.merge(metadata.keys)
201
213
  @proc_keys.merge(proc_keys_from metadata)
@@ -1,3 +1,4 @@
1
+ RSpec::Support.require_rspec_core "formatters/console_codes"
1
2
  RSpec::Support.require_rspec_core "formatters/exception_presenter"
2
3
  RSpec::Support.require_rspec_core "formatters/helpers"
3
4
  RSpec::Support.require_rspec_core "shell_escape"
@@ -110,7 +111,7 @@ module RSpec::Core
110
111
  formatted = "\nFailures:\n"
111
112
 
112
113
  failure_notifications.each_with_index do |failure, index|
113
- formatted << failure.fully_formatted(index.next, colorizer)
114
+ formatted += failure.fully_formatted(index.next, colorizer)
114
115
  end
115
116
 
116
117
  formatted
@@ -119,7 +120,7 @@ module RSpec::Core
119
120
  # @return [String] The list of pending examples, fully formatted in the
120
121
  # way that RSpec's built-in formatters emit.
121
122
  def fully_formatted_pending_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
122
- formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n"
123
+ formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n".dup
123
124
 
124
125
  pending_notifications.each_with_index do |notification, index|
125
126
  formatted << notification.fully_formatted(index.next, colorizer)
@@ -199,6 +200,12 @@ module RSpec::Core
199
200
  @exception_presenter.fully_formatted(failure_number, colorizer)
200
201
  end
201
202
 
203
+ # @return [Array<string>] The failure information fully formatted in the way that
204
+ # RSpec's built-in formatters emit, split by line.
205
+ def fully_formatted_lines(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
206
+ @exception_presenter.fully_formatted_lines(failure_number, colorizer)
207
+ end
208
+
202
209
  private
203
210
 
204
211
  def initialize(example, exception_presenter=Formatters::ExceptionPresenter::Factory.new(example).build)
@@ -225,9 +232,14 @@ module RSpec::Core
225
232
  # RSpec's built-in formatters emit.
226
233
  def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
227
234
  formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location)
228
- colorizer.wrap("\n #{pending_number}) #{example.full_description}", :pending) << "\n " <<
229
- Formatters::ExceptionPresenter::PENDING_DETAIL_FORMATTER.call(example, colorizer) <<
230
- "\n" << colorizer.wrap(" # #{formatted_caller}\n", :detail)
235
+
236
+ [
237
+ colorizer.wrap("\n #{pending_number}) #{example.full_description}", :pending),
238
+ "\n ",
239
+ Formatters::ExceptionPresenter::PENDING_DETAIL_FORMATTER.call(example, colorizer),
240
+ "\n",
241
+ colorizer.wrap(" # #{formatted_caller}\n", :detail)
242
+ ].join("")
231
243
  end
232
244
  end
233
245
 
@@ -280,8 +292,12 @@ module RSpec::Core
280
292
  # @attr pending_examples [Array<RSpec::Core::Example>] the pending examples
281
293
  # @attr load_time [Float] the number of seconds taken to boot RSpec
282
294
  # and load the spec files
295
+ # @attr errors_outside_of_examples_count [Integer] the number of errors that
296
+ # have occurred processing
297
+ # the spec suite
283
298
  SummaryNotification = Struct.new(:duration, :examples, :failed_examples,
284
- :pending_examples, :load_time)
299
+ :pending_examples, :load_time,
300
+ :errors_outside_of_examples_count)
285
301
  class SummaryNotification
286
302
  # @api
287
303
  # @return [Fixnum] the number of examples run
@@ -304,9 +320,16 @@ module RSpec::Core
304
320
  # @api
305
321
  # @return [String] A line summarising the result totals of the spec run.
306
322
  def totals_line
307
- summary = Formatters::Helpers.pluralize(example_count, "example")
308
- summary << ", " << Formatters::Helpers.pluralize(failure_count, "failure")
309
- summary << ", #{pending_count} pending" if pending_count > 0
323
+ summary = Formatters::Helpers.pluralize(example_count, "example") +
324
+ ", " + Formatters::Helpers.pluralize(failure_count, "failure")
325
+ summary += ", #{pending_count} pending" if pending_count > 0
326
+ if errors_outside_of_examples_count > 0
327
+ summary += (
328
+ ", " +
329
+ Formatters::Helpers.pluralize(errors_outside_of_examples_count, "error") +
330
+ " occurred outside of examples"
331
+ )
332
+ end
310
333
  summary
311
334
  end
312
335
 
@@ -320,7 +343,7 @@ module RSpec::Core
320
343
  # specific colors.
321
344
  # @return [String] A colorized results line.
322
345
  def colorized_totals_line(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
323
- if failure_count > 0
346
+ if failure_count > 0 || errors_outside_of_examples_count > 0
324
347
  colorizer.wrap(totals_line, RSpec.configuration.failure_color)
325
348
  elsif pending_count > 0
326
349
  colorizer.wrap(totals_line, RSpec.configuration.pending_color)
@@ -364,7 +387,7 @@ module RSpec::Core
364
387
  "#{colorized_totals_line(colorizer)}\n"
365
388
 
366
389
  unless failed_examples.empty?
367
- formatted << colorized_rerun_commands(colorizer) << "\n"
390
+ formatted += (colorized_rerun_commands(colorizer) + "\n")
368
391
  end
369
392
 
370
393
  formatted
@@ -37,8 +37,11 @@ module RSpec::Core
37
37
  # rubocop:disable Metrics/AbcSize
38
38
  # rubocop:disable CyclomaticComplexity
39
39
  # rubocop:disable PerceivedComplexity
40
+ # rubocop:disable Metrics/BlockLength
40
41
  def parser(options)
41
42
  OptionParser.new do |parser|
43
+ parser.summary_width = 34
44
+
42
45
  parser.banner = "Usage: rspec [options] [files or directories]\n\n"
43
46
 
44
47
  parser.on('-I PATH', 'Specify PATH to add to $LOAD_PATH (may be used more than once).') do |dirs|
@@ -109,6 +112,7 @@ module RSpec::Core
109
112
  ' [d]ocumentation (group and example names)',
110
113
  ' [h]tml',
111
114
  ' [j]son',
115
+ ' [f]ailures ("file:line:reason", suitable for editors integration)',
112
116
  ' custom formatter class name') do |o|
113
117
  options[:formatters] ||= []
114
118
  options[:formatters] << [o]
@@ -131,8 +135,24 @@ module RSpec::Core
131
135
  options[:full_backtrace] = true
132
136
  end
133
137
 
134
- parser.on('-c', '--[no-]color', '--[no-]colour', 'Enable color in the output.') do |o|
135
- options[:color] = o
138
+ parser.on('-c', '--color', '--colour', '') do |_o|
139
+ # flag will be excluded from `--help` output because it is deprecated
140
+ options[:color] = true
141
+ options[:color_mode] = :automatic
142
+ end
143
+
144
+ parser.on('--force-color', '--force-colour', 'Force the output to be in color, even if the output is not a TTY') do |_o|
145
+ if options[:color_mode] == :off
146
+ abort "Please only use one of `--force-color` and `--no-color`"
147
+ end
148
+ options[:color_mode] = :on
149
+ end
150
+
151
+ parser.on('--no-color', '--no-colour', 'Force the output to not be in color, even if the output is a TTY') do |_o|
152
+ if options[:color_mode] == :on
153
+ abort "Please only use one of --force-color and --no-color"
154
+ end
155
+ options[:color_mode] = :off
136
156
  end
137
157
 
138
158
  parser.on('-p', '--[no-]profile [COUNT]',
@@ -183,7 +203,7 @@ FILTERING
183
203
  configure_only_failures(options)
184
204
  end
185
205
 
186
- parser.on("--next-failure", "Apply `--only-failures` and abort after one failure.",
206
+ parser.on("-n", "--next-failure", "Apply `--only-failures` and abort after one failure.",
187
207
  " (Equivalent to `--only-failures --fail-fast --order defined`)") do
188
208
  configure_only_failures(options)
189
209
  set_fail_fast(options, 1)
@@ -208,6 +228,11 @@ FILTERING
208
228
  (options[:full_description] ||= []) << Regexp.compile(Regexp.escape(o))
209
229
  end
210
230
 
231
+ parser.on('-E', '--example-matches REGEX', "Run examples whose full nested names match REGEX (may be",
232
+ " used more than once)") do |o|
233
+ (options[:full_description] ||= []) << Regexp.compile(o)
234
+ end
235
+
211
236
  parser.on('-t', '--tag TAG[:VALUE]',
212
237
  'Run examples with the specified tag, or exclude examples',
213
238
  'by adding ~ before the tag.',
@@ -256,8 +281,10 @@ FILTERING
256
281
  # trigger --default-path.
257
282
  invalid_options = %w[-d --I]
258
283
 
284
+ hidden_options = invalid_options + %w[-c]
285
+
259
286
  parser.on_tail('-h', '--help', "You're looking at it.") do
260
- options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, invalid_options)
287
+ options[:runner] = RSpec::Core::Invocations::PrintHelp.new(parser, hidden_options)
261
288
  end
262
289
 
263
290
  # This prevents usage of the invalid_options.
@@ -268,6 +295,7 @@ FILTERING
268
295
  end
269
296
  end
270
297
  end
298
+ # rubocop:enable Metrics/BlockLength
271
299
  # rubocop:enable Metrics/AbcSize
272
300
  # rubocop:enable MethodLength
273
301
  # rubocop:enable CyclomaticComplexity
@@ -0,0 +1,29 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ class OutputWrapper
5
+ # @private
6
+ attr_accessor :output
7
+
8
+ # @private
9
+ def initialize(output)
10
+ @output = output
11
+ end
12
+
13
+ def respond_to?(name, priv=false)
14
+ output.respond_to?(name, priv)
15
+ end
16
+
17
+ def method_missing(name, *args, &block)
18
+ output.send(name, *args, &block)
19
+ end
20
+
21
+ # Redirect calls for IO interface methods
22
+ IO.instance_methods(false).each do |method|
23
+ define_method(method) do |*args, &block|
24
+ output.send(method, *args, &block)
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end