rspec-core 3.0.4 → 3.12.2

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 (85) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +2 -1
  5. data/Changelog.md +888 -2
  6. data/{License.txt → LICENSE.md} +6 -5
  7. data/README.md +165 -24
  8. data/lib/rspec/autorun.rb +1 -0
  9. data/lib/rspec/core/backtrace_formatter.rb +19 -20
  10. data/lib/rspec/core/bisect/coordinator.rb +62 -0
  11. data/lib/rspec/core/bisect/example_minimizer.rb +173 -0
  12. data/lib/rspec/core/bisect/fork_runner.rb +138 -0
  13. data/lib/rspec/core/bisect/server.rb +61 -0
  14. data/lib/rspec/core/bisect/shell_command.rb +126 -0
  15. data/lib/rspec/core/bisect/shell_runner.rb +73 -0
  16. data/lib/rspec/core/bisect/utilities.rb +69 -0
  17. data/lib/rspec/core/configuration.rb +1287 -246
  18. data/lib/rspec/core/configuration_options.rb +95 -35
  19. data/lib/rspec/core/did_you_mean.rb +46 -0
  20. data/lib/rspec/core/drb.rb +21 -12
  21. data/lib/rspec/core/dsl.rb +10 -6
  22. data/lib/rspec/core/example.rb +305 -113
  23. data/lib/rspec/core/example_group.rb +431 -223
  24. data/lib/rspec/core/example_status_persister.rb +235 -0
  25. data/lib/rspec/core/filter_manager.rb +86 -115
  26. data/lib/rspec/core/flat_map.rb +6 -4
  27. data/lib/rspec/core/formatters/base_bisect_formatter.rb +45 -0
  28. data/lib/rspec/core/formatters/base_formatter.rb +14 -116
  29. data/lib/rspec/core/formatters/base_text_formatter.rb +18 -21
  30. data/lib/rspec/core/formatters/bisect_drb_formatter.rb +29 -0
  31. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +157 -0
  32. data/lib/rspec/core/formatters/console_codes.rb +29 -18
  33. data/lib/rspec/core/formatters/deprecation_formatter.rb +16 -16
  34. data/lib/rspec/core/formatters/documentation_formatter.rb +49 -16
  35. data/lib/rspec/core/formatters/exception_presenter.rb +525 -0
  36. data/lib/rspec/core/formatters/failure_list_formatter.rb +23 -0
  37. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  38. data/lib/rspec/core/formatters/helpers.rb +45 -15
  39. data/lib/rspec/core/formatters/html_formatter.rb +33 -28
  40. data/lib/rspec/core/formatters/html_printer.rb +30 -20
  41. data/lib/rspec/core/formatters/html_snippet_extractor.rb +120 -0
  42. data/lib/rspec/core/formatters/json_formatter.rb +18 -9
  43. data/lib/rspec/core/formatters/profile_formatter.rb +10 -9
  44. data/lib/rspec/core/formatters/progress_formatter.rb +5 -4
  45. data/lib/rspec/core/formatters/protocol.rb +182 -0
  46. data/lib/rspec/core/formatters/snippet_extractor.rb +113 -82
  47. data/lib/rspec/core/formatters/syntax_highlighter.rb +91 -0
  48. data/lib/rspec/core/formatters.rb +81 -41
  49. data/lib/rspec/core/hooks.rb +314 -244
  50. data/lib/rspec/core/invocations.rb +87 -0
  51. data/lib/rspec/core/memoized_helpers.rb +161 -51
  52. data/lib/rspec/core/metadata.rb +132 -61
  53. data/lib/rspec/core/metadata_filter.rb +224 -64
  54. data/lib/rspec/core/minitest_assertions_adapter.rb +6 -3
  55. data/lib/rspec/core/mocking_adapters/flexmock.rb +4 -2
  56. data/lib/rspec/core/mocking_adapters/mocha.rb +11 -9
  57. data/lib/rspec/core/mocking_adapters/null.rb +2 -0
  58. data/lib/rspec/core/mocking_adapters/rr.rb +3 -1
  59. data/lib/rspec/core/mocking_adapters/rspec.rb +3 -1
  60. data/lib/rspec/core/notifications.rb +192 -206
  61. data/lib/rspec/core/option_parser.rb +174 -69
  62. data/lib/rspec/core/ordering.rb +48 -35
  63. data/lib/rspec/core/output_wrapper.rb +29 -0
  64. data/lib/rspec/core/pending.rb +25 -33
  65. data/lib/rspec/core/profiler.rb +34 -0
  66. data/lib/rspec/core/project_initializer/.rspec +0 -2
  67. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +59 -39
  68. data/lib/rspec/core/project_initializer.rb +5 -3
  69. data/lib/rspec/core/rake_task.rb +99 -55
  70. data/lib/rspec/core/reporter.rb +128 -15
  71. data/lib/rspec/core/ruby_project.rb +14 -6
  72. data/lib/rspec/core/runner.rb +96 -45
  73. data/lib/rspec/core/sandbox.rb +37 -0
  74. data/lib/rspec/core/set.rb +54 -0
  75. data/lib/rspec/core/shared_example_group.rb +133 -43
  76. data/lib/rspec/core/shell_escape.rb +49 -0
  77. data/lib/rspec/core/test_unit_assertions_adapter.rb +4 -4
  78. data/lib/rspec/core/version.rb +1 -1
  79. data/lib/rspec/core/warnings.rb +6 -6
  80. data/lib/rspec/core/world.rb +172 -68
  81. data/lib/rspec/core.rb +66 -21
  82. data.tar.gz.sig +0 -0
  83. metadata +93 -69
  84. metadata.gz.sig +0 -0
  85. data/lib/rspec/core/backport_random.rb +0 -336
@@ -1,12 +1,16 @@
1
+ RSpec::Support.require_rspec_core "formatters/console_codes"
2
+ RSpec::Support.require_rspec_core "formatters/exception_presenter"
1
3
  RSpec::Support.require_rspec_core "formatters/helpers"
4
+ RSpec::Support.require_rspec_core "shell_escape"
2
5
 
3
6
  module RSpec::Core
4
7
  # Notifications are value objects passed to formatters to provide them
5
8
  # with information about a particular event of interest.
6
9
  module Notifications
7
-
8
10
  # @private
9
- class NullColorizer
11
+ module NullColorizer
12
+ module_function
13
+
10
14
  def wrap(line, _code_or_symbol)
11
15
  line
12
16
  end
@@ -31,17 +35,26 @@ module RSpec::Core
31
35
  # end
32
36
  #
33
37
  # @attr example [RSpec::Core::Example] the current example
34
- ExampleNotification = Struct.new(:example) do
38
+ ExampleNotification = Struct.new(:example)
39
+ class ExampleNotification
35
40
  # @private
36
41
  def self.for(example)
37
- if example.execution_result.pending_fixed?
38
- PendingExampleFixedNotification.new(example)
39
- elsif example.execution_result.status == :failed
40
- FailedExampleNotification.new(example)
41
- else
42
- new(example)
43
- end
42
+ execution_result = example.execution_result
43
+
44
+ return SkippedExampleNotification.new(example) if execution_result.example_skipped?
45
+ return new(example) unless execution_result.status == :pending || execution_result.status == :failed
46
+
47
+ klass = if execution_result.pending_fixed?
48
+ PendingExampleFixedNotification
49
+ elsif execution_result.status == :pending
50
+ PendingExampleFailedAsExpectedNotification
51
+ else
52
+ FailedExampleNotification
53
+ end
54
+
55
+ klass.new(example)
44
56
  end
57
+
45
58
  private_class_method :new
46
59
  end
47
60
 
@@ -54,62 +67,63 @@ module RSpec::Core
54
67
  # end
55
68
  #
56
69
  class ExamplesNotification
57
-
58
70
  def initialize(reporter)
59
71
  @reporter = reporter
60
72
  end
61
73
 
62
- # @return [Array(RSpec::Core::Example)] list of examples
74
+ # @return [Array<RSpec::Core::Example>] list of examples
63
75
  def examples
64
76
  @reporter.examples
65
77
  end
66
78
 
67
- # @return [Array(RSpec::Core::Example)] list of failed examples
79
+ # @return [Array<RSpec::Core::Example>] list of failed examples
68
80
  def failed_examples
69
81
  @reporter.failed_examples
70
82
  end
71
83
 
72
- # @return [Array(RSpec::Core::Example)] list of pending examples
84
+ # @return [Array<RSpec::Core::Example>] list of pending examples
73
85
  def pending_examples
74
86
  @reporter.pending_examples
75
87
  end
76
88
 
77
- # @return [Array(Rspec::Core::Notifications::ExampleNotification]
89
+ # @return [Array<RSpec::Core::Notifications::ExampleNotification>]
78
90
  # returns examples as notifications
79
91
  def notifications
80
- @notifications ||= format(examples)
92
+ @notifications ||= format_examples(examples)
81
93
  end
82
94
 
83
- # @return [Array(Rspec::Core::Notifications::FailedExampleNotification]
95
+ # @return [Array<RSpec::Core::Notifications::FailedExampleNotification>]
84
96
  # returns failed examples as notifications
85
97
  def failure_notifications
86
- @failed_notifications ||= format(failed_examples)
98
+ @failed_notifications ||= format_examples(failed_examples)
87
99
  end
88
100
 
89
- # @return [String] The list of failed examples, fully formatted in the way that
90
- # RSpec's built-in formatters emit.
91
- def fully_formatted_failed_examples(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
101
+ # @return [Array<RSpec::Core::Notifications::SkippedExampleNotification,
102
+ # RSpec::Core::Notifications::PendingExampleFailedAsExpectedNotification>]
103
+ # returns pending examples as notifications
104
+ def pending_notifications
105
+ @pending_notifications ||= format_examples(pending_examples)
106
+ end
107
+
108
+ # @return [String] The list of failed examples, fully formatted in the way
109
+ # that RSpec's built-in formatters emit.
110
+ def fully_formatted_failed_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
92
111
  formatted = "\nFailures:\n"
93
112
 
94
113
  failure_notifications.each_with_index do |failure, index|
95
- formatted << failure.fully_formatted(index.next, colorizer)
114
+ formatted += failure.fully_formatted(index.next, colorizer)
96
115
  end
97
116
 
98
117
  formatted
99
118
  end
100
119
 
101
- # @return [String] The list of pending examples, fully formatted in the way that
102
- # RSpec's built-in formatters emit.
103
- def fully_formatted_pending_examples(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
104
- formatted = "\nPending:\n"
105
-
106
- pending_examples.each do |example|
107
- formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location)
120
+ # @return [String] The list of pending examples, fully formatted in the
121
+ # way that RSpec's built-in formatters emit.
122
+ def fully_formatted_pending_examples(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
123
+ formatted = "\nPending: (Failures listed here are expected and do not affect your suite's status)\n".dup
108
124
 
109
- formatted <<
110
- " #{colorizer.wrap(example.full_description, :pending)}\n" <<
111
- " # #{colorizer.wrap(example.execution_result.pending_message, :detail)}\n" <<
112
- " # #{colorizer.wrap(formatted_caller, :detail)}\n"
125
+ pending_notifications.each_with_index do |notification, index|
126
+ formatted << notification.fully_formatted(index.next, colorizer)
113
127
  end
114
128
 
115
129
  formatted
@@ -117,16 +131,16 @@ module RSpec::Core
117
131
 
118
132
  private
119
133
 
120
- def format(examples)
134
+ def format_examples(examples)
121
135
  examples.map do |example|
122
136
  ExampleNotification.for(example)
123
137
  end
124
138
  end
125
-
126
139
  end
127
140
 
128
141
  # The `FailedExampleNotification` extends `ExampleNotification` with
129
- # things useful for failed specs.
142
+ # things useful for examples that have failure info -- typically a
143
+ # failed or pending spec.
130
144
  #
131
145
  # @example
132
146
  # def example_failed(notification)
@@ -142,173 +156,97 @@ module RSpec::Core
142
156
 
143
157
  # @return [Exception] The example failure
144
158
  def exception
145
- example.execution_result.exception
159
+ @exception_presenter.exception
146
160
  end
147
161
 
148
162
  # @return [String] The example description
149
163
  def description
150
- example.full_description
164
+ @exception_presenter.description
151
165
  end
152
166
 
153
167
  # Returns the message generated for this failure line by line.
154
168
  #
155
- # @return [Array(String)] The example failure message
169
+ # @return [Array<String>] The example failure message
156
170
  def message_lines
157
- add_shared_group_line(failure_lines, NullColorizer)
171
+ @exception_presenter.message_lines
158
172
  end
159
173
 
160
174
  # Returns the message generated for this failure colorized line by line.
161
175
  #
162
176
  # @param colorizer [#wrap] An object to colorize the message_lines by
163
- # @return [Array(String)] The example failure message colorized
164
- def colorized_message_lines(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
165
- add_shared_group_line(failure_lines, colorizer).map do |line|
166
- colorizer.wrap line, RSpec.configuration.failure_color
167
- end
177
+ # @return [Array<String>] The example failure message colorized
178
+ def colorized_message_lines(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
179
+ @exception_presenter.colorized_message_lines(colorizer)
168
180
  end
169
181
 
170
182
  # Returns the failures formatted backtrace.
171
183
  #
172
- # @return [Array(String)] the examples backtrace lines
184
+ # @return [Array<String>] the examples backtrace lines
173
185
  def formatted_backtrace
174
- backtrace_formatter.format_backtrace(exception.backtrace, example.metadata)
186
+ @exception_presenter.formatted_backtrace
175
187
  end
176
188
 
177
189
  # Returns the failures colorized formatted backtrace.
178
190
  #
179
191
  # @param colorizer [#wrap] An object to colorize the message_lines by
180
- # @return [Array(String)] the examples colorized backtrace lines
181
- def colorized_formatted_backtrace(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
182
- formatted_backtrace.map do |backtrace_info|
183
- colorizer.wrap "# #{backtrace_info}", RSpec.configuration.detail_color
184
- end
192
+ # @return [Array<String>] the examples colorized backtrace lines
193
+ def colorized_formatted_backtrace(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
194
+ @exception_presenter.colorized_formatted_backtrace(colorizer)
185
195
  end
186
196
 
187
197
  # @return [String] The failure information fully formatted in the way that
188
198
  # RSpec's built-in formatters emit.
189
- def fully_formatted(failure_number, colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
190
- formatted = "\n #{failure_number}) #{description}\n"
191
-
192
- colorized_message_lines(colorizer).each do |line|
193
- formatted << " #{line}\n"
194
- end
195
-
196
- colorized_formatted_backtrace(colorizer).each do |line|
197
- formatted << " #{line}\n"
198
- end
199
-
200
- formatted
201
- end
202
-
203
- private
204
-
205
- def backtrace_formatter
206
- RSpec.configuration.backtrace_formatter
199
+ def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
200
+ @exception_presenter.fully_formatted(failure_number, colorizer)
207
201
  end
208
202
 
209
- def exception_class_name
210
- name = exception.class.name.to_s
211
- name ="(anonymous error class)" if name == ''
212
- name
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)
213
207
  end
214
208
 
215
- def failure_lines
216
- @failure_lines ||=
217
- begin
218
- lines = ["Failure/Error: #{read_failed_line.strip}"]
219
- lines << "#{exception_class_name}:" unless exception_class_name =~ /RSpec/
220
- exception.message.to_s.split("\n").each do |line|
221
- lines << " #{line}" if exception.message
222
- end
223
- lines
224
- end
225
- end
226
-
227
- def add_shared_group_line(lines, colorizer)
228
- unless shared_group_line == ""
229
- lines << colorizer.wrap(shared_group_line, RSpec.configuration.default_color)
230
- end
231
- lines
232
- end
233
-
234
- def shared_group
235
- @shared_group ||= group_and_parent_groups.find { |group| group.metadata[:shared_group_name] }
236
- end
237
-
238
- def shared_group_line
239
- @shared_group_line ||=
240
- if shared_group
241
- "Shared Example Group: \"#{shared_group.metadata[:shared_group_name]}\"" +
242
- " called from #{backtrace_formatter.backtrace_line(shared_group.location)}"
243
- else
244
- ""
245
- end
246
- end
209
+ private
247
210
 
248
- def group_and_parent_groups
249
- example.example_group.parent_groups + [example.example_group]
211
+ def initialize(example, exception_presenter=Formatters::ExceptionPresenter::Factory.new(example).build)
212
+ @exception_presenter = exception_presenter
213
+ super(example)
250
214
  end
215
+ end
251
216
 
252
- def read_failed_line
253
- unless matching_line = find_failed_line
254
- return "Unable to find matching line from backtrace"
255
- end
256
-
257
- file_path, line_number = matching_line.match(/(.+?):(\d+)(|:\d+)/)[1..2]
258
-
259
- if File.exist?(file_path)
260
- File.readlines(file_path)[line_number.to_i - 1] ||
261
- "Unable to find matching line in #{file_path}"
262
- else
263
- "Unable to find #{file_path} to read failed line"
264
- end
265
- rescue SecurityError
266
- "Unable to read failed line"
267
- end
217
+ # @deprecated Use {FailedExampleNotification} instead.
218
+ class PendingExampleFixedNotification < FailedExampleNotification; end
268
219
 
269
- def find_failed_line
270
- path = File.expand_path(example.file_path)
271
- exception.backtrace.detect do |line|
272
- match = line.match(/(.+?):(\d+)(|:\d+)/)
273
- match && match[1].downcase == path.downcase
274
- end
275
- end
276
- end
220
+ # @deprecated Use {FailedExampleNotification} instead.
221
+ class PendingExampleFailedAsExpectedNotification < FailedExampleNotification; end
277
222
 
278
- # The `PendingExampleFixedNotification` extends `ExampleNotification` with
279
- # things useful for specs that pass when they are expected to fail.
223
+ # The `SkippedExampleNotification` extends `ExampleNotification` with
224
+ # things useful for specs that are skipped.
280
225
  #
281
226
  # @attr [RSpec::Core::Example] example the current example
282
227
  # @see ExampleNotification
283
- class PendingExampleFixedNotification < FailedExampleNotification
228
+ class SkippedExampleNotification < ExampleNotification
284
229
  public_class_method :new
285
230
 
286
- # Returns the examples description
287
- #
288
- # @return [String] The example description
289
- def description
290
- "#{example.full_description} FIXED"
291
- end
292
-
293
- # Returns the message generated for this failure line by line.
294
- #
295
- # @return [Array(String)] The example failure message
296
- def message_lines
297
- ["Expected pending '#{example.execution_result.pending_message}' to fail. No Error was raised."]
298
- end
231
+ # @return [String] The pending detail fully formatted in the way that
232
+ # RSpec's built-in formatters emit.
233
+ def fully_formatted(pending_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
234
+ formatted_caller = RSpec.configuration.backtrace_formatter.backtrace_line(example.location)
299
235
 
300
- # Returns the message generated for this failure colorized line by line.
301
- #
302
- # @param colorizer [#wrap] An object to colorize the message_lines by
303
- # @return [Array(String)] The example failure message colorized
304
- def colorized_message_lines(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
305
- message_lines.map { |line| colorizer.wrap(line, RSpec.configuration.fixed_color) }
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("")
306
243
  end
307
244
  end
308
245
 
309
- # The `GroupNotification` represents notifications sent by the reporter which
310
- # contain information about the currently running (or soon to be) example group
311
- # It is used by formatters to access information about that group.
246
+ # The `GroupNotification` represents notifications sent by the reporter
247
+ # which contain information about the currently running (or soon to be)
248
+ # example group. It is used by formatters to access information about that
249
+ # group.
312
250
  #
313
251
  # @example
314
252
  # def example_group_started(notification)
@@ -324,11 +262,12 @@ module RSpec::Core
324
262
  MessageNotification = Struct.new(:message)
325
263
 
326
264
  # The `SeedNotification` holds the seed used to randomize examples and
327
- # wether that seed has been used or not.
265
+ # whether that seed has been used or not.
328
266
  #
329
267
  # @attr seed [Fixnum] the seed used to randomize ordering
330
- # @attr used [Boolean] wether the seed has been used or not
331
- SeedNotification = Struct.new(:seed, :used) do
268
+ # @attr used [Boolean] whether the seed has been used or not
269
+ SeedNotification = Struct.new(:seed, :used)
270
+ class SeedNotification
332
271
  # @api
333
272
  # @return [Boolean] has the seed been used?
334
273
  def seed_used?
@@ -339,7 +278,7 @@ module RSpec::Core
339
278
  # @return [String] The seed information fully formatted in the way that
340
279
  # RSpec's built-in formatters emit.
341
280
  def fully_formatted
342
- "\nRandomized with seed #{seed}\n\n"
281
+ "\nRandomized with seed #{seed}\n"
343
282
  end
344
283
  end
345
284
 
@@ -348,13 +287,18 @@ module RSpec::Core
348
287
  # of the test run.
349
288
  #
350
289
  # @attr duration [Float] the time taken (in seconds) to run the suite
351
- # @attr examples [Array(RSpec::Core::Example)] the examples run
352
- # @attr failed_examples [Array(RSpec::Core::Example)] the failed examples
353
- # @attr pending_examples [Array(RSpec::Core::Example)] the pending examples
290
+ # @attr examples [Array<RSpec::Core::Example>] the examples run
291
+ # @attr failed_examples [Array<RSpec::Core::Example>] the failed examples
292
+ # @attr pending_examples [Array<RSpec::Core::Example>] the pending examples
354
293
  # @attr load_time [Float] the number of seconds taken to boot RSpec
355
294
  # and load the spec files
356
- SummaryNotification = Struct.new(:duration, :examples, :failed_examples, :pending_examples, :load_time) do
357
-
295
+ # @attr errors_outside_of_examples_count [Integer] the number of errors that
296
+ # have occurred processing
297
+ # the spec suite
298
+ SummaryNotification = Struct.new(:duration, :examples, :failed_examples,
299
+ :pending_examples, :load_time,
300
+ :errors_outside_of_examples_count)
301
+ class SummaryNotification
358
302
  # @api
359
303
  # @return [Fixnum] the number of examples run
360
304
  def example_count
@@ -376,9 +320,16 @@ module RSpec::Core
376
320
  # @api
377
321
  # @return [String] A line summarising the result totals of the spec run.
378
322
  def totals_line
379
- summary = Formatters::Helpers.pluralize(example_count, "example")
380
- summary << ", " << Formatters::Helpers.pluralize(failure_count, "failure")
381
- 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
382
333
  summary
383
334
  end
384
335
 
@@ -391,8 +342,8 @@ module RSpec::Core
391
342
  # @param colorizer [#wrap] An object which supports wrapping text with
392
343
  # specific colors.
393
344
  # @return [String] A colorized results line.
394
- def colorized_totals_line(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
395
- if failure_count > 0
345
+ def colorized_totals_line(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
346
+ if failure_count > 0 || errors_outside_of_examples_count > 0
396
347
  colorizer.wrap(totals_line, RSpec.configuration.failure_color)
397
348
  elsif pending_count > 0
398
349
  colorizer.wrap(totals_line, RSpec.configuration.pending_color)
@@ -408,50 +359,81 @@ module RSpec::Core
408
359
  # @param colorizer [#wrap] An object which supports wrapping text with
409
360
  # specific colors.
410
361
  # @return [String] A colorized summary line.
411
- def colorized_rerun_commands(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
362
+ def colorized_rerun_commands(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
412
363
  "\nFailed examples:\n\n" +
413
364
  failed_examples.map do |example|
414
- colorizer.wrap("rspec #{example.location}", RSpec.configuration.failure_color) + " " +
415
- colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color)
365
+ colorizer.wrap("rspec #{rerun_argument_for(example)}", RSpec.configuration.failure_color) + " " +
366
+ colorizer.wrap("# #{example.full_description}", RSpec.configuration.detail_color)
416
367
  end.join("\n")
417
368
  end
418
369
 
419
- # @return [String] a formatted version of the time it took to run the suite
370
+ # @return [String] a formatted version of the time it took to run the
371
+ # suite
420
372
  def formatted_duration
421
373
  Formatters::Helpers.format_duration(duration)
422
374
  end
423
375
 
424
- # @return [String] a formatted version of the time it took to boot RSpec and
425
- # load the spec files
376
+ # @return [String] a formatted version of the time it took to boot RSpec
377
+ # and load the spec files
426
378
  def formatted_load_time
427
379
  Formatters::Helpers.format_duration(load_time)
428
380
  end
429
381
 
430
382
  # @return [String] The summary information fully formatted in the way that
431
383
  # RSpec's built-in formatters emit.
432
- def fully_formatted(colorizer = ::RSpec::Core::Formatters::ConsoleCodes)
384
+ def fully_formatted(colorizer=::RSpec::Core::Formatters::ConsoleCodes)
433
385
  formatted = "\nFinished in #{formatted_duration} " \
434
386
  "(files took #{formatted_load_time} to load)\n" \
435
387
  "#{colorized_totals_line(colorizer)}\n"
436
388
 
437
389
  unless failed_examples.empty?
438
- formatted << colorized_rerun_commands(colorizer) << "\n"
390
+ formatted += (colorized_rerun_commands(colorizer) + "\n")
439
391
  end
440
392
 
441
393
  formatted
442
394
  end
395
+
396
+ private
397
+
398
+ include RSpec::Core::ShellEscape
399
+
400
+ def rerun_argument_for(example)
401
+ location = example.location_rerun_argument
402
+ return location unless duplicate_rerun_locations.include?(location)
403
+ conditionally_quote(example.id)
404
+ end
405
+
406
+ def duplicate_rerun_locations
407
+ @duplicate_rerun_locations ||= begin
408
+ locations = RSpec.world.all_examples.map(&:location_rerun_argument)
409
+
410
+ Set.new.tap do |s|
411
+ locations.group_by { |l| l }.each do |l, ls|
412
+ s << l if ls.count > 1
413
+ end
414
+ end
415
+ end
416
+ end
443
417
  end
444
418
 
445
- # The `ProfileNotification` holds information about the results of running
446
- # a test suite when profiling is enabled. It is used by formatters to provide
419
+ # The `ProfileNotification` holds information about the results of running a
420
+ # test suite when profiling is enabled. It is used by formatters to provide
447
421
  # information at the end of the test run for profiling information.
448
422
  #
449
423
  # @attr duration [Float] the time taken (in seconds) to run the suite
450
- # @attr examples [Array(RSpec::Core::Example)] the examples run
424
+ # @attr examples [Array<RSpec::Core::Example>] the examples run
451
425
  # @attr number_of_examples [Fixnum] the number of examples to profile
452
- ProfileNotification = Struct.new(:duration, :examples, :number_of_examples) do
426
+ # @attr example_groups [Array<RSpec::Core::Profiler>] example groups run
427
+ class ProfileNotification
428
+ def initialize(duration, examples, number_of_examples, example_groups)
429
+ @duration = duration
430
+ @examples = examples
431
+ @number_of_examples = number_of_examples
432
+ @example_groups = example_groups
433
+ end
434
+ attr_reader :duration, :examples, :number_of_examples
453
435
 
454
- # @return [Array(RSpec::Core::Example)] the slowest examples
436
+ # @return [Array<RSpec::Core::Example>] the slowest examples
455
437
  def slowest_examples
456
438
  @slowest_examples ||=
457
439
  examples.sort_by do |example|
@@ -476,7 +458,7 @@ module RSpec::Core
476
458
  end
477
459
  end
478
460
 
479
- # @return [Array(RSpec::Core::Example)] the slowest example groups
461
+ # @return [Array<RSpec::Core::Example>] the slowest example groups
480
462
  def slowest_groups
481
463
  @slowest_groups ||= calculate_slowest_groups
482
464
  end
@@ -484,39 +466,30 @@ module RSpec::Core
484
466
  private
485
467
 
486
468
  def calculate_slowest_groups
487
- example_groups = {}
488
-
489
- examples.each do |example|
490
- location = example.example_group.parent_groups.last.metadata[:location]
491
-
492
- location_hash = example_groups[location] ||= Hash.new(0)
493
- location_hash[:total_time] += example.execution_result.run_time
494
- location_hash[:count] += 1
495
- unless location_hash.has_key?(:description)
496
- location_hash[:description] = example.example_group.top_level_description
497
- end
498
- end
499
-
500
469
  # stop if we've only one example group
501
- return {} if example_groups.keys.length <= 1
470
+ return {} if @example_groups.keys.length <= 1
502
471
 
503
- example_groups.each_value do |hash|
472
+ @example_groups.each_value do |hash|
504
473
  hash[:average] = hash[:total_time].to_f / hash[:count]
505
474
  end
506
475
 
507
- example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples)
476
+ groups = @example_groups.sort_by { |_, hash| -hash[:average] }.first(number_of_examples)
477
+ groups.map { |group, data| [group.location, data] }
508
478
  end
509
479
  end
510
480
 
511
481
  # The `DeprecationNotification` is issued by the reporter when a deprecated
512
- # part of RSpec is encountered. It represents information about the deprecated
513
- # call site.
482
+ # part of RSpec is encountered. It represents information about the
483
+ # deprecated call site.
514
484
  #
515
485
  # @attr message [String] A custom message about the deprecation
516
- # @attr deprecated [String] A custom message about the deprecation (alias of message)
486
+ # @attr deprecated [String] A custom message about the deprecation (alias of
487
+ # message)
517
488
  # @attr replacement [String] An optional replacement for the deprecation
518
- # @attr call_site [String] An optional call site from which the deprecation was issued
519
- DeprecationNotification = Struct.new(:deprecated, :message, :replacement, :call_site) do
489
+ # @attr call_site [String] An optional call site from which the deprecation
490
+ # was issued
491
+ DeprecationNotification = Struct.new(:deprecated, :message, :replacement, :call_site)
492
+ class DeprecationNotification
520
493
  private_class_method :new
521
494
 
522
495
  # @api
@@ -531,5 +504,18 @@ module RSpec::Core
531
504
  class NullNotification
532
505
  end
533
506
 
507
+ # `CustomNotification` is used when sending custom events to formatters /
508
+ # other registered listeners, it creates attributes based on supplied hash
509
+ # of options.
510
+ class CustomNotification < Struct
511
+ # @param options [Hash] A hash of method / value pairs to create on this notification
512
+ # @return [CustomNotification]
513
+ #
514
+ # Build a custom notification based on the supplied option key / values.
515
+ def self.for(options={})
516
+ return NullNotification if options.keys.empty?
517
+ new(*options.keys).new(*options.values)
518
+ end
519
+ end
534
520
  end
535
521
  end