rspec-core 3.7.0 → 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 (45) 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 +80 -2
  5. data/README.md +16 -16
  6. data/lib/rspec/core.rb +1 -0
  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 +5 -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 +219 -62
  15. data/lib/rspec/core/configuration_options.rb +41 -4
  16. data/lib/rspec/core/did_you_mean.rb +46 -0
  17. data/lib/rspec/core/example.rb +8 -5
  18. data/lib/rspec/core/example_group.rb +8 -3
  19. data/lib/rspec/core/formatters.rb +13 -6
  20. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  21. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  22. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +29 -16
  23. data/lib/rspec/core/formatters/deprecation_formatter.rb +3 -1
  24. data/lib/rspec/core/formatters/documentation_formatter.rb +35 -3
  25. data/lib/rspec/core/formatters/exception_presenter.rb +13 -1
  26. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  27. data/lib/rspec/core/formatters/html_printer.rb +0 -2
  28. data/lib/rspec/core/formatters/protocol.rb +17 -17
  29. data/lib/rspec/core/formatters/syntax_highlighter.rb +19 -19
  30. data/lib/rspec/core/hooks.rb +15 -9
  31. data/lib/rspec/core/invocations.rb +8 -6
  32. data/lib/rspec/core/memoized_helpers.rb +33 -14
  33. data/lib/rspec/core/metadata.rb +2 -3
  34. data/lib/rspec/core/option_parser.rb +8 -0
  35. data/lib/rspec/core/profiler.rb +3 -1
  36. data/lib/rspec/core/rake_task.rb +21 -1
  37. data/lib/rspec/core/reporter.rb +11 -6
  38. data/lib/rspec/core/runner.rb +25 -14
  39. data/lib/rspec/core/shared_example_group.rb +2 -4
  40. data/lib/rspec/core/shell_escape.rb +2 -2
  41. data/lib/rspec/core/version.rb +1 -1
  42. data/lib/rspec/core/world.rb +11 -0
  43. metadata +22 -13
  44. metadata.gz.sig +0 -0
  45. data/lib/rspec/core/formatters/bisect_formatter.rb +0 -69
@@ -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,6 +193,17 @@ 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
189
209
  # :nocov:
@@ -191,6 +211,23 @@ module RSpec
191
211
  nil
192
212
  # :nocov:
193
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:
230
+ end
194
231
  end
195
232
  end
196
233
  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
@@ -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
@@ -393,7 +396,7 @@ module RSpec
393
396
  end
394
397
  end
395
398
 
396
- # rubocop:disable Style/AccessorMethodName
399
+ # rubocop:disable Naming/AccessorMethodName
397
400
 
398
401
  # @private
399
402
  #
@@ -420,7 +423,7 @@ module RSpec
420
423
  self.display_exception = exception
421
424
  end
422
425
 
423
- # rubocop:enable Style/AccessorMethodName
426
+ # rubocop:enable Naming/AccessorMethodName
424
427
 
425
428
  # @private
426
429
  #
@@ -642,12 +645,12 @@ module RSpec
642
645
  @reporter = reporter
643
646
  end
644
647
 
645
- # rubocop:disable Style/AccessorMethodName
648
+ # rubocop:disable Naming/AccessorMethodName
646
649
  def set_exception(exception)
647
650
  reporter.notify_non_example_exception(exception, "An error occurred in #{description}.")
648
651
  RSpec.world.wants_to_quit = true
649
652
  end
650
- # rubocop:enable Style/AccessorMethodName
653
+ # rubocop:enable Naming/AccessorMethodName
651
654
  end
652
655
  end
653
656
  end
@@ -7,7 +7,7 @@ module RSpec
7
7
  # ExampleGroup and {Example} are the main structural elements of
8
8
  # rspec-core. Consider this example:
9
9
  #
10
- # describe Thing do
10
+ # RSpec.describe Thing do
11
11
  # it "does something" do
12
12
  # end
13
13
  # end
@@ -90,7 +90,7 @@ module RSpec
90
90
  # Returns the class or module passed to the `describe` method (or alias).
91
91
  # Returns nil if the subject is not a class or module.
92
92
  # @example
93
- # describe Thing do
93
+ # RSpec.describe Thing do
94
94
  # it "does something" do
95
95
  # described_class == Thing
96
96
  # end
@@ -107,6 +107,7 @@ module RSpec
107
107
  # @private
108
108
  # @macro [attach] define_example_method
109
109
  # @!scope class
110
+ # @method $1
110
111
  # @overload $1
111
112
  # @overload $1(&example_implementation)
112
113
  # @param example_implementation [Block] The implementation of the example.
@@ -423,11 +424,15 @@ module RSpec
423
424
  superclass.method(:next_runnable_index_for),
424
425
  description, *args, &example_group_block
425
426
  )
427
+
428
+ config = RSpec.configuration
429
+ config.apply_derived_metadata_to(@metadata)
430
+
426
431
  ExampleGroups.assign_const(self)
427
432
 
428
433
  @currently_executing_a_context_hook = false
429
434
 
430
- RSpec.configuration.configure_group(self)
435
+ config.configure_group(self)
431
436
  end
432
437
 
433
438
  # @private
@@ -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
@@ -133,9 +134,6 @@ module RSpec::Core::Formatters
133
134
  end
134
135
 
135
136
  return unless RSpec.configuration.profile_examples?
136
-
137
- @reporter.setup_profiler
138
-
139
137
  return if existing_formatter_implements?(:dump_profile)
140
138
 
141
139
  add RSpec::Core::Formatters::ProfileFormatter, output_stream
@@ -143,6 +141,13 @@ module RSpec::Core::Formatters
143
141
 
144
142
  # @private
145
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
+
146
151
  formatter_class = find_formatter(formatter_to_use)
147
152
 
148
153
  args = paths.map { |p| p.respond_to?(:puts) ? p : open_stream(p) }
@@ -206,8 +211,10 @@ module RSpec::Core::Formatters
206
211
  ProgressFormatter
207
212
  when 'j', 'json'
208
213
  JsonFormatter
209
- when 'bisect'
210
- BisectFormatter
214
+ when 'bisect-drb'
215
+ BisectDRbFormatter
216
+ when 'f', 'failures'
217
+ FailureListFormatter
211
218
  end
212
219
  end
213
220
 
@@ -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
@@ -0,0 +1,29 @@
1
+ require 'drb/drb'
2
+ RSpec::Support.require_rspec_core "formatters/base_bisect_formatter"
3
+
4
+ module RSpec
5
+ module Core
6
+ module Formatters
7
+ # Used by `--bisect`. When it shells out and runs a portion of the suite, it uses
8
+ # this formatter as a means to have the status reported back to it, via DRb.
9
+ #
10
+ # Note that since DRb calls carry considerable overhead compared to normal
11
+ # method calls, we try to minimize the number of DRb calls for perf reasons,
12
+ # opting to communicate only at the start and the end of the run, rather than
13
+ # after each example.
14
+ # @private
15
+ class BisectDRbFormatter < BaseBisectFormatter
16
+ def initialize(_output)
17
+ drb_uri = "druby://localhost:#{RSpec.configuration.drb_port}"
18
+ @bisect_server = DRbObject.new_with_uri(drb_uri)
19
+ RSpec.configuration.files_or_directories_to_run = @bisect_server.files_or_directories_to_run
20
+ super(Set.new(@bisect_server.expected_failures))
21
+ end
22
+
23
+ def notify_results(results)
24
+ @bisect_server.latest_run_results = results
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -6,19 +6,14 @@ module RSpec
6
6
  # @private
7
7
  # Produces progress output while bisecting.
8
8
  class BisectProgressFormatter < BaseTextFormatter
9
- # We've named all events with a `bisect_` prefix to prevent naming collisions.
10
- Formatters.register self, :bisect_starting, :bisect_original_run_complete,
11
- :bisect_round_started, :bisect_individual_run_complete,
12
- :bisect_complete, :bisect_repro_command,
13
- :bisect_failed, :bisect_aborted,
14
- :bisect_round_ignoring_ids, :bisect_round_detected_multiple_culprits,
15
- :bisect_dependency_check_started, :bisect_dependency_check_passed,
16
- :bisect_dependency_check_failed
9
+ def initialize(output, bisect_runner)
10
+ super(output)
11
+ @bisect_runner = bisect_runner
12
+ end
17
13
 
18
14
  def bisect_starting(notification)
19
15
  @round_count = 0
20
- options = notification.original_cli_args.join(' ')
21
- output.puts "Bisect started using options: #{options.inspect}"
16
+ output.puts bisect_started_message(notification)
22
17
  output.print "Running suite to find failures..."
23
18
  end
24
19
 
@@ -40,6 +35,16 @@ module RSpec
40
35
 
41
36
  def bisect_dependency_check_failed(_notification)
42
37
  output.puts " failure(s) do not require any non-failures to run first"
38
+
39
+ if @bisect_runner == :fork
40
+ output.puts
41
+ output.puts "=" * 80
42
+ output.puts "NOTE: this bisect run used `config.bisect_runner = :fork`, which generally"
43
+ output.puts "provides significantly faster bisection runs than the old shell-based runner,"
44
+ output.puts "but may inaccurately report that no non-failures are required. If this result"
45
+ output.puts "is unexpected, consider setting `config.bisect_runner = :shell` and trying again."
46
+ output.puts "=" * 80
47
+ end
43
48
  end
44
49
 
45
50
  def bisect_round_started(notification, include_trailing_space=true)
@@ -85,16 +90,20 @@ module RSpec
85
90
  output.puts "\n\nBisect aborted!"
86
91
  output.puts "\nThe most minimal reproduction command discovered so far is:\n #{notification.repro}"
87
92
  end
93
+
94
+ private
95
+
96
+ def bisect_started_message(notification)
97
+ options = notification.original_cli_args.join(' ')
98
+ "Bisect started using options: #{options.inspect}"
99
+ end
88
100
  end
89
101
 
90
102
  # @private
91
- # Produces detailed debug output while bisecting. Used when
92
- # bisect is performed while the `DEBUG_RSPEC_BISECT` ENV var is used.
93
- # Designed to provide details for us when we need to troubleshoot bisect bugs.
103
+ # Produces detailed debug output while bisecting. Used when bisect is
104
+ # performed with `--bisect=verbose`. Designed to provide details for
105
+ # us when we need to troubleshoot bisect bugs.
94
106
  class BisectDebugFormatter < BisectProgressFormatter
95
- Formatters.register self, :bisect_original_run_complete, :bisect_individual_run_start,
96
- :bisect_individual_run_complete, :bisect_round_ignoring_ids
97
-
98
107
  def bisect_original_run_complete(notification)
99
108
  output.puts " (#{Helpers.format_duration(notification.duration)})"
100
109
 
@@ -138,6 +147,10 @@ module RSpec
138
147
  formatted_ids = organized_ids.map { |id| " - #{id}" }.join("\n")
139
148
  "#{description} (#{ids.size}):\n#{formatted_ids}"
140
149
  end
150
+
151
+ def bisect_started_message(notification)
152
+ "#{super} and bisect runner: #{notification.bisect_runner.inspect}"
153
+ end
141
154
  end
142
155
  end
143
156
  end
@@ -62,6 +62,7 @@ module RSpec
62
62
  TOO_MANY_WARNINGS_NOTICE = "Too many similar deprecation messages " \
63
63
  "reported, disregarding further reports. #{DEPRECATION_STREAM_NOTICE}"
64
64
 
65
+ # @private
65
66
  SpecifiedDeprecationMessage = Struct.new(:type) do
66
67
  def initialize(data)
67
68
  @message = data.message
@@ -80,7 +81,7 @@ module RSpec
80
81
 
81
82
  def output_formatted(str)
82
83
  return str unless str.lines.count > 1
83
- separator = "#{'-' * 80}"
84
+ separator = '-' * 80
84
85
  "#{separator}\n#{str.chomp}\n#{separator}"
85
86
  end
86
87
 
@@ -89,6 +90,7 @@ module RSpec
89
90
  end
90
91
  end
91
92
 
93
+ # @private
92
94
  GeneratedDeprecationMessage = Struct.new(:type) do
93
95
  def initialize(data)
94
96
  @data = data
@@ -6,12 +6,19 @@ module RSpec
6
6
  module Formatters
7
7
  # @private
8
8
  class DocumentationFormatter < BaseTextFormatter
9
- Formatters.register self, :example_group_started, :example_group_finished,
9
+ Formatters.register self, :example_started, :example_group_started, :example_group_finished,
10
10
  :example_passed, :example_pending, :example_failed
11
11
 
12
12
  def initialize(output)
13
13
  super
14
14
  @group_level = 0
15
+
16
+ @example_running = false
17
+ @messages = []
18
+ end
19
+
20
+ def example_started(_notification)
21
+ @example_running = true
15
22
  end
16
23
 
17
24
  def example_group_started(notification)
@@ -27,19 +34,44 @@ module RSpec
27
34
 
28
35
  def example_passed(passed)
29
36
  output.puts passed_output(passed.example)
37
+
38
+ flush_messages
39
+ @example_running = false
30
40
  end
31
41
 
32
42
  def example_pending(pending)
33
43
  output.puts pending_output(pending.example,
34
44
  pending.example.execution_result.pending_message)
45
+
46
+ flush_messages
47
+ @example_running = false
35
48
  end
36
49
 
37
50
  def example_failed(failure)
38
51
  output.puts failure_output(failure.example)
52
+
53
+ flush_messages
54
+ @example_running = false
55
+ end
56
+
57
+ def message(notification)
58
+ if @example_running
59
+ @messages << notification.message
60
+ else
61
+ output.puts "#{current_indentation}#{notification.message}"
62
+ end
39
63
  end
40
64
 
41
65
  private
42
66
 
67
+ def flush_messages
68
+ @messages.each do |message|
69
+ output.puts "#{current_indentation(1)}#{message}"
70
+ end
71
+
72
+ @messages.clear
73
+ end
74
+
43
75
  def passed_output(example)
44
76
  ConsoleCodes.wrap("#{current_indentation}#{example.description.strip}", :success)
45
77
  end
@@ -61,8 +93,8 @@ module RSpec
61
93
  @next_failure_index += 1
62
94
  end
63
95
 
64
- def current_indentation
65
- ' ' * @group_level
96
+ def current_indentation(offset=0)
97
+ ' ' * (@group_level + offset)
66
98
  end
67
99
  end
68
100
  end