rspec-expectations 3.0.4 → 3.12.3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +5 -5
  2. checksums.yaml.gz.sig +0 -0
  3. data/.document +1 -1
  4. data/.yardopts +1 -1
  5. data/Changelog.md +530 -5
  6. data/{License.txt → LICENSE.md} +5 -4
  7. data/README.md +73 -31
  8. data/lib/rspec/expectations/block_snippet_extractor.rb +253 -0
  9. data/lib/rspec/expectations/configuration.rb +96 -1
  10. data/lib/rspec/expectations/expectation_target.rb +82 -38
  11. data/lib/rspec/expectations/fail_with.rb +11 -6
  12. data/lib/rspec/expectations/failure_aggregator.rb +229 -0
  13. data/lib/rspec/expectations/handler.rb +36 -15
  14. data/lib/rspec/expectations/minitest_integration.rb +43 -2
  15. data/lib/rspec/expectations/syntax.rb +5 -5
  16. data/lib/rspec/expectations/version.rb +1 -1
  17. data/lib/rspec/expectations.rb +15 -1
  18. data/lib/rspec/matchers/aliased_matcher.rb +79 -4
  19. data/lib/rspec/matchers/built_in/all.rb +11 -0
  20. data/lib/rspec/matchers/built_in/base_matcher.rb +111 -28
  21. data/lib/rspec/matchers/built_in/be.rb +28 -114
  22. data/lib/rspec/matchers/built_in/be_between.rb +1 -1
  23. data/lib/rspec/matchers/built_in/be_instance_of.rb +5 -1
  24. data/lib/rspec/matchers/built_in/be_kind_of.rb +5 -1
  25. data/lib/rspec/matchers/built_in/be_within.rb +5 -12
  26. data/lib/rspec/matchers/built_in/change.rb +171 -63
  27. data/lib/rspec/matchers/built_in/compound.rb +201 -30
  28. data/lib/rspec/matchers/built_in/contain_exactly.rb +73 -12
  29. data/lib/rspec/matchers/built_in/count_expectation.rb +169 -0
  30. data/lib/rspec/matchers/built_in/eq.rb +3 -38
  31. data/lib/rspec/matchers/built_in/eql.rb +2 -2
  32. data/lib/rspec/matchers/built_in/equal.rb +3 -3
  33. data/lib/rspec/matchers/built_in/exist.rb +7 -3
  34. data/lib/rspec/matchers/built_in/has.rb +93 -30
  35. data/lib/rspec/matchers/built_in/have_attributes.rb +114 -0
  36. data/lib/rspec/matchers/built_in/include.rb +133 -25
  37. data/lib/rspec/matchers/built_in/match.rb +79 -2
  38. data/lib/rspec/matchers/built_in/operators.rb +14 -5
  39. data/lib/rspec/matchers/built_in/output.rb +59 -2
  40. data/lib/rspec/matchers/built_in/raise_error.rb +130 -27
  41. data/lib/rspec/matchers/built_in/respond_to.rb +117 -15
  42. data/lib/rspec/matchers/built_in/satisfy.rb +28 -14
  43. data/lib/rspec/matchers/built_in/{start_and_end_with.rb → start_or_end_with.rb} +20 -8
  44. data/lib/rspec/matchers/built_in/throw_symbol.rb +15 -5
  45. data/lib/rspec/matchers/built_in/yield.rb +129 -156
  46. data/lib/rspec/matchers/built_in.rb +5 -3
  47. data/lib/rspec/matchers/composable.rb +24 -36
  48. data/lib/rspec/matchers/dsl.rb +203 -37
  49. data/lib/rspec/matchers/english_phrasing.rb +58 -0
  50. data/lib/rspec/matchers/expecteds_for_multiple_diffs.rb +82 -0
  51. data/lib/rspec/matchers/fail_matchers.rb +42 -0
  52. data/lib/rspec/matchers/generated_descriptions.rb +1 -2
  53. data/lib/rspec/matchers/matcher_delegator.rb +3 -4
  54. data/lib/rspec/matchers/matcher_protocol.rb +105 -0
  55. data/lib/rspec/matchers.rb +267 -144
  56. data.tar.gz.sig +0 -0
  57. metadata +71 -49
  58. metadata.gz.sig +0 -0
  59. data/lib/rspec/matchers/pretty.rb +0 -77
@@ -5,7 +5,7 @@ module RSpec
5
5
  # Base class for `and` and `or` compound matchers.
6
6
  class Compound < BaseMatcher
7
7
  # @private
8
- attr_reader :matcher_1, :matcher_2
8
+ attr_reader :matcher_1, :matcher_2, :evaluator
9
9
 
10
10
  def initialize(matcher_1, matcher_2)
11
11
  @matcher_1 = matcher_1
@@ -14,14 +14,56 @@ module RSpec
14
14
 
15
15
  # @private
16
16
  def does_not_match?(_actual)
17
- raise NotImplementedError, "`expect(...).not_to " \
18
- "matcher.#{conjunction} matcher` is not supported"
17
+ raise NotImplementedError, "`expect(...).not_to matcher.#{conjunction} matcher` " \
18
+ "is not supported, since it creates a bit of an ambiguity. Instead, define negated versions " \
19
+ "of whatever matchers you wish to negate with `RSpec::Matchers.define_negated_matcher` and " \
20
+ "use `expect(...).to matcher.#{conjunction} matcher`."
19
21
  end
20
22
 
21
23
  # @api private
22
24
  # @return [String]
23
25
  def description
24
- singleline_message(matcher_1.description, matcher_2.description)
26
+ "#{matcher_1.description} #{conjunction} #{matcher_2.description}"
27
+ end
28
+
29
+ # @api private
30
+ def supports_block_expectations?
31
+ matcher_supports_block_expectations?(matcher_1) &&
32
+ matcher_supports_block_expectations?(matcher_2)
33
+ end
34
+
35
+ # @api private
36
+ def supports_value_expectations?
37
+ matcher_supports_value_expectations?(matcher_1) &&
38
+ matcher_supports_value_expectations?(matcher_2)
39
+ end
40
+
41
+ # @api private
42
+ def expects_call_stack_jump?
43
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_1) ||
44
+ NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
45
+ end
46
+
47
+ # @api private
48
+ # @return [Boolean]
49
+ def diffable?
50
+ matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2)
51
+ end
52
+
53
+ # @api private
54
+ # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
55
+ def expected
56
+ return nil unless evaluator
57
+ ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
58
+ end
59
+
60
+ protected
61
+
62
+ def diffable_matcher_list
63
+ list = []
64
+ list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches?
65
+ list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches?
66
+ list
25
67
  end
26
68
 
27
69
  private
@@ -32,6 +74,16 @@ module RSpec
32
74
  super
33
75
  end
34
76
 
77
+ def match(_expected, actual)
78
+ evaluator_klass = if supports_block_expectations? && Proc === actual
79
+ NestedEvaluator
80
+ else
81
+ SequentialEvaluator
82
+ end
83
+
84
+ @evaluator = evaluator_klass.new(actual, matcher_1, matcher_2)
85
+ end
86
+
35
87
  def indent_multiline_message(message)
36
88
  message.lines.map do |line|
37
89
  line =~ /\S/ ? ' ' + line : line
@@ -39,30 +91,150 @@ module RSpec
39
91
  end
40
92
 
41
93
  def compound_failure_message
42
- message_1 = matcher_1.failure_message
43
- message_2 = matcher_2.failure_message
94
+ "#{indent_multiline_message(matcher_1.failure_message.sub(/\n+\z/, ''))}" \
95
+ "\n\n...#{conjunction}:" \
96
+ "\n\n#{indent_multiline_message(matcher_2.failure_message.sub(/\A\n+/, ''))}"
97
+ end
44
98
 
45
- if multiline?(message_1) || multiline?(message_2)
46
- multiline_message(message_1, message_2)
47
- else
48
- singleline_message(message_1, message_2)
49
- end
99
+ def matcher_1_matches?
100
+ evaluator.matcher_matches?(matcher_1)
101
+ end
102
+
103
+ def matcher_2_matches?
104
+ evaluator.matcher_matches?(matcher_2)
105
+ end
106
+
107
+ def matcher_supports_block_expectations?(matcher)
108
+ matcher.supports_block_expectations?
109
+ rescue NoMethodError
110
+ false
50
111
  end
51
112
 
52
- def multiline_message(message_1, message_2)
53
- [
54
- indent_multiline_message(message_1.sub(/\n+\z/, '')),
55
- "...#{conjunction}:",
56
- indent_multiline_message(message_2.sub(/\A\n+/, ''))
57
- ].join("\n\n")
113
+ def matcher_supports_value_expectations?(matcher)
114
+ matcher.supports_value_expectations?
115
+ rescue NoMethodError
116
+ true
58
117
  end
59
118
 
60
- def multiline?(message)
61
- message.lines.count > 1
119
+ def matcher_is_diffable?(matcher)
120
+ matcher.diffable?
121
+ rescue NoMethodError
122
+ false
62
123
  end
63
124
 
64
- def singleline_message(message_1, message_2)
65
- [message_1, conjunction, message_2].join(' ')
125
+ def diffable_matcher_list_for(matcher)
126
+ return [] unless matcher_is_diffable?(matcher)
127
+ return matcher.diffable_matcher_list if Compound === matcher
128
+ [matcher]
129
+ end
130
+
131
+ # For value expectations, we can evaluate the matchers sequentially.
132
+ class SequentialEvaluator
133
+ def initialize(actual, *)
134
+ @actual = actual
135
+ end
136
+
137
+ def matcher_matches?(matcher)
138
+ matcher.matches?(@actual)
139
+ end
140
+ end
141
+
142
+ # Normally, we evaluate the matching sequentially. For an expression like
143
+ # `expect(x).to foo.and bar`, this becomes:
144
+ #
145
+ # expect(x).to foo
146
+ # expect(x).to bar
147
+ #
148
+ # For block expectations, we need to nest them instead, so that
149
+ # `expect { x }.to foo.and bar` becomes:
150
+ #
151
+ # expect {
152
+ # expect { x }.to foo
153
+ # }.to bar
154
+ #
155
+ # This is necessary so that the `expect` block is only executed once.
156
+ class NestedEvaluator
157
+ def initialize(actual, matcher_1, matcher_2)
158
+ @actual = actual
159
+ @matcher_1 = matcher_1
160
+ @matcher_2 = matcher_2
161
+ @match_results = {}
162
+
163
+ inner, outer = order_block_matchers
164
+
165
+ @match_results[outer] = outer.matches?(Proc.new do |*args|
166
+ @match_results[inner] = inner.matches?(inner_matcher_block(args))
167
+ end)
168
+ end
169
+
170
+ def matcher_matches?(matcher)
171
+ @match_results.fetch(matcher) do
172
+ raise ArgumentError, "Your #{matcher.description} has no match " \
173
+ "results, this can occur when an unexpected call stack or " \
174
+ "local jump occurs. Perhaps one of your matchers needs to " \
175
+ "declare `expects_call_stack_jump?` as `true`?"
176
+ end
177
+ end
178
+
179
+ private
180
+
181
+ # Some block matchers (such as `yield_xyz`) pass args to the `expect` block.
182
+ # When such a matcher is used as the outer matcher, we need to forward the
183
+ # the args on to the `expect` block.
184
+ def inner_matcher_block(outer_args)
185
+ return @actual if outer_args.empty?
186
+
187
+ Proc.new do |*inner_args|
188
+ unless inner_args.empty?
189
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
190
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
191
+ "since both matchers pass arguments to the block."
192
+ end
193
+
194
+ @actual.call(*outer_args)
195
+ end
196
+ end
197
+
198
+ # For a matcher like `raise_error` or `throw_symbol`, where the block will jump
199
+ # up the call stack, we need to order things so that it is the inner matcher.
200
+ # For example, we need it to be this:
201
+ #
202
+ # expect {
203
+ # expect {
204
+ # x += 1
205
+ # raise "boom"
206
+ # }.to raise_error("boom")
207
+ # }.to change { x }.by(1)
208
+ #
209
+ # ...rather than:
210
+ #
211
+ # expect {
212
+ # expect {
213
+ # x += 1
214
+ # raise "boom"
215
+ # }.to change { x }.by(1)
216
+ # }.to raise_error("boom")
217
+ #
218
+ # In the latter case, the after-block logic in the `change` matcher would never
219
+ # get executed because the `raise "boom"` line would jump to the `rescue` in the
220
+ # `raise_error` logic, so only the former case will work properly.
221
+ #
222
+ # This method figures out which matcher should be the inner matcher and which
223
+ # should be the outer matcher.
224
+ def order_block_matchers
225
+ return @matcher_1, @matcher_2 unless self.class.matcher_expects_call_stack_jump?(@matcher_2)
226
+ return @matcher_2, @matcher_1 unless self.class.matcher_expects_call_stack_jump?(@matcher_1)
227
+
228
+ raise ArgumentError, "(#{@matcher_1.description}) and " \
229
+ "(#{@matcher_2.description}) cannot be combined in a compound expectation " \
230
+ "because they both expect a call stack jump."
231
+ end
232
+
233
+ def self.matcher_expects_call_stack_jump?(matcher)
234
+ matcher.expects_call_stack_jump?
235
+ rescue NoMethodError
236
+ false
237
+ end
66
238
  end
67
239
 
68
240
  # @api public
@@ -71,9 +243,9 @@ module RSpec
71
243
  # @api private
72
244
  # @return [String]
73
245
  def failure_message
74
- if @matcher_1_matches
246
+ if matcher_1_matches?
75
247
  matcher_2.failure_message
76
- elsif @matcher_2_matches
248
+ elsif matcher_2_matches?
77
249
  matcher_1.failure_message
78
250
  else
79
251
  compound_failure_message
@@ -82,11 +254,9 @@ module RSpec
82
254
 
83
255
  private
84
256
 
85
- def match(_expected, actual)
86
- @matcher_1_matches = matcher_1.matches?(actual)
87
- @matcher_2_matches = matcher_2.matches?(actual)
88
-
89
- @matcher_1_matches && @matcher_2_matches
257
+ def match(*)
258
+ super
259
+ matcher_1_matches? && matcher_2_matches?
90
260
  end
91
261
 
92
262
  def conjunction
@@ -105,8 +275,9 @@ module RSpec
105
275
 
106
276
  private
107
277
 
108
- def match(_expected, actual)
109
- matcher_1.matches?(actual) || matcher_2.matches?(actual)
278
+ def match(*)
279
+ super
280
+ matcher_1_matches? || matcher_2_matches?
110
281
  end
111
282
 
112
283
  def conjunction
@@ -1,6 +1,7 @@
1
1
  module RSpec
2
2
  module Matchers
3
3
  module BuiltIn
4
+ # rubocop:disable Metrics/ClassLength
4
5
  # @api private
5
6
  # Provides the implementation for `contain_exactly` and `match_array`.
6
7
  # Not intended to be instantiated directly.
@@ -9,31 +10,74 @@ module RSpec
9
10
  # @return [String]
10
11
  def failure_message
11
12
  if Array === actual
12
- message = "expected collection contained: #{safe_sort(surface_descriptions_in expected).inspect}\n"
13
- message += "actual collection contained: #{safe_sort(actual).inspect}\n"
14
- message += "the missing elements were: #{safe_sort(surface_descriptions_in missing_items).inspect}\n" unless missing_items.empty?
15
- message += "the extra elements were: #{safe_sort(extra_items).inspect}\n" unless extra_items.empty?
16
- message
13
+ generate_failure_message
17
14
  else
18
15
  "expected a collection that can be converted to an array with " \
19
- "`#to_ary` or `#to_a`, but got #{actual.inspect}"
16
+ "`#to_ary` or `#to_a`, but got #{actual_formatted}"
20
17
  end
21
18
  end
22
19
 
23
20
  # @api private
24
21
  # @return [String]
25
22
  def failure_message_when_negated
26
- "`contain_exactly` does not support negation"
23
+ list = EnglishPhrasing.list(surface_descriptions_in(expected))
24
+ "expected #{actual_formatted} not to contain exactly#{list}"
27
25
  end
28
26
 
29
27
  # @api private
30
28
  # @return [String]
31
29
  def description
32
- "contain exactly#{to_sentence(surface_descriptions_in expected)}"
30
+ list = EnglishPhrasing.list(surface_descriptions_in(expected))
31
+ "contain exactly#{list}"
32
+ end
33
+
34
+ def matches?(actual)
35
+ @pairings_maximizer = nil
36
+ @best_solution = nil
37
+ @extra_items = nil
38
+ @missing_items = nil
39
+ super(actual)
33
40
  end
34
41
 
35
42
  private
36
43
 
44
+ def generate_failure_message
45
+ message = expected_collection_line
46
+ message += actual_collection_line
47
+ message += missing_elements_line unless missing_items.empty?
48
+ message += extra_elements_line unless extra_items.empty?
49
+ message
50
+ end
51
+
52
+ def expected_collection_line
53
+ message_line('expected collection contained', expected, true)
54
+ end
55
+
56
+ def actual_collection_line
57
+ message_line('actual collection contained', actual)
58
+ end
59
+
60
+ def missing_elements_line
61
+ message_line('the missing elements were', missing_items, true)
62
+ end
63
+
64
+ def extra_elements_line
65
+ message_line('the extra elements were', extra_items)
66
+ end
67
+
68
+ def describe_collection(collection, surface_descriptions=false)
69
+ if surface_descriptions
70
+ "#{description_of(safe_sort(surface_descriptions_in collection))}\n"
71
+ else
72
+ "#{description_of(safe_sort(collection))}\n"
73
+ end
74
+ end
75
+
76
+ def message_line(prefix, collection, surface_descriptions=false)
77
+ "%-32s%s" % [prefix + ':',
78
+ describe_collection(collection, surface_descriptions)]
79
+ end
80
+
37
81
  def match(_expected, _actual)
38
82
  return false unless convert_actual_to_an_array
39
83
  match_when_sorted? || (extra_items.empty? && missing_items.empty?)
@@ -50,15 +94,30 @@ module RSpec
50
94
  def convert_actual_to_an_array
51
95
  if actual.respond_to?(:to_ary)
52
96
  @actual = actual.to_ary
53
- elsif enumerable?(actual) && actual.respond_to?(:to_a)
97
+ elsif actual.respond_to?(:to_a) && !to_a_disallowed?(actual)
54
98
  @actual = actual.to_a
55
99
  else
56
- return false
100
+ false
57
101
  end
58
102
  end
59
103
 
60
104
  def safe_sort(array)
61
- array.sort rescue array
105
+ array.sort
106
+ rescue Support::AllExceptionsExceptOnesWeMustNotRescue
107
+ array
108
+ end
109
+
110
+ if RUBY_VERSION == "1.8.7"
111
+ def to_a_disallowed?(object)
112
+ case object
113
+ when NilClass, String then true
114
+ else Kernel == RSpec::Support.method_handle_for(object, :to_a).owner
115
+ end
116
+ end
117
+ else
118
+ def to_a_disallowed?(object)
119
+ NilClass === object
120
+ end
62
121
  end
63
122
 
64
123
  def missing_items
@@ -125,6 +184,7 @@ module RSpec
125
184
  #
126
185
  # @private
127
186
  class PairingsMaximizer
187
+ # @private
128
188
  Solution = Struct.new(:unmatched_expected_indexes, :unmatched_actual_indexes,
129
189
  :indeterminate_expected_indexes, :indeterminate_actual_indexes) do
130
190
  def worse_than?(other)
@@ -227,7 +287,7 @@ module RSpec
227
287
 
228
288
  modified_expecteds.delete(expected_index)
229
289
 
230
- modified_actuals = apply_pairing_to(
290
+ modified_actuals = apply_pairing_to(
231
291
  solution.indeterminate_actual_indexes,
232
292
  actual_to_expected_matched_indexes, expected_index)
233
293
 
@@ -244,6 +304,7 @@ module RSpec
244
304
  end
245
305
  end
246
306
  end
307
+ # rubocop:enable Metrics/ClassLength
247
308
  end
248
309
  end
249
310
  end
@@ -0,0 +1,169 @@
1
+ module RSpec
2
+ module Matchers
3
+ module BuiltIn
4
+ # @api private
5
+ # Abstract class to implement `once`, `at_least` and other
6
+ # count constraints.
7
+ module CountExpectation
8
+ # @api public
9
+ # Specifies that the method is expected to match once.
10
+ def once
11
+ exactly(1)
12
+ end
13
+
14
+ # @api public
15
+ # Specifies that the method is expected to match twice.
16
+ def twice
17
+ exactly(2)
18
+ end
19
+
20
+ # @api public
21
+ # Specifies that the method is expected to match thrice.
22
+ def thrice
23
+ exactly(3)
24
+ end
25
+
26
+ # @api public
27
+ # Specifies that the method is expected to match the given number of times.
28
+ def exactly(number)
29
+ set_expected_count(:==, number)
30
+ self
31
+ end
32
+
33
+ # @api public
34
+ # Specifies the maximum number of times the method is expected to match
35
+ def at_most(number)
36
+ set_expected_count(:<=, number)
37
+ self
38
+ end
39
+
40
+ # @api public
41
+ # Specifies the minimum number of times the method is expected to match
42
+ def at_least(number)
43
+ set_expected_count(:>=, number)
44
+ self
45
+ end
46
+
47
+ # @api public
48
+ # No-op. Provides syntactic sugar.
49
+ def times
50
+ self
51
+ end
52
+
53
+ protected
54
+ # @api private
55
+ attr_reader :count_expectation_type, :expected_count
56
+
57
+ private
58
+
59
+ if RUBY_VERSION.to_f > 1.8
60
+ def cover?(count, number)
61
+ count.cover?(number)
62
+ end
63
+ else
64
+ def cover?(count, number)
65
+ number >= count.first && number <= count.last
66
+ end
67
+ end
68
+
69
+ def expected_count_matches?(actual_count)
70
+ @actual_count = actual_count
71
+ return @actual_count > 0 unless count_expectation_type
72
+ return cover?(expected_count, actual_count) if count_expectation_type == :<=>
73
+
74
+ @actual_count.__send__(count_expectation_type, expected_count)
75
+ end
76
+
77
+ def has_expected_count?
78
+ !!count_expectation_type
79
+ end
80
+
81
+ def set_expected_count(relativity, n)
82
+ raise_unsupported_count_expectation if unsupported_count_expectation?(relativity)
83
+
84
+ count = count_constraint_to_number(n)
85
+
86
+ if count_expectation_type == :<= && relativity == :>=
87
+ raise_impossible_count_expectation(count) if count > expected_count
88
+ @count_expectation_type = :<=>
89
+ @expected_count = count..expected_count
90
+ elsif count_expectation_type == :>= && relativity == :<=
91
+ raise_impossible_count_expectation(count) if count < expected_count
92
+ @count_expectation_type = :<=>
93
+ @expected_count = expected_count..count
94
+ else
95
+ @count_expectation_type = relativity
96
+ @expected_count = count
97
+ end
98
+ end
99
+
100
+ def raise_impossible_count_expectation(count)
101
+ text =
102
+ case count_expectation_type
103
+ when :<= then "at_least(#{count}).at_most(#{expected_count})"
104
+ when :>= then "at_least(#{expected_count}).at_most(#{count})"
105
+ end
106
+ raise ArgumentError, "The constraint #{text} is not possible"
107
+ end
108
+
109
+ def raise_unsupported_count_expectation
110
+ text =
111
+ case count_expectation_type
112
+ when :<= then "at_least"
113
+ when :>= then "at_most"
114
+ when :<=> then "at_least/at_most combination"
115
+ else "count"
116
+ end
117
+ raise ArgumentError, "Multiple #{text} constraints are not supported"
118
+ end
119
+
120
+ def count_constraint_to_number(n)
121
+ case n
122
+ when Numeric then n
123
+ when :once then 1
124
+ when :twice then 2
125
+ when :thrice then 3
126
+ else
127
+ raise ArgumentError, "Expected a number, :once, :twice or :thrice," \
128
+ " but got #{n}"
129
+ end
130
+ end
131
+
132
+ def unsupported_count_expectation?(relativity)
133
+ return true if count_expectation_type == :==
134
+ return true if count_expectation_type == :<=>
135
+ (count_expectation_type == :<= && relativity == :<=) ||
136
+ (count_expectation_type == :>= && relativity == :>=)
137
+ end
138
+
139
+ def count_expectation_description
140
+ "#{human_readable_expectation_type}#{human_readable_count(expected_count)}"
141
+ end
142
+
143
+ def count_failure_reason(action)
144
+ "#{count_expectation_description}" \
145
+ " but #{action}#{human_readable_count(@actual_count)}"
146
+ end
147
+
148
+ def human_readable_expectation_type
149
+ case count_expectation_type
150
+ when :<= then ' at most'
151
+ when :>= then ' at least'
152
+ when :<=> then ' between'
153
+ else ''
154
+ end
155
+ end
156
+
157
+ def human_readable_count(count)
158
+ case count
159
+ when Range then " #{count.first} and #{count.last} times"
160
+ when nil then ''
161
+ when 1 then ' once'
162
+ when 2 then ' twice'
163
+ else " #{count} times"
164
+ end
165
+ end
166
+ end
167
+ end
168
+ end
169
+ end
@@ -8,19 +8,19 @@ module RSpec
8
8
  # @api private
9
9
  # @return [String]
10
10
  def failure_message
11
- "\nexpected: #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
11
+ "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
12
12
  end
13
13
 
14
14
  # @api private
15
15
  # @return [String]
16
16
  def failure_message_when_negated
17
- "\nexpected: value != #{format_object(expected)}\n got: #{format_object(actual)}\n\n(compared using ==)\n"
17
+ "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using ==)\n"
18
18
  end
19
19
 
20
20
  # @api private
21
21
  # @return [String]
22
22
  def description
23
- "#{name_to_sentence} #{@expected.inspect}"
23
+ "eq #{expected_formatted}"
24
24
  end
25
25
 
26
26
  # @api private
@@ -34,41 +34,6 @@ module RSpec
34
34
  def match(expected, actual)
35
35
  actual == expected
36
36
  end
37
-
38
- def format_object(object)
39
- if Time === object
40
- format_time(object)
41
- elsif defined?(DateTime) && DateTime === object
42
- format_date_time(object)
43
- elsif defined?(BigDecimal) && BigDecimal === object
44
- "#{object.to_s 'F'} (#{object.inspect})"
45
- else
46
- object.inspect
47
- end
48
- end
49
-
50
- TIME_FORMAT = "%Y-%m-%d %H:%M:%S"
51
-
52
- if Time.method_defined?(:nsec)
53
- def format_time(time)
54
- time.strftime("#{TIME_FORMAT}.#{"%09d" % time.nsec} %z")
55
- end
56
- else # for 1.8.7
57
- def format_time(time)
58
- time.strftime("#{TIME_FORMAT}.#{"%06d" % time.usec} %z")
59
- end
60
- end
61
-
62
- DATE_TIME_FORMAT = "%a, %d %b %Y %H:%M:%S.%N %z"
63
- # ActiveSupport sometimes overrides inspect. If `ActiveSupport` is
64
- # defined use a custom format string that includes more time precision.
65
- def format_date_time(date_time)
66
- if defined?(ActiveSupport)
67
- date_time.strftime(DATE_TIME_FORMAT)
68
- else
69
- date_time.inspect
70
- end
71
- end
72
37
  end
73
38
  end
74
39
  end
@@ -8,13 +8,13 @@ module RSpec
8
8
  # @api private
9
9
  # @return [String]
10
10
  def failure_message
11
- "\nexpected: #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
11
+ "\nexpected: #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
12
12
  end
13
13
 
14
14
  # @api private
15
15
  # @return [String]
16
16
  def failure_message_when_negated
17
- "\nexpected: value != #{expected.inspect}\n got: #{actual.inspect}\n\n(compared using eql?)\n"
17
+ "\nexpected: value != #{expected_formatted}\n got: #{actual_formatted}\n\n(compared using eql?)\n"
18
18
  end
19
19
 
20
20
  # @api private