rspec-expectations 3.9.0 → 3.10.1

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.
@@ -93,7 +93,7 @@ module RSpec
93
93
  end
94
94
 
95
95
  def respond_to_matcher
96
- @respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments
96
+ @respond_to_matcher ||= RespondTo.new(*expected.keys).with(0).arguments.tap { |m| m.ignoring_method_signature_failure! }
97
97
  end
98
98
 
99
99
  def respond_to_failure_message_or
@@ -1,13 +1,17 @@
1
+ require 'rspec/matchers/built_in/count_expectation'
2
+
1
3
  module RSpec
2
4
  module Matchers
3
5
  module BuiltIn
4
6
  # @api private
5
7
  # Provides the implementation for `include`.
6
8
  # Not intended to be instantiated directly.
7
- class Include < BaseMatcher
9
+ class Include < BaseMatcher # rubocop:disable Metrics/ClassLength
10
+ include CountExpectation
8
11
  # @private
9
12
  attr_reader :expecteds
10
13
 
14
+ # @api private
11
15
  def initialize(*expecteds)
12
16
  @expecteds = expecteds
13
17
  end
@@ -15,21 +19,29 @@ module RSpec
15
19
  # @api private
16
20
  # @return [Boolean]
17
21
  def matches?(actual)
18
- actual = actual.to_hash if convert_to_hash?(actual)
19
- perform_match(actual) { |v| v }
22
+ check_actual?(actual) &&
23
+ if check_expected_count?
24
+ expected_count_matches?(count_inclusions)
25
+ else
26
+ perform_match { |v| v }
27
+ end
20
28
  end
21
29
 
22
30
  # @api private
23
31
  # @return [Boolean]
24
32
  def does_not_match?(actual)
25
- actual = actual.to_hash if convert_to_hash?(actual)
26
- perform_match(actual) { |v| !v }
33
+ check_actual?(actual) &&
34
+ if check_expected_count?
35
+ !expected_count_matches?(count_inclusions)
36
+ else
37
+ perform_match { |v| !v }
38
+ end
27
39
  end
28
40
 
29
41
  # @api private
30
42
  # @return [String]
31
43
  def description
32
- improve_hash_formatting("include#{readable_list_of(expecteds)}")
44
+ improve_hash_formatting("include#{readable_list_of(expecteds)}#{count_expectation_description}")
33
45
  end
34
46
 
35
47
  # @api private
@@ -62,12 +74,33 @@ module RSpec
62
74
 
63
75
  private
64
76
 
65
- def format_failure_message(preposition)
66
- if actual.respond_to?(:include?)
67
- improve_hash_formatting("expected #{description_of @actual} #{preposition} include#{readable_list_of @divergent_items}")
68
- else
69
- improve_hash_formatting(yield) + ", but it does not respond to `include?`"
77
+ def check_actual?(actual)
78
+ actual = actual.to_hash if convert_to_hash?(actual)
79
+ @actual = actual
80
+ @actual.respond_to?(:include?)
81
+ end
82
+
83
+ def check_expected_count?
84
+ case
85
+ when !has_expected_count?
86
+ return false
87
+ when expecteds.size != 1
88
+ raise NotImplementedError, 'Count constraint supported only when testing for a single value being included'
89
+ when actual.is_a?(Hash)
90
+ raise NotImplementedError, 'Count constraint on hash keys not implemented'
70
91
  end
92
+ true
93
+ end
94
+
95
+ def format_failure_message(preposition)
96
+ msg = if actual.respond_to?(:include?)
97
+ "expected #{description_of @actual} #{preposition}" \
98
+ " include#{readable_list_of @divergent_items}" \
99
+ "#{count_failure_reason('it is included') if has_expected_count?}"
100
+ else
101
+ "#{yield}, but it does not respond to `include?`"
102
+ end
103
+ improve_hash_formatting(msg)
71
104
  end
72
105
 
73
106
  def readable_list_of(items)
@@ -79,10 +112,9 @@ module RSpec
79
112
  end
80
113
  end
81
114
 
82
- def perform_match(actual, &block)
83
- @actual = actual
115
+ def perform_match(&block)
84
116
  @divergent_items = excluded_from_actual(&block)
85
- actual.respond_to?(:include?) && @divergent_items.empty?
117
+ @divergent_items.empty?
86
118
  end
87
119
 
88
120
  def excluded_from_actual
@@ -107,7 +139,10 @@ module RSpec
107
139
  end
108
140
 
109
141
  def actual_hash_includes?(expected_key, expected_value)
110
- actual_value = actual.fetch(expected_key) { return false }
142
+ actual_value =
143
+ actual.fetch(expected_key) do
144
+ actual.find(Proc.new { return false }) { |actual_key, _| values_match?(expected_key, actual_key) }[1]
145
+ end
111
146
  values_match?(expected_value, actual_value)
112
147
  end
113
148
 
@@ -131,6 +166,28 @@ module RSpec
131
166
  actual.any? { |value| values_match?(expected_item, value) }
132
167
  end
133
168
 
169
+ if RUBY_VERSION < '1.9'
170
+ def count_enumerable(expected_item)
171
+ actual.select { |value| values_match?(expected_item, value) }.size
172
+ end
173
+ else
174
+ def count_enumerable(expected_item)
175
+ actual.count { |value| values_match?(expected_item, value) }
176
+ end
177
+ end
178
+
179
+ def count_inclusions
180
+ @divergent_items = expected
181
+ case actual
182
+ when String
183
+ actual.scan(expected.first).length
184
+ when Enumerable
185
+ count_enumerable(expected.first)
186
+ else
187
+ raise NotImplementedError, 'Count constraints are implemented for Enumerable and String values only'
188
+ end
189
+ end
190
+
134
191
  def diff_would_wrongly_highlight_matched_item?
135
192
  return false unless actual.is_a?(String) && expected.is_a?(Array)
136
193
 
@@ -9,13 +9,20 @@ module RSpec
9
9
  class RaiseError
10
10
  include Composable
11
11
 
12
- def initialize(expected_error_or_message=nil, expected_message=nil, &block)
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)
13
19
  @block = block
14
20
  @actual_error = nil
15
- @warn_about_bare_error = expected_error_or_message.nil?
21
+ @warn_about_bare_error = UndefinedValue === expected_error_or_message
22
+ @warn_about_nil_error = expected_error_or_message.nil?
16
23
 
17
24
  case expected_error_or_message
18
- when nil
25
+ when nil, UndefinedValue
19
26
  @expected_error = Exception
20
27
  @expected_message = expected_message
21
28
  when String
@@ -58,8 +65,11 @@ module RSpec
58
65
  end
59
66
  end
60
67
 
61
- warn_about_bare_error if warning_about_bare_error && !negative_expectation
62
- eval_block if !negative_expectation && ready_to_eval_block?
68
+ unless negative_expectation
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?
72
+ end
63
73
 
64
74
  expectation_matched?
65
75
  end
@@ -67,7 +77,7 @@ module RSpec
67
77
 
68
78
  # @private
69
79
  def does_not_match?(given_proc)
70
- warn_for_false_positives
80
+ warn_for_negative_false_positives!
71
81
  !matches?(given_proc, :negative_expectation) && Proc === given_proc
72
82
  end
73
83
 
@@ -131,29 +141,35 @@ module RSpec
131
141
  values_match?(@expected_message, @actual_error.message.to_s)
132
142
  end
133
143
 
134
- def warn_for_false_positives
144
+ def warn_for_negative_false_positives!
135
145
  expression = if expecting_specific_exception? && @expected_message
136
146
  "`expect { }.not_to raise_error(SpecificErrorClass, message)`"
137
147
  elsif expecting_specific_exception?
138
148
  "`expect { }.not_to raise_error(SpecificErrorClass)`"
139
149
  elsif @expected_message
140
150
  "`expect { }.not_to raise_error(message)`"
151
+ elsif @warn_about_nil_error
152
+ "`expect { }.not_to raise_error(nil)`"
141
153
  end
142
154
 
143
155
  return unless expression
144
156
 
145
- warn_about_negative_false_positive expression
157
+ warn_about_negative_false_positive! expression
146
158
  end
147
159
 
148
160
  def handle_warning(message)
149
161
  RSpec::Expectations.configuration.false_positives_handler.call(message)
150
162
  end
151
163
 
152
- def warning_about_bare_error
164
+ def warn_about_bare_error?
153
165
  @warn_about_bare_error && @block.nil?
154
166
  end
155
167
 
156
- def warn_about_bare_error
168
+ def warn_about_nil_error?
169
+ @warn_about_nil_error
170
+ end
171
+
172
+ def warn_about_bare_error!
157
173
  handle_warning("Using the `raise_error` matcher without providing a specific " \
158
174
  "error or message risks false positives, since `raise_error` " \
159
175
  "will match when Ruby raises a `NoMethodError`, `NameError` or " \
@@ -166,11 +182,24 @@ module RSpec
166
182
  "_positives = :nothing`")
167
183
  end
168
184
 
169
- def warn_about_negative_false_positive(expression)
185
+ def warn_about_nil_error!
186
+ handle_warning("Using the `raise_error` matcher with a `nil` error is probably " \
187
+ "unintentional, it risks false positives, since `raise_error` " \
188
+ "will match when Ruby raises a `NoMethodError`, `NameError` or " \
189
+ "`ArgumentError`, potentially allowing the expectation to pass " \
190
+ "without even executing the method you are intending to call. " \
191
+ "#{warning}"\
192
+ "Instead consider providing a specific error class or message. " \
193
+ "This message can be suppressed by setting: " \
194
+ "`RSpec::Expectations.configuration.on_potential_false" \
195
+ "_positives = :nothing`")
196
+ end
197
+
198
+ def warn_about_negative_false_positive!(expression)
170
199
  handle_warning("Using #{expression} risks false positives, since literally " \
171
200
  "any other error would cause the expectation to pass, " \
172
- "including those raised by Ruby (e.g. NoMethodError, NameError " \
173
- "and ArgumentError), meaning the code you are intending to test " \
201
+ "including those raised by Ruby (e.g. `NoMethodError`, `NameError` " \
202
+ "and `ArgumentError`), meaning the code you are intending to test " \
174
203
  "may not even get reached. Instead consider using " \
175
204
  "`expect { }.not_to raise_error` or `expect { }.to raise_error" \
176
205
  "(DifferentSpecificErrorClass)`. This message can be suppressed by " \
@@ -11,6 +11,7 @@ module RSpec
11
11
  @names = names
12
12
  @expected_arity = nil
13
13
  @expected_keywords = []
14
+ @ignoring_method_signature_failure = false
14
15
  @unlimited_arguments = nil
15
16
  @arbitrary_keywords = nil
16
17
  end
@@ -100,6 +101,12 @@ module RSpec
100
101
  "respond to #{pp_names}#{with_arity}"
101
102
  end
102
103
 
104
+ # @api private
105
+ # Used by other matchers to suppress a check
106
+ def ignoring_method_signature_failure!
107
+ @ignoring_method_signature_failure = true
108
+ end
109
+
103
110
  private
104
111
 
105
112
  def find_failing_method_names(actual, filter_method)
@@ -110,33 +117,14 @@ module RSpec
110
117
  end
111
118
 
112
119
  def matches_arity?(actual, name)
113
- expectation = Support::MethodSignatureExpectation.new
114
-
115
- if @expected_arity.is_a?(Range)
116
- expectation.min_count = @expected_arity.min
117
- expectation.max_count = @expected_arity.max
118
- else
119
- expectation.min_count = @expected_arity
120
- end
121
-
122
- expectation.keywords = @expected_keywords
123
- expectation.expect_unlimited_arguments = @unlimited_arguments
124
- expectation.expect_arbitrary_keywords = @arbitrary_keywords
125
-
126
- return true if expectation.empty?
127
-
128
- Support::StrictSignatureVerifier.new(method_signature_for(actual, name)).
129
- with_expectation(expectation).valid?
130
- end
131
-
132
- def method_signature_for(actual, name)
133
- method_handle = Support.method_handle_for(actual, name)
134
-
135
- if name == :new && method_handle.owner === ::Class && ::Class === actual
136
- Support::MethodSignature.new(actual.instance_method(:initialize))
137
- else
138
- Support::MethodSignature.new(method_handle)
139
- end
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."
140
128
  end
141
129
 
142
130
  def with_arity
@@ -168,6 +156,44 @@ module RSpec
168
156
  def pp_names
169
157
  @names.length == 1 ? "##{@names.first}" : description_of(@names)
170
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
196
+ end
171
197
  end
172
198
  end
173
199
  end
@@ -1,3 +1,5 @@
1
+ require 'rspec/matchers/built_in/count_expectation'
2
+
1
3
  RSpec::Support.require_rspec_support 'method_signature_verifier'
2
4
 
3
5
  module RSpec
@@ -97,64 +99,12 @@ module RSpec
97
99
  # Provides the implementation for `yield_control`.
98
100
  # Not intended to be instantiated directly.
99
101
  class YieldControl < BaseMatcher
100
- def initialize
101
- at_least(:once)
102
- end
103
-
104
- # @api public
105
- # Specifies that the method is expected to yield once.
106
- def once
107
- exactly(1)
108
- self
109
- end
110
-
111
- # @api public
112
- # Specifies that the method is expected to yield twice.
113
- def twice
114
- exactly(2)
115
- self
116
- end
117
-
118
- # @api public
119
- # Specifies that the method is expected to yield thrice.
120
- def thrice
121
- exactly(3)
122
- self
123
- end
124
-
125
- # @api public
126
- # Specifies that the method is expected to yield the given number of times.
127
- def exactly(number)
128
- set_expected_yields_count(:==, number)
129
- self
130
- end
131
-
132
- # @api public
133
- # Specifies the maximum number of times the method is expected to yield
134
- def at_most(number)
135
- set_expected_yields_count(:<=, number)
136
- self
137
- end
138
-
139
- # @api public
140
- # Specifies the minimum number of times the method is expected to yield
141
- def at_least(number)
142
- set_expected_yields_count(:>=, number)
143
- self
144
- end
145
-
146
- # @api public
147
- # No-op. Provides syntactic sugar.
148
- def times
149
- self
150
- end
151
-
102
+ include CountExpectation
152
103
  # @private
153
104
  def matches?(block)
154
105
  @probe = YieldProbe.probe(block)
155
106
  return false unless @probe.has_block?
156
-
157
- @probe.num_yields.__send__(@expectation_type, @expected_yields_count)
107
+ expected_count_matches?(@probe.num_yields)
158
108
  end
159
109
 
160
110
  # @private
@@ -181,37 +131,10 @@ module RSpec
181
131
 
182
132
  private
183
133
 
184
- def set_expected_yields_count(relativity, n)
185
- @expectation_type = relativity
186
- @expected_yields_count = case n
187
- when Numeric then n
188
- when :once then 1
189
- when :twice then 2
190
- when :thrice then 3
191
- end
192
- end
193
-
194
134
  def failure_reason
195
135
  return ' but was not a block' unless @probe.has_block?
196
- return '' unless @expected_yields_count
197
- " #{human_readable_expectation_type}#{human_readable_count(@expected_yields_count)}" \
198
- " but yielded #{human_readable_count(@probe.num_yields)}"
199
- end
200
-
201
- def human_readable_expectation_type
202
- case @expectation_type
203
- when :<= then 'at most '
204
- when :>= then 'at least '
205
- else ''
206
- end
207
- end
208
-
209
- def human_readable_count(count)
210
- case count
211
- when 1 then 'once'
212
- when 2 then 'twice'
213
- else "#{count} times"
214
- end
136
+ return "#{count_expectation_description} but did not yield" if @probe.num_yields == 0
137
+ count_failure_reason('yielded')
215
138
  end
216
139
  end
217
140