rspec-core 3.2.3 → 3.3.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 (55) hide show
  1. checksums.yaml +4 -4
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +0 -0
  4. data/Changelog.md +75 -0
  5. data/README.md +137 -20
  6. data/lib/rspec/autorun.rb +1 -0
  7. data/lib/rspec/core.rb +8 -16
  8. data/lib/rspec/core/backtrace_formatter.rb +1 -3
  9. data/lib/rspec/core/bisect/coordinator.rb +66 -0
  10. data/lib/rspec/core/bisect/example_minimizer.rb +130 -0
  11. data/lib/rspec/core/bisect/runner.rb +139 -0
  12. data/lib/rspec/core/bisect/server.rb +61 -0
  13. data/lib/rspec/core/bisect/subset_enumerator.rb +39 -0
  14. data/lib/rspec/core/configuration.rb +134 -5
  15. data/lib/rspec/core/configuration_options.rb +21 -10
  16. data/lib/rspec/core/example.rb +84 -50
  17. data/lib/rspec/core/example_group.rb +46 -18
  18. data/lib/rspec/core/example_status_persister.rb +235 -0
  19. data/lib/rspec/core/filter_manager.rb +43 -28
  20. data/lib/rspec/core/flat_map.rb +2 -0
  21. data/lib/rspec/core/formatters.rb +30 -20
  22. data/lib/rspec/core/formatters/base_text_formatter.rb +1 -0
  23. data/lib/rspec/core/formatters/bisect_formatter.rb +68 -0
  24. data/lib/rspec/core/formatters/bisect_progress_formatter.rb +115 -0
  25. data/lib/rspec/core/formatters/deprecation_formatter.rb +0 -1
  26. data/lib/rspec/core/formatters/documentation_formatter.rb +0 -4
  27. data/lib/rspec/core/formatters/exception_presenter.rb +389 -0
  28. data/lib/rspec/core/formatters/fallback_message_formatter.rb +28 -0
  29. data/lib/rspec/core/formatters/helpers.rb +22 -2
  30. data/lib/rspec/core/formatters/html_formatter.rb +1 -4
  31. data/lib/rspec/core/formatters/html_printer.rb +2 -6
  32. data/lib/rspec/core/formatters/json_formatter.rb +6 -4
  33. data/lib/rspec/core/formatters/snippet_extractor.rb +12 -7
  34. data/lib/rspec/core/hooks.rb +8 -2
  35. data/lib/rspec/core/memoized_helpers.rb +77 -17
  36. data/lib/rspec/core/metadata.rb +24 -10
  37. data/lib/rspec/core/metadata_filter.rb +16 -3
  38. data/lib/rspec/core/mutex.rb +63 -0
  39. data/lib/rspec/core/notifications.rb +84 -189
  40. data/lib/rspec/core/option_parser.rb +105 -32
  41. data/lib/rspec/core/ordering.rb +28 -25
  42. data/lib/rspec/core/profiler.rb +32 -0
  43. data/lib/rspec/core/project_initializer/spec/spec_helper.rb +6 -1
  44. data/lib/rspec/core/rake_task.rb +6 -20
  45. data/lib/rspec/core/reentrant_mutex.rb +52 -0
  46. data/lib/rspec/core/reporter.rb +65 -17
  47. data/lib/rspec/core/runner.rb +38 -14
  48. data/lib/rspec/core/set.rb +49 -0
  49. data/lib/rspec/core/shared_example_group.rb +3 -1
  50. data/lib/rspec/core/shell_escape.rb +49 -0
  51. data/lib/rspec/core/version.rb +1 -1
  52. data/lib/rspec/core/world.rb +31 -20
  53. metadata +35 -7
  54. metadata.gz.sig +0 -0
  55. data/lib/rspec/core/backport_random.rb +0 -339
@@ -0,0 +1,28 @@
1
+ module RSpec
2
+ module Core
3
+ module Formatters
4
+ # @api private
5
+ # Formatter for providing message output as a fallback when no other
6
+ # profiler implements #message
7
+ class FallbackMessageFormatter
8
+ Formatters.register self, :message
9
+
10
+ def initialize(output)
11
+ @output = output
12
+ end
13
+
14
+ # @private
15
+ attr_reader :output
16
+
17
+ # @api public
18
+ #
19
+ # Used by the reporter to send messages to the output stream.
20
+ #
21
+ # @param notification [MessageNotification] containing message
22
+ def message(notification)
23
+ output.puts notification.message
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
@@ -1,3 +1,5 @@
1
+ RSpec::Support.require_rspec_core "shell_escape"
2
+
1
3
  module RSpec
2
4
  module Core
3
5
  module Formatters
@@ -65,11 +67,13 @@ module RSpec
65
67
  #
66
68
  # Remove trailing zeros from a string.
67
69
  #
70
+ # Only remove trailing zeros after a decimal place.
71
+ # see: http://rubular.com/r/ojtTydOgpn
72
+ #
68
73
  # @param string [String] string with trailing zeros
69
74
  # @return [String] string with trailing zeros removed
70
75
  def self.strip_trailing_zeroes(string)
71
- stripped = string.sub(/[^1-9]+$/, '')
72
- stripped.empty? ? "0" : stripped
76
+ string.sub(/(?:(\..*[^0])0+|\.0+)$/, '\1')
73
77
  end
74
78
  private_class_method :strip_trailing_zeroes
75
79
 
@@ -83,6 +87,22 @@ module RSpec
83
87
  def self.pluralize(count, string)
84
88
  "#{count} #{string}#{'s' unless count.to_f == 1}"
85
89
  end
90
+
91
+ # @api private
92
+ # Given a list of example ids, organizes them into a compact, ordered list.
93
+ def self.organize_ids(ids)
94
+ grouped = ids.inject(Hash.new { |h, k| h[k] = [] }) do |hash, id|
95
+ file, id = id.split(Configuration::ON_SQUARE_BRACKETS)
96
+ hash[file] << id
97
+ hash
98
+ end
99
+
100
+ grouped.sort_by(&:first).map do |file, grouped_ids|
101
+ grouped_ids = grouped_ids.sort_by { |id| id.split(':').map(&:to_i) }
102
+ id = Metadata.id_from(:rerun_file_path => file, :scoped_id => grouped_ids.join(','))
103
+ ShellEscape.conditionally_quote(id)
104
+ end
105
+ end
86
106
  end
87
107
  end
88
108
  end
@@ -74,8 +74,6 @@ module RSpec
74
74
  :message => exception.message,
75
75
  :backtrace => failure.formatted_backtrace.join("\n")
76
76
  }
77
- else
78
- false
79
77
  end
80
78
  extra = extra_failure_content(failure)
81
79
 
@@ -85,8 +83,7 @@ module RSpec
85
83
  example.execution_result.run_time,
86
84
  @failed_examples.size,
87
85
  exception_details,
88
- (extra == "") ? false : extra,
89
- true
86
+ (extra == "") ? false : extra
90
87
  )
91
88
  @printer.flush
92
89
  end
@@ -35,7 +35,7 @@ module RSpec
35
35
 
36
36
  # rubocop:disable Style/ParameterLists
37
37
  def print_example_failed(pending_fixed, description, run_time, failure_id,
38
- exception, extra_content, escape_backtrace=false)
38
+ exception, extra_content)
39
39
  # rubocop:enable Style/ParameterLists
40
40
  formatted_run_time = "%.5f" % run_time
41
41
 
@@ -45,11 +45,7 @@ module RSpec
45
45
  @output.puts " <div class=\"failure\" id=\"failure_#{failure_id}\">"
46
46
  if exception
47
47
  @output.puts " <div class=\"message\"><pre>#{h(exception[:message])}</pre></div>"
48
- if escape_backtrace
49
- @output.puts " <div class=\"backtrace\"><pre>#{h exception[:backtrace]}</pre></div>"
50
- else
51
- @output.puts " <div class=\"backtrace\"><pre>#{exception[:backtrace]}</pre></div>"
52
- end
48
+ @output.puts " <div class=\"backtrace\"><pre>#{h exception[:backtrace]}</pre></div>"
53
49
  end
54
50
  @output.puts extra_content if extra_content
55
51
  @output.puts " </div>"
@@ -12,7 +12,9 @@ module RSpec
12
12
 
13
13
  def initialize(output)
14
14
  super
15
- @output_hash = {}
15
+ @output_hash = {
16
+ :version => RSpec::Core::Version::STRING
17
+ }
16
18
  end
17
19
 
18
20
  def message(notification)
@@ -58,8 +60,7 @@ module RSpec
58
60
  # @api private
59
61
  def dump_profile_slowest_examples(profile)
60
62
  @output_hash[:profile] = {}
61
- sorted_examples = profile.slowest_examples
62
- @output_hash[:profile][:examples] = sorted_examples.map do |example|
63
+ @output_hash[:profile][:examples] = profile.slowest_examples.map do |example|
63
64
  format_example(example).tap do |hash|
64
65
  hash[:run_time] = example.execution_result.run_time
65
66
  end
@@ -85,7 +86,8 @@ module RSpec
85
86
  :status => example.execution_result.status.to_s,
86
87
  :file_path => example.metadata[:file_path],
87
88
  :line_number => example.metadata[:line_number],
88
- :run_time => example.execution_result.run_time
89
+ :run_time => example.execution_result.run_time,
90
+ :pending_message => example.execution_result.pending_message,
89
91
  }
90
92
  end
91
93
  end
@@ -7,26 +7,30 @@ module RSpec
7
7
  # and applies synax highlighting and line numbers using html.
8
8
  class SnippetExtractor
9
9
  # @private
10
- class NullConverter
11
- def convert(code)
10
+ module NullConverter
11
+ def self.convert(code)
12
12
  %Q(#{code}\n<span class="comment"># Install the coderay gem to get syntax highlighting</span>)
13
13
  end
14
14
  end
15
15
 
16
16
  # @private
17
- class CoderayConverter
18
- def convert(code)
17
+ module CoderayConverter
18
+ def self.convert(code)
19
19
  CodeRay.scan(code, :ruby).html(:line_numbers => false)
20
20
  end
21
21
  end
22
22
 
23
+ # rubocop:disable Style/ClassVars
24
+ @@converter = NullConverter
23
25
  begin
24
26
  require 'coderay'
25
- # rubocop:disable Style/ClassVars
26
- @@converter = CoderayConverter.new
27
+ @@converter = CoderayConverter
28
+ # rubocop:disable Lint/HandleExceptions
27
29
  rescue LoadError
28
- @@converter = NullConverter.new
30
+ # it'll fall back to the NullConverter assigned above
31
+ # rubocop:enable Lint/HandleExceptions
29
32
  end
33
+
30
34
  # rubocop:enable Style/ClassVars
31
35
 
32
36
  # @api private
@@ -43,6 +47,7 @@ module RSpec
43
47
  highlighted = @@converter.convert(raw_code)
44
48
  post_process(highlighted, line)
45
49
  end
50
+ # rubocop:enable Style/ClassVars
46
51
 
47
52
  # @api private
48
53
  #
@@ -361,7 +361,9 @@ module RSpec
361
361
  # @private
362
362
  class AfterHook < Hook
363
363
  def run(example)
364
- example.instance_exec_with_rescue("in an after hook", &block)
364
+ example.instance_exec(example, &block)
365
+ rescue Exception => ex
366
+ example.set_exception(ex)
365
367
  end
366
368
  end
367
369
 
@@ -394,10 +396,12 @@ EOS
394
396
  def hook_description
395
397
  "around hook at #{Metadata.relative_path(block.source_location.join(':'))}"
396
398
  end
397
- else
399
+ else # for 1.8.7
400
+ # :nocov:
398
401
  def hook_description
399
402
  "around hook"
400
403
  end
404
+ # :nocov:
401
405
  end
402
406
  end
403
407
 
@@ -622,9 +626,11 @@ EOS
622
626
  @owner.parent_groups
623
627
  end
624
628
  else # Ruby < 2.1 (see https://bugs.ruby-lang.org/issues/8035)
629
+ # :nocov:
625
630
  def owner_parent_groups
626
631
  @owner_parent_groups ||= [@owner] + @owner.parent_groups
627
632
  end
633
+ # :nocov:
628
634
  end
629
635
  end
630
636
  end
@@ -1,3 +1,5 @@
1
+ require 'rspec/core/reentrant_mutex'
2
+
1
3
  module RSpec
2
4
  module Core
3
5
  # This module is included in {ExampleGroup}, making the methods
@@ -53,11 +55,9 @@ module RSpec
53
55
  # @see #should_not
54
56
  # @see #is_expected
55
57
  def subject
56
- __memoized.fetch(:subject) do
57
- __memoized[:subject] = begin
58
- described = described_class || self.class.metadata.fetch(:description_args).first
59
- Class === described ? described.new : described
60
- end
58
+ __memoized.fetch_or_store(:subject) do
59
+ described = described_class || self.class.metadata.fetch(:description_args).first
60
+ Class === described ? described.new : described
61
61
  end
62
62
  end
63
63
 
@@ -119,33 +119,85 @@ module RSpec
119
119
  expect(subject)
120
120
  end
121
121
 
122
+ # @private
123
+ # should just be placed in private section,
124
+ # but Ruby issues warnings on private attributes.
125
+ # and expanding it to the equivalent method upsets Rubocop,
126
+ # b/c it should obviously be a reader
127
+ attr_reader :__memoized
128
+ private :__memoized
129
+
122
130
  private
123
131
 
124
132
  # @private
125
- def __memoized
126
- @__memoized ||= {}
133
+ def initialize(*)
134
+ __init_memoized
135
+ super
136
+ end
137
+
138
+ # @private
139
+ def __init_memoized
140
+ @__memoized = if RSpec.configuration.threadsafe?
141
+ ThreadsafeMemoized.new
142
+ else
143
+ NonThreadSafeMemoized.new
144
+ end
145
+ end
146
+
147
+ # @private
148
+ class ThreadsafeMemoized
149
+ def initialize
150
+ @memoized = {}
151
+ @mutex = ReentrantMutex.new
152
+ end
153
+
154
+ def fetch_or_store(key)
155
+ @memoized.fetch(key) do # only first access pays for synchronization
156
+ @mutex.synchronize do
157
+ @memoized.fetch(key) { @memoized[key] = yield }
158
+ end
159
+ end
160
+ end
161
+ end
162
+
163
+ # @private
164
+ class NonThreadSafeMemoized
165
+ def initialize
166
+ @memoized = {}
167
+ end
168
+
169
+ def fetch_or_store(key)
170
+ @memoized.fetch(key) { @memoized[key] = yield }
171
+ end
127
172
  end
128
173
 
129
174
  # Used internally to customize the behavior of the
130
175
  # memoized hash when used in a `before(:context)` hook.
131
176
  #
132
177
  # @private
133
- class ContextHookMemoizedHash
178
+ class ContextHookMemoized
134
179
  def self.isolate_for_context_hook(example_group_instance)
135
- hash = self
180
+ exploding_memoized = self
136
181
 
137
182
  example_group_instance.instance_exec do
138
- @__memoized = hash
183
+ @__memoized = exploding_memoized
139
184
 
140
185
  begin
141
186
  yield
142
187
  ensure
143
- @__memoized = nil
188
+ # This is doing a reset instead of just isolating for context hook.
189
+ # Really, this should set the old @__memoized back into place.
190
+ #
191
+ # Caller is the before and after context hooks
192
+ # which are both called from self.run
193
+ # I didn't look at why it made tests fail, maybe an object was getting reused in RSpec tests,
194
+ # if so, then that probably already works, and its the tests that are wrong.
195
+ __init_memoized
144
196
  end
145
197
  end
146
198
  end
147
199
 
148
- def self.fetch(key, &_block)
200
+ def self.fetch_or_store(key, &_block)
149
201
  description = if key == :subject
150
202
  "subject"
151
203
  else
@@ -206,9 +258,10 @@ EOS
206
258
  # maybe 3 declarations) in any given example group, but that can
207
259
  # quickly degrade with overuse. YMMV.
208
260
  #
209
- # @note `let` uses an `||=` conditional that has the potential to
210
- # behave in surprising ways in examples that spawn separate threads,
211
- # though we have yet to see this in practice. You've been warned.
261
+ # @note `let` can be configured to be threadsafe or not.
262
+ # If it is threadsafe, it will take longer to access the value.
263
+ # If it is not threadsafe, it may behave in surprising ways in examples
264
+ # that spawn separate threads. Specify this on `RSpec.configure`
212
265
  #
213
266
  # @note Because `let` is designed to create state that is reset between
214
267
  # each example, and `before(:context)` is designed to setup state that
@@ -237,9 +290,9 @@ EOS
237
290
  # Apply the memoization. The method has been defined in an ancestor
238
291
  # module so we can use `super` here to get the value.
239
292
  if block.arity == 1
240
- define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = super(RSpec.current_example, &nil) } }
293
+ define_method(name) { __memoized.fetch_or_store(name) { super(RSpec.current_example, &nil) } }
241
294
  else
242
- define_method(name) { __memoized.fetch(name) { |k| __memoized[k] = super(&nil) } }
295
+ define_method(name) { __memoized.fetch_or_store(name) { super(&nil) } }
243
296
  end
244
297
  end
245
298
 
@@ -312,6 +365,11 @@ EOS
312
365
  #
313
366
  # When given a `name`, calling `super` in the block is not supported.
314
367
  #
368
+ # @note `subject` can be configured to be threadsafe or not.
369
+ # If it is threadsafe, it will take longer to access the value.
370
+ # If it is not threadsafe, it may behave in surprising ways in examples
371
+ # that spawn separate threads. Specify this on `RSpec.configure`
372
+ #
315
373
  # @param name [String,Symbol] used to define an accessor with an
316
374
  # intention revealing name
317
375
  # @param block defines the value to be returned by `subject` in examples
@@ -443,6 +501,7 @@ EOS
443
501
  # Gets the named constant or yields.
444
502
  # On 1.8, const_defined? / const_get do not take into
445
503
  # account the inheritance hierarchy.
504
+ # :nocov:
446
505
  def self.get_constant_or_yield(example_group, name)
447
506
  if example_group.const_defined?(name)
448
507
  example_group.const_get(name)
@@ -450,6 +509,7 @@ EOS
450
509
  yield
451
510
  end
452
511
  end
512
+ # :nocov:
453
513
  else
454
514
  # @private
455
515
  #
@@ -100,9 +100,8 @@ module RSpec
100
100
  end
101
101
 
102
102
  # @private
103
- def self.backtrace_from(block)
104
- return caller unless block.respond_to?(:source_location)
105
- [block.source_location.join(':')]
103
+ def self.id_from(metadata)
104
+ "#{metadata[:rerun_file_path]}[#{metadata[:scoped_id]}]"
106
105
  end
107
106
 
108
107
  # @private
@@ -111,9 +110,10 @@ module RSpec
111
110
  class HashPopulator
112
111
  attr_reader :metadata, :user_metadata, :description_args, :block
113
112
 
114
- def initialize(metadata, user_metadata, description_args, block)
113
+ def initialize(metadata, user_metadata, index_provider, description_args, block)
115
114
  @metadata = metadata
116
115
  @user_metadata = user_metadata
116
+ @index_provider = index_provider
117
117
  @description_args = description_args
118
118
  @block = block
119
119
  end
@@ -151,6 +151,8 @@ module RSpec
151
151
  metadata[:line_number] = line_number.to_i
152
152
  metadata[:location] = "#{relative_file_path}:#{line_number}"
153
153
  metadata[:absolute_file_path] = File.expand_path(relative_file_path)
154
+ metadata[:rerun_file_path] ||= relative_file_path
155
+ metadata[:scoped_id] = build_scoped_id_for(relative_file_path)
154
156
  end
155
157
 
156
158
  def file_path_and_line_number_from(backtrace)
@@ -173,6 +175,12 @@ module RSpec
173
175
  (parent_description.to_s + separator) << my_description.to_s
174
176
  end
175
177
 
178
+ def build_scoped_id_for(file_path)
179
+ index = @index_provider.call(file_path).to_s
180
+ parent_scoped_id = metadata.fetch(:scoped_id) { return index }
181
+ "#{parent_scoped_id}:#{index}"
182
+ end
183
+
176
184
  def ensure_valid_user_keys
177
185
  RESERVED_KEYS.each do |key|
178
186
  next unless user_metadata.key?(key)
@@ -196,7 +204,7 @@ module RSpec
196
204
 
197
205
  # @private
198
206
  class ExampleHash < HashPopulator
199
- def self.create(group_metadata, user_metadata, description, block)
207
+ def self.create(group_metadata, user_metadata, index_provider, description, block)
200
208
  example_metadata = group_metadata.dup
201
209
  group_metadata = Hash.new(&ExampleGroupHash.backwards_compatibility_default_proc do |hash|
202
210
  hash[:parent_example_group]
@@ -208,7 +216,7 @@ module RSpec
208
216
  example_metadata.delete(:parent_example_group)
209
217
 
210
218
  description_args = description.nil? ? [] : [description]
211
- hash = new(example_metadata, user_metadata, description_args, block)
219
+ hash = new(example_metadata, user_metadata, index_provider, description_args, block)
212
220
  hash.populate
213
221
  hash.metadata
214
222
  end
@@ -229,7 +237,7 @@ module RSpec
229
237
 
230
238
  # @private
231
239
  class ExampleGroupHash < HashPopulator
232
- def self.create(parent_group_metadata, user_metadata, *args, &block)
240
+ def self.create(parent_group_metadata, user_metadata, example_group_index, *args, &block)
233
241
  group_metadata = hash_with_backwards_compatibility_default_proc
234
242
 
235
243
  if parent_group_metadata
@@ -237,7 +245,7 @@ module RSpec
237
245
  group_metadata[:parent_example_group] = parent_group_metadata
238
246
  end
239
247
 
240
- hash = new(group_metadata, user_metadata, args, block)
248
+ hash = new(group_metadata, user_metadata, example_group_index, args, block)
241
249
  hash.populate
242
250
  hash.metadata
243
251
  end
@@ -262,7 +270,7 @@ module RSpec
262
270
  # that take a metadata hash, and MetadataFilter sets this thread
263
271
  # local to silence the warning here since it would be so
264
272
  # confusing.
265
- unless RSpec.thread_local_metadata[:silence_metadata_example_group_deprecations]
273
+ unless RSpec::Support.thread_local_data[:silence_metadata_example_group_deprecations]
266
274
  RSpec.deprecate("The `:example_group` key in an example group's metadata hash",
267
275
  :replacement => "the example group's hash directly for the " \
268
276
  "computed keys and `:parent_example_group` to access the parent " \
@@ -308,15 +316,21 @@ module RSpec
308
316
  # @private
309
317
  RESERVED_KEYS = [
310
318
  :description,
319
+ :description_args,
320
+ :described_class,
311
321
  :example_group,
312
322
  :parent_example_group,
313
323
  :execution_result,
324
+ :last_run_status,
314
325
  :file_path,
315
326
  :absolute_file_path,
327
+ :rerun_file_path,
316
328
  :full_description,
317
329
  :line_number,
318
330
  :location,
319
- :block
331
+ :scoped_id,
332
+ :block,
333
+ :shared_group_inclusion_backtrace
320
334
  ]
321
335
  end
322
336