rspec-expectations 3.8.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (58) hide show
  1. checksums.yaml +7 -0
  2. checksums.yaml.gz.sig +0 -0
  3. data.tar.gz.sig +5 -0
  4. data/.document +5 -0
  5. data/.yardopts +6 -0
  6. data/Changelog.md +1156 -0
  7. data/LICENSE.md +25 -0
  8. data/README.md +305 -0
  9. data/lib/rspec/expectations.rb +82 -0
  10. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  11. data/lib/rspec/expectations/configuration.rb +215 -0
  12. data/lib/rspec/expectations/expectation_target.rb +127 -0
  13. data/lib/rspec/expectations/fail_with.rb +39 -0
  14. data/lib/rspec/expectations/failure_aggregator.rb +194 -0
  15. data/lib/rspec/expectations/handler.rb +170 -0
  16. data/lib/rspec/expectations/minitest_integration.rb +58 -0
  17. data/lib/rspec/expectations/syntax.rb +132 -0
  18. data/lib/rspec/expectations/version.rb +8 -0
  19. data/lib/rspec/matchers.rb +1034 -0
  20. data/lib/rspec/matchers/aliased_matcher.rb +116 -0
  21. data/lib/rspec/matchers/built_in.rb +52 -0
  22. data/lib/rspec/matchers/built_in/all.rb +86 -0
  23. data/lib/rspec/matchers/built_in/base_matcher.rb +193 -0
  24. data/lib/rspec/matchers/built_in/be.rb +288 -0
  25. data/lib/rspec/matchers/built_in/be_between.rb +77 -0
  26. data/lib/rspec/matchers/built_in/be_instance_of.rb +26 -0
  27. data/lib/rspec/matchers/built_in/be_kind_of.rb +20 -0
  28. data/lib/rspec/matchers/built_in/be_within.rb +72 -0
  29. data/lib/rspec/matchers/built_in/change.rb +428 -0
  30. data/lib/rspec/matchers/built_in/compound.rb +271 -0
  31. data/lib/rspec/matchers/built_in/contain_exactly.rb +302 -0
  32. data/lib/rspec/matchers/built_in/cover.rb +24 -0
  33. data/lib/rspec/matchers/built_in/eq.rb +40 -0
  34. data/lib/rspec/matchers/built_in/eql.rb +34 -0
  35. data/lib/rspec/matchers/built_in/equal.rb +81 -0
  36. data/lib/rspec/matchers/built_in/exist.rb +90 -0
  37. data/lib/rspec/matchers/built_in/has.rb +103 -0
  38. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  39. data/lib/rspec/matchers/built_in/include.rb +149 -0
  40. data/lib/rspec/matchers/built_in/match.rb +106 -0
  41. data/lib/rspec/matchers/built_in/operators.rb +128 -0
  42. data/lib/rspec/matchers/built_in/output.rb +200 -0
  43. data/lib/rspec/matchers/built_in/raise_error.rb +230 -0
  44. data/lib/rspec/matchers/built_in/respond_to.rb +165 -0
  45. data/lib/rspec/matchers/built_in/satisfy.rb +60 -0
  46. data/lib/rspec/matchers/built_in/start_or_end_with.rb +94 -0
  47. data/lib/rspec/matchers/built_in/throw_symbol.rb +132 -0
  48. data/lib/rspec/matchers/built_in/yield.rb +432 -0
  49. data/lib/rspec/matchers/composable.rb +171 -0
  50. data/lib/rspec/matchers/dsl.rb +527 -0
  51. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  52. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +73 -0
  53. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  54. data/lib/rspec/matchers/generated_descriptions.rb +41 -0
  55. data/lib/rspec/matchers/matcher_delegator.rb +35 -0
  56. data/lib/rspec/matchers/matcher_protocol.rb +99 -0
  57. metadata +215 -0
  58. 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