rspec-support 3.0.4 → 3.12.1

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 (41) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/Changelog.md +322 -0
  4. data/{LICENSE.txt → LICENSE.md} +3 -2
  5. data/README.md +29 -6
  6. data/lib/rspec/support/caller_filter.rb +35 -16
  7. data/lib/rspec/support/comparable_version.rb +46 -0
  8. data/lib/rspec/support/differ.rb +51 -41
  9. data/lib/rspec/support/directory_maker.rb +63 -0
  10. data/lib/rspec/support/encoded_string.rb +110 -15
  11. data/lib/rspec/support/fuzzy_matcher.rb +5 -6
  12. data/lib/rspec/support/hunk_generator.rb +0 -1
  13. data/lib/rspec/support/matcher_definition.rb +42 -0
  14. data/lib/rspec/support/method_signature_verifier.rb +287 -54
  15. data/lib/rspec/support/mutex.rb +73 -0
  16. data/lib/rspec/support/object_formatter.rb +275 -0
  17. data/lib/rspec/support/recursive_const_methods.rb +76 -0
  18. data/lib/rspec/support/reentrant_mutex.rb +78 -0
  19. data/lib/rspec/support/ruby_features.rb +177 -14
  20. data/lib/rspec/support/source/location.rb +21 -0
  21. data/lib/rspec/support/source/node.rb +110 -0
  22. data/lib/rspec/support/source/token.rb +94 -0
  23. data/lib/rspec/support/source.rb +85 -0
  24. data/lib/rspec/support/spec/deprecation_helpers.rb +19 -32
  25. data/lib/rspec/support/spec/diff_helpers.rb +31 -0
  26. data/lib/rspec/support/spec/in_sub_process.rb +43 -16
  27. data/lib/rspec/support/spec/library_wide_checks.rb +150 -0
  28. data/lib/rspec/support/spec/shell_out.rb +108 -0
  29. data/lib/rspec/support/spec/stderr_splitter.rb +31 -9
  30. data/lib/rspec/support/spec/string_matcher.rb +45 -0
  31. data/lib/rspec/support/spec/with_isolated_directory.rb +13 -0
  32. data/lib/rspec/support/spec/with_isolated_stderr.rb +0 -2
  33. data/lib/rspec/support/spec.rb +46 -26
  34. data/lib/rspec/support/version.rb +1 -1
  35. data/lib/rspec/support/warnings.rb +6 -6
  36. data/lib/rspec/support/with_keywords_when_needed.rb +33 -0
  37. data/lib/rspec/support.rb +87 -3
  38. data.tar.gz.sig +0 -0
  39. metadata +70 -52
  40. metadata.gz.sig +0 -0
  41. data/lib/rspec/support/version_checker.rb +0 -53
@@ -1,5 +1,6 @@
1
1
  require 'rspec/support'
2
2
  RSpec::Support.require_rspec_support "ruby_features"
3
+ RSpec::Support.require_rspec_support "matcher_definition"
3
4
 
4
5
  module RSpec
5
6
  module Support
@@ -7,19 +8,40 @@ module RSpec
7
8
  # keyword args of a given method.
8
9
  #
9
10
  # @private
10
- class MethodSignature
11
- attr_reader :min_non_kw_args, :max_non_kw_args
11
+ class MethodSignature # rubocop:disable Metrics/ClassLength
12
+ attr_reader :min_non_kw_args, :max_non_kw_args, :optional_kw_args, :required_kw_args
12
13
 
13
14
  def initialize(method)
14
- @method = method
15
+ @method = method
16
+ @optional_kw_args = []
17
+ @required_kw_args = []
15
18
  classify_parameters
16
19
  end
17
20
 
18
21
  def non_kw_args_arity_description
19
22
  case max_non_kw_args
20
- when min_non_kw_args then min_non_kw_args.to_s
21
- when INFINITY then "#{min_non_kw_args} or more"
22
- else "#{min_non_kw_args} to #{max_non_kw_args}"
23
+ when min_non_kw_args then min_non_kw_args.to_s
24
+ when INFINITY then "#{min_non_kw_args} or more"
25
+ else "#{min_non_kw_args} to #{max_non_kw_args}"
26
+ end
27
+ end
28
+
29
+ def valid_non_kw_args?(positional_arg_count, optional_max_arg_count=positional_arg_count)
30
+ return true if positional_arg_count.nil?
31
+
32
+ min_non_kw_args <= positional_arg_count &&
33
+ optional_max_arg_count <= max_non_kw_args
34
+ end
35
+
36
+ def classify_arity(arity=@method.arity)
37
+ if arity < 0
38
+ # `~` inverts the one's complement and gives us the
39
+ # number of required args
40
+ @min_non_kw_args = ~arity
41
+ @max_non_kw_args = INFINITY
42
+ else
43
+ @min_non_kw_args = arity
44
+ @max_non_kw_args = arity
23
45
  end
24
46
  end
25
47
 
@@ -40,9 +62,7 @@ module RSpec
40
62
  parts << "required keyword args (#{@required_kw_args.map(&:inspect).join(", ")})"
41
63
  end
42
64
 
43
- if @allows_any_kw_args
44
- parts << "any additional keyword args"
45
- end
65
+ parts << "any additional keyword args" if @allows_any_kw_args
46
66
 
47
67
  parts.join(" and ")
48
68
  end
@@ -57,36 +77,52 @@ module RSpec
57
77
  given_kw_args - @allowed_kw_args
58
78
  end
59
79
 
80
+ # If the last argument is Hash, Ruby will treat only symbol keys as keyword arguments
81
+ # the rest will be grouped in another Hash and passed as positional argument.
60
82
  def has_kw_args_in?(args)
61
- return false unless Hash === args.last
83
+ Hash === args.last &&
84
+ could_contain_kw_args?(args) &&
85
+ (args.last.empty? || args.last.keys.any? { |x| x.is_a?(Symbol) })
86
+ end
87
+
88
+ # Without considering what the last arg is, could it
89
+ # contain keyword arguments?
90
+ def could_contain_kw_args?(args)
62
91
  return false if args.count <= min_non_kw_args
63
92
 
64
93
  @allows_any_kw_args || @allowed_kw_args.any?
65
94
  end
66
95
 
96
+ def arbitrary_kw_args?
97
+ @allows_any_kw_args
98
+ end
99
+
100
+ def unlimited_args?
101
+ @max_non_kw_args == INFINITY
102
+ end
103
+
67
104
  def classify_parameters
68
105
  optional_non_kw_args = @min_non_kw_args = 0
69
- @optional_kw_args, @required_kw_args = [], []
70
106
  @allows_any_kw_args = false
71
107
 
72
108
  @method.parameters.each do |(type, name)|
73
109
  case type
74
- # def foo(a:)
75
- when :keyreq then @required_kw_args << name
76
- # def foo(a: 1)
77
- when :key then @optional_kw_args << name
78
- # def foo(**kw_args)
79
- when :keyrest then @allows_any_kw_args = true
80
- # def foo(a)
81
- when :req then @min_non_kw_args += 1
82
- # def foo(a = 1)
83
- when :opt then optional_non_kw_args += 1
84
- # def foo(*a)
85
- when :rest then optional_non_kw_args = INFINITY
110
+ # def foo(a:)
111
+ when :keyreq then @required_kw_args << name
112
+ # def foo(a: 1)
113
+ when :key then @optional_kw_args << name
114
+ # def foo(**kw_args)
115
+ when :keyrest then @allows_any_kw_args = true
116
+ # def foo(a)
117
+ when :req then @min_non_kw_args += 1
118
+ # def foo(a = 1)
119
+ when :opt then optional_non_kw_args += 1
120
+ # def foo(*a)
121
+ when :rest then optional_non_kw_args = INFINITY
86
122
  end
87
123
  end
88
124
 
89
- @max_non_kw_args = @min_non_kw_args + optional_non_kw_args
125
+ @max_non_kw_args = @min_non_kw_args + optional_non_kw_args
90
126
  @allowed_kw_args = @required_kw_args + @optional_kw_args
91
127
  end
92
128
  else
@@ -94,42 +130,137 @@ module RSpec
94
130
  "arity of #{non_kw_args_arity_description}"
95
131
  end
96
132
 
97
- def missing_kw_args_from(given_kw_args)
133
+ def missing_kw_args_from(_given_kw_args)
98
134
  []
99
135
  end
100
136
 
101
- def invalid_kw_args_from(given_kw_args)
137
+ def invalid_kw_args_from(_given_kw_args)
102
138
  []
103
139
  end
104
140
 
105
- def has_kw_args_in?(args)
141
+ def has_kw_args_in?(_args)
106
142
  false
107
143
  end
108
144
 
109
- def classify_parameters
110
- arity = @method.arity
111
- if arity < 0
112
- # `~` inverts the one's complement and gives us the
113
- # number of required args
114
- @min_non_kw_args = ~arity
115
- @max_non_kw_args = INFINITY
116
- else
117
- @min_non_kw_args = arity
118
- @max_non_kw_args = arity
145
+ def could_contain_kw_args?(*)
146
+ false
147
+ end
148
+
149
+ def arbitrary_kw_args?
150
+ false
151
+ end
152
+
153
+ def unlimited_args?
154
+ false
155
+ end
156
+
157
+ alias_method :classify_parameters, :classify_arity
158
+ end
159
+
160
+ INFINITY = 1 / 0.0
161
+ end
162
+
163
+ if RSpec::Support::Ruby.jruby?
164
+ # JRuby has only partial support for UnboundMethod#parameters, so we fall back on using #arity
165
+ # https://github.com/jruby/jruby/issues/2816 and https://github.com/jruby/jruby/issues/2817
166
+ if RubyFeatures.optional_and_splat_args_supported? &&
167
+ Java::JavaLang::String.instance_method(:char_at).parameters == []
168
+
169
+ class MethodSignature < remove_const(:MethodSignature)
170
+ private
171
+
172
+ def classify_parameters
173
+ super
174
+ if (arity = @method.arity) != 0 && @method.parameters.empty?
175
+ classify_arity(arity)
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ # JRuby used to always report -1 arity for Java proxy methods.
182
+ # The workaround essentially makes use of Java's introspection to figure
183
+ # out matching methods (which could be more than one partly because Java
184
+ # supports multiple overloads, and partly because JRuby introduces
185
+ # aliases to make method names look more Rubyesque). If there is only a
186
+ # single match, we can use that methods arity directly instead of the
187
+ # default -1 arity.
188
+ #
189
+ # This workaround only works for Java proxy methods, and in order to
190
+ # support regular methods and blocks, we need to be careful about calling
191
+ # owner and java_class as they might not be available
192
+ if Java::JavaLang::String.instance_method(:char_at).arity == -1
193
+ class MethodSignature < remove_const(:MethodSignature)
194
+ private
195
+
196
+ def classify_parameters
197
+ super
198
+ return unless @method.arity == -1
199
+ return unless @method.respond_to?(:owner)
200
+ return unless @method.owner.respond_to?(:java_class)
201
+ java_instance_methods = @method.owner.java_class.java_instance_methods
202
+ compatible_overloads = java_instance_methods.select do |java_method|
203
+ @method == @method.owner.instance_method(java_method.name)
204
+ end
205
+ if compatible_overloads.size == 1
206
+ classify_arity(compatible_overloads.first.arity)
207
+ end
119
208
  end
120
209
  end
121
210
  end
211
+ end
212
+
213
+ # Encapsulates expectations about the number of arguments and
214
+ # allowed/required keyword args of a given method.
215
+ #
216
+ # @api private
217
+ class MethodSignatureExpectation
218
+ def initialize
219
+ @min_count = nil
220
+ @max_count = nil
221
+ @keywords = []
222
+
223
+ @expect_unlimited_arguments = false
224
+ @expect_arbitrary_keywords = false
225
+ end
226
+
227
+ attr_reader :min_count, :max_count, :keywords
228
+
229
+ attr_accessor :expect_unlimited_arguments, :expect_arbitrary_keywords
230
+
231
+ def max_count=(number)
232
+ raise ArgumentError, 'must be a non-negative integer or nil' \
233
+ unless number.nil? || (number.is_a?(Integer) && number >= 0)
234
+
235
+ @max_count = number
236
+ end
237
+
238
+ def min_count=(number)
239
+ raise ArgumentError, 'must be a non-negative integer or nil' \
240
+ unless number.nil? || (number.is_a?(Integer) && number >= 0)
241
+
242
+ @min_count = number
243
+ end
244
+
245
+ def empty?
246
+ @min_count.nil? &&
247
+ @keywords.to_a.empty? &&
248
+ !@expect_arbitrary_keywords &&
249
+ !@expect_unlimited_arguments
250
+ end
122
251
 
123
- INFINITY = 1/0.0
252
+ def keywords=(values)
253
+ @keywords = values.to_a || []
254
+ end
124
255
  end
125
256
 
126
257
  # Deals with the slightly different semantics of block arguments.
127
258
  # For methods, arguments are required unless a default value is provided.
128
259
  # For blocks, arguments are optional, even if no default value is provided.
129
260
  #
130
- # However, we want to treat block args as required since you virtually always
131
- # want to pass a value for each received argument and our `and_yield` has
132
- # treated block args as required for many years.
261
+ # However, we want to treat block args as required since you virtually
262
+ # always want to pass a value for each received argument and our
263
+ # `and_yield` has treated block args as required for many years.
133
264
  #
134
265
  # @api private
135
266
  class BlockSignature < MethodSignature
@@ -141,22 +272,53 @@ module RSpec
141
272
  end
142
273
  end
143
274
 
144
- # Figures out wheter a given method can accept various arguments.
145
- # Surprisingly non-trivial.
275
+ # Abstract base class for signature verifiers.
146
276
  #
147
- # @private
277
+ # @api private
148
278
  class MethodSignatureVerifier
149
- attr_reader :non_kw_args, :kw_args
279
+ attr_reader :non_kw_args, :kw_args, :min_non_kw_args, :max_non_kw_args
150
280
 
151
- def initialize(signature, args)
281
+ def initialize(signature, args=[])
152
282
  @signature = signature
153
283
  @non_kw_args, @kw_args = split_args(*args)
284
+ @min_non_kw_args = @max_non_kw_args = @non_kw_args
285
+ @arbitrary_kw_args = @unlimited_args = false
286
+ end
287
+
288
+ def with_expectation(expectation) # rubocop:disable Metrics/MethodLength
289
+ return self unless MethodSignatureExpectation === expectation
290
+
291
+ if expectation.empty?
292
+ @min_non_kw_args = @max_non_kw_args = @non_kw_args = nil
293
+ @kw_args = []
294
+ else
295
+ @min_non_kw_args = @non_kw_args = expectation.min_count || 0
296
+ @max_non_kw_args = expectation.max_count || @min_non_kw_args
297
+
298
+ if RubyFeatures.optional_and_splat_args_supported?
299
+ @unlimited_args = expectation.expect_unlimited_arguments
300
+ else
301
+ @unlimited_args = false
302
+ end
303
+
304
+ if RubyFeatures.kw_args_supported?
305
+ @kw_args = expectation.keywords
306
+ @arbitrary_kw_args = expectation.expect_arbitrary_keywords
307
+ else
308
+ @kw_args = []
309
+ @arbitrary_kw_args = false
310
+ end
311
+ end
312
+
313
+ self
154
314
  end
155
315
 
156
316
  def valid?
157
- missing_kw_args.empty? &&
317
+ missing_kw_args.empty? &&
158
318
  invalid_kw_args.empty? &&
159
- valid_non_kw_args?
319
+ valid_non_kw_args? &&
320
+ arbitrary_kw_args? &&
321
+ unlimited_args?
160
322
  end
161
323
 
162
324
  def error_message
@@ -171,7 +333,7 @@ module RSpec
171
333
  elsif !valid_non_kw_args?
172
334
  "Wrong number of arguments. Expected %s, got %s." % [
173
335
  @signature.non_kw_args_arity_description,
174
- non_kw_args.length
336
+ non_kw_args
175
337
  ]
176
338
  end
177
339
  end
@@ -179,8 +341,7 @@ module RSpec
179
341
  private
180
342
 
181
343
  def valid_non_kw_args?
182
- actual = non_kw_args.length
183
- @signature.min_non_kw_args <= actual && actual <= @signature.max_non_kw_args
344
+ @signature.valid_non_kw_args?(min_non_kw_args, max_non_kw_args)
184
345
  end
185
346
 
186
347
  def missing_kw_args
@@ -191,14 +352,86 @@ module RSpec
191
352
  @signature.invalid_kw_args_from(kw_args)
192
353
  end
193
354
 
355
+ def arbitrary_kw_args?
356
+ !@arbitrary_kw_args || @signature.arbitrary_kw_args?
357
+ end
358
+
359
+ def unlimited_args?
360
+ !@unlimited_args || @signature.unlimited_args?
361
+ end
362
+
194
363
  def split_args(*args)
195
364
  kw_args = if @signature.has_kw_args_in?(args)
196
- args.pop.keys
197
- else
365
+ last = args.pop
366
+ non_kw_args = last.reject { |k, _| k.is_a?(Symbol) }
367
+ if non_kw_args.empty?
368
+ last.keys
369
+ else
370
+ args << non_kw_args
371
+ last.select { |k, _| k.is_a?(Symbol) }.keys
372
+ end
373
+ else
374
+ []
375
+ end
376
+
377
+ [args.length, kw_args]
378
+ end
379
+ end
380
+
381
+ # Figures out wether a given method can accept various arguments.
382
+ # Surprisingly non-trivial.
383
+ #
384
+ # @private
385
+ StrictSignatureVerifier = MethodSignatureVerifier
386
+
387
+ # Allows matchers to be used instead of providing keyword arguments. In
388
+ # practice, when this happens only the arity of the method is verified.
389
+ #
390
+ # @private
391
+ class LooseSignatureVerifier < MethodSignatureVerifier
392
+ private
393
+
394
+ def split_args(*args)
395
+ if RSpec::Support.is_a_matcher?(args.last) && @signature.could_contain_kw_args?(args)
396
+ args.pop
397
+ @signature = SignatureWithKeywordArgumentsMatcher.new(@signature)
398
+ end
399
+
400
+ super(*args)
401
+ end
402
+
403
+ # If a matcher is used in a signature in place of keyword arguments, all
404
+ # keyword argument validation needs to be skipped since the matcher is
405
+ # opaque.
406
+ #
407
+ # Instead, keyword arguments will be validated when the method is called
408
+ # and they are actually known.
409
+ #
410
+ # @private
411
+ class SignatureWithKeywordArgumentsMatcher
412
+ def initialize(signature)
413
+ @signature = signature
414
+ end
415
+
416
+ def missing_kw_args_from(_kw_args)
417
+ []
418
+ end
419
+
420
+ def invalid_kw_args_from(_kw_args)
198
421
  []
199
422
  end
200
423
 
201
- [args, kw_args]
424
+ def non_kw_args_arity_description
425
+ @signature.non_kw_args_arity_description
426
+ end
427
+
428
+ def valid_non_kw_args?(*args)
429
+ @signature.valid_non_kw_args?(*args)
430
+ end
431
+
432
+ def has_kw_args_in?(args)
433
+ @signature.has_kw_args_in?(args)
434
+ end
202
435
  end
203
436
  end
204
437
  end
@@ -0,0 +1,73 @@
1
+ module RSpec
2
+ module Support
3
+ # On 1.8.7, it's in the stdlib.
4
+ # We don't want to load the stdlib, b/c this is a test tool, and can affect
5
+ # the test environment, causing tests to pass where they should fail.
6
+ #
7
+ # So we're transcribing/modifying it from
8
+ # https://github.com/ruby/ruby/blob/v1_8_7_374/lib/thread.rb#L56
9
+ # Some methods we don't need are deleted. Anything I don't
10
+ # understand (there's quite a bit, actually) is left in.
11
+ #
12
+ # Some formatting changes are made to appease the robot overlord:
13
+ # https://travis-ci.org/rspec/rspec-core/jobs/54410874
14
+ # @private
15
+ class Mutex
16
+ def initialize
17
+ @waiting = []
18
+ @locked = false
19
+ @waiting.taint
20
+ taint
21
+ end
22
+
23
+ # @private
24
+ def lock
25
+ while Thread.critical = true && @locked
26
+ @waiting.push Thread.current
27
+ Thread.stop
28
+ end
29
+ @locked = true
30
+ Thread.critical = false
31
+ self
32
+ end
33
+
34
+ # @private
35
+ def unlock
36
+ return unless @locked
37
+ Thread.critical = true
38
+ @locked = false
39
+ wakeup_and_run_waiting_thread
40
+ self
41
+ end
42
+
43
+ # @private
44
+ def synchronize
45
+ lock
46
+ begin
47
+ yield
48
+ ensure
49
+ unlock
50
+ end
51
+ end
52
+
53
+ private
54
+
55
+ def wakeup_and_run_waiting_thread
56
+ begin
57
+ t = @waiting.shift
58
+ t.wakeup if t
59
+ rescue ThreadError
60
+ retry
61
+ end
62
+ Thread.critical = false
63
+ begin
64
+ t.run if t
65
+ rescue ThreadError
66
+ :noop
67
+ end
68
+ end
69
+
70
+ # Avoid warnings for library wide checks spec
71
+ end unless defined?(::RSpec::Support::Mutex) || defined?(::Mutex)
72
+ end
73
+ end