rspec-core 3.2.3 → 3.3.0

Sign up to get free protection for your applications and to get access to all the features.
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