PageRenderTime 46ms CodeModel.GetById 14ms app.highlight 28ms RepoModel.GetById 1ms app.codeStats 1ms

/vendor/bundle/jruby/2.1/gems/rspec-core-2.14.8/lib/rspec/core/memoized_helpers.rb

https://github.com/delowong/logstash
Ruby | 514 lines | 152 code | 30 blank | 332 comment | 11 complexity | 2b651e67d9d969c9c64bdabcef5e5f97 MD5 | raw file
  1module RSpec
  2  module Core
  3    module MemoizedHelpers
  4      # @note `subject` was contributed by Joe Ferris to support the one-liner
  5      #   syntax embraced by shoulda matchers:
  6      #
  7      #       describe Widget do
  8      #         it { should validate_presence_of(:name) }
  9      #       end
 10      #
 11      #   While the examples below demonstrate how to use `subject`
 12      #   explicitly in examples, we recommend that you define a method with
 13      #   an intention revealing name instead.
 14      #
 15      # @example
 16      #
 17      #   # explicit declaration of subject
 18      #   describe Person do
 19      #     subject { Person.new(:birthdate => 19.years.ago) }
 20      #     it "should be eligible to vote" do
 21      #       subject.should be_eligible_to_vote
 22      #       # ^ ^ explicit reference to subject not recommended
 23      #     end
 24      #   end
 25      #
 26      #   # implicit subject => { Person.new }
 27      #   describe Person do
 28      #     it "should be eligible to vote" do
 29      #       subject.should be_eligible_to_vote
 30      #       # ^ ^ explicit reference to subject not recommended
 31      #     end
 32      #   end
 33      #
 34      #   # one-liner syntax - should is invoked on subject
 35      #   describe Person do
 36      #     it { should be_eligible_to_vote }
 37      #   end
 38      #
 39      # @note Because `subject` is designed to create state that is reset between
 40      #   each example, and `before(:all)` is designed to setup state that is
 41      #   shared across _all_ examples in an example group, `subject` is _not_
 42      #   intended to be used in a `before(:all)` hook. RSpec 2.13.1 prints
 43      #   a warning when you reference a `subject` from `before(:all)` and we plan
 44      #   to have it raise an error in RSpec 3.
 45      #
 46      # @see #should
 47      def subject
 48        __memoized.fetch(:subject) do
 49          __memoized[:subject] = begin
 50            described = described_class || self.class.description
 51            Class === described ? described.new : described
 52          end
 53        end
 54      end
 55
 56      # When `should` is called with no explicit receiver, the call is
 57      # delegated to the object returned by `subject`. Combined with an
 58      # implicit subject this supports very concise expressions.
 59      #
 60      # @example
 61      #
 62      #   describe Person do
 63      #     it { should be_eligible_to_vote }
 64      #   end
 65      #
 66      # @see #subject
 67      def should(matcher=nil, message=nil)
 68        RSpec::Expectations::PositiveExpectationHandler.handle_matcher(subject, matcher, message)
 69      end
 70
 71      # Just like `should`, `should_not` delegates to the subject (implicit or
 72      # explicit) of the example group.
 73      #
 74      # @example
 75      #
 76      #   describe Person do
 77      #     it { should_not be_eligible_to_vote }
 78      #   end
 79      #
 80      # @see #subject
 81      def should_not(matcher=nil, message=nil)
 82        RSpec::Expectations::NegativeExpectationHandler.handle_matcher(subject, matcher, message)
 83      end
 84
 85      private
 86
 87      # @private
 88      def __memoized
 89        @__memoized ||= {}
 90      end
 91
 92      # Used internally to customize the behavior of the
 93      # memoized hash when used in a `before(:all)` hook.
 94      #
 95      # @private
 96      class BeforeAllMemoizedHash
 97        def initialize(example_group_instance)
 98          @example_group_instance = example_group_instance
 99          @hash = {}
100        end
101
102        def self.isolate_for_before_all(example_group_instance)
103          example_group_instance.instance_eval do
104            @__memoized = BeforeAllMemoizedHash.new(self)
105
106            begin
107              yield
108            ensure
109              @__memoized.preserve_accessed_lets
110              @__memoized = nil
111            end
112          end
113        end
114
115        def fetch(key, &block)
116          description = if key == :subject
117            "subject"
118          else
119            "let declaration `#{key}`"
120          end
121
122          ::RSpec.warn_deprecation <<-EOS
123WARNING: #{description} accessed in a `before(:all)` hook at:
124  #{caller[1]}
125
126This is deprecated behavior that will not be supported in RSpec 3.
127
128`let` and `subject` declarations are not intended to be called
129in a `before(:all)` hook, as they exist to define state that
130is reset between each example, while `before(:all)` exists to
131define state that is shared across examples in an example group.
132EOS
133
134          @hash.fetch(key, &block)
135        end
136
137        def []=(key, value)
138          @hash[key] = value
139        end
140
141        def preserve_accessed_lets
142          hash = @hash
143
144          @example_group_instance.class.class_eval do
145            hash.each do |key, value|
146              undef_method(key) if method_defined?(key)
147              define_method(key) { value }
148            end
149          end
150        end
151      end
152
153      def self.included(mod)
154        mod.extend(ClassMethods)
155      end
156
157      module ClassMethods
158        # Generates a method whose return value is memoized after the first
159        # call. Useful for reducing duplication between examples that assign
160        # values to the same local variable.
161        #
162        # @note `let` _can_ enhance readability when used sparingly (1,2, or
163        #   maybe 3 declarations) in any given example group, but that can
164        #   quickly degrade with overuse. YMMV.
165        #
166        # @note `let` uses an `||=` conditional that has the potential to
167        #   behave in surprising ways in examples that spawn separate threads,
168        #   though we have yet to see this in practice. You've been warned.
169        #
170        # @note Because `let` is designed to create state that is reset between
171        #   each example, and `before(:all)` is designed to setup state that is
172        #   shared across _all_ examples in an example group, `let` is _not_
173        #   intended to be used in a `before(:all)` hook. RSpec 2.13.1 prints
174        #   a warning when you reference a `let` from `before(:all)` and we plan
175        #   to have it raise an error in RSpec 3.
176        #
177        # @example
178        #
179        #   describe Thing do
180        #     let(:thing) { Thing.new }
181        #
182        #     it "does something" do
183        #       # first invocation, executes block, memoizes and returns result
184        #       thing.do_something
185        #
186        #       # second invocation, returns the memoized value
187        #       thing.should be_something
188        #     end
189        #   end
190        def let(name, &block)
191          # We have to pass the block directly to `define_method` to
192          # allow it to use method constructs like `super` and `return`.
193          raise "#let or #subject called without a block" if block.nil?
194          MemoizedHelpers.module_for(self).send(:define_method, name, &block)
195
196          # Apply the memoization. The method has been defined in an ancestor
197          # module so we can use `super` here to get the value.
198          define_method(name) do
199            __memoized.fetch(name) { |k| __memoized[k] = super(&nil) }
200          end
201        end
202
203        # Just like `let`, except the block is invoked by an implicit `before`
204        # hook. This serves a dual purpose of setting up state and providing a
205        # memoized reference to that state.
206        #
207        # @example
208        #
209        #   class Thing
210        #     def self.count
211        #       @count ||= 0
212        #     end
213        #
214        #     def self.count=(val)
215        #       @count += val
216        #     end
217        #
218        #     def self.reset_count
219        #       @count = 0
220        #     end
221        #
222        #     def initialize
223        #       self.class.count += 1
224        #     end
225        #   end
226        #
227        #   describe Thing do
228        #     after(:each) { Thing.reset_count }
229        #
230        #     context "using let" do
231        #       let(:thing) { Thing.new }
232        #
233        #       it "is not invoked implicitly" do
234        #         Thing.count.should eq(0)
235        #       end
236        #
237        #       it "can be invoked explicitly" do
238        #         thing
239        #         Thing.count.should eq(1)
240        #       end
241        #     end
242        #
243        #     context "using let!" do
244        #       let!(:thing) { Thing.new }
245        #
246        #       it "is invoked implicitly" do
247        #         Thing.count.should eq(1)
248        #       end
249        #
250        #       it "returns memoized version on first invocation" do
251        #         thing
252        #         Thing.count.should eq(1)
253        #       end
254        #     end
255        #   end
256        def let!(name, &block)
257          let(name, &block)
258          before { __send__(name) }
259        end
260
261        # Declares a `subject` for an example group which can then be the
262        # implicit receiver (through delegation) of calls to `should`.
263        #
264        # Given a `name`, defines a method with that name which returns the
265        # `subject`. This lets you declare the subject once and access it
266        # implicitly in one-liners and explicitly using an intention revealing
267        # name.
268        #
269        # @param [String,Symbol] name used to define an accessor with an
270        #   intention revealing name
271        # @param block defines the value to be returned by `subject` in examples
272        #
273        # @example
274        #
275        #   describe CheckingAccount, "with $50" do
276        #     subject { CheckingAccount.new(Money.new(50, :USD)) }
277        #     it { should have_a_balance_of(Money.new(50, :USD)) }
278        #     it { should_not be_overdrawn }
279        #   end
280        #
281        #   describe CheckingAccount, "with a non-zero starting balance" do
282        #     subject(:account) { CheckingAccount.new(Money.new(50, :USD)) }
283        #     it { should_not be_overdrawn }
284        #     it "has a balance equal to the starting balance" do
285        #       account.balance.should eq(Money.new(50, :USD))
286        #     end
287        #   end
288        #
289        # @see MemoizedHelpers#should
290        def subject(name=nil, &block)
291          if name
292            let(name, &block)
293            alias_method :subject, name
294
295            self::NamedSubjectPreventSuper.send(:define_method, name) do
296              raise NotImplementedError, "`super` in named subjects is not supported"
297            end
298          else
299            let(:subject, &block)
300          end
301        end
302
303        # Just like `subject`, except the block is invoked by an implicit `before`
304        # hook. This serves a dual purpose of setting up state and providing a
305        # memoized reference to that state.
306        #
307        # @example
308        #
309        #   class Thing
310        #     def self.count
311        #       @count ||= 0
312        #     end
313        #
314        #     def self.count=(val)
315        #       @count += val
316        #     end
317        #
318        #     def self.reset_count
319        #       @count = 0
320        #     end
321        #
322        #     def initialize
323        #       self.class.count += 1
324        #     end
325        #   end
326        #
327        #   describe Thing do
328        #     after(:each) { Thing.reset_count }
329        #
330        #     context "using subject" do
331        #       subject { Thing.new }
332        #
333        #       it "is not invoked implicitly" do
334        #         Thing.count.should eq(0)
335        #       end
336        #
337        #       it "can be invoked explicitly" do
338        #         subject
339        #         Thing.count.should eq(1)
340        #       end
341        #     end
342        #
343        #     context "using subject!" do
344        #       subject!(:thing) { Thing.new }
345        #
346        #       it "is invoked implicitly" do
347        #         Thing.count.should eq(1)
348        #       end
349        #
350        #       it "returns memoized version on first invocation" do
351        #         subject
352        #         Thing.count.should eq(1)
353        #       end
354        #     end
355        #   end
356        def subject!(name=nil, &block)
357          subject(name, &block)
358          before { subject }
359        end
360
361        # Creates a nested example group named by the submitted `attribute`,
362        # and then generates an example using the submitted block.
363        #
364        # @example
365        #
366        #   # This ...
367        #   describe Array do
368        #     its(:size) { should eq(0) }
369        #   end
370        #
371        #   # ... generates the same runtime structure as this:
372        #   describe Array do
373        #     describe "size" do
374        #       it "should eq(0)" do
375        #         subject.size.should eq(0)
376        #       end
377        #     end
378        #   end
379        #
380        # The attribute can be a `Symbol` or a `String`. Given a `String`
381        # with dots, the result is as though you concatenated that `String`
382        # onto the subject in an expression.
383        #
384        # @example
385        #
386        #   describe Person do
387        #     subject do
388        #       Person.new.tap do |person|
389        #         person.phone_numbers << "555-1212"
390        #       end
391        #     end
392        #
393        #     its("phone_numbers.first") { should eq("555-1212") }
394        #   end
395        #
396        # When the subject is a `Hash`, you can refer to the Hash keys by
397        # specifying a `Symbol` or `String` in an array.
398        #
399        # @example
400        #
401        #   describe "a configuration Hash" do
402        #     subject do
403        #       { :max_users => 3,
404        #         'admin' => :all_permissions }
405        #     end
406        #
407        #     its([:max_users]) { should eq(3) }
408        #     its(['admin']) { should eq(:all_permissions) }
409        #
410        #     # You can still access to its regular methods this way:
411        #     its(:keys) { should include(:max_users) }
412        #     its(:count) { should eq(2) }
413        #   end
414        #
415        # Note that this method does not modify `subject` in any way, so if you
416        # refer to `subject` in `let` or `before` blocks, you're still
417        # referring to the outer subject.
418        #
419        # @example
420        #
421        #   describe Person do
422        #     subject { Person.new }
423        #     before { subject.age = 25 }
424        #     its(:age) { should eq(25) }
425        #   end
426        def its(attribute, &block)
427          describe(attribute) do
428            if Array === attribute
429              let(:__its_subject) { subject[*attribute] }
430            else
431              let(:__its_subject) do
432                attribute_chain = attribute.to_s.split('.')
433                attribute_chain.inject(subject) do |inner_subject, attr|
434                  inner_subject.send(attr)
435                end
436              end
437            end
438
439            def should(matcher=nil, message=nil)
440              RSpec::Expectations::PositiveExpectationHandler.handle_matcher(__its_subject, matcher, message)
441            end
442
443            def should_not(matcher=nil, message=nil)
444              RSpec::Expectations::NegativeExpectationHandler.handle_matcher(__its_subject, matcher, message)
445            end
446
447            example(&block)
448          end
449        end
450      end
451
452      # @api private
453      #
454      # Gets the LetDefinitions module. The module is mixed into
455      # the example group and is used to hold all let definitions.
456      # This is done so that the block passed to `let` can be
457      # forwarded directly on to `define_method`, so that all method
458      # constructs (including `super` and `return`) can be used in
459      # a `let` block.
460      #
461      # The memoization is provided by a method definition on the
462      # example group that supers to the LetDefinitions definition
463      # in order to get the value to memoize.
464      def self.module_for(example_group)
465        get_constant_or_yield(example_group, :LetDefinitions) do
466          mod = Module.new do
467            include Module.new {
468              example_group.const_set(:NamedSubjectPreventSuper, self)
469            }
470          end
471
472          example_group.const_set(:LetDefinitions, mod)
473          mod
474        end
475      end
476
477      # @api private
478      def self.define_helpers_on(example_group)
479        example_group.send(:include, module_for(example_group))
480      end
481
482      if Module.method(:const_defined?).arity == 1 # for 1.8
483        # @api private
484        #
485        # Gets the named constant or yields.
486        # On 1.8, const_defined? / const_get do not take into
487        # account the inheritance hierarchy.
488        def self.get_constant_or_yield(example_group, name)
489          if example_group.const_defined?(name)
490            example_group.const_get(name)
491          else
492            yield
493          end
494        end
495      else
496        # @api private
497        #
498        # Gets the named constant or yields.
499        # On 1.9, const_defined? / const_get take into account the
500        # the inheritance by default, and accept an argument to
501        # disable this behavior. It's important that we don't
502        # consider inheritance here; each example group level that
503        # uses a `let` should get its own `LetDefinitions` module.
504        def self.get_constant_or_yield(example_group, name)
505          if example_group.const_defined?(name, (check_ancestors = false))
506            example_group.const_get(name, check_ancestors)
507          else
508            yield
509          end
510        end
511      end
512    end
513  end
514end