rspec-expectations 3.1.2 → 3.2.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -24,7 +24,7 @@ module RSpec
24
24
  autoload :Compound, 'rspec/matchers/built_in/compound'
25
25
  autoload :ContainExactly, 'rspec/matchers/built_in/contain_exactly'
26
26
  autoload :Cover, 'rspec/matchers/built_in/cover'
27
- autoload :EndWith, 'rspec/matchers/built_in/start_and_end_with'
27
+ autoload :EndWith, 'rspec/matchers/built_in/start_or_end_with'
28
28
  autoload :Eq, 'rspec/matchers/built_in/eq'
29
29
  autoload :Eql, 'rspec/matchers/built_in/eql'
30
30
  autoload :Equal, 'rspec/matchers/built_in/equal'
@@ -41,7 +41,7 @@ module RSpec
41
41
  autoload :RaiseError, 'rspec/matchers/built_in/raise_error'
42
42
  autoload :RespondTo, 'rspec/matchers/built_in/respond_to'
43
43
  autoload :Satisfy, 'rspec/matchers/built_in/satisfy'
44
- autoload :StartWith, 'rspec/matchers/built_in/start_and_end_with'
44
+ autoload :StartWith, 'rspec/matchers/built_in/start_or_end_with'
45
45
  autoload :ThrowSymbol, 'rspec/matchers/built_in/throw_symbol'
46
46
  autoload :YieldControl, 'rspec/matchers/built_in/yield'
47
47
  autoload :YieldSuccessiveArgs, 'rspec/matchers/built_in/yield'
@@ -215,7 +215,7 @@ module RSpec
215
215
  private
216
216
 
217
217
  def predicate_accessible?
218
- !private_predicate? && predicate_exists?
218
+ actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
219
219
  end
220
220
 
221
221
  # support 1.8.7, evaluate once at load time for performance
@@ -229,10 +229,6 @@ module RSpec
229
229
  end
230
230
  end
231
231
 
232
- def predicate_exists?
233
- actual.respond_to?(predicate) || actual.respond_to?(present_tense_predicate)
234
- end
235
-
236
232
  def predicate_matches?
237
233
  method_name = actual.respond_to?(predicate) ? predicate : present_tense_predicate
238
234
  @predicate_matches = actual.__send__(method_name, *@args, &@block)
@@ -265,11 +261,11 @@ module RSpec
265
261
  end
266
262
 
267
263
  def validity_message
268
- if private_predicate?
269
- "expected #{@actual} to respond to `#{predicate}` but `#{predicate}` is a private method"
270
- elsif !predicate_exists?
271
- "expected #{@actual} to respond to `#{predicate}`"
272
- end
264
+ return nil if predicate_accessible?
265
+
266
+ msg = "expected #{@actual} to respond to `#{predicate}`"
267
+ msg << " but `#{predicate}` is a private method" if private_predicate?
268
+ msg
273
269
  end
274
270
  end
275
271
  end
@@ -3,9 +3,10 @@ module RSpec
3
3
  module BuiltIn
4
4
  # @api private
5
5
  # Base class for `and` and `or` compound matchers.
6
+ # rubocop:disable ClassLength
6
7
  class Compound < BaseMatcher
7
8
  # @private
8
- attr_reader :matcher_1, :matcher_2
9
+ attr_reader :matcher_1, :matcher_2, :evaluator
9
10
 
10
11
  def initialize(matcher_1, matcher_2)
11
12
  @matcher_1 = matcher_1
@@ -36,6 +37,28 @@ module RSpec
36
37
  NestedEvaluator.matcher_expects_call_stack_jump?(matcher_2)
37
38
  end
38
39
 
40
+ # @api private
41
+ # @return [Boolean]
42
+ def diffable?
43
+ matcher_is_diffable?(matcher_1) || matcher_is_diffable?(matcher_2)
44
+ end
45
+
46
+ # @api private
47
+ # @return [RSpec::Matchers::ExpectedsForMultipleDiffs]
48
+ def expected
49
+ return nil unless evaluator
50
+ ::RSpec::Matchers::ExpectedsForMultipleDiffs.for_many_matchers(diffable_matcher_list)
51
+ end
52
+
53
+ protected
54
+
55
+ def diffable_matcher_list
56
+ list = []
57
+ list.concat(diffable_matcher_list_for(matcher_1)) unless matcher_1_matches?
58
+ list.concat(diffable_matcher_list_for(matcher_2)) unless matcher_2_matches?
59
+ list
60
+ end
61
+
39
62
  private
40
63
 
41
64
  def initialize_copy(other)
@@ -88,11 +111,11 @@ module RSpec
88
111
  end
89
112
 
90
113
  def matcher_1_matches?
91
- @evaluator.matcher_matches?(matcher_1)
114
+ evaluator.matcher_matches?(matcher_1)
92
115
  end
93
116
 
94
117
  def matcher_2_matches?
95
- @evaluator.matcher_matches?(matcher_2)
118
+ evaluator.matcher_matches?(matcher_2)
96
119
  end
97
120
 
98
121
  def matcher_supports_block_expectations?(matcher)
@@ -101,6 +124,18 @@ module RSpec
101
124
  false
102
125
  end
103
126
 
127
+ def matcher_is_diffable?(matcher)
128
+ matcher.diffable?
129
+ rescue NoMethodError
130
+ false
131
+ end
132
+
133
+ def diffable_matcher_list_for(matcher)
134
+ return [] unless matcher_is_diffable?(matcher)
135
+ return matcher.diffable_matcher_list if Compound === matcher
136
+ [matcher]
137
+ end
138
+
104
139
  # For value expectations, we can evaluate the matchers sequentially.
105
140
  class SequentialEvaluator
106
141
  def initialize(actual, *)
@@ -10,13 +10,21 @@ module RSpec
10
10
 
11
11
  def initialize(expected)
12
12
  @expected = expected
13
+ @values = {}
13
14
  @respond_to_failed = false
15
+ @negated = false
16
+ end
17
+
18
+ # @private
19
+ def actual
20
+ @values
14
21
  end
15
22
 
16
23
  # @api private
17
24
  # @return [Boolean]
18
25
  def matches?(actual)
19
26
  @actual = actual
27
+ @negated = false
20
28
  return false unless respond_to_attributes?
21
29
  perform_match(:all?)
22
30
  end
@@ -25,6 +33,7 @@ module RSpec
25
33
  # @return [Boolean]
26
34
  def does_not_match?(actual)
27
35
  @actual = actual
36
+ @negated = true
28
37
  return false unless respond_to_attributes?
29
38
  perform_match(:none?)
30
39
  end
@@ -36,33 +45,49 @@ module RSpec
36
45
  improve_hash_formatting "have attributes #{described_items.inspect}"
37
46
  end
38
47
 
48
+ # @api private
49
+ # @return [Boolean]
50
+ def diffable?
51
+ !@respond_to_failed && !@negated
52
+ end
53
+
39
54
  # @api private
40
55
  # @return [String]
41
56
  def failure_message
42
- respond_to_failure_message_or { super }
57
+ respond_to_failure_message_or do
58
+ "expected #{@actual.inspect} to #{description} but had attributes #{ formatted_values }"
59
+ end
43
60
  end
44
61
 
45
62
  # @api private
46
63
  # @return [String]
47
64
  def failure_message_when_negated
48
- respond_to_failure_message_or { super }
65
+ respond_to_failure_message_or { "expected #{@actual.inspect} not to #{description}" }
49
66
  end
50
67
 
51
68
  private
52
69
 
70
+ def cache_all_values
71
+ @values = {}
72
+ expected.each do |attribute_key, _attribute_value|
73
+ actual_value = @actual.__send__(attribute_key)
74
+ @values[attribute_key] = actual_value
75
+ end
76
+ end
77
+
53
78
  def perform_match(predicate)
79
+ cache_all_values
54
80
  expected.__send__(predicate) do |attribute_key, attribute_value|
55
81
  actual_has_attribute?(attribute_key, attribute_value)
56
82
  end
57
83
  end
58
84
 
59
85
  def actual_has_attribute?(attribute_key, attribute_value)
60
- actual_value = actual.__send__(attribute_key)
61
- values_match?(attribute_value, actual_value)
86
+ values_match?(attribute_value, @values.fetch(attribute_key))
62
87
  end
63
88
 
64
89
  def respond_to_attributes?
65
- matches = respond_to_matcher.matches?(actual)
90
+ matches = respond_to_matcher.matches?(@actual)
66
91
  @respond_to_failed = !matches
67
92
  matches
68
93
  end
@@ -78,6 +103,10 @@ module RSpec
78
103
  improve_hash_formatting(yield)
79
104
  end
80
105
  end
106
+
107
+ def formatted_values
108
+ improve_hash_formatting(@values.inspect)
109
+ end
81
110
  end
82
111
  end
83
112
  end
@@ -33,13 +33,13 @@ module RSpec
33
33
  # @api private
34
34
  # @return [String]
35
35
  def failure_message
36
- improve_hash_formatting(super) + invalid_type_message
36
+ improve_hash_formatting(super) + invalid_object_message
37
37
  end
38
38
 
39
39
  # @api private
40
40
  # @return [String]
41
41
  def failure_message_when_negated
42
- improve_hash_formatting(super) + invalid_type_message
42
+ improve_hash_formatting(super) + invalid_object_message
43
43
  end
44
44
 
45
45
  # @api private
@@ -50,7 +50,7 @@ module RSpec
50
50
 
51
51
  private
52
52
 
53
- def invalid_type_message
53
+ def invalid_object_message
54
54
  return '' if actual.respond_to?(:include?)
55
55
  ", but it does not respond to `include?`"
56
56
  end
@@ -1,4 +1,5 @@
1
1
  require 'stringio'
2
+ require 'tempfile'
2
3
 
3
4
  module RSpec
4
5
  module Matchers
@@ -27,6 +28,7 @@ module RSpec
27
28
 
28
29
  # @api public
29
30
  # Tells the matcher to match against stdout.
31
+ # Works only when the main Ruby process prints to stdout
30
32
  def to_stdout
31
33
  @stream_capturer = CaptureStdout
32
34
  self
@@ -34,11 +36,30 @@ module RSpec
34
36
 
35
37
  # @api public
36
38
  # Tells the matcher to match against stderr.
39
+ # Works only when the main Ruby process prints to stderr
37
40
  def to_stderr
38
41
  @stream_capturer = CaptureStderr
39
42
  self
40
43
  end
41
44
 
45
+ # @api public
46
+ # Tells the matcher to match against stdout.
47
+ # Works when subprocesses print to stdout as well.
48
+ # This is significantly (~30x) slower than `to_stdout`
49
+ def to_stdout_from_any_process
50
+ @stream_capturer = CaptureStreamToTempfile.new("stdout", $stdout)
51
+ self
52
+ end
53
+
54
+ # @api public
55
+ # Tells the matcher to match against stderr.
56
+ # Works when subprocesses print to stderr as well.
57
+ # This is significantly (~30x) slower than `to_stderr`
58
+ def to_stderr_from_any_process
59
+ @stream_capturer = CaptureStreamToTempfile.new("stderr", $stderr)
60
+ self
61
+ end
62
+
42
63
  # @api private
43
64
  # @return [String]
44
65
  def failure_message
@@ -147,6 +168,26 @@ module RSpec
147
168
  $stderr = original_stream
148
169
  end
149
170
  end
171
+
172
+ # @private
173
+ class CaptureStreamToTempfile < Struct.new(:name, :stream)
174
+ def capture(block)
175
+ original_stream = stream.clone
176
+ captured_stream = Tempfile.new(name)
177
+
178
+ begin
179
+ captured_stream.sync = true
180
+ stream.reopen(captured_stream)
181
+ block.call
182
+ captured_stream.rewind
183
+ captured_stream.read
184
+ ensure
185
+ stream.reopen(original_stream)
186
+ captured_stream.close
187
+ captured_stream.unlink
188
+ end
189
+ end
190
+ end
150
191
  end
151
192
  end
152
193
  end
@@ -4,7 +4,7 @@ module RSpec
4
4
  # @api private
5
5
  # Base class for the `end_with` and `start_with` matchers.
6
6
  # Not intended to be instantiated directly.
7
- class StartAndEndWith < BaseMatcher
7
+ class StartOrEndWith < BaseMatcher
8
8
  def initialize(*expected)
9
9
  @actual_does_not_have_ordered_elements = false
10
10
  @expected = expected.length == 1 ? expected.first : expected
@@ -31,11 +31,11 @@ module RSpec
31
31
 
32
32
  private
33
33
 
34
- def match(expected, actual)
34
+ def match(_expected, actual)
35
35
  return false unless actual.respond_to?(:[])
36
36
 
37
37
  begin
38
- return subset_matches? if !(Struct === expected) && expected.respond_to?(:length)
38
+ return true if subsets_comparable? && subset_matches?
39
39
  element_matches?
40
40
  rescue ArgumentError
41
41
  @actual_does_not_have_ordered_elements = true
@@ -43,15 +43,25 @@ module RSpec
43
43
  end
44
44
  end
45
45
 
46
- def actual_is_unordered
47
- ArgumentError.new("#{actual.inspect} does not have ordered elements")
46
+ def subsets_comparable?
47
+ # Structs support the Enumerable interface but don't really have
48
+ # the semantics of a subset of a larger set...
49
+ return false if Struct === expected
50
+
51
+ expected.respond_to?(:length)
48
52
  end
49
53
  end
50
54
 
55
+ # For RSpec 3.1, the base class was named `StartAndEndWith`. For SemVer reasons,
56
+ # we still provide this constant until 4.0.
57
+ # @deprecated Use StartOrEndWith instead.
58
+ # @private
59
+ StartAndEndWith = StartOrEndWith
60
+
51
61
  # @api private
52
62
  # Provides the implementation for `start_with`.
53
63
  # Not intended to be instantiated directly.
54
- class StartWith < StartAndEndWith
64
+ class StartWith < StartOrEndWith
55
65
  private
56
66
 
57
67
  def subset_matches?
@@ -66,7 +76,7 @@ module RSpec
66
76
  # @api private
67
77
  # Provides the implementation for `end_with`.
68
78
  # Not intended to be instantiated directly.
69
- class EndWith < StartAndEndWith
79
+ class EndWith < StartOrEndWith
70
80
  private
71
81
 
72
82
  def subset_matches?
@@ -94,8 +94,7 @@ module RSpec
94
94
  # Not intended to be instantiated directly.
95
95
  class YieldControl < BaseMatcher
96
96
  def initialize
97
- @expectation_type = nil
98
- @expected_yields_count = nil
97
+ at_least(:once)
99
98
  end
100
99
 
101
100
  # @api public
@@ -151,11 +150,7 @@ module RSpec
151
150
  @probe = YieldProbe.probe(block)
152
151
  return false unless @probe.has_block?
153
152
 
154
- if @expectation_type
155
- @probe.num_yields.__send__(@expectation_type, @expected_yields_count)
156
- else
157
- @probe.yielded_once?(:yield_control)
158
- end
153
+ @probe.num_yields.__send__(@expectation_type, @expected_yields_count)
159
154
  end
160
155
 
161
156
  # @private
@@ -195,10 +190,11 @@ module RSpec
195
190
  def failure_reason
196
191
  return " but was not a block" unless @probe.has_block?
197
192
  return '' unless @expected_yields_count
198
- " #{human_readable_expecation_type}#{human_readable_count}"
193
+ " #{human_readable_expectation_type}#{human_readable_count(@expected_yields_count)}" \
194
+ " but yielded #{human_readable_count(@probe.num_yields)}"
199
195
  end
200
196
 
201
- def human_readable_expecation_type
197
+ def human_readable_expectation_type
202
198
  case @expectation_type
203
199
  when :<= then 'at most '
204
200
  when :>= then 'at least '
@@ -206,11 +202,11 @@ module RSpec
206
202
  end
207
203
  end
208
204
 
209
- def human_readable_count
210
- case @expected_yields_count
205
+ def human_readable_count(count)
206
+ case count
211
207
  when 1 then "once"
212
208
  when 2 then "twice"
213
- else "#{@expected_yields_count} times"
209
+ else "#{count} times"
214
210
  end
215
211
  end
216
212
  end
@@ -5,12 +5,30 @@ module RSpec
5
5
  # Defines a custom matcher.
6
6
  # @see RSpec::Matchers
7
7
  def define(name, &declarations)
8
- define_method name do |*expected|
9
- RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected)
8
+ warn_about_block_args(name, declarations)
9
+ define_method name do |*expected, &block_arg|
10
+ RSpec::Matchers::DSL::Matcher.new(name, declarations, self, *expected, &block_arg)
10
11
  end
11
12
  end
12
13
  alias_method :matcher, :define
13
14
 
15
+ private
16
+
17
+ if Proc.method_defined?(:parameters)
18
+ def warn_about_block_args(name, declarations)
19
+ declarations.parameters.each do |type, arg_name|
20
+ next unless type == :block
21
+ RSpec.warning("Your `#{name}` custom matcher receives a block argument (`#{arg_name}`), " \
22
+ "but due to limitations in ruby, RSpec cannot provide the block. Instead, " \
23
+ "use the `block_arg` method to access the block")
24
+ end
25
+ end
26
+ else
27
+ def warn_about_block_args(*)
28
+ # There's no way to detect block params on 1.8 since the method reflection APIs don't expose it
29
+ end
30
+ end
31
+
14
32
  RSpec.configure { |c| c.extend self } if RSpec.respond_to?(:configure)
15
33
 
16
34
  # Contains the methods that are available from within the
@@ -165,6 +183,12 @@ module RSpec
165
183
  # hash been enabled, the chained method name and args will be added to the
166
184
  # default description and failure message.
167
185
  #
186
+ # In the common case where you just want the chained method to store some
187
+ # value(s) for later use (e.g. in `match`), you can provide one or more
188
+ # attribute names instead of a block; the chained method will store its
189
+ # arguments in instance variables with those names, and the values will
190
+ # be exposed via getters.
191
+ #
168
192
  # @example
169
193
  #
170
194
  # RSpec::Matchers.define :have_errors_on do |key|
@@ -178,19 +202,40 @@ module RSpec
178
202
  # end
179
203
  #
180
204
  # expect(minor).to have_errors_on(:age).with("Not old enough to participate")
181
- def chain(name, &definition)
182
- define_user_override(name, definition) do |*args, &block|
205
+ def chain(method_name, *attr_names, &definition)
206
+ unless block_given? ^ attr_names.any?
207
+ raise ArgumentError, "You must pass either a block or some attribute names (but not both) to `chain`."
208
+ end
209
+
210
+ definition = assign_attributes(attr_names) if attr_names.any?
211
+
212
+ define_user_override(method_name, definition) do |*args, &block|
183
213
  super(*args, &block)
184
- @chained_method_clauses.push([name, args])
214
+ @chained_method_clauses.push([method_name, args])
185
215
  self
186
216
  end
187
217
  end
188
218
 
219
+ def assign_attributes(attr_names)
220
+ attr_reader(*attr_names)
221
+ private(*attr_names)
222
+
223
+ lambda do |*attr_values|
224
+ attr_names.zip(attr_values) do |attr_name, attr_value|
225
+ instance_variable_set(:"@#{attr_name}", attr_value)
226
+ end
227
+ end
228
+ end
229
+
230
+ # assign_attributes isn't defined in the private section below because
231
+ # that makes MRI 1.9.2 emit a warning about private attributes.
232
+ private :assign_attributes
233
+
189
234
  private
190
235
 
191
236
  # Does the following:
192
237
  #
193
- # - Defines the named method usign a user-provided block
238
+ # - Defines the named method using a user-provided block
194
239
  # in @user_method_defs, which is included as an ancestor
195
240
  # in the singleton class in which we eval the `define` block.
196
241
  # - Defines an overriden definition for the same method
@@ -309,13 +354,17 @@ module RSpec
309
354
  # Could be useful to extract details for a failure message.
310
355
  attr_reader :rescued_exception
311
356
 
357
+ # The block parameter used in the expectation
358
+ attr_reader :block_arg
359
+
312
360
  # @api private
313
- def initialize(name, declarations, matcher_execution_context, *expected)
361
+ def initialize(name, declarations, matcher_execution_context, *expected, &block_arg)
314
362
  @name = name
315
363
  @actual = nil
316
364
  @expected_as_array = expected
317
365
  @matcher_execution_context = matcher_execution_context
318
366
  @chained_method_clauses = []
367
+ @block_arg = block_arg
319
368
 
320
369
  class << self
321
370
  # See `Macros#define_user_override` above, for an explanation.