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
@@ -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