rspec-expectations 3.0.4 → 3.12.3
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.
- checksums.yaml +5 -5
- checksums.yaml.gz.sig +0 -0
- data/.document +1 -1
- data/.yardopts +1 -1
- data/Changelog.md +530 -5
- data/{License.txt → LICENSE.md} +5 -4
- data/README.md +73 -31
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +96 -1
- data/lib/rspec/expectations/expectation_target.rb +82 -38
- data/lib/rspec/expectations/fail_with.rb +11 -6
- data/lib/rspec/expectations/failure_aggregator.rb +229 -0
- data/lib/rspec/expectations/handler.rb +36 -15
- data/lib/rspec/expectations/minitest_integration.rb +43 -2
- data/lib/rspec/expectations/syntax.rb +5 -5
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/expectations.rb +15 -1
- data/lib/rspec/matchers/aliased_matcher.rb +79 -4
- data/lib/rspec/matchers/built_in/all.rb +11 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +111 -28
- data/lib/rspec/matchers/built_in/be.rb +28 -114
- data/lib/rspec/matchers/built_in/be_between.rb +1 -1
- data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
- data/lib/rspec/matchers/built_in/be_within.rb +5 -12
- data/lib/rspec/matchers/built_in/change.rb +171 -63
- data/lib/rspec/matchers/built_in/compound.rb +201 -30
- data/lib/rspec/matchers/built_in/contain_exactly.rb +73 -12
- data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
- data/lib/rspec/matchers/built_in/eq.rb +3 -38
- data/lib/rspec/matchers/built_in/eql.rb +2 -2
- data/lib/rspec/matchers/built_in/equal.rb +3 -3
- data/lib/rspec/matchers/built_in/exist.rb +7 -3
- data/lib/rspec/matchers/built_in/has.rb +93 -30
- data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
- data/lib/rspec/matchers/built_in/include.rb +133 -25
- data/lib/rspec/matchers/built_in/match.rb +79 -2
- data/lib/rspec/matchers/built_in/operators.rb +14 -5
- data/lib/rspec/matchers/built_in/output.rb +59 -2
- data/lib/rspec/matchers/built_in/raise_error.rb +130 -27
- data/lib/rspec/matchers/built_in/respond_to.rb +117 -15
- data/lib/rspec/matchers/built_in/satisfy.rb +28 -14
- data/lib/rspec/matchers/built_in/{start_and_end_with.rb → start_or_end_with.rb} +20 -8
- data/lib/rspec/matchers/built_in/throw_symbol.rb +15 -5
- data/lib/rspec/matchers/built_in/yield.rb +129 -156
- data/lib/rspec/matchers/built_in.rb +5 -3
- data/lib/rspec/matchers/composable.rb +24 -36
- data/lib/rspec/matchers/dsl.rb +203 -37
- data/lib/rspec/matchers/english_phrasing.rb +58 -0
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/generated_descriptions.rb +1 -2
- data/lib/rspec/matchers/matcher_delegator.rb +3 -4
- data/lib/rspec/matchers/matcher_protocol.rb +105 -0
- data/lib/rspec/matchers.rb +267 -144
- data.tar.gz.sig +0 -0
- metadata +71 -49
- metadata.gz.sig +0 -0
- data/lib/rspec/matchers/pretty.rb +0 -77
@@ -74,7 +74,7 @@ module RSpec
|
|
74
74
|
# @api private
|
75
75
|
# @return [String]
|
76
76
|
def description
|
77
|
-
"#{@operator} #{@expected
|
77
|
+
"#{@operator} #{RSpec::Support::ObjectFormatter.format(@expected)}"
|
78
78
|
end
|
79
79
|
|
80
80
|
private
|
@@ -98,10 +98,15 @@ module RSpec
|
|
98
98
|
def __delegate_operator(actual, operator, expected)
|
99
99
|
if actual.__send__(operator, expected)
|
100
100
|
true
|
101
|
-
elsif ['==', '===', '=~'].include?(operator)
|
102
|
-
fail_with_message("expected: #{expected.inspect}\n got: #{actual.inspect} (using #{operator})")
|
103
101
|
else
|
104
|
-
|
102
|
+
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
|
103
|
+
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
|
104
|
+
|
105
|
+
if ['==', '===', '=~'].include?(operator)
|
106
|
+
fail_with_message("expected: #{expected_formatted}\n got: #{actual_formatted} (using #{operator})")
|
107
|
+
else
|
108
|
+
fail_with_message("expected: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
|
109
|
+
end
|
105
110
|
end
|
106
111
|
end
|
107
112
|
end
|
@@ -111,7 +116,11 @@ module RSpec
|
|
111
116
|
class NegativeOperatorMatcher < OperatorMatcher
|
112
117
|
def __delegate_operator(actual, operator, expected)
|
113
118
|
return false unless actual.__send__(operator, expected)
|
114
|
-
|
119
|
+
|
120
|
+
expected_formatted = RSpec::Support::ObjectFormatter.format(expected)
|
121
|
+
actual_formatted = RSpec::Support::ObjectFormatter.format(actual)
|
122
|
+
|
123
|
+
fail_with_message("expected not: #{operator} #{expected_formatted}\n got: #{operator.gsub(/./, ' ')} #{actual_formatted}")
|
115
124
|
end
|
116
125
|
end
|
117
126
|
end
|
@@ -8,7 +8,9 @@ module RSpec
|
|
8
8
|
# Not intended to be instantiated directly.
|
9
9
|
class Output < BaseMatcher
|
10
10
|
def initialize(expected)
|
11
|
-
@expected
|
11
|
+
@expected = expected
|
12
|
+
@actual = ""
|
13
|
+
@block = nil
|
12
14
|
@stream_capturer = NullCapture
|
13
15
|
end
|
14
16
|
|
@@ -25,6 +27,7 @@ module RSpec
|
|
25
27
|
|
26
28
|
# @api public
|
27
29
|
# Tells the matcher to match against stdout.
|
30
|
+
# Works only when the main Ruby process prints to stdout
|
28
31
|
def to_stdout
|
29
32
|
@stream_capturer = CaptureStdout
|
30
33
|
self
|
@@ -32,11 +35,30 @@ module RSpec
|
|
32
35
|
|
33
36
|
# @api public
|
34
37
|
# Tells the matcher to match against stderr.
|
38
|
+
# Works only when the main Ruby process prints to stderr
|
35
39
|
def to_stderr
|
36
40
|
@stream_capturer = CaptureStderr
|
37
41
|
self
|
38
42
|
end
|
39
43
|
|
44
|
+
# @api public
|
45
|
+
# Tells the matcher to match against stdout.
|
46
|
+
# Works when subprocesses print to stdout as well.
|
47
|
+
# This is significantly (~30x) slower than `to_stdout`
|
48
|
+
def to_stdout_from_any_process
|
49
|
+
@stream_capturer = CaptureStreamToTempfile.new("stdout", $stdout)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# @api public
|
54
|
+
# Tells the matcher to match against stderr.
|
55
|
+
# Works when subprocesses print to stderr as well.
|
56
|
+
# This is significantly (~30x) slower than `to_stderr`
|
57
|
+
def to_stderr_from_any_process
|
58
|
+
@stream_capturer = CaptureStreamToTempfile.new("stderr", $stderr)
|
59
|
+
self
|
60
|
+
end
|
61
|
+
|
40
62
|
# @api private
|
41
63
|
# @return [String]
|
42
64
|
def failure_message
|
@@ -72,6 +94,13 @@ module RSpec
|
|
72
94
|
true
|
73
95
|
end
|
74
96
|
|
97
|
+
# @api private
|
98
|
+
# Indicates this matcher matches against a block only.
|
99
|
+
# @return [False]
|
100
|
+
def supports_value_expectations?
|
101
|
+
false
|
102
|
+
end
|
103
|
+
|
75
104
|
private
|
76
105
|
|
77
106
|
def captured?
|
@@ -91,7 +120,7 @@ module RSpec
|
|
91
120
|
|
92
121
|
def actual_output_description
|
93
122
|
return "nothing" unless captured?
|
94
|
-
|
123
|
+
actual_formatted
|
95
124
|
end
|
96
125
|
end
|
97
126
|
|
@@ -145,6 +174,34 @@ module RSpec
|
|
145
174
|
$stderr = original_stream
|
146
175
|
end
|
147
176
|
end
|
177
|
+
|
178
|
+
# @private
|
179
|
+
class CaptureStreamToTempfile < Struct.new(:name, :stream)
|
180
|
+
def capture(block)
|
181
|
+
# We delay loading tempfile until it is actually needed because
|
182
|
+
# we want to minimize stdlibs loaded so that users who use a
|
183
|
+
# portion of the stdlib can't have passing specs while forgetting
|
184
|
+
# to load it themselves. `CaptureStreamToTempfile` is rarely used
|
185
|
+
# and `tempfile` pulls in a bunch of things (delegate, tmpdir,
|
186
|
+
# thread, fileutils, etc), so it's worth delaying it until this point.
|
187
|
+
require 'tempfile'
|
188
|
+
|
189
|
+
original_stream = stream.clone
|
190
|
+
captured_stream = Tempfile.new(name)
|
191
|
+
|
192
|
+
begin
|
193
|
+
captured_stream.sync = true
|
194
|
+
stream.reopen(captured_stream)
|
195
|
+
block.call
|
196
|
+
captured_stream.rewind
|
197
|
+
captured_stream.read
|
198
|
+
ensure
|
199
|
+
stream.reopen(original_stream)
|
200
|
+
captured_stream.close
|
201
|
+
captured_stream.unlink
|
202
|
+
end
|
203
|
+
end
|
204
|
+
end
|
148
205
|
end
|
149
206
|
end
|
150
207
|
end
|
@@ -4,17 +4,33 @@ module RSpec
|
|
4
4
|
# @api private
|
5
5
|
# Provides the implementation for `raise_error`.
|
6
6
|
# Not intended to be instantiated directly.
|
7
|
+
# rubocop:disable Metrics/ClassLength
|
8
|
+
# rubocop:disable Lint/RescueException
|
7
9
|
class RaiseError
|
8
10
|
include Composable
|
9
11
|
|
10
|
-
|
12
|
+
# Used as a sentinel value to be able to tell when the user did not pass an
|
13
|
+
# argument. We can't use `nil` for that because we need to warn when `nil` is
|
14
|
+
# passed in a different way. It's an Object, not a Module, since Module's `===`
|
15
|
+
# does not evaluate to true when compared to itself.
|
16
|
+
UndefinedValue = Object.new.freeze
|
17
|
+
|
18
|
+
def initialize(expected_error_or_message, expected_message, &block)
|
11
19
|
@block = block
|
12
20
|
@actual_error = nil
|
21
|
+
@warn_about_bare_error = UndefinedValue === expected_error_or_message
|
22
|
+
@warn_about_nil_error = expected_error_or_message.nil?
|
23
|
+
|
13
24
|
case expected_error_or_message
|
14
|
-
when
|
15
|
-
@expected_error
|
25
|
+
when nil, UndefinedValue
|
26
|
+
@expected_error = Exception
|
27
|
+
@expected_message = expected_message
|
28
|
+
when String
|
29
|
+
@expected_error = Exception
|
30
|
+
@expected_message = expected_error_or_message
|
16
31
|
else
|
17
|
-
@expected_error
|
32
|
+
@expected_error = expected_error_or_message
|
33
|
+
@expected_message = expected_message
|
18
34
|
end
|
19
35
|
end
|
20
36
|
|
@@ -22,11 +38,12 @@ module RSpec
|
|
22
38
|
# Specifies the expected error message.
|
23
39
|
def with_message(expected_message)
|
24
40
|
raise_message_already_set if @expected_message
|
41
|
+
@warn_about_bare_error = false
|
25
42
|
@expected_message = expected_message
|
26
43
|
self
|
27
44
|
end
|
28
45
|
|
29
|
-
# rubocop:disable MethodLength
|
46
|
+
# rubocop:disable Metrics/MethodLength
|
30
47
|
# @private
|
31
48
|
def matches?(given_proc, negative_expectation=false, &block)
|
32
49
|
@given_proc = given_proc
|
@@ -41,23 +58,26 @@ module RSpec
|
|
41
58
|
begin
|
42
59
|
given_proc.call
|
43
60
|
rescue Exception => @actual_error
|
44
|
-
if values_match?(@expected_error, @actual_error)
|
61
|
+
if values_match?(@expected_error, @actual_error) ||
|
62
|
+
values_match?(@expected_error, actual_error_message)
|
45
63
|
@raised_expected_error = true
|
46
64
|
@with_expected_message = verify_message
|
47
65
|
end
|
48
66
|
end
|
49
67
|
|
50
68
|
unless negative_expectation
|
51
|
-
|
69
|
+
warn_about_bare_error! if warn_about_bare_error?
|
70
|
+
warn_about_nil_error! if warn_about_nil_error?
|
71
|
+
eval_block if ready_to_eval_block?
|
52
72
|
end
|
53
73
|
|
54
74
|
expectation_matched?
|
55
75
|
end
|
56
|
-
# rubocop:enable MethodLength
|
76
|
+
# rubocop:enable Metrics/MethodLength
|
57
77
|
|
58
78
|
# @private
|
59
79
|
def does_not_match?(given_proc)
|
60
|
-
|
80
|
+
warn_for_negative_false_positives!
|
61
81
|
!matches?(given_proc, :negative_expectation) && Proc === given_proc
|
62
82
|
end
|
63
83
|
|
@@ -66,10 +86,20 @@ module RSpec
|
|
66
86
|
true
|
67
87
|
end
|
68
88
|
|
89
|
+
# @private
|
90
|
+
def supports_value_expectations?
|
91
|
+
false
|
92
|
+
end
|
93
|
+
|
94
|
+
# @private
|
95
|
+
def expects_call_stack_jump?
|
96
|
+
true
|
97
|
+
end
|
98
|
+
|
69
99
|
# @api private
|
70
100
|
# @return [String]
|
71
101
|
def failure_message
|
72
|
-
@eval_block ?
|
102
|
+
@eval_block ? actual_error_message : "expected #{expected_error}#{given_error}"
|
73
103
|
end
|
74
104
|
|
75
105
|
# @api private
|
@@ -86,6 +116,12 @@ module RSpec
|
|
86
116
|
|
87
117
|
private
|
88
118
|
|
119
|
+
def actual_error_message
|
120
|
+
return nil unless @actual_error
|
121
|
+
|
122
|
+
@actual_error.respond_to?(:original_message) ? @actual_error.original_message : @actual_error.message
|
123
|
+
end
|
124
|
+
|
89
125
|
def expectation_matched?
|
90
126
|
error_and_message_match? && block_matches?
|
91
127
|
end
|
@@ -98,6 +134,10 @@ module RSpec
|
|
98
134
|
@eval_block ? @eval_block_passed : true
|
99
135
|
end
|
100
136
|
|
137
|
+
def ready_to_eval_block?
|
138
|
+
@raised_expected_error && @with_expected_message && @block
|
139
|
+
end
|
140
|
+
|
101
141
|
def eval_block
|
102
142
|
@eval_block = true
|
103
143
|
begin
|
@@ -110,32 +150,87 @@ module RSpec
|
|
110
150
|
|
111
151
|
def verify_message
|
112
152
|
return true if @expected_message.nil?
|
113
|
-
values_match?(@expected_message,
|
153
|
+
values_match?(@expected_message, actual_error_message.to_s)
|
114
154
|
end
|
115
155
|
|
116
|
-
def
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
156
|
+
def warn_for_negative_false_positives!
|
157
|
+
expression = if expecting_specific_exception? && @expected_message
|
158
|
+
"`expect { }.not_to raise_error(SpecificErrorClass, message)`"
|
159
|
+
elsif expecting_specific_exception?
|
160
|
+
"`expect { }.not_to raise_error(SpecificErrorClass)`"
|
161
|
+
elsif @expected_message
|
162
|
+
"`expect { }.not_to raise_error(message)`"
|
163
|
+
elsif @warn_about_nil_error
|
164
|
+
"`expect { }.not_to raise_error(nil)`"
|
165
|
+
end
|
124
166
|
|
125
|
-
return unless
|
167
|
+
return unless expression
|
126
168
|
|
127
|
-
|
128
|
-
|
169
|
+
warn_about_negative_false_positive! expression
|
170
|
+
end
|
171
|
+
|
172
|
+
def handle_warning(message)
|
173
|
+
RSpec::Expectations.configuration.false_positives_handler.call(message)
|
174
|
+
end
|
175
|
+
|
176
|
+
def warn_about_bare_error?
|
177
|
+
@warn_about_bare_error && @block.nil?
|
178
|
+
end
|
179
|
+
|
180
|
+
def warn_about_nil_error?
|
181
|
+
@warn_about_nil_error
|
182
|
+
end
|
183
|
+
|
184
|
+
def warn_about_bare_error!
|
185
|
+
handle_warning("Using the `raise_error` matcher without providing a specific " \
|
186
|
+
"error or message risks false positives, since `raise_error` " \
|
187
|
+
"will match when Ruby raises a `NoMethodError`, `NameError` or " \
|
188
|
+
"`ArgumentError`, potentially allowing the expectation to pass " \
|
189
|
+
"without even executing the method you are intending to call. " \
|
190
|
+
"#{warning}"\
|
191
|
+
"Instead consider providing a specific error class or message. " \
|
192
|
+
"This message can be suppressed by setting: " \
|
193
|
+
"`RSpec::Expectations.configuration.on_potential_false" \
|
194
|
+
"_positives = :nothing`")
|
195
|
+
end
|
196
|
+
|
197
|
+
def warn_about_nil_error!
|
198
|
+
handle_warning("Using the `raise_error` matcher with a `nil` error is probably " \
|
199
|
+
"unintentional, it risks false positives, since `raise_error` " \
|
200
|
+
"will match when Ruby raises a `NoMethodError`, `NameError` or " \
|
201
|
+
"`ArgumentError`, potentially allowing the expectation to pass " \
|
202
|
+
"without even executing the method you are intending to call. " \
|
203
|
+
"#{warning}"\
|
204
|
+
"Instead consider providing a specific error class or message. " \
|
205
|
+
"This message can be suppressed by setting: " \
|
206
|
+
"`RSpec::Expectations.configuration.on_potential_false" \
|
207
|
+
"_positives = :nothing`")
|
208
|
+
end
|
209
|
+
|
210
|
+
def warn_about_negative_false_positive!(expression)
|
211
|
+
handle_warning("Using #{expression} risks false positives, since literally " \
|
212
|
+
"any other error would cause the expectation to pass, " \
|
213
|
+
"including those raised by Ruby (e.g. `NoMethodError`, `NameError` " \
|
214
|
+
"and `ArgumentError`), meaning the code you are intending to test " \
|
215
|
+
"may not even get reached. Instead consider using " \
|
216
|
+
"`expect { }.not_to raise_error` or `expect { }.to raise_error" \
|
217
|
+
"(DifferentSpecificErrorClass)`. This message can be suppressed by " \
|
218
|
+
"setting: `RSpec::Expectations.configuration.on_potential_false" \
|
219
|
+
"_positives = :nothing`")
|
129
220
|
end
|
130
221
|
|
131
222
|
def expected_error
|
132
223
|
case @expected_message
|
133
224
|
when nil
|
134
|
-
|
225
|
+
if RSpec::Support.is_a_matcher?(@expected_error)
|
226
|
+
"Exception with #{description_of(@expected_error)}"
|
227
|
+
else
|
228
|
+
description_of(@expected_error)
|
229
|
+
end
|
135
230
|
when Regexp
|
136
|
-
"#{@expected_error} with message matching #{@expected_message
|
231
|
+
"#{@expected_error} with message matching #{description_of(@expected_message)}"
|
137
232
|
else
|
138
|
-
"#{@expected_error} with #{description_of
|
233
|
+
"#{@expected_error} with #{description_of(@expected_message)}"
|
139
234
|
end
|
140
235
|
end
|
141
236
|
|
@@ -150,7 +245,7 @@ module RSpec
|
|
150
245
|
|
151
246
|
backtrace = format_backtrace(@actual_error.backtrace)
|
152
247
|
[
|
153
|
-
", got #{@actual_error
|
248
|
+
", got #{description_of(@actual_error)} with backtrace:",
|
154
249
|
*backtrace
|
155
250
|
].join("\n # ")
|
156
251
|
end
|
@@ -160,9 +255,17 @@ module RSpec
|
|
160
255
|
end
|
161
256
|
|
162
257
|
def raise_message_already_set
|
163
|
-
raise "`expect { }.to raise_error(message).with_message(message)` is not valid.
|
258
|
+
raise "`expect { }.to raise_error(message).with_message(message)` is not valid. " \
|
259
|
+
'The matcher only allows the expected message to be specified once'
|
260
|
+
end
|
261
|
+
|
262
|
+
def warning
|
263
|
+
warning = "Actual error raised was #{description_of(@actual_error)}. "
|
264
|
+
warning if @actual_error
|
164
265
|
end
|
165
266
|
end
|
267
|
+
# rubocop:enable Lint/RescueException
|
268
|
+
# rubocop:enable Metrics/ClassLength
|
166
269
|
end
|
167
270
|
end
|
168
271
|
end
|
@@ -6,12 +6,14 @@ module RSpec
|
|
6
6
|
# @api private
|
7
7
|
# Provides the implementation for `respond_to`.
|
8
8
|
# Not intended to be instantiated directly.
|
9
|
-
class RespondTo
|
10
|
-
include Composable
|
11
|
-
|
9
|
+
class RespondTo < BaseMatcher
|
12
10
|
def initialize(*names)
|
13
11
|
@names = names
|
14
12
|
@expected_arity = nil
|
13
|
+
@expected_keywords = []
|
14
|
+
@ignoring_method_signature_failure = false
|
15
|
+
@unlimited_arguments = nil
|
16
|
+
@arbitrary_keywords = nil
|
15
17
|
end
|
16
18
|
|
17
19
|
# @api public
|
@@ -24,6 +26,43 @@ module RSpec
|
|
24
26
|
self
|
25
27
|
end
|
26
28
|
|
29
|
+
# @api public
|
30
|
+
# Specifies keyword arguments, if any.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# expect(obj).to respond_to(:message).with_keywords(:color, :shape)
|
34
|
+
# @example with an expected number of arguments
|
35
|
+
# expect(obj).to respond_to(:message).with(3).arguments.and_keywords(:color, :shape)
|
36
|
+
def with_keywords(*keywords)
|
37
|
+
@expected_keywords = keywords
|
38
|
+
self
|
39
|
+
end
|
40
|
+
alias :and_keywords :with_keywords
|
41
|
+
|
42
|
+
# @api public
|
43
|
+
# Specifies that the method accepts any keyword, i.e. the method has
|
44
|
+
# a splatted keyword parameter of the form **kw_args.
|
45
|
+
#
|
46
|
+
# @example
|
47
|
+
# expect(obj).to respond_to(:message).with_any_keywords
|
48
|
+
def with_any_keywords
|
49
|
+
@arbitrary_keywords = true
|
50
|
+
self
|
51
|
+
end
|
52
|
+
alias :and_any_keywords :with_any_keywords
|
53
|
+
|
54
|
+
# @api public
|
55
|
+
# Specifies that the number of arguments has no upper limit, i.e. the
|
56
|
+
# method has a splatted parameter of the form *args.
|
57
|
+
#
|
58
|
+
# @example
|
59
|
+
# expect(obj).to respond_to(:message).with_unlimited_arguments
|
60
|
+
def with_unlimited_arguments
|
61
|
+
@unlimited_arguments = true
|
62
|
+
self
|
63
|
+
end
|
64
|
+
alias :and_unlimited_arguments :with_unlimited_arguments
|
65
|
+
|
27
66
|
# @api public
|
28
67
|
# No-op. Intended to be used as syntactic sugar when using `with`.
|
29
68
|
#
|
@@ -47,7 +86,7 @@ module RSpec
|
|
47
86
|
# @api private
|
48
87
|
# @return [String]
|
49
88
|
def failure_message
|
50
|
-
"expected #{
|
89
|
+
"expected #{actual_formatted} to respond to #{@failing_method_names.map { |name| description_of(name) }.join(', ')}#{with_arity}"
|
51
90
|
end
|
52
91
|
|
53
92
|
# @api private
|
@@ -62,9 +101,10 @@ module RSpec
|
|
62
101
|
"respond to #{pp_names}#{with_arity}"
|
63
102
|
end
|
64
103
|
|
65
|
-
# @private
|
66
|
-
|
67
|
-
|
104
|
+
# @api private
|
105
|
+
# Used by other matchers to suppress a check
|
106
|
+
def ignoring_method_signature_failure!
|
107
|
+
@ignoring_method_signature_failure = true
|
68
108
|
end
|
69
109
|
|
70
110
|
private
|
@@ -77,20 +117,82 @@ module RSpec
|
|
77
117
|
end
|
78
118
|
|
79
119
|
def matches_arity?(actual, name)
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
120
|
+
ArityCheck.new(@expected_arity, @expected_keywords, @arbitrary_keywords, @unlimited_arguments).matches?(actual, name)
|
121
|
+
rescue NameError
|
122
|
+
return true if @ignoring_method_signature_failure
|
123
|
+
raise ArgumentError, "The #{matcher_name} matcher requires that " \
|
124
|
+
"the actual object define the method(s) in " \
|
125
|
+
"order to check arity, but the method " \
|
126
|
+
"`#{name}` is not defined. Remove the arity " \
|
127
|
+
"check or define the method to continue."
|
84
128
|
end
|
85
129
|
|
86
130
|
def with_arity
|
87
|
-
|
88
|
-
" with #{
|
131
|
+
str = ''.dup
|
132
|
+
str << " with #{with_arity_string}" if @expected_arity
|
133
|
+
str << " #{str.length == 0 ? 'with' : 'and'} #{with_keywords_string}" if @expected_keywords && @expected_keywords.count > 0
|
134
|
+
str << " #{str.length == 0 ? 'with' : 'and'} unlimited arguments" if @unlimited_arguments
|
135
|
+
str << " #{str.length == 0 ? 'with' : 'and'} any keywords" if @arbitrary_keywords
|
136
|
+
str
|
137
|
+
end
|
138
|
+
|
139
|
+
def with_arity_string
|
140
|
+
"#{@expected_arity} argument#{@expected_arity == 1 ? '' : 's'}"
|
141
|
+
end
|
142
|
+
|
143
|
+
def with_keywords_string
|
144
|
+
kw_str = case @expected_keywords.count
|
145
|
+
when 1
|
146
|
+
@expected_keywords.first.inspect
|
147
|
+
when 2
|
148
|
+
@expected_keywords.map(&:inspect).join(' and ')
|
149
|
+
else
|
150
|
+
"#{@expected_keywords[0...-1].map(&:inspect).join(', ')}, and #{@expected_keywords.last.inspect}"
|
151
|
+
end
|
152
|
+
|
153
|
+
"keyword#{@expected_keywords.count == 1 ? '' : 's'} #{kw_str}"
|
89
154
|
end
|
90
155
|
|
91
156
|
def pp_names
|
92
|
-
|
93
|
-
|
157
|
+
@names.length == 1 ? "##{@names.first}" : description_of(@names)
|
158
|
+
end
|
159
|
+
|
160
|
+
# @private
|
161
|
+
class ArityCheck
|
162
|
+
def initialize(expected_arity, expected_keywords, arbitrary_keywords, unlimited_arguments)
|
163
|
+
expectation = Support::MethodSignatureExpectation.new
|
164
|
+
|
165
|
+
if expected_arity.is_a?(Range)
|
166
|
+
expectation.min_count = expected_arity.min
|
167
|
+
expectation.max_count = expected_arity.max
|
168
|
+
else
|
169
|
+
expectation.min_count = expected_arity
|
170
|
+
end
|
171
|
+
|
172
|
+
expectation.keywords = expected_keywords
|
173
|
+
expectation.expect_unlimited_arguments = unlimited_arguments
|
174
|
+
expectation.expect_arbitrary_keywords = arbitrary_keywords
|
175
|
+
@expectation = expectation
|
176
|
+
end
|
177
|
+
|
178
|
+
def matches?(actual, name)
|
179
|
+
return true if @expectation.empty?
|
180
|
+
verifier_for(actual, name).with_expectation(@expectation).valid?
|
181
|
+
end
|
182
|
+
|
183
|
+
def verifier_for(actual, name)
|
184
|
+
Support::StrictSignatureVerifier.new(method_signature_for(actual, name))
|
185
|
+
end
|
186
|
+
|
187
|
+
def method_signature_for(actual, name)
|
188
|
+
method_handle = Support.method_handle_for(actual, name)
|
189
|
+
|
190
|
+
if name == :new && method_handle.owner === ::Class && ::Class === actual
|
191
|
+
Support::MethodSignature.new(actual.instance_method(:initialize))
|
192
|
+
else
|
193
|
+
Support::MethodSignature.new(method_handle)
|
194
|
+
end
|
195
|
+
end
|
94
196
|
end
|
95
197
|
end
|
96
198
|
end
|
@@ -4,10 +4,9 @@ module RSpec
|
|
4
4
|
# @api private
|
5
5
|
# Provides the implementation for `satisfy`.
|
6
6
|
# Not intended to be instantiated directly.
|
7
|
-
class Satisfy
|
8
|
-
|
9
|
-
|
10
|
-
def initialize(&block)
|
7
|
+
class Satisfy < BaseMatcher
|
8
|
+
def initialize(description=nil, &block)
|
9
|
+
@description = description
|
11
10
|
@block = block
|
12
11
|
end
|
13
12
|
|
@@ -18,27 +17,42 @@ module RSpec
|
|
18
17
|
@block.call(actual)
|
19
18
|
end
|
20
19
|
|
20
|
+
# @private
|
21
|
+
def description
|
22
|
+
@description ||= "satisfy #{block_representation}"
|
23
|
+
end
|
24
|
+
|
21
25
|
# @api private
|
22
26
|
# @return [String]
|
23
27
|
def failure_message
|
24
|
-
"expected #{
|
28
|
+
"expected #{actual_formatted} to #{description}"
|
25
29
|
end
|
26
30
|
|
27
31
|
# @api private
|
28
32
|
# @return [String]
|
29
33
|
def failure_message_when_negated
|
30
|
-
"expected #{
|
34
|
+
"expected #{actual_formatted} not to #{description}"
|
31
35
|
end
|
32
36
|
|
33
|
-
|
34
|
-
# @return [String]
|
35
|
-
def description
|
36
|
-
"satisfy block"
|
37
|
-
end
|
37
|
+
private
|
38
38
|
|
39
|
-
|
40
|
-
|
41
|
-
|
39
|
+
if RSpec::Support::RubyFeatures.ripper_supported?
|
40
|
+
def block_representation
|
41
|
+
if (block_snippet = extract_block_snippet)
|
42
|
+
"expression `#{block_snippet}`"
|
43
|
+
else
|
44
|
+
'block'
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def extract_block_snippet
|
49
|
+
return nil unless @block
|
50
|
+
Expectations::BlockSnippetExtractor.try_extracting_single_line_body_of(@block, matcher_name)
|
51
|
+
end
|
52
|
+
else
|
53
|
+
def block_representation
|
54
|
+
'block'
|
55
|
+
end
|
42
56
|
end
|
43
57
|
end
|
44
58
|
end
|