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
@@ -4,8 +4,9 @@ require 'shellwords'
4
4
  module RSpec
5
5
  module Core
6
6
  # Responsible for utilizing externally provided configuration options,
7
- # whether via the command line, `.rspec`, `~/.rspec`, `.rspec-local`
8
- # or a custom options file.
7
+ # whether via the command line, `.rspec`, `~/.rspec`,
8
+ # `$XDG_CONFIG_HOME/rspec/options`, `.rspec-local` or a custom options
9
+ # file.
9
10
  class ConfigurationOptions
10
11
  # @param args [Array<String>] command line arguments
11
12
  def initialize(args)
@@ -118,7 +119,11 @@ module RSpec
118
119
  end
119
120
 
120
121
  def file_options
121
- custom_options_file ? [custom_options] : [global_options, project_options, local_options]
122
+ if custom_options_file
123
+ [custom_options]
124
+ else
125
+ [global_options, project_options, local_options]
126
+ end
122
127
  end
123
128
 
124
129
  def env_options
@@ -168,7 +173,11 @@ module RSpec
168
173
  end
169
174
 
170
175
  def options_file_as_erb_string(path)
171
- ERB.new(File.read(path), nil, '-').result(binding)
176
+ if RUBY_VERSION >= '2.6'
177
+ ERB.new(File.read(path), :trim_mode => '-').result(binding)
178
+ else
179
+ ERB.new(File.read(path), nil, '-').result(binding)
180
+ end
172
181
  end
173
182
 
174
183
  def custom_options_file
@@ -184,10 +193,40 @@ module RSpec
184
193
  end
185
194
 
186
195
  def global_options_file
196
+ xdg_options_file_if_exists || home_options_file_path
197
+ end
198
+
199
+ def xdg_options_file_if_exists
200
+ path = xdg_options_file_path
201
+ if path && File.exist?(path)
202
+ path
203
+ end
204
+ end
205
+
206
+ def home_options_file_path
187
207
  File.join(File.expand_path("~"), ".rspec")
188
208
  rescue ArgumentError
209
+ # :nocov:
189
210
  RSpec.warning "Unable to find ~/.rspec because the HOME environment variable is not set"
190
211
  nil
212
+ # :nocov:
213
+ end
214
+
215
+ def xdg_options_file_path
216
+ xdg_config_home = resolve_xdg_config_home
217
+ if xdg_config_home
218
+ File.join(xdg_config_home, "rspec", "options")
219
+ end
220
+ end
221
+
222
+ def resolve_xdg_config_home
223
+ File.expand_path(ENV.fetch("XDG_CONFIG_HOME", "~/.config"))
224
+ rescue ArgumentError
225
+ # :nocov:
226
+ # On Ruby 2.4, `File.expand("~")` works even if `ENV['HOME']` is not set.
227
+ # But on earlier versions, it fails.
228
+ nil
229
+ # :nocov:
191
230
  end
192
231
  end
193
232
  end
@@ -0,0 +1,46 @@
1
+ module RSpec
2
+ module Core
3
+ # @private
4
+ # Wrapper around Ruby's `DidYouMean::SpellChecker` when available to provide file name suggestions.
5
+ class DidYouMean
6
+ attr_reader :relative_file_name
7
+
8
+ def initialize(relative_file_name)
9
+ @relative_file_name = relative_file_name
10
+ end
11
+
12
+ if defined?(::DidYouMean::SpellChecker)
13
+ # provide probable suggestions
14
+ def call
15
+ checker = ::DidYouMean::SpellChecker.new(:dictionary => Dir["spec/**/*.rb"])
16
+ probables = checker.correct(relative_file_name.sub('./', ''))[0..2]
17
+ return '' unless probables.any?
18
+
19
+ formats probables
20
+ end
21
+ else
22
+ # return a hint if API for ::DidYouMean::SpellChecker not supported
23
+ def call
24
+ "\nHint: Install the `did_you_mean` gem in order to provide suggestions for similarly named files."
25
+ end
26
+ end
27
+
28
+ private
29
+
30
+ def formats(probables)
31
+ rspec_format = probables.map { |s, _| "rspec ./#{s}" }
32
+ red_font(top_and_tail rspec_format)
33
+ end
34
+
35
+ def top_and_tail(rspec_format)
36
+ spaces = ' ' * 20
37
+ rspec_format.insert(0, ' - Did you mean?').join("\n#{spaces}") + "\n"
38
+ end
39
+
40
+ def red_font(mytext)
41
+ colorizer = ::RSpec::Core::Formatters::ConsoleCodes
42
+ colorizer.wrap mytext, :failure
43
+ end
44
+ end
45
+ end
46
+ end
@@ -41,6 +41,8 @@ module RSpec
41
41
  def options
42
42
  argv = []
43
43
  argv << "--color" if @submitted_options[:color]
44
+ argv << "--force-color" if @submitted_options[:color_mode] == :on
45
+ argv << "--no-color" if @submitted_options[:color_mode] == :off
44
46
  argv << "--profile" if @submitted_options[:profile_examples]
45
47
  argv << "--backtrace" if @submitted_options[:full_backtrace]
46
48
  argv << "--tty" if @submitted_options[:tty]
@@ -82,7 +84,7 @@ module RSpec
82
84
  def add_filter(argv, name, hash)
83
85
  hash.each_pair do |k, v|
84
86
  next if CONDITIONAL_FILTERS.include?(k)
85
- tag = name == :inclusion ? k.to_s : "~#{k}"
87
+ tag = name == :inclusion ? k.to_s : "~#{k}".dup
86
88
  tag << ":#{v}" if v.is_a?(String)
87
89
  argv << "--tag" << tag
88
90
  end unless hash.empty?
@@ -87,7 +87,7 @@ module RSpec
87
87
  def inspect_output
88
88
  inspect_output = "\"#{description}\""
89
89
  unless metadata[:description].to_s.empty?
90
- inspect_output << " (#{location})"
90
+ inspect_output += " (#{location})"
91
91
  end
92
92
  inspect_output
93
93
  end
@@ -203,10 +203,13 @@ module RSpec
203
203
  description, example_block
204
204
  )
205
205
 
206
+ config = RSpec.configuration
207
+ config.apply_derived_metadata_to(@metadata)
208
+
206
209
  # This should perhaps be done in `Metadata::ExampleHash.create`,
207
210
  # but the logic there has no knowledge of `RSpec.world` and we
208
211
  # want to keep it that way. It's easier to just assign it here.
209
- @metadata[:last_run_status] = RSpec.configuration.last_run_statuses[id]
212
+ @metadata[:last_run_status] = config.last_run_statuses[id]
210
213
 
211
214
  @example_group_instance = @exception = nil
212
215
  @clock = RSpec::Core::Time
@@ -260,7 +263,11 @@ module RSpec
260
263
  'Expected example to fail since it is pending, but it passed.',
261
264
  [location]
262
265
  end
263
- rescue Pending::SkipDeclaredInExample
266
+ rescue Pending::SkipDeclaredInExample => _
267
+ # The "=> _" is normally useless but on JRuby it is a workaround
268
+ # for a bug that prevents us from getting backtraces:
269
+ # https://github.com/jruby/jruby/issues/4467
270
+ #
264
271
  # no-op, required metadata has already been set by the `skip`
265
272
  # method.
266
273
  rescue AllExceptionsExcludingDangerousOnesOnRubiesThatAllowIt => e
@@ -389,7 +396,7 @@ module RSpec
389
396
  end
390
397
  end
391
398
 
392
- # rubocop:disable Style/AccessorMethodName
399
+ # rubocop:disable Naming/AccessorMethodName
393
400
 
394
401
  # @private
395
402
  #
@@ -416,7 +423,7 @@ module RSpec
416
423
  self.display_exception = exception
417
424
  end
418
425
 
419
- # rubocop:enable Style/AccessorMethodName
426
+ # rubocop:enable Naming/AccessorMethodName
420
427
 
421
428
  # @private
422
429
  #
@@ -465,22 +472,22 @@ module RSpec
465
472
 
466
473
  if @exception
467
474
  execution_result.exception = @exception
468
- record_finished :failed
475
+ record_finished :failed, reporter
469
476
  reporter.example_failed self
470
477
  false
471
478
  elsif pending_message
472
479
  execution_result.pending_message = pending_message
473
- record_finished :pending
480
+ record_finished :pending, reporter
474
481
  reporter.example_pending self
475
482
  true
476
483
  else
477
- record_finished :passed
484
+ record_finished :passed, reporter
478
485
  reporter.example_passed self
479
486
  true
480
487
  end
481
488
  end
482
489
 
483
- def record_finished(status)
490
+ def record_finished(status, reporter)
484
491
  execution_result.record_finished(status, clock.now)
485
492
  reporter.example_finished(self)
486
493
  end
@@ -519,7 +526,7 @@ module RSpec
519
526
  def assign_generated_description
520
527
  if metadata[:description].empty? && (description = generate_description)
521
528
  metadata[:description] = description
522
- metadata[:full_description] << description
529
+ metadata[:full_description] += description
523
530
  end
524
531
  ensure
525
532
  RSpec::Matchers.clear_generated_description
@@ -638,12 +645,12 @@ module RSpec
638
645
  @reporter = reporter
639
646
  end
640
647
 
641
- # rubocop:disable Style/AccessorMethodName
648
+ # rubocop:disable Naming/AccessorMethodName
642
649
  def set_exception(exception)
643
650
  reporter.notify_non_example_exception(exception, "An error occurred in #{description}.")
644
651
  RSpec.world.wants_to_quit = true
645
652
  end
646
- # rubocop:enable Style/AccessorMethodName
653
+ # rubocop:enable Naming/AccessorMethodName
647
654
  end
648
655
  end
649
656
  end
@@ -3,10 +3,11 @@ RSpec::Support.require_rspec_support 'recursive_const_methods'
3
3
  module RSpec
4
4
  module Core
5
5
  # rubocop:disable Metrics/ClassLength
6
+
6
7
  # ExampleGroup and {Example} are the main structural elements of
7
8
  # rspec-core. Consider this example:
8
9
  #
9
- # describe Thing do
10
+ # RSpec.describe Thing do
10
11
  # it "does something" do
11
12
  # end
12
13
  # end
@@ -89,7 +90,7 @@ module RSpec
89
90
  # Returns the class or module passed to the `describe` method (or alias).
90
91
  # Returns nil if the subject is not a class or module.
91
92
  # @example
92
- # describe Thing do
93
+ # RSpec.describe Thing do
93
94
  # it "does something" do
94
95
  # described_class == Thing
95
96
  # end
@@ -106,6 +107,7 @@ module RSpec
106
107
  # @private
107
108
  # @macro [attach] define_example_method
108
109
  # @!scope class
110
+ # @method $1
109
111
  # @overload $1
110
112
  # @overload $1(&example_implementation)
111
113
  # @param example_implementation [Block] The implementation of the example.
@@ -422,11 +424,15 @@ module RSpec
422
424
  superclass.method(:next_runnable_index_for),
423
425
  description, *args, &example_group_block
424
426
  )
427
+
428
+ config = RSpec.configuration
429
+ config.apply_derived_metadata_to(@metadata)
430
+
425
431
  ExampleGroups.assign_const(self)
426
432
 
427
433
  @currently_executing_a_context_hook = false
428
434
 
429
- RSpec.configuration.configure_group(self)
435
+ config.configure_group(self)
430
436
  end
431
437
 
432
438
  # @private
@@ -793,8 +799,12 @@ module RSpec
793
799
  # @private
794
800
  def self.with_frame(name, location)
795
801
  current_stack = shared_example_group_inclusions
796
- current_stack << new(name, location)
797
- yield
802
+ if current_stack.any? { |frame| frame.shared_group_name == name }
803
+ raise ArgumentError, "can't include shared examples recursively"
804
+ else
805
+ current_stack << new(name, location)
806
+ yield
807
+ end
798
808
  ensure
799
809
  current_stack.pop
800
810
  end
@@ -834,10 +844,10 @@ module RSpec
834
844
  end
835
845
 
836
846
  def self.base_name_for(group)
837
- return "Anonymous" if group.description.empty?
847
+ return "Anonymous".dup if group.description.empty?
838
848
 
839
849
  # Convert to CamelCase.
840
- name = ' ' << group.description
850
+ name = ' ' + group.description
841
851
  name.gsub!(/[^0-9a-zA-Z]+([0-9a-zA-Z])/) do
842
852
  match = ::Regexp.last_match[1]
843
853
  match.upcase!
@@ -24,7 +24,7 @@ RSpec::Support.require_rspec_support "directory_maker"
24
24
  # ## Custom Formatters
25
25
  #
26
26
  # You can tell RSpec to use a custom formatter by passing its path and name to
27
- # the `rspec` commmand. For example, if you define MyCustomFormatter in
27
+ # the `rspec` command. For example, if you define MyCustomFormatter in
28
28
  # path/to/my_custom_formatter.rb, you would type this command:
29
29
  #
30
30
  # rspec --require path/to/my_custom_formatter.rb --format MyCustomFormatter
@@ -72,8 +72,9 @@ module RSpec::Core::Formatters
72
72
  autoload :ProgressFormatter, 'rspec/core/formatters/progress_formatter'
73
73
  autoload :ProfileFormatter, 'rspec/core/formatters/profile_formatter'
74
74
  autoload :JsonFormatter, 'rspec/core/formatters/json_formatter'
75
- autoload :BisectFormatter, 'rspec/core/formatters/bisect_formatter'
75
+ autoload :BisectDRbFormatter, 'rspec/core/formatters/bisect_drb_formatter'
76
76
  autoload :ExceptionPresenter, 'rspec/core/formatters/exception_presenter'
77
+ autoload :FailureListFormatter, 'rspec/core/formatters/failure_list_formatter'
77
78
 
78
79
  # Register the formatter class
79
80
  # @param formatter_class [Class] formatter class to register
@@ -115,6 +116,11 @@ module RSpec::Core::Formatters
115
116
  # @return [String] the default formatter to setup, defaults to `progress`
116
117
  attr_accessor :default_formatter
117
118
 
119
+ # @private
120
+ def prepare_default(output_stream, deprecation_stream)
121
+ reporter.prepare_default(self, output_stream, deprecation_stream)
122
+ end
123
+
118
124
  # @private
119
125
  def setup_default(output_stream, deprecation_stream)
120
126
  add default_formatter, output_stream if @formatters.empty?
@@ -128,9 +134,6 @@ module RSpec::Core::Formatters
128
134
  end
129
135
 
130
136
  return unless RSpec.configuration.profile_examples?
131
-
132
- @reporter.setup_profiler
133
-
134
137
  return if existing_formatter_implements?(:dump_profile)
135
138
 
136
139
  add RSpec::Core::Formatters::ProfileFormatter, output_stream
@@ -138,9 +141,16 @@ module RSpec::Core::Formatters
138
141
 
139
142
  # @private
140
143
  def add(formatter_to_use, *paths)
144
+ # If a formatter instance was passed, we can register it directly,
145
+ # with no need for any of the further processing that happens below.
146
+ if Loader.formatters.key?(formatter_to_use.class)
147
+ register formatter_to_use, notifications_for(formatter_to_use.class)
148
+ return
149
+ end
150
+
141
151
  formatter_class = find_formatter(formatter_to_use)
142
152
 
143
- args = paths.map { |p| p.respond_to?(:puts) ? p : file_at(p) }
153
+ args = paths.map { |p| p.respond_to?(:puts) ? p : open_stream(p) }
144
154
 
145
155
  if !Loader.formatters[formatter_class].nil?
146
156
  formatter = formatter_class.new(*args)
@@ -201,8 +211,10 @@ module RSpec::Core::Formatters
201
211
  ProgressFormatter
202
212
  when 'j', 'json'
203
213
  JsonFormatter
204
- when 'bisect'
205
- BisectFormatter
214
+ when 'bisect-drb'
215
+ BisectDRbFormatter
216
+ when 'f', 'failures'
217
+ FailureListFormatter
206
218
  end
207
219
  end
208
220
 
@@ -247,9 +259,14 @@ module RSpec::Core::Formatters
247
259
  word
248
260
  end
249
261
 
250
- def file_at(path)
251
- RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(path))
252
- File.new(path, 'w')
262
+ def open_stream(path_or_wrapper)
263
+ if RSpec::Core::OutputWrapper === path_or_wrapper
264
+ path_or_wrapper.output = open_stream(path_or_wrapper.output)
265
+ path_or_wrapper
266
+ else
267
+ RSpec::Support::DirectoryMaker.mkdir_p(File.dirname(path_or_wrapper))
268
+ File.new(path_or_wrapper, 'w')
269
+ end
253
270
  end
254
271
  end
255
272
  end
@@ -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