Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- require 'rubygems'
- require 'spec'
- if not defined? BlankSlate
- class BlankSlate
- instance_methods.each { |m| undef_method m unless m =~ /^(__|instance_eval)/ }
- end
- end
- class RDingus < BlankSlate
- def initialize(parent = nil, invocation = nil)
- @parent = parent
- @invocation = invocation
- @invocations = InvocationList.new
- @invocation_expectations = InvocationResultHash.new
- @children = []
- @call_count = 0
- end
- def method_missing(name, *args, &block)
- @call_count += 1
- invocation = InvocationRecord.new(self, name, args)
- @invocations << invocation
- if @invocation_expectations.defined?(invocation)
- result = @invocation_expectations[invocation]
- begin
- if result._dingus?
- result
- end
- rescue NoMethodError
- # We use this kludge, because it's the only way to test for a dingus
- # We need to know it's not a dingus before calling other methods
- if result.is_a?(Proc)
- obj = Object.new
- obj.extend(DingusProc)
- obj.args = args
- obj.method = name
- obj.block = block
- obj.instance_eval &result
- else
- result
- end
- end
- else
- dingus = RDingus.new(self, invocation)
- @children << dingus
- @invocation_expectations[invocation] = dingus
- end
- end
- def _define(&block)
- proxy = DefinitionProxy.new(self, @invocation_expectations)
- if block
- proxy.instance_eval &block
- else
- proxy
- end
- end
- def _calls
- @invocations
- end
- def _dingus?
- true
- end
- def inspect
- "#<RDingus #{__id__}, calls=#{@call_count}>"
- end
- def dup
- self
- end
- def clone
- dup
- end
- public
- module DingusProc
- attr_accessor :method, :args, :block
- end
- class DefinitionProxy < BlankSlate
- def initialize(parent = nil, list = nil)
- @parent = parent
- @list = list || InvocationResultHash.new
- end
- def method_missing(m, *args, &block)
- if block
- invocation = InvocationRecord.new(@parent, m, args, block)
- @list[invocation] = block
- else
- if args.empty?
- invocation = InvocationRecord.new(@parent, m, args)
- dingus = RDingus.new(@parent, invocation)
- @list[invocation] = dingus
- dingus._define
- else
- invocation = InvocationRecord.new(@parent, m, [], args.first)
- @list[invocation] = args.first
- end
- end
- end
- def _define(&block)
- self.instance_eval &block
- end
- def _list
- @list
- end
- end
- class InvocationRecord
- attr_reader :dingus, :method, :args
- attr_accessor :value
- def initialize(dingus, method, args = [], *optional)
- @dingus = dingus
- @method = method
- @args = args
- @value = optional.first
- @has_value = !optional.empty?
- end
- def value=(v)
- @has_value = true
- @value = v
- end
- def has_value?
- @has_value
- end
- def eql?(b)
- self.method == b.method && ((self.args == b.args) || self.any? || b.any?)
- end
- def ==(b)
- self.eql?(b)
- end
- def any?
- @args && @args.first == :any
- end
- def to_s
- "#{method}(#{args.map{|i|i.inspect}.join(", ")})"
- end
- def inspect
- retval = @has_value ? " => #{@value.inspect}" : ""
- "#<Invocation \"#{self}\"#{retval}>"
- end
- end
- class InvocationResultHash
- def initialize
- @list = []
- end
- def []=(invocation, value)
- @list << [invocation, value]
- value
- end
- def [](invocation)
- @list.reverse.each do |inv, value|
- return value if inv == invocation
- end
- nil
- end
- def defined?(invocation)
- @list.any? {|inv, value| inv == invocation}
- end
- def empty?
- @list.empty?
- end
- end
- class InvocationList < Array
- def include?(method, *args)
- if method.is_a? Symbol
- self.any? do |i|
- i.method == method
- end
- else
- raise "Haven't implemented include? for anything but Symbols (given #{method.class})"
- end
- end
- alias_method :has_received?, :include?
- def [](index)
- case index
- when Fixnum
- self.at(index)
- when Symbol
- InvocationList.new self.select{|i| i.method == index }
- else
- raise "Unknown index type: #{index.class}"
- end
- end
- def count
- length
- end
- end
- end
- def should_be_a_dingus(object)
- lambda {
- object._dingus?
- }.should_not raise_error(NoMethodError)
- end
- def should_not_be_a_dingus(object)
- lambda {
- object._dingus?
- }.should raise_error(NoMethodError)
- end
- describe RDingus::DefinitionProxy do
- before :each do
- @rdingus = RDingus.new
- @def = RDingus::DefinitionProxy.new(@rdingus)
- end
- describe "when given a sub defining block" do
- before :each do
- @def._define do
- b_method :result
- end
- @invocation = RDingus::InvocationRecord.new(@rdingus, :b_method)
- end
- it "should record the method's result" do
- should_not_be_a_dingus @def._list[@invocation]
- @def._list[@invocation].should == :result
- end
- end
- describe "when calling methods to record" do
- before :each do
- @def.a_method(:arguments) { :return_value }
- @invocation = RDingus::InvocationRecord.new(@rdingus, :a_method)
- end
- it "should record them in an InvocationResultHash" do
- @def._list.should be_kind_of(RDingus::InvocationResultHash)
- end
- it "should use the given InvocationResultHash if present" do
- @hash = RDingus::InvocationResultHash.new
- @def = RDingus::DefinitionProxy.new @rdingus, @hash
- @def.a_method { :test }
- @def._list[@invocation].should_not be_nil
- end
- describe "with a block" do
- it "should store the block" do
- @def.a_method { :block }
- should_not_be_a_dingus @def._list[@invocation]
- @def._list[@invocation].should be_instance_of(Proc)
- end
- end
- describe "with just a return value" do
- it "should store the value" do
- @def.a_method :value
- should_not_be_a_dingus @def._list[@invocation]
- @def._list[@invocation].should == :value
- end
- end
- describe "with no value or block" do
- it "should return a new dingus' definition proxy" do
- should_not_be_a_dingus @def.a_method._list
- @def.a_method._list.should be_empty
- end
- end
- end
- end
- describe RDingus do
- before :each do
- @rdingus = RDingus.new
- end
- it "should be recognized by the custom matcher 'be_a_dingus'" do
- should_be_a_dingus(@rdingus)
- end
- it "should return an RDingus on method calls" do
- should_be_a_dingus(@rdingus.a_method)
- end
- it "should return the same RDingus for the same method invocation" do
- @rdingus.a_method.__id__.should == @rdingus.a_method.__id__
- end
- it "should return a different RDingus for a different method invocation" do
- @rdingus.a_method.__id__.should_not == @rdingus.b_method.__id__
- end
- it "should return a different RDingus for the same method invocation but with different arguments" do
- @rdingus.a_method.__id__.should_not == @rdingus.a_method(:argument).__id__
- end
- it "should return self on dup" do
- @rdingus.dup.__id__.should == @rdingus.__id__
- end
- it "should return self on clone" do
- @rdingus.clone.__id__.should == @rdingus.__id__
- end
- describe "when calling .inspect" do
- it "should not return an RDingus" do
- should_not_be_a_dingus @rdingus.inspect
- end
- it "should return a string" do
- # This has to be done in reverse because "should" is not
- # defined on a dingus
- String.should === @rdingus.inspect
- end
- end
- describe "when defining return values" do
- # Comparisons with a dingus can be tricky. They will always be "true"
- # (or technically "non nil/false") if compared with something else.
- # You have to check to make sure it's *not* a dingus before making
- # a comparison.
- it "should return what's assigned" do
- @rdingus._define.a_method { :shell }
- should_not_be_a_dingus @rdingus.a_method
- :shell.should == @rdingus.a_method
- end
- it "should return false if assigned" do
- @rdingus._define.a_method { false }
- should_not_be_a_dingus @rdingus.a_method
- false.should == @rdingus.a_method
- end
- it "should return nil if assigned" do
- @rdingus._define.a_method { nil }
- should_not_be_a_dingus @rdingus.a_method
- nil.should == @rdingus.a_method
- end
- it "should not remember as a method call" do
- @rdingus._define.a_method { "something" }
- @rdingus._calls.should be_empty
- end
- it "should not forget other method calls" do
- @rdingus.remember_me
- @rdingus._define.a_method "something"
- @rdingus._calls.map{|i|i.method}.should == [:remember_me]
- end
- describe "and within the result proc" do
- it "should provide access to method name" do
- @rdingus._define.a_method { raise method.to_s }
- lambda { @rdingus.a_method }.should raise_error("a_method")
- end
- it "should provide access to called arguments" do
- @rdingus._define.a_method("arguments") { raise args.first }
- lambda { @rdingus.a_method("arguments") }.should raise_error("arguments")
- end
- it "should provide access to passed block" do
- @rdingus._define.a_method { raise block.call }
- lambda {
- @rdingus.a_method do
- "raise me"
- end
- }.should raise_error("raise me")
- end
- end
- describe "of nested method calls" do
- it "should return what's assigned" do
- @rdingus._define.a_method.b_method :stump
- should_not_be_a_dingus @rdingus.a_method.b_method
- :stump.should == @rdingus.a_method.b_method
- end
- it "should not remember as a method call" do
- @rdingus._define.a_method.b_method "something"
- @rdingus._calls.should be_empty
- end
- end
- describe "with arbitrary arguments" do
- before :each do
- @rdingus._define.a_method(:any) { :stump }
- end
- it "should return what's assigned given any arguments" do
- Symbol.should === @rdingus.a_method(:argument)
- :stump.should == @rdingus.a_method(:argument)
- end
- it "should return what's assigned with no arguments" do
- Symbol.should === @rdingus.a_method
- :stump.should == @rdingus.a_method
- end
- end
- describe "with specific arguments" do
- before :each do
- @rdingus._define.a_method(:argument) { :stump }
- end
- it "should return what's assigned" do
- Symbol.should === @rdingus.a_method(:argument)
- :stump.should == @rdingus.a_method(:argument)
- end
- it "should return an RDingus if arguments don't match" do
- should_be_a_dingus @rdingus.a_method(:argument, :argument2)
- end
- end
- describe "with block syntax" do
- before :each do
- @rdingus._define do
- a_method { :stump }
- end
- end
- it "should return what's assigned" do
- should_not_be_a_dingus @rdingus.a_method
- @rdingus.a_method.should == :stump
- end
- describe "within a block syntax" do
- it "should return what's assigned" do
- @rdingus._define do
- a_method._define do
- b_method :stump
- end
- end
- should_not_be_a_dingus @rdingus.a_method.b_method
- @rdingus.a_method.b_method.should == :stump
- end
- end
- end
- end
- describe "when specifying exceptions" do
- it "should raise given exception" do
- @rdingus._define.raise_me { raise "Exception" }
- lambda { @rdingus.raise_me }.should raise_error("Exception")
- end
- it "should not remember as a method call" do
- @rdingus._define.raise_me {raise "Exception" }
- lambda { @rdingus.raise_me }.should raise_error("Exception")
- @rdingus._calls.map{|i|i.method}.should == [:raise_me]
- end
- it "should not forget other method calls" do
- @rdingus.remember_me
- @rdingus._define.raise_me { raise "Exception" }
- lambda { @rdingus.raise_me }.should raise_error("Exception")
- @rdingus._calls.map{|i|i.method}.should == [:remember_me, :raise_me]
- end
- end
- describe "recording invocations" do
- it "should remember methods invoked" do
- @rdingus.sandwich
- :sandwich.should == @rdingus._calls.first.method
- end
- it "should remember with arguments invoked" do
- @rdingus.sandwich(:with_cheese)
- :with_cheese.should == @rdingus._calls.first.args.first
- end
- it "should count method invocations" do
- @rdingus.sandwich
- @rdingus._calls.count.should == 1
- end
- end
- end
- describe RDingus::InvocationRecord do
- before :each do
- @rdingus = RDingus.new
- @invocation_record = RDingus::InvocationRecord.new(@rdingus, :method, [:arg1, :arg2])
- end
- it "should provide access to it's owning RDingus" do
- @invocation_record.dingus.should == @rdingus
- end
- it "should be equal for same method and arguments" do
- RDingus::InvocationRecord.new(@rdingus, :method, [:arg1, :arg2]).should == @invocation_record
- end
- it "should not be equal for different method" do
- RDingus::InvocationRecord.new(@rdingus, :method2, [:arg1, :arg2]).should_not == @invocation_record
- end
- it "should not be equal for different arguments" do
- RDingus::InvocationRecord.new(@rdingus, :method, [:arg2, :arg3]).should_not == @invocation_record
- end
- it "should define == the same as eql?" do
- RDingus::InvocationRecord.new(@rdingus, :method, [:arg1, :arg2]).should be_eql(@invocation_record)
- end
- describe "when told to accept any arguments" do
- before :each do
- @invocation_record = RDingus::InvocationRecord.new(@rdingus, :method, [:any])
- end
- it "should be equal for same method and same arguments" do
- RDingus::InvocationRecord.new(@rdingus, :method, [:any]).should == @invocation_record
- end
- it "should be equal for same method and different arguments" do
- RDingus::InvocationRecord.new(@rdingus, :method, [:other, :args]).should == @invocation_record
- end
- it "should not be equal for different method" do
- RDingus::InvocationRecord.new(@rdingus, :method2, [:arg1, :arg2]).should_not == @invocation_record
- end
- end
- it "should convert to a useful string" do
- @invocation_record.to_s.should == "method(:arg1, :arg2)"
- end
- it "should have a useful inspect result" do
- @invocation_record.value = :arg3
- @invocation_record.inspect.should == "#<Invocation \"method(:arg1, :arg2)\" => :arg3>"
- end
- end
- describe RDingus::InvocationResultHash do
- before :each do
- @rdingus = RDingus.new
- @invocation = RDingus::InvocationRecord.new(@rdingus, :method, [:arg1, :arg2])
- @invocation_list = RDingus::InvocationResultHash.new
- end
- it "should store and retrieve for same invocation" do
- @invocation_list[@invocation] = "test"
- @invocation_list[@invocation].should == "test"
- end
- it "should store and retrieve for equivalent invocations" do
- @invocation_list[RDingus::InvocationRecord.new(@rdingus, :method, [:any])] = 'test'
- @invocation_list[@invocation].should == "test"
- end
- it "should not store and retrieve for non equivalent invocations" do
- @invocation_list[RDingus::InvocationRecord.new(@rdingus, :method2)] = "test"
- @invocation_list[@invocation].should_not == "test"
- end
- it "should know if a value is defined" do
- @invocation_list[@invocation] = "test"
- @invocation_list.defined?(@invocation).should be_true
- end
- it "should know it has a value even if value is nil" do
- @invocation_list[@invocation] = false
- @invocation_list[@invocation].should == false
- @invocation_list.defined?(@invocation).should be_true
- end
- it "should know it has a value even if value is false" do
- @invocation_list[@invocation] = nil
- @invocation_list[@invocation].should == nil
- @invocation_list.defined?(@invocation).should be_true
- end
- describe "when redefining invocation" do
- it "should use the last one" do
- @invocation_list[@invocation] = "first"
- @invocation_list[RDingus::InvocationRecord.new(@rdingus, :method, [:any])] = "second"
- @invocation_list[@invocation].should == "second"
- end
- end
- end
- describe RDingus::InvocationList do
- before :each do
- @rdingus = RDingus.new
- @list = RDingus::InvocationList.new
- end
- describe "when adding method invocations" do
- it "should allow append" do
- @list << :test
- @list.to_a.should == [:test]
- end
- end
- describe "when counting method invocations" do
- before :each do
- @list << RDingus::InvocationRecord.new(@rdingus, :some_method)
- @list << RDingus::InvocationRecord.new(@rdingus, :some_method_2)
- end
- describe "without sub calls" do
- it "should be equal to length" do
- @list.count.should == @list.length
- end
- end
- end
- describe "when querying using include?" do
- describe "with just a method name symbol" do
- before :each do
- @invocation = RDingus::InvocationRecord.new(@rdingus, :some_method)
- end
- describe "that is in the list" do
- before :each do
- @list << @invocation
- end
- it "should find the invocation" do
- @list.should include(:some_method)
- end
- end
- describe "that is not in the list" do
- it "should not find the invocation" do
- @list.should_not include(:some_method)
- end
- end
- end
- it "should alias has_received? to include?" do
- @list << RDingus::InvocationRecord.new(@rdingus, :some_method)
- @list.should have_received(:some_method)
- end
- end
- describe "when indexing via []" do
- before :each do
- @invocation1 = RDingus::InvocationRecord.new(@rdingus, :some_method)
- @invocation2 = RDingus::InvocationRecord.new(@rdingus, :method_2, [:arg1, :arg2])
- @invocation3 = RDingus::InvocationRecord.new(@rdingus, :method_3)
- @list << @invocation1; @list << @invocation2; @list << @invocation3
- end
- describe "with integers" do
- it "should return the requested invocation in the sequence" do
- @list[2].method.should == :method_3
- end
- end
- describe "with a method name symbol" do
- before :each do
- @list << @invocation2
- end
- it "should return a new list" do
- @list[:method_2].should be_kind_of(RDingus::InvocationList)
- end
- it "should have proper number of elements" do
- @list[:method_2].length.should == 2
- end
- it "should only have invocations of the given method" do
- @list[:method_2].each {|i| i.method.should == :method_2 }
- end
- end
- end
- end
- class Person
- attr_accessor :name
- attr_accessor :shoe_size
- def eat(sandwich)
- @sandwich = sandwich
- @sandwich.cut :in => 2
- @sandwich.take_a_bite
- end
- def drop(sandwich)
- sandwich.drop
- end
- def opinion_of_sandwich
- if @sandwich.cheese.spicy?
- "yummy!"
- else
- "bland"
- end
- end
- end
- describe Person do
- before :each do
- @person = Person.new
- end
- describe "when specifying a shoe size" do
- it "should save the shoe size" do
- @person.shoe_size = 4
- @person.shoe_size.should == 4
- end
- end
- describe "when using a sandwich" do
- before :each do
- @sandwich = RDingus.new
- @person.eat(@sandwich)
- end
- it "should be able to cut a sandwhich" do
- @sandwich._calls.first.method.should == :cut
- end
- it "should cut into two slices" do
- @sandwich._calls[0].args[0].should == {:in => 2}
- end
- it "should take a bite" do
- @sandwich._calls.should have_received(:take_a_bite)
- end
- it "should not have been dropped" do
- @sandwich._calls.should_not have_received(:dropped)
- end
- it "should raise an exception if it drops the sandwich" do
- @sandwich._define.drop { raise "Dropped Sandwich!" }
- lambda { @person.drop(@sandwich) }.should raise_error("Dropped Sandwich!")
- end
- describe "that has spicy cheese" do
- before :each do
- @sandwich._define.cheese.spicy? true
- end
- it "should have an opinion of 'yummy!'" do
- @person.opinion_of_sandwich.should == "yummy!"
- end
- end
- describe "that does not have spicy cheese (block syntax)" do
- before :each do
- @sandwich._define do
- cheese.spicy? false
- end
- end
- it "should have an opinion of 'bland'" do
- @person.opinion_of_sandwich.should == "bland"
- end
- end
- describe "that does not have spicy cheese" do
- before :each do
- @sandwich._define.cheese.spicy? false
- end
- it "should have an opinion of 'bland'" do
- @person.opinion_of_sandwich.should == "bland"
- end
- end
- end
- end
Add Comment
Please, Sign In to add comment