rspec-core 3.5.4 → 3.9.0

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