rspec-expectations 2.13.0 → 3.0.0
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 +15 -0
- checksums.yaml.gz.sig +0 -0
- data/Changelog.md +413 -23
- data/License.txt +1 -0
- data/README.md +109 -80
- data/lib/rspec/{matchers → expectations}/configuration.rb +44 -14
- data/lib/rspec/expectations/expectation_target.rb +71 -38
- data/lib/rspec/expectations/fail_with.rb +12 -44
- data/lib/rspec/expectations/handler.rb +135 -35
- data/lib/rspec/expectations/minitest_integration.rb +16 -0
- data/lib/rspec/expectations/syntax.rb +60 -94
- data/lib/rspec/expectations/version.rb +1 -1
- data/lib/rspec/expectations.rb +46 -24
- data/lib/rspec/matchers/aliased_matcher.rb +41 -0
- data/lib/rspec/matchers/built_in/all.rb +78 -0
- data/lib/rspec/matchers/built_in/base_matcher.rb +63 -15
- data/lib/rspec/matchers/built_in/be.rb +170 -79
- data/lib/rspec/matchers/built_in/be_between.rb +77 -0
- data/lib/rspec/matchers/built_in/be_instance_of.rb +11 -3
- data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -0
- data/lib/rspec/matchers/built_in/be_within.rb +40 -16
- data/lib/rspec/matchers/built_in/change.rb +275 -74
- data/lib/rspec/matchers/built_in/compound.rb +122 -0
- data/lib/rspec/matchers/built_in/contain_exactly.rb +256 -0
- data/lib/rspec/matchers/built_in/cover.rb +3 -0
- data/lib/rspec/matchers/built_in/eq.rb +60 -6
- data/lib/rspec/matchers/built_in/eql.rb +17 -5
- data/lib/rspec/matchers/built_in/equal.rb +55 -21
- data/lib/rspec/matchers/built_in/exist.rb +74 -10
- data/lib/rspec/matchers/built_in/has.rb +71 -15
- data/lib/rspec/matchers/built_in/include.rb +58 -29
- data/lib/rspec/matchers/built_in/match.rb +19 -1
- data/lib/rspec/matchers/built_in/operators.rb +119 -0
- data/lib/rspec/matchers/built_in/output.rb +150 -0
- data/lib/rspec/matchers/built_in/raise_error.rb +91 -33
- data/lib/rspec/matchers/built_in/respond_to.rb +42 -20
- data/lib/rspec/matchers/built_in/satisfy.rb +19 -3
- data/lib/rspec/matchers/built_in/start_and_end_with.rb +54 -18
- data/lib/rspec/matchers/built_in/throw_symbol.rb +38 -8
- data/lib/rspec/matchers/built_in/yield.rb +237 -55
- data/lib/rspec/matchers/built_in.rb +45 -33
- data/lib/rspec/matchers/composable.rb +179 -0
- data/lib/rspec/matchers/dsl.rb +362 -5
- data/lib/rspec/matchers/generated_descriptions.rb +13 -6
- data/lib/rspec/matchers/matcher_delegator.rb +37 -0
- data/lib/rspec/matchers/pretty.rb +33 -20
- data/lib/rspec/matchers.rb +450 -227
- data.tar.gz.sig +1 -0
- metadata +100 -215
- metadata.gz.sig +0 -0
- data/features/README.md +0 -49
- data/features/Upgrade.md +0 -53
- data/features/built_in_matchers/README.md +0 -90
- data/features/built_in_matchers/be.feature +0 -175
- data/features/built_in_matchers/be_within.feature +0 -48
- data/features/built_in_matchers/cover.feature +0 -47
- data/features/built_in_matchers/end_with.feature +0 -48
- data/features/built_in_matchers/equality.feature +0 -139
- data/features/built_in_matchers/exist.feature +0 -45
- data/features/built_in_matchers/expect_change.feature +0 -59
- data/features/built_in_matchers/expect_error.feature +0 -144
- data/features/built_in_matchers/have.feature +0 -109
- data/features/built_in_matchers/include.feature +0 -174
- data/features/built_in_matchers/match.feature +0 -52
- data/features/built_in_matchers/operators.feature +0 -227
- data/features/built_in_matchers/predicates.feature +0 -137
- data/features/built_in_matchers/respond_to.feature +0 -84
- data/features/built_in_matchers/satisfy.feature +0 -33
- data/features/built_in_matchers/start_with.feature +0 -48
- data/features/built_in_matchers/throw_symbol.feature +0 -91
- data/features/built_in_matchers/types.feature +0 -116
- data/features/built_in_matchers/yield.feature +0 -146
- data/features/custom_matchers/access_running_example.feature +0 -53
- data/features/custom_matchers/define_diffable_matcher.feature +0 -27
- data/features/custom_matchers/define_matcher.feature +0 -368
- data/features/custom_matchers/define_matcher_outside_rspec.feature +0 -38
- data/features/custom_matchers/define_matcher_with_fluent_interface.feature +0 -24
- data/features/customized_message.feature +0 -22
- data/features/diffing.feature +0 -85
- data/features/implicit_docstrings.feature +0 -52
- data/features/step_definitions/additional_cli_steps.rb +0 -22
- data/features/support/env.rb +0 -5
- data/features/syntax_configuration.feature +0 -68
- data/features/test_frameworks/test_unit.feature +0 -46
- data/lib/rspec/expectations/deprecation.rb +0 -38
- data/lib/rspec/expectations/differ.rb +0 -115
- data/lib/rspec/expectations/errors.rb +0 -9
- data/lib/rspec/expectations/extensions/array.rb +0 -9
- data/lib/rspec/expectations/extensions/object.rb +0 -39
- data/lib/rspec/expectations/extensions.rb +0 -2
- data/lib/rspec/matchers/be_close.rb +0 -9
- data/lib/rspec/matchers/built_in/have.rb +0 -108
- data/lib/rspec/matchers/built_in/match_array.rb +0 -51
- data/lib/rspec/matchers/compatibility.rb +0 -14
- data/lib/rspec/matchers/extensions/instance_eval_with_args.rb +0 -39
- data/lib/rspec/matchers/matcher.rb +0 -300
- data/lib/rspec/matchers/method_missing.rb +0 -12
- data/lib/rspec/matchers/operator_matcher.rb +0 -109
- data/lib/rspec-expectations.rb +0 -1
- data/spec/rspec/expectations/differ_spec.rb +0 -170
- data/spec/rspec/expectations/expectation_target_spec.rb +0 -75
- data/spec/rspec/expectations/extensions/kernel_spec.rb +0 -67
- data/spec/rspec/expectations/fail_with_spec.rb +0 -95
- data/spec/rspec/expectations/handler_spec.rb +0 -206
- data/spec/rspec/expectations/syntax_spec.rb +0 -97
- data/spec/rspec/matchers/base_matcher_spec.rb +0 -62
- data/spec/rspec/matchers/be_close_spec.rb +0 -22
- data/spec/rspec/matchers/be_instance_of_spec.rb +0 -63
- data/spec/rspec/matchers/be_kind_of_spec.rb +0 -41
- data/spec/rspec/matchers/be_spec.rb +0 -516
- data/spec/rspec/matchers/be_within_spec.rb +0 -129
- data/spec/rspec/matchers/change_spec.rb +0 -542
- data/spec/rspec/matchers/configuration_spec.rb +0 -238
- data/spec/rspec/matchers/cover_spec.rb +0 -69
- data/spec/rspec/matchers/description_generation_spec.rb +0 -176
- data/spec/rspec/matchers/dsl_spec.rb +0 -57
- data/spec/rspec/matchers/eq_spec.rb +0 -60
- data/spec/rspec/matchers/eql_spec.rb +0 -41
- data/spec/rspec/matchers/equal_spec.rb +0 -78
- data/spec/rspec/matchers/exist_spec.rb +0 -124
- data/spec/rspec/matchers/has_spec.rb +0 -122
- data/spec/rspec/matchers/have_spec.rb +0 -445
- data/spec/rspec/matchers/include_matcher_integration_spec.rb +0 -30
- data/spec/rspec/matchers/include_spec.rb +0 -531
- data/spec/rspec/matchers/match_array_spec.rb +0 -194
- data/spec/rspec/matchers/match_spec.rb +0 -61
- data/spec/rspec/matchers/matcher_spec.rb +0 -471
- data/spec/rspec/matchers/matchers_spec.rb +0 -37
- data/spec/rspec/matchers/method_missing_spec.rb +0 -28
- data/spec/rspec/matchers/operator_matcher_spec.rb +0 -223
- data/spec/rspec/matchers/raise_error_spec.rb +0 -395
- data/spec/rspec/matchers/respond_to_spec.rb +0 -292
- data/spec/rspec/matchers/satisfy_spec.rb +0 -44
- data/spec/rspec/matchers/start_with_end_with_spec.rb +0 -186
- data/spec/rspec/matchers/throw_symbol_spec.rb +0 -116
- data/spec/rspec/matchers/yield_spec.rb +0 -437
- data/spec/spec_helper.rb +0 -54
- data/spec/support/classes.rb +0 -56
- data/spec/support/in_sub_process.rb +0 -38
- data/spec/support/matchers.rb +0 -22
- data/spec/support/ruby_version.rb +0 -10
- data/spec/support/shared_examples.rb +0 -13
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
module RSpec
|
|
2
|
+
module Matchers
|
|
3
|
+
module BuiltIn
|
|
4
|
+
# @api private
|
|
5
|
+
# Provides the implementation for `contain_exactly` and `match_array`.
|
|
6
|
+
# Not intended to be instantiated directly.
|
|
7
|
+
class ContainExactly < BaseMatcher
|
|
8
|
+
|
|
9
|
+
# @api private
|
|
10
|
+
# @return [String]
|
|
11
|
+
def failure_message
|
|
12
|
+
if Array === actual
|
|
13
|
+
message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n"
|
|
14
|
+
message += "actual collection contained: #{safe_sort(actual).inspect}\n"
|
|
15
|
+
message += "the missing elements were: #{safe_sort(surface_descriptions_in missing_items).inspect}\n" unless missing_items.empty?
|
|
16
|
+
message += "the extra elements were: #{safe_sort(extra_items).inspect}\n" unless extra_items.empty?
|
|
17
|
+
message
|
|
18
|
+
else
|
|
19
|
+
"expected a collection that can be converted to an array with " +
|
|
20
|
+
"`#to_ary` or `#to_a`, but got #{actual.inspect}"
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# @api private
|
|
25
|
+
# @return [String]
|
|
26
|
+
def failure_message_when_negated
|
|
27
|
+
"`contain_exactly` does not support negation"
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# @api private
|
|
31
|
+
# @return [String]
|
|
32
|
+
def description
|
|
33
|
+
"contain exactly#{to_sentence(surface_descriptions_in expected)}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
private
|
|
37
|
+
|
|
38
|
+
def match(expected, actual)
|
|
39
|
+
convert_actual_to_an_array or return false
|
|
40
|
+
match_when_sorted? || (extra_items.empty? && missing_items.empty?)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# This cannot always work (e.g. when dealing with unsortable items,
|
|
44
|
+
# or matchers as expected items), but it's practically free compared to
|
|
45
|
+
# the slowness of the full matching algorithm, and in common cases this
|
|
46
|
+
# works, so it's worth a try.
|
|
47
|
+
def match_when_sorted?
|
|
48
|
+
values_match?(safe_sort(expected), safe_sort(actual))
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def convert_actual_to_an_array
|
|
52
|
+
if actual.respond_to?(:to_ary)
|
|
53
|
+
@actual = actual.to_ary
|
|
54
|
+
elsif enumerable?(actual) && actual.respond_to?(:to_a)
|
|
55
|
+
@actual = actual.to_a
|
|
56
|
+
else
|
|
57
|
+
return false
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def safe_sort(array)
|
|
62
|
+
array.sort rescue array
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def missing_items
|
|
66
|
+
@missing_items ||= best_solution.unmatched_expected_indexes.map do |index|
|
|
67
|
+
expected[index]
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def extra_items
|
|
72
|
+
@extra_items ||= best_solution.unmatched_actual_indexes.map do |index|
|
|
73
|
+
actual[index]
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def best_solution
|
|
78
|
+
@best_solution ||= pairings_maximizer.find_best_solution
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def pairings_maximizer
|
|
82
|
+
@pairings_maximizer ||= begin
|
|
83
|
+
expected_matches = {}
|
|
84
|
+
actual_matches = {}
|
|
85
|
+
|
|
86
|
+
expected.each_with_index do |e, ei|
|
|
87
|
+
expected_matches[ei] ||= []
|
|
88
|
+
|
|
89
|
+
actual.each_with_index do |a, ai|
|
|
90
|
+
actual_matches[ai] ||= []
|
|
91
|
+
|
|
92
|
+
# Normally we'd call `values_match?(e, a)` here but that contains
|
|
93
|
+
# some extra checks we don't need (e.g. to support nested data
|
|
94
|
+
# structures), and given that it's called N*M times here, it helps
|
|
95
|
+
# perf significantly to implement the matching bit ourselves.
|
|
96
|
+
if (e === a || a == e)
|
|
97
|
+
expected_matches[ei] << ai
|
|
98
|
+
actual_matches[ai] << ei
|
|
99
|
+
end
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
PairingsMaximizer.new(expected_matches, actual_matches)
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
# Once we started supporting composing matchers, the algorithm for this matcher got
|
|
108
|
+
# much more complicated. Consider this expression:
|
|
109
|
+
#
|
|
110
|
+
# expect(["fool", "food"]).to contain_exactly(/foo/, /fool/)
|
|
111
|
+
#
|
|
112
|
+
# This should pass (because we can pair /fool/ with "fool" and /foo/ with "food"), but
|
|
113
|
+
# the original algorithm used by this matcher would pair the first elements it could
|
|
114
|
+
# (/foo/ with "fool"), which would leave /fool/ and "food" unmatched. When we have
|
|
115
|
+
# an expected element which is a matcher that matches a superset of actual items
|
|
116
|
+
# compared to another expected element matcher, we need to consider every possible pairing.
|
|
117
|
+
#
|
|
118
|
+
# This class is designed to maximize the number of actual/expected pairings -- or,
|
|
119
|
+
# conversely, to minimize the number of unpaired items. It's essentially a brute
|
|
120
|
+
# force solution, but with a few heuristics applied to reduce the size of the
|
|
121
|
+
# problem space:
|
|
122
|
+
#
|
|
123
|
+
# * Any items which match none of the items in the other list are immediately
|
|
124
|
+
# placed into the `unmatched_expected_indexes` or `unmatched_actual_indexes` array.
|
|
125
|
+
# The extra items and missing items in the matcher failure message are derived
|
|
126
|
+
# from these arrays.
|
|
127
|
+
# * Any items which reciprocally match only each other are paired up and not
|
|
128
|
+
# considered further.
|
|
129
|
+
#
|
|
130
|
+
# What's left is only the items which match multiple items from the other list
|
|
131
|
+
# (or vice versa). From here, it performs a brute-force depth-first search,
|
|
132
|
+
# looking for a solution which pairs all elements in both lists, or, barring that,
|
|
133
|
+
# that produces the fewest unmatched items.
|
|
134
|
+
#
|
|
135
|
+
# @private
|
|
136
|
+
class PairingsMaximizer
|
|
137
|
+
Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
|
|
138
|
+
:indeterminate_expected_indexes, :indeterminate_actual_indexes) do
|
|
139
|
+
def worse_than?(other)
|
|
140
|
+
unmatched_item_count > other.unmatched_item_count
|
|
141
|
+
end
|
|
142
|
+
|
|
143
|
+
def candidate?
|
|
144
|
+
indeterminate_expected_indexes.empty? &&
|
|
145
|
+
indeterminate_actual_indexes.empty?
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def ideal?
|
|
149
|
+
candidate? && (
|
|
150
|
+
unmatched_expected_indexes.empty? ||
|
|
151
|
+
unmatched_actual_indexes.empty?
|
|
152
|
+
)
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def unmatched_item_count
|
|
156
|
+
unmatched_expected_indexes.count + unmatched_actual_indexes.count
|
|
157
|
+
end
|
|
158
|
+
|
|
159
|
+
def +(derived_candidate_solution)
|
|
160
|
+
self.class.new(
|
|
161
|
+
unmatched_expected_indexes + derived_candidate_solution.unmatched_expected_indexes,
|
|
162
|
+
unmatched_actual_indexes + derived_candidate_solution.unmatched_actual_indexes,
|
|
163
|
+
# Ignore the indeterminate indexes: by the time we get here,
|
|
164
|
+
# we've dealt with all indeterminates.
|
|
165
|
+
[], []
|
|
166
|
+
)
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
attr_reader :expected_to_actual_matched_indexes, :actual_to_expected_matched_indexes, :solution
|
|
171
|
+
|
|
172
|
+
def initialize(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
|
|
173
|
+
@expected_to_actual_matched_indexes = expected_to_actual_matched_indexes
|
|
174
|
+
@actual_to_expected_matched_indexes = actual_to_expected_matched_indexes
|
|
175
|
+
|
|
176
|
+
unmatched_expected_indexes, indeterminate_expected_indexes =
|
|
177
|
+
categorize_indexes(expected_to_actual_matched_indexes, actual_to_expected_matched_indexes)
|
|
178
|
+
|
|
179
|
+
unmatched_actual_indexes, indeterminate_actual_indexes =
|
|
180
|
+
categorize_indexes(actual_to_expected_matched_indexes, expected_to_actual_matched_indexes)
|
|
181
|
+
|
|
182
|
+
@solution = Solution.new(unmatched_expected_indexes, unmatched_actual_indexes,
|
|
183
|
+
indeterminate_expected_indexes, indeterminate_actual_indexes)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def find_best_solution
|
|
187
|
+
return solution if solution.candidate?
|
|
188
|
+
best_solution_so_far = NullSolution
|
|
189
|
+
|
|
190
|
+
expected_index = solution.indeterminate_expected_indexes.first
|
|
191
|
+
actuals = expected_to_actual_matched_indexes[expected_index]
|
|
192
|
+
|
|
193
|
+
actuals.each do |actual_index|
|
|
194
|
+
solution = best_solution_for_pairing(expected_index, actual_index)
|
|
195
|
+
return solution if solution.ideal?
|
|
196
|
+
best_solution_so_far = solution if best_solution_so_far.worse_than?(solution)
|
|
197
|
+
end
|
|
198
|
+
|
|
199
|
+
best_solution_so_far
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
private
|
|
203
|
+
|
|
204
|
+
# @private
|
|
205
|
+
# Starting solution that is worse than any other real solution.
|
|
206
|
+
NullSolution = Class.new do
|
|
207
|
+
def self.worse_than?(other); true; end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
def categorize_indexes(indexes_to_categorize, other_indexes)
|
|
211
|
+
unmatched = []
|
|
212
|
+
indeterminate = []
|
|
213
|
+
|
|
214
|
+
indexes_to_categorize.each_pair do |index, matches|
|
|
215
|
+
if matches.empty?
|
|
216
|
+
unmatched << index
|
|
217
|
+
elsif !reciprocal_single_match?(matches, index, other_indexes)
|
|
218
|
+
indeterminate << index
|
|
219
|
+
end
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
return unmatched, indeterminate
|
|
223
|
+
end
|
|
224
|
+
|
|
225
|
+
def reciprocal_single_match?(matches, index, other_list)
|
|
226
|
+
return false unless matches.one?
|
|
227
|
+
other_list[matches.first] == [index]
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def best_solution_for_pairing(expected_index, actual_index)
|
|
231
|
+
modified_expecteds = apply_pairing_to(
|
|
232
|
+
solution.indeterminate_expected_indexes,
|
|
233
|
+
expected_to_actual_matched_indexes, actual_index)
|
|
234
|
+
|
|
235
|
+
modified_expecteds.delete(expected_index)
|
|
236
|
+
|
|
237
|
+
modified_actuals = apply_pairing_to(
|
|
238
|
+
solution.indeterminate_actual_indexes,
|
|
239
|
+
actual_to_expected_matched_indexes, expected_index)
|
|
240
|
+
|
|
241
|
+
modified_actuals.delete(actual_index)
|
|
242
|
+
|
|
243
|
+
solution + self.class.new(modified_expecteds, modified_actuals).find_best_solution
|
|
244
|
+
end
|
|
245
|
+
|
|
246
|
+
def apply_pairing_to(indeterminates, original_matches, other_list_index)
|
|
247
|
+
indeterminates.inject({}) do |accum, index|
|
|
248
|
+
accum[index] = original_matches[index] - [other_list_index]
|
|
249
|
+
accum
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
253
|
+
end
|
|
254
|
+
end
|
|
255
|
+
end
|
|
256
|
+
end
|
|
@@ -1,22 +1,76 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Matchers
|
|
3
3
|
module BuiltIn
|
|
4
|
+
# @api private
|
|
5
|
+
# Provides the implementation for `eq`.
|
|
6
|
+
# Not intended to be instantiated directly.
|
|
4
7
|
class Eq < BaseMatcher
|
|
8
|
+
|
|
9
|
+
# @api private
|
|
10
|
+
# @return [String]
|
|
11
|
+
def failure_message
|
|
12
|
+
"\nexpected: #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# @api private
|
|
16
|
+
# @return [String]
|
|
17
|
+
def failure_message_when_negated
|
|
18
|
+
"\nexpected: value != #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# @api private
|
|
22
|
+
# @return [String]
|
|
23
|
+
def description
|
|
24
|
+
"#{name_to_sentence} #{@expected.inspect}"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
# @api private
|
|
28
|
+
# @return [Boolean]
|
|
29
|
+
def diffable?
|
|
30
|
+
true
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
5
35
|
def match(expected, actual)
|
|
6
36
|
actual == expected
|
|
7
37
|
end
|
|
8
38
|
|
|
9
|
-
def
|
|
10
|
-
|
|
39
|
+
def format_object(object)
|
|
40
|
+
if Time === object
|
|
41
|
+
format_time(object)
|
|
42
|
+
elsif defined?(DateTime) && DateTime === object
|
|
43
|
+
format_date_time(object)
|
|
44
|
+
elsif defined?(BigDecimal) && BigDecimal === object
|
|
45
|
+
"#{object.to_s 'F'} (#{object.inspect})"
|
|
46
|
+
else
|
|
47
|
+
object.inspect
|
|
48
|
+
end
|
|
11
49
|
end
|
|
12
50
|
|
|
13
|
-
|
|
14
|
-
|
|
51
|
+
TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
|
|
52
|
+
|
|
53
|
+
if Time.method_defined?(:nsec)
|
|
54
|
+
def format_time(time)
|
|
55
|
+
time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
|
|
56
|
+
end
|
|
57
|
+
else # for 1.8.7
|
|
58
|
+
def format_time(time)
|
|
59
|
+
time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
|
|
60
|
+
end
|
|
15
61
|
end
|
|
16
62
|
|
|
17
|
-
|
|
63
|
+
DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
|
|
64
|
+
# ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
|
|
65
|
+
# defined use a custom format string that includes more time precision.
|
|
66
|
+
def format_date_time(date_time)
|
|
67
|
+
if defined?(ActiveSupport)
|
|
68
|
+
date_time.strftime(DATE_TIME_FORMAT)
|
|
69
|
+
else
|
|
70
|
+
date_time.inspect
|
|
71
|
+
end
|
|
72
|
+
end
|
|
18
73
|
end
|
|
19
74
|
end
|
|
20
75
|
end
|
|
21
76
|
end
|
|
22
|
-
|
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Matchers
|
|
3
3
|
module BuiltIn
|
|
4
|
+
# @api private
|
|
5
|
+
# Provides the implementation for `eql`.
|
|
6
|
+
# Not intended to be instantiated directly.
|
|
4
7
|
class Eql < BaseMatcher
|
|
5
|
-
def match(expected, actual)
|
|
6
|
-
actual.eql? expected
|
|
7
|
-
end
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
# @api private
|
|
10
|
+
# @return [String]
|
|
11
|
+
def failure_message
|
|
10
12
|
"\nexpected: #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
|
|
11
13
|
end
|
|
12
14
|
|
|
13
|
-
|
|
15
|
+
# @api private
|
|
16
|
+
# @return [String]
|
|
17
|
+
def failure_message_when_negated
|
|
14
18
|
"\nexpected: value != #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
|
|
15
19
|
end
|
|
16
20
|
|
|
21
|
+
# @api private
|
|
22
|
+
# @return [Boolean]
|
|
17
23
|
def diffable?
|
|
18
24
|
true
|
|
19
25
|
end
|
|
26
|
+
|
|
27
|
+
private
|
|
28
|
+
|
|
29
|
+
def match(expected, actual)
|
|
30
|
+
actual.eql? expected
|
|
31
|
+
end
|
|
20
32
|
end
|
|
21
33
|
end
|
|
22
34
|
end
|
|
@@ -1,46 +1,80 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Matchers
|
|
3
3
|
module BuiltIn
|
|
4
|
+
# @api private
|
|
5
|
+
# Provides the implementation for `equal`.
|
|
6
|
+
# Not intended to be instantiated directly.
|
|
4
7
|
class Equal < BaseMatcher
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
|
|
9
|
+
# @api private
|
|
10
|
+
# @return [String]
|
|
11
|
+
def failure_message
|
|
12
|
+
if expected_is_a_literal_singleton?
|
|
13
|
+
simple_failure_message
|
|
14
|
+
else
|
|
15
|
+
detailed_failure_message
|
|
16
|
+
end
|
|
7
17
|
end
|
|
8
18
|
|
|
9
|
-
|
|
19
|
+
# @api private
|
|
20
|
+
# @return [String]
|
|
21
|
+
def failure_message_when_negated
|
|
10
22
|
return <<-MESSAGE
|
|
11
23
|
|
|
12
|
-
expected #{inspect_object(
|
|
13
|
-
|
|
24
|
+
expected not #{inspect_object(actual)}
|
|
25
|
+
got #{inspect_object(expected)}
|
|
14
26
|
|
|
15
|
-
Compared using equal?, which compares object identity
|
|
16
|
-
but expected and actual are not the same object. Use
|
|
17
|
-
`#{eq_expression}` if you don't care about
|
|
18
|
-
object identity in this example.
|
|
27
|
+
Compared using equal?, which compares object identity.
|
|
19
28
|
|
|
20
29
|
MESSAGE
|
|
21
30
|
end
|
|
22
31
|
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
# @api private
|
|
33
|
+
# @return [Boolean]
|
|
34
|
+
def diffable?
|
|
35
|
+
!expected_is_a_literal_singleton?
|
|
36
|
+
end
|
|
25
37
|
|
|
26
|
-
|
|
27
|
-
got #{inspect_object(expected)}
|
|
38
|
+
private
|
|
28
39
|
|
|
29
|
-
|
|
40
|
+
def match(expected, actual)
|
|
41
|
+
actual.equal? expected
|
|
42
|
+
end
|
|
30
43
|
|
|
31
|
-
|
|
44
|
+
LITERAL_SINGLETONS = [true, false, nil]
|
|
45
|
+
|
|
46
|
+
def expected_is_a_literal_singleton?
|
|
47
|
+
LITERAL_SINGLETONS.include?(expected)
|
|
32
48
|
end
|
|
33
49
|
|
|
34
|
-
def
|
|
50
|
+
def actual_inspected
|
|
51
|
+
if LITERAL_SINGLETONS.include?(actual)
|
|
52
|
+
actual.inspect
|
|
53
|
+
else
|
|
54
|
+
inspect_object(actual)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
35
57
|
|
|
36
|
-
|
|
58
|
+
def simple_failure_message
|
|
59
|
+
"\nexpected #{expected.inspect}\n got #{actual_inspected}\n"
|
|
60
|
+
end
|
|
37
61
|
|
|
38
|
-
def
|
|
39
|
-
|
|
62
|
+
def detailed_failure_message
|
|
63
|
+
return <<-MESSAGE
|
|
64
|
+
|
|
65
|
+
expected #{inspect_object(expected)}
|
|
66
|
+
got #{inspect_object(actual)}
|
|
67
|
+
|
|
68
|
+
Compared using equal?, which compares object identity,
|
|
69
|
+
but expected and actual are not the same object. Use
|
|
70
|
+
`expect(actual).to eq(expected)` if you don't care about
|
|
71
|
+
object identity in this example.
|
|
72
|
+
|
|
73
|
+
MESSAGE
|
|
40
74
|
end
|
|
41
75
|
|
|
42
|
-
def
|
|
43
|
-
|
|
76
|
+
def inspect_object(o)
|
|
77
|
+
"#<#{o.class}:#{o.object_id}> => #{o.inspect}"
|
|
44
78
|
end
|
|
45
79
|
end
|
|
46
80
|
end
|
|
@@ -1,25 +1,89 @@
|
|
|
1
1
|
module RSpec
|
|
2
2
|
module Matchers
|
|
3
3
|
module BuiltIn
|
|
4
|
+
# @api private
|
|
5
|
+
# Provides the implementation for `exist`.
|
|
6
|
+
# Not intended to be instantiated directly.
|
|
4
7
|
class Exist < BaseMatcher
|
|
5
8
|
def initialize(*expected)
|
|
6
9
|
@expected = expected
|
|
7
10
|
end
|
|
8
11
|
|
|
12
|
+
# @api private
|
|
13
|
+
# @return [Boolean]
|
|
9
14
|
def matches?(actual)
|
|
10
15
|
@actual = actual
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
@test = ExistenceTest.new @actual, @expected
|
|
17
|
+
@test.valid_test? && @test.actual_exists?
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @api private
|
|
21
|
+
# @return [Boolean]
|
|
22
|
+
def does_not_match?(actual)
|
|
23
|
+
@actual = actual
|
|
24
|
+
@test = ExistenceTest.new @actual, @expected
|
|
25
|
+
@test.valid_test? && !@test.actual_exists?
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# @api private
|
|
29
|
+
# @return [String]
|
|
30
|
+
def failure_message
|
|
31
|
+
"expected #{@actual.inspect} to exist#{@test.validity_message}"
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
# @api private
|
|
35
|
+
# @return [String]
|
|
36
|
+
def failure_message_when_negated
|
|
37
|
+
"expected #{@actual.inspect} not to exist#{@test.validity_message}"
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
private
|
|
41
|
+
|
|
42
|
+
# @api private
|
|
43
|
+
# Simple class for memoizing actual/expected for this matcher
|
|
44
|
+
# and examining the match
|
|
45
|
+
class ExistenceTest < Struct.new(:actual, :expected)
|
|
46
|
+
|
|
47
|
+
# @api private
|
|
48
|
+
# @return [Boolean]
|
|
49
|
+
def valid_test?
|
|
50
|
+
uniq_truthy_values.size == 1
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# @api private
|
|
54
|
+
# @return [Boolean]
|
|
55
|
+
def actual_exists?
|
|
56
|
+
existence_values.first
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @api private
|
|
60
|
+
# @return [String]
|
|
61
|
+
def validity_message
|
|
62
|
+
case uniq_truthy_values.size
|
|
63
|
+
when 0
|
|
64
|
+
" but it does not respond to either `exist?` or `exists?`"
|
|
65
|
+
when 2
|
|
66
|
+
" but `exist?` and `exists?` returned different values:\n\n"\
|
|
67
|
+
" exist?: #{existence_values.first}\n"\
|
|
68
|
+
"exists?: #{existence_values.last}"
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
private
|
|
73
|
+
|
|
74
|
+
def uniq_truthy_values
|
|
75
|
+
@uniq_truthy_values ||= existence_values.map { |v| !!v }.uniq
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def existence_values
|
|
79
|
+
@existence_values ||= predicates.map { |p| actual.__send__(p, *expected) }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def predicates
|
|
83
|
+
@predicates ||= [:exist?, :exists?].select { |p| actual.respond_to?(p) }
|
|
21
84
|
end
|
|
22
85
|
end
|
|
86
|
+
|
|
23
87
|
end
|
|
24
88
|
end
|
|
25
89
|
end
|