rspec-expectations 3.8.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- checksums.yaml.gz.sig +0 -0
- data.tar.gz.sig +5 -0
- data/.document +5 -0
- data/.yardopts +6 -0
- data/Changelog.md +1156 -0
- data/LICENSE.md +25 -0
- data/README.md +305 -0
- data/lib/rspec/expectations.rb +82 -0
- data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
- data/lib/rspec/expectations/configuration.rb +215 -0
- data/lib/rspec/expectations/expectation_target.rb +127 -0
- data/lib/rspec/expectations/fail_with.rb +39 -0
- data/lib/rspec/expectations/failure_aggregator.rb +194 -0
- data/lib/rspec/expectations/handler.rb +170 -0
- data/lib/rspec/expectations/minitest_integration.rb +58 -0
- data/lib/rspec/expectations/syntax.rb +132 -0
- data/lib/rspec/expectations/version.rb +8 -0
- data/lib/rspec/matchers.rb +1034 -0
- data/lib/rspec/matchers/aliased_matcher.rb +116 -0
- data/lib/rspec/matchers/built_in.rb +52 -0
- data/lib/rspec/matchers/built_in/all.rb +86 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +193 -0
- data/lib/rspec/matchers/built_in/be.rb +288 -0
- data/lib/rspec/matchers/built_in/be_between.rb +77 -0
- data/lib/rspec/matchers/built_in/be_instance_of.rb +26 -0
- data/lib/rspec/matchers/built_in/be_kind_of.rb +20 -0
- data/lib/rspec/matchers/built_in/be_within.rb +72 -0
- data/lib/rspec/matchers/built_in/change.rb +428 -0
- data/lib/rspec/matchers/built_in/compound.rb +271 -0
- data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
- data/lib/rspec/matchers/built_in/cover.rb +24 -0
- data/lib/rspec/matchers/built_in/eq.rb +40 -0
- data/lib/rspec/matchers/built_in/eql.rb +34 -0
- data/lib/rspec/matchers/built_in/equal.rb +81 -0
- data/lib/rspec/matchers/built_in/exist.rb +90 -0
- data/lib/rspec/matchers/built_in/has.rb +103 -0
- data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
- data/lib/rspec/matchers/built_in/include.rb +149 -0
- data/lib/rspec/matchers/built_in/match.rb +106 -0
- data/lib/rspec/matchers/built_in/operators.rb +128 -0
- data/lib/rspec/matchers/built_in/output.rb +200 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +230 -0
- data/lib/rspec/matchers/built_in/respond_to.rb +165 -0
- data/lib/rspec/matchers/built_in/satisfy.rb +60 -0
- data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
- data/lib/rspec/matchers/built_in/throw_symbol.rb +132 -0
- data/lib/rspec/matchers/built_in/yield.rb +432 -0
- data/lib/rspec/matchers/composable.rb +171 -0
- data/lib/rspec/matchers/dsl.rb +527 -0
- data/lib/rspec/matchers/english_phrasing.rb +58 -0
- data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +73 -0
- data/lib/rspec/matchers/fail_matchers.rb +42 -0
- data/lib/rspec/matchers/generated_descriptions.rb +41 -0
- data/lib/rspec/matchers/matcher_delegator.rb +35 -0
- data/lib/rspec/matchers/matcher_protocol.rb +99 -0
- metadata +215 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,302 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# rubocop:disable ClassLength
|
5
|
+
# @api private
|
6
|
+
# Provides the implementation for `contain_exactly` and `match_array`.
|
7
|
+
# Not intended to be instantiated directly.
|
8
|
+
class ContainExactly < BaseMatcher
|
9
|
+
# @api private
|
10
|
+
# @return [String]
|
11
|
+
def failure_message
|
12
|
+
if Array === actual
|
13
|
+
generate_failure_message
|
14
|
+
else
|
15
|
+
"expected a collection that can be converted to an array with " \
|
16
|
+
"`#to_ary` or `#to_a`, but got #{actual_formatted}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
# @return [String]
|
22
|
+
def failure_message_when_negated
|
23
|
+
list = EnglishPhrasing.list(surface_descriptions_in(expected))
|
24
|
+
"expected #{actual_formatted} not to contain exactly#{list}"
|
25
|
+
end
|
26
|
+
|
27
|
+
# @api private
|
28
|
+
# @return [String]
|
29
|
+
def description
|
30
|
+
list = EnglishPhrasing.list(surface_descriptions_in(expected))
|
31
|
+
"contain exactly#{list}"
|
32
|
+
end
|
33
|
+
|
34
|
+
private
|
35
|
+
|
36
|
+
def generate_failure_message
|
37
|
+
message = expected_collection_line
|
38
|
+
message += actual_collection_line
|
39
|
+
message += missing_elements_line unless missing_items.empty?
|
40
|
+
message += extra_elements_line unless extra_items.empty?
|
41
|
+
message
|
42
|
+
end
|
43
|
+
|
44
|
+
def expected_collection_line
|
45
|
+
message_line('expected collection contained', expected, true)
|
46
|
+
end
|
47
|
+
|
48
|
+
def actual_collection_line
|
49
|
+
message_line('actual collection contained', actual)
|
50
|
+
end
|
51
|
+
|
52
|
+
def missing_elements_line
|
53
|
+
message_line('the missing elements were', missing_items, true)
|
54
|
+
end
|
55
|
+
|
56
|
+
def extra_elements_line
|
57
|
+
message_line('the extra elements were', extra_items)
|
58
|
+
end
|
59
|
+
|
60
|
+
def describe_collection(collection, surface_descriptions=false)
|
61
|
+
if surface_descriptions
|
62
|
+
"#{description_of(safe_sort(surface_descriptions_in collection))}\n"
|
63
|
+
else
|
64
|
+
"#{description_of(safe_sort(collection))}\n"
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def message_line(prefix, collection, surface_descriptions=false)
|
69
|
+
"%-32s%s" % [prefix + ':',
|
70
|
+
describe_collection(collection, surface_descriptions)]
|
71
|
+
end
|
72
|
+
|
73
|
+
def match(_expected, _actual)
|
74
|
+
return false unless convert_actual_to_an_array
|
75
|
+
match_when_sorted? || (extra_items.empty? && missing_items.empty?)
|
76
|
+
end
|
77
|
+
|
78
|
+
# This cannot always work (e.g. when dealing with unsortable items,
|
79
|
+
# or matchers as expected items), but it's practically free compared to
|
80
|
+
# the slowness of the full matching algorithm, and in common cases this
|
81
|
+
# works, so it's worth a try.
|
82
|
+
def match_when_sorted?
|
83
|
+
values_match?(safe_sort(expected), safe_sort(actual))
|
84
|
+
end
|
85
|
+
|
86
|
+
def convert_actual_to_an_array
|
87
|
+
if actual.respond_to?(:to_ary)
|
88
|
+
@actual = actual.to_ary
|
89
|
+
elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
|
90
|
+
@actual = actual.to_a
|
91
|
+
else
|
92
|
+
false
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
def safe_sort(array)
|
97
|
+
array.sort
|
98
|
+
rescue Support::AllExceptionsExceptOnesWeMustNotRescue
|
99
|
+
array
|
100
|
+
end
|
101
|
+
|
102
|
+
if RUBY_VERSION == "1.8.7"
|
103
|
+
def to_a_disallowed?(object)
|
104
|
+
case object
|
105
|
+
when NilClass, String then true
|
106
|
+
else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
|
107
|
+
end
|
108
|
+
end
|
109
|
+
else
|
110
|
+
def to_a_disallowed?(object)
|
111
|
+
NilClass === object
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
def missing_items
|
116
|
+
@missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
|
117
|
+
expected[index]
|
118
|
+
end
|
119
|
+
end
|
120
|
+
|
121
|
+
def extra_items
|
122
|
+
@extra_items ||= best_solution.unmatched_actual_indexes.map do |index|
|
123
|
+
actual[index]
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
def best_solution
|
128
|
+
@best_solution ||= pairings_maximizer.find_best_solution
|
129
|
+
end
|
130
|
+
|
131
|
+
def pairings_maximizer
|
132
|
+
@pairings_maximizer ||= begin
|
133
|
+
expected_matches = Hash[Array.new(expected.size) { |i| [i, []] }]
|
134
|
+
actual_matches = Hash[Array.new(actual.size) { |i| [i, []] }]
|
135
|
+
|
136
|
+
expected.each_with_index do |e, ei|
|
137
|
+
actual.each_with_index do |a, ai|
|
138
|
+
next unless values_match?(e, a)
|
139
|
+
|
140
|
+
expected_matches[ei] << ai
|
141
|
+
actual_matches[ai] << ei
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
PairingsMaximizer.new(expected_matches, actual_matches)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
# Once we started supporting composing matchers, the algorithm for this matcher got
|
150
|
+
# much more complicated. Consider this expression:
|
151
|
+
#
|
152
|
+
# expect(["fool", "food"]).to contain_exactly(/foo/, /fool/)
|
153
|
+
#
|
154
|
+
# This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but
|
155
|
+
# the original algorithm used by this matcher would pair the first elements it could
|
156
|
+
# (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have
|
157
|
+
# an expected element which is a matcher that matches a superset of actual items
|
158
|
+
# compared to another expected element matcher, we need to consider every possible pairing.
|
159
|
+
#
|
160
|
+
# This class is designed to maximize the number of actual/expected pairings -- or,
|
161
|
+
# conversely, to minimize the number of unpaired items. It's essentially a brute
|
162
|
+
# force solution, but with a few heuristics applied to reduce the size of the
|
163
|
+
# problem space:
|
164
|
+
#
|
165
|
+
# * Any items which match none of the items in the other list are immediately
|
166
|
+
# placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array.
|
167
|
+
# The extra items and missing items in the matcher failure message are derived
|
168
|
+
# from these arrays.
|
169
|
+
# * Any items which reciprocally match only each other are paired up and not
|
170
|
+
# considered further.
|
171
|
+
#
|
172
|
+
# What's left is only the items which match multiple items from the other list
|
173
|
+
# (or vice versa). From here, it performs a brute-force depth-first search,
|
174
|
+
# looking for a solution which pairs all elements in both lists, or, barring that,
|
175
|
+
# that produces the fewest unmatched items.
|
176
|
+
#
|
177
|
+
# @private
|
178
|
+
class PairingsMaximizer
|
179
|
+
# @private
|
180
|
+
Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
|
181
|
+
:indeterminate_expected_indexes, :indeterminate_actual_indexes) do
|
182
|
+
def worse_than?(other)
|
183
|
+
unmatched_item_count > other.unmatched_item_count
|
184
|
+
end
|
185
|
+
|
186
|
+
def candidate?
|
187
|
+
indeterminate_expected_indexes.empty? &&
|
188
|
+
indeterminate_actual_indexes.empty?
|
189
|
+
end
|
190
|
+
|
191
|
+
def ideal?
|
192
|
+
candidate? && (
|
193
|
+
unmatched_expected_indexes.empty? ||
|
194
|
+
unmatched_actual_indexes.empty?
|
195
|
+
)
|
196
|
+
end
|
197
|
+
|
198
|
+
def unmatched_item_count
|
199
|
+
unmatched_expected_indexes.count + unmatched_actual_indexes.count
|
200
|
+
end
|
201
|
+
|
202
|
+
def +(derived_candidate_solution)
|
203
|
+
self.class.new(
|
204
|
+
unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes,
|
205
|
+
unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes,
|
206
|
+
# Ignore the indeterminate indexes: by the time we get here,
|
207
|
+
# we've dealt with all indeterminates.
|
208
|
+
[], []
|
209
|
+
)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
|
214
|
+
|
215
|
+
def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
|
216
|
+
@expected_to_actual_matched_indexes = expected_to_actual_matched_indexes
|
217
|
+
@actual_to_expected_matched_indexes = actual_to_expected_matched_indexes
|
218
|
+
|
219
|
+
unmatched_expected_indexes, indeterminate_expected_indexes =
|
220
|
+
categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
|
221
|
+
|
222
|
+
unmatched_actual_indexes, indeterminate_actual_indexes =
|
223
|
+
categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes)
|
224
|
+
|
225
|
+
@solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes,
|
226
|
+
indeterminate_expected_indexes, indeterminate_actual_indexes)
|
227
|
+
end
|
228
|
+
|
229
|
+
def find_best_solution
|
230
|
+
return solution if solution.candidate?
|
231
|
+
best_solution_so_far = NullSolution
|
232
|
+
|
233
|
+
expected_index = solution.indeterminate_expected_indexes.first
|
234
|
+
actuals = expected_to_actual_matched_indexes[expected_index]
|
235
|
+
|
236
|
+
actuals.each do |actual_index|
|
237
|
+
solution = best_solution_for_pairing(expected_index, actual_index)
|
238
|
+
return solution if solution.ideal?
|
239
|
+
best_solution_so_far = solution if best_solution_so_far.worse_than?(solution)
|
240
|
+
end
|
241
|
+
|
242
|
+
best_solution_so_far
|
243
|
+
end
|
244
|
+
|
245
|
+
private
|
246
|
+
|
247
|
+
# @private
|
248
|
+
# Starting solution that is worse than any other real solution.
|
249
|
+
NullSolution = Class.new do
|
250
|
+
def self.worse_than?(_other)
|
251
|
+
true
|
252
|
+
end
|
253
|
+
end
|
254
|
+
|
255
|
+
def categorize_indexes(indexes_to_categorize, other_indexes)
|
256
|
+
unmatched = []
|
257
|
+
indeterminate = []
|
258
|
+
|
259
|
+
indexes_to_categorize.each_pair do |index, matches|
|
260
|
+
if matches.empty?
|
261
|
+
unmatched << index
|
262
|
+
elsif !reciprocal_single_match?(matches, index, other_indexes)
|
263
|
+
indeterminate << index
|
264
|
+
end
|
265
|
+
end
|
266
|
+
|
267
|
+
return unmatched, indeterminate
|
268
|
+
end
|
269
|
+
|
270
|
+
def reciprocal_single_match?(matches, index, other_list)
|
271
|
+
return false unless matches.one?
|
272
|
+
other_list[matches.first] == [index]
|
273
|
+
end
|
274
|
+
|
275
|
+
def best_solution_for_pairing(expected_index, actual_index)
|
276
|
+
modified_expecteds = apply_pairing_to(
|
277
|
+
solution.indeterminate_expected_indexes,
|
278
|
+
expected_to_actual_matched_indexes, actual_index)
|
279
|
+
|
280
|
+
modified_expecteds.delete(expected_index)
|
281
|
+
|
282
|
+
modified_actuals = apply_pairing_to(
|
283
|
+
solution.indeterminate_actual_indexes,
|
284
|
+
actual_to_expected_matched_indexes, expected_index)
|
285
|
+
|
286
|
+
modified_actuals.delete(actual_index)
|
287
|
+
|
288
|
+
solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution
|
289
|
+
end
|
290
|
+
|
291
|
+
def apply_pairing_to(indeterminates, original_matches, other_list_index)
|
292
|
+
indeterminates.inject({}) do |accum, index|
|
293
|
+
accum[index] = original_matches[index] - [other_list_index]
|
294
|
+
accum
|
295
|
+
end
|
296
|
+
end
|
297
|
+
end
|
298
|
+
end
|
299
|
+
# rubocop:enable ClassLength
|
300
|
+
end
|
301
|
+
end
|
302
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `cover`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class Cover < BaseMatcher
|
8
|
+
def initialize(*expected)
|
9
|
+
@expected = expected
|
10
|
+
end
|
11
|
+
|
12
|
+
def matches?(range)
|
13
|
+
@actual = range
|
14
|
+
@expected.all? { |e| range.cover?(e) }
|
15
|
+
end
|
16
|
+
|
17
|
+
def does_not_match?(range)
|
18
|
+
@actual = range
|
19
|
+
expected.none? { |e| range.cover?(e) }
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,40 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `eq`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class Eq < BaseMatcher
|
8
|
+
# @api private
|
9
|
+
# @return [String]
|
10
|
+
def failure_message
|
11
|
+
"\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
# @return [String]
|
16
|
+
def failure_message_when_negated
|
17
|
+
"\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
# @return [String]
|
22
|
+
def description
|
23
|
+
"eq #{expected_formatted}"
|
24
|
+
end
|
25
|
+
|
26
|
+
# @api private
|
27
|
+
# @return [Boolean]
|
28
|
+
def diffable?
|
29
|
+
true
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
def match(expected, actual)
|
35
|
+
actual == expected
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `eql`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class Eql < BaseMatcher
|
8
|
+
# @api private
|
9
|
+
# @return [String]
|
10
|
+
def failure_message
|
11
|
+
"\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
|
12
|
+
end
|
13
|
+
|
14
|
+
# @api private
|
15
|
+
# @return [String]
|
16
|
+
def failure_message_when_negated
|
17
|
+
"\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
|
18
|
+
end
|
19
|
+
|
20
|
+
# @api private
|
21
|
+
# @return [Boolean]
|
22
|
+
def diffable?
|
23
|
+
true
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def match(expected, actual)
|
29
|
+
actual.eql? expected
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
module RSpec
|
2
|
+
module Matchers
|
3
|
+
module BuiltIn
|
4
|
+
# @api private
|
5
|
+
# Provides the implementation for `equal`.
|
6
|
+
# Not intended to be instantiated directly.
|
7
|
+
class Equal < BaseMatcher
|
8
|
+
# @api private
|
9
|
+
# @return [String]
|
10
|
+
def failure_message
|
11
|
+
if expected_is_a_literal_singleton?
|
12
|
+
simple_failure_message
|
13
|
+
else
|
14
|
+
detailed_failure_message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
# @api private
|
19
|
+
# @return [String]
|
20
|
+
def failure_message_when_negated
|
21
|
+
<<-MESSAGE
|
22
|
+
|
23
|
+
expected not #{inspect_object(actual)}
|
24
|
+
got #{inspect_object(expected)}
|
25
|
+
|
26
|
+
Compared using equal?, which compares object identity.
|
27
|
+
|
28
|
+
MESSAGE
|
29
|
+
end
|
30
|
+
|
31
|
+
# @api private
|
32
|
+
# @return [Boolean]
|
33
|
+
def diffable?
|
34
|
+
!expected_is_a_literal_singleton?
|
35
|
+
end
|
36
|
+
|
37
|
+
private
|
38
|
+
|
39
|
+
def match(expected, actual)
|
40
|
+
actual.equal? expected
|
41
|
+
end
|
42
|
+
|
43
|
+
LITERAL_SINGLETONS = [true, false, nil]
|
44
|
+
|
45
|
+
def expected_is_a_literal_singleton?
|
46
|
+
LITERAL_SINGLETONS.include?(expected)
|
47
|
+
end
|
48
|
+
|
49
|
+
def actual_inspected
|
50
|
+
if LITERAL_SINGLETONS.include?(actual)
|
51
|
+
actual_formatted
|
52
|
+
else
|
53
|
+
inspect_object(actual)
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def simple_failure_message
|
58
|
+
"\nexpected #{expected_formatted}\n got #{actual_inspected}\n"
|
59
|
+
end
|
60
|
+
|
61
|
+
def detailed_failure_message
|
62
|
+
<<-MESSAGE
|
63
|
+
|
64
|
+
expected #{inspect_object(expected)}
|
65
|
+
got #{inspect_object(actual)}
|
66
|
+
|
67
|
+
Compared using equal?, which compares object identity,
|
68
|
+
but expected and actual are not the same object. Use
|
69
|
+
`expect(actual).to eq(expected)` if you don't care about
|
70
|
+
object identity in this example.
|
71
|
+
|
72
|
+
MESSAGE
|
73
|
+
end
|
74
|
+
|
75
|
+
def inspect_object(o)
|
76
|
+
"#<#{o.class}:#{o.object_id}> => #{RSpec::Support::ObjectFormatter.format(o)}"
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|