File: matcher.rb

package info (click to toggle)
ruby-rspec-expectations 2.14.2-1~bpo70%2B1
  • links: PTS, VCS
  • area: main
  • in suites: wheezy-backports
  • size: 920 kB
  • sloc: ruby: 8,202; makefile: 4
file content (300 lines) | stat: -rw-r--r-- 10,209 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
require 'set'

module RSpec
  module Matchers
    module DSL
      # Provides the context in which the block passed to RSpec::Matchers.define
      # will be evaluated.
      class Matcher
        include RSpec::Matchers::Extensions::InstanceEvalWithArgs
        include RSpec::Matchers::Pretty
        include RSpec::Matchers

        attr_reader :expected, :actual, :rescued_exception
        attr_accessor :matcher_execution_context

        # @api private
        def initialize(name, &declarations)
          @name         = name
          @declarations = declarations
          @actual       = nil
          @diffable     = false
          @expected_exception, @rescued_exception = nil, nil
          @match_for_should_not_block = nil
          @messages = {}
        end

        PERSISTENT_INSTANCE_VARIABLES = [
          :@name, :@declarations, :@diffable,
          :@match_block, :@match_for_should_not_block,
          :@expected_exception
        ].to_set

        # @api private
        def for_expected(*expected)
          @expected = expected
          dup.instance_eval do
            instance_variables.map {|ivar| ivar.intern}.each do |ivar|
              instance_variable_set(ivar, nil) unless (PERSISTENT_INSTANCE_VARIABLES + [:@expected]).include?(ivar)
            end
            @messages = {}
            making_declared_methods_public do
              instance_eval_with_args(*@expected, &@declarations)
            end
            self
          end
        end

        # @api private
        # Used internally by +should+ and +should_not+.
        def matches?(actual)
          @actual = actual
          if @expected_exception
            begin
              instance_eval_with_args(actual, &@match_block)
              true
            rescue @expected_exception => @rescued_exception
              false
            end
          else
            begin
              instance_eval_with_args(actual, &@match_block)
            rescue RSpec::Expectations::ExpectationNotMetError
              false
            end
          end
        end

        # Stores the block that is used to determine whether this matcher passes
        # or fails. The block should return a boolean value. When the matcher is
        # passed to `should` and the block returns `true`, then the expectation
        # passes. Similarly, when the matcher is passed to `should_not` and the
        # block returns `false`, then the expectation passes.
        #
        # Use `match_for_should` when used in conjuntion with
        # `match_for_should_not`.
        #
        # @example
        #
        #     RSpec::Matchers.define :be_even do
        #       match do |actual|
        #         actual.even?
        #       end
        #     end
        #
        #     4.should be_even     # passes
        #     3.should_not be_even # passes
        #     3.should be_even     # fails
        #     4.should_not be_even # fails
        #
        # @yield [Object] actual the actual value (or receiver of should)
        def match(&block)
          @match_block = block
        end

        alias_method :match_for_should, :match

        # Use this to define the block for a negative expectation (`should_not`)
        # when the positive and negative forms require different handling. This
        # is rarely necessary, but can be helpful, for example, when specifying
        # asynchronous processes that require different timeouts.
        #
        # @yield [Object] actual the actual value (or receiver of should)
        def match_for_should_not(&block)
          @match_for_should_not_block = block
        end

        # Use this instead of `match` when the block will raise an exception
        # rather than returning false to indicate a failure.
        #
        # @example
        #
        #     RSpec::Matchers.define :accept_as_valid do |candidate_address|
        #       match_unless_raises ValidationException do |validator|
        #         validator.validate(candidate_address)
        #       end
        #     end
        #
        #     email_validator.should accept_as_valid("person@company.com")
        def match_unless_raises(exception=Exception, &block)
          @expected_exception = exception
          match(&block)
        end

        # Customize the failure messsage to use when this matcher is invoked with
        # `should`. Only use this when the message generated by default doesn't
        # suit your needs.
        #
        # @example
        #
        #     RSpec::Matchers.define :have_strength do |expected|
        #       match { ... }
        #
        #       failure_message_for_should do |actual|
        #         "Expected strength of #{expected}, but had #{actual.strength}"
        #       end
        #     end
        #
        # @yield [Object] actual the actual object
        def failure_message_for_should(&block)
          cache_or_call_cached(:failure_message_for_should, &block)
        end

        # Customize the failure messsage to use when this matcher is invoked with
        # `should_not`. Only use this when the message generated by default
        # doesn't suit your needs.
        #
        # @example
        #
        #     RSpec::Matchers.define :have_strength do |expected|
        #       match { ... }
        #
        #       failure_message_for_should_not do |actual|
        #         "Expected not to have strength of #{expected}, but did"
        #       end
        #     end
        #
        # @yield [Object] actual the actual object
        # @yield [Object] actual the actual object
        def failure_message_for_should_not(&block)
          cache_or_call_cached(:failure_message_for_should_not, &block)
        end


        # Customize the description to use for one-liners.  Only use this when
        # the description generated by default doesn't suit your needs.
        #
        # @example
        #
        #     RSpec::Matchers.define :qualify_for do |expected|
        #       match { ... }
        #
        #       description do
        #         "qualify for #{expected}"
        #       end
        #     end
        def description(&block)
          cache_or_call_cached(:description, &block)
        end

        # Tells the matcher to diff the actual and expected values in the failure
        # message.
        def diffable
          @diffable = true
        end

        # Convenience for defining methods on this matcher to create a fluent
        # interface. The trick about fluent interfaces is that each method must
        # return self in order to chain methods together. `chain` handles that
        # for you.
        #
        # @example
        #
        #     RSpec::Matchers.define :have_errors_on do |key|
        #       chain :with do |message|
        #         @message = message
        #       end
        #
        #       match do |actual|
        #         actual.errors[key] == @message
        #       end
        #     end
        #
        #     minor.should have_errors_on(:age).with("Not old enough to participate")
        def chain(method, &block)
          define_method method do |*args|
            block.call(*args)
            self
          end
        end

        # @api private
        # Used internally by objects returns by +should+ and +should_not+.
        def diffable?
          @diffable
        end

        # @api private
        # Used internally by +should_not+
        def does_not_match?(actual)
          @actual = actual
          @match_for_should_not_block ?
            instance_eval_with_args(actual, &@match_for_should_not_block) :
            !matches?(actual)
        end

        def respond_to?(method, include_private=false)
          super || matcher_execution_context.respond_to?(method, include_private)
        end

        private

        def method_missing(method, *args, &block)
          if matcher_execution_context.respond_to?(method)
            matcher_execution_context.__send__ method, *args, &block
          else
            super(method, *args, &block)
          end
        end

        def include(*args)
          singleton_class.__send__(:include, *args)
        end

        def define_method(name, &block)
          singleton_class.__send__(:define_method, name, &block)
        end

        def making_declared_methods_public
          # Our home-grown instance_exec in ruby 1.8.6 results in any methods
          # declared in the block eval'd by instance_exec in the block to which we
          # are yielding here are scoped private. This is NOT the case for Ruby
          # 1.8.7 or 1.9.
          #
          # Also, due some crazy scoping that I don't understand, these methods
          # are actually available in the specs (something about the matcher being
          # defined in the scope of RSpec::Matchers or within an example), so not
          # doing the following will not cause specs to fail, but they *will*
          # cause features to fail and that will make users unhappy. So don't.
          orig_private_methods = private_methods
          yield
          (private_methods - orig_private_methods).each {|m| singleton_class.__send__ :public, m}
        end

        def cache_or_call_cached(key, &block)
          block ? cache(key, &block) : call_cached(key)
        end

        def cache(key, &block)
          @messages[key] = block
        end

        def call_cached(key)
          if @messages.has_key?(key)
            @messages[key].arity == 1 ? @messages[key].call(@actual) : @messages[key].call
          else
            __send__("default_#{key}")
          end
        end

        def default_description
          "#{name_to_sentence}#{expected_to_sentence}"
        end

        def default_failure_message_for_should
          "expected #{actual.inspect} to #{name_to_sentence}#{expected_to_sentence}"
        end

        def default_failure_message_for_should_not
          "expected #{actual.inspect} not to #{name_to_sentence}#{expected_to_sentence}"
        end

        unless method_defined?(:singleton_class)
          def singleton_class
            class << self; self; end
          end
        end
      end
    end
  end
end