diff options
Diffstat (limited to 'vendor/plugins/rspec/lib/spec/mocks')
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/argument_constraint_matchers.rb | 27 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/argument_expectation.rb | 183 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/error_generator.rb | 84 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/errors.rb | 10 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/extensions/object.rb | 3 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/message_expectation.rb | 242 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/methods.rb | 39 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/mock.rb | 29 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/order_group.rb | 29 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/proxy.rb | 167 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/space.rb | 28 | ||||
-rw-r--r-- | vendor/plugins/rspec/lib/spec/mocks/spec_methods.rb | 30 |
12 files changed, 871 insertions, 0 deletions
diff --git a/vendor/plugins/rspec/lib/spec/mocks/argument_constraint_matchers.rb b/vendor/plugins/rspec/lib/spec/mocks/argument_constraint_matchers.rb new file mode 100644 index 000000000..0e4777082 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/argument_constraint_matchers.rb @@ -0,0 +1,27 @@ +module Spec + module Mocks + module ArgumentConstraintMatchers + + # Shortcut for creating an instance of Spec::Mocks::DuckTypeArgConstraint + def duck_type(*args) + DuckTypeArgConstraint.new(*args) + end + + def any_args + AnyArgsConstraint.new + end + + def anything + AnyArgConstraint.new(nil) + end + + def boolean + BooleanArgConstraint.new(nil) + end + + def no_args + NoArgsConstraint.new + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/argument_expectation.rb b/vendor/plugins/rspec/lib/spec/mocks/argument_expectation.rb new file mode 100644 index 000000000..5da069b87 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/argument_expectation.rb @@ -0,0 +1,183 @@ +module Spec + module Mocks + + class MatcherConstraint + def initialize(matcher) + @matcher = matcher + end + + def matches?(value) + @matcher.matches?(value) + end + end + + class LiteralArgConstraint + def initialize(literal) + @literal_value = literal + end + + def matches?(value) + @literal_value == value + end + end + + class RegexpArgConstraint + def initialize(regexp) + @regexp = regexp + end + + def matches?(value) + return value =~ @regexp unless value.is_a?(Regexp) + value == @regexp + end + end + + class AnyArgConstraint + def initialize(ignore) + end + + def ==(other) + true + end + + # TODO - need this? + def matches?(value) + true + end + end + + class AnyArgsConstraint + def description + "any args" + end + end + + class NoArgsConstraint + def description + "no args" + end + + def ==(args) + args == [] + end + end + + class NumericArgConstraint + def initialize(ignore) + end + + def matches?(value) + value.is_a?(Numeric) + end + end + + class BooleanArgConstraint + def initialize(ignore) + end + + def ==(value) + matches?(value) + end + + def matches?(value) + return true if value.is_a?(TrueClass) + return true if value.is_a?(FalseClass) + false + end + end + + class StringArgConstraint + def initialize(ignore) + end + + def matches?(value) + value.is_a?(String) + end + end + + class DuckTypeArgConstraint + def initialize(*methods_to_respond_to) + @methods_to_respond_to = methods_to_respond_to + end + + def matches?(value) + @methods_to_respond_to.all? { |sym| value.respond_to?(sym) } + end + + def description + "duck_type" + end + end + + class ArgumentExpectation + attr_reader :args + @@constraint_classes = Hash.new { |hash, key| LiteralArgConstraint} + @@constraint_classes[:anything] = AnyArgConstraint + @@constraint_classes[:numeric] = NumericArgConstraint + @@constraint_classes[:boolean] = BooleanArgConstraint + @@constraint_classes[:string] = StringArgConstraint + + def initialize(args) + @args = args + if [:any_args] == args + @expected_params = nil + warn_deprecated(:any_args.inspect, "any_args()") + elsif args.length == 1 && args[0].is_a?(AnyArgsConstraint) then @expected_params = nil + elsif [:no_args] == args + @expected_params = [] + warn_deprecated(:no_args.inspect, "no_args()") + elsif args.length == 1 && args[0].is_a?(NoArgsConstraint) then @expected_params = [] + else @expected_params = process_arg_constraints(args) + end + end + + def process_arg_constraints(constraints) + constraints.collect do |constraint| + convert_constraint(constraint) + end + end + + def warn_deprecated(deprecated_method, instead) + STDERR.puts "The #{deprecated_method} constraint is deprecated. Use #{instead} instead." + end + + def convert_constraint(constraint) + if [:anything, :numeric, :boolean, :string].include?(constraint) + case constraint + when :anything + instead = "anything()" + when :boolean + instead = "boolean()" + when :numeric + instead = "an_instance_of(Numeric)" + when :string + instead = "an_instance_of(String)" + end + warn_deprecated(constraint.inspect, instead) + return @@constraint_classes[constraint].new(constraint) + end + return MatcherConstraint.new(constraint) if is_matcher?(constraint) + return RegexpArgConstraint.new(constraint) if constraint.is_a?(Regexp) + return LiteralArgConstraint.new(constraint) + end + + def is_matcher?(obj) + return obj.respond_to?(:matches?) && obj.respond_to?(:description) + end + + def check_args(args) + return true if @expected_params.nil? + return true if @expected_params == args + return constraints_match?(args) + end + + def constraints_match?(args) + return false if args.length != @expected_params.length + @expected_params.each_index { |i| return false unless @expected_params[i].matches?(args[i]) } + return true + end + + end + + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/error_generator.rb b/vendor/plugins/rspec/lib/spec/mocks/error_generator.rb new file mode 100644 index 000000000..01d8f720d --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/error_generator.rb @@ -0,0 +1,84 @@ +module Spec + module Mocks + class ErrorGenerator + attr_writer :opts + + def initialize(target, name) + @target = target + @name = name + end + + def opts + @opts ||= {} + end + + def raise_unexpected_message_error(sym, *args) + __raise "#{intro} received unexpected message :#{sym}#{arg_message(*args)}" + end + + def raise_unexpected_message_args_error(expectation, *args) + expected_args = format_args(*expectation.expected_args) + actual_args = args.empty? ? "(no args)" : format_args(*args) + __raise "#{intro} expected #{expectation.sym.inspect} with #{expected_args} but received it with #{actual_args}" + end + + def raise_expectation_error(sym, expected_received_count, actual_received_count, *args) + __raise "#{intro} expected :#{sym}#{arg_message(*args)} #{count_message(expected_received_count)}, but received it #{count_message(actual_received_count)}" + end + + def raise_out_of_order_error(sym) + __raise "#{intro} received :#{sym} out of order" + end + + def raise_block_failed_error(sym, detail) + __raise "#{intro} received :#{sym} but passed block failed with: #{detail}" + end + + def raise_missing_block_error(args_to_yield) + __raise "#{intro} asked to yield |#{arg_list(*args_to_yield)}| but no block was passed" + end + + def raise_wrong_arity_error(args_to_yield, arity) + __raise "#{intro} yielded |#{arg_list(*args_to_yield)}| to block with arity of #{arity}" + end + + private + def intro + @name ? "Mock '#{@name}'" : @target.inspect + end + + def __raise(message) + message = opts[:message] unless opts[:message].nil? + Kernel::raise(Spec::Mocks::MockExpectationError, message) + end + + def arg_message(*args) + " with " + format_args(*args) + end + + def format_args(*args) + return "(no args)" if args.empty? || args == [:no_args] + return "(any args)" if args == [:any_args] + "(" + arg_list(*args) + ")" + end + + def arg_list(*args) + args.collect do |arg| + arg.respond_to?(:description) ? arg.description : arg.inspect + end.join(", ") + end + + def count_message(count) + return "at least #{pretty_print(count.abs)}" if count < 0 + return pretty_print(count) + end + + def pretty_print(count) + return "once" if count == 1 + return "twice" if count == 2 + return "#{count} times" + end + + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/errors.rb b/vendor/plugins/rspec/lib/spec/mocks/errors.rb new file mode 100644 index 000000000..68fdfe006 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/errors.rb @@ -0,0 +1,10 @@ +module Spec + module Mocks + class MockExpectationError < StandardError + end + + class AmbiguousReturnError < StandardError + end + end +end + diff --git a/vendor/plugins/rspec/lib/spec/mocks/extensions/object.rb b/vendor/plugins/rspec/lib/spec/mocks/extensions/object.rb new file mode 100644 index 000000000..4b7531066 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/extensions/object.rb @@ -0,0 +1,3 @@ +class Object + include Spec::Mocks::Methods +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/message_expectation.rb b/vendor/plugins/rspec/lib/spec/mocks/message_expectation.rb new file mode 100644 index 000000000..74ade3c58 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/message_expectation.rb @@ -0,0 +1,242 @@ +module Spec + module Mocks + + class BaseExpectation + attr_reader :sym + + def initialize(error_generator, expectation_ordering, expected_from, sym, method_block, expected_received_count=1, opts={}) + @error_generator = error_generator + @error_generator.opts = opts + @expected_from = expected_from + @sym = sym + @method_block = method_block + @return_block = lambda {} + @received_count = 0 + @expected_received_count = expected_received_count + @args_expectation = ArgumentExpectation.new([AnyArgsConstraint.new]) + @consecutive = false + @exception_to_raise = nil + @symbol_to_throw = nil + @order_group = expectation_ordering + @at_least = nil + @at_most = nil + @args_to_yield = nil + end + + def expected_args + @args_expectation.args + end + + def and_return(*values, &return_block) + Kernel::raise AmbiguousReturnError unless @method_block.nil? + if values.size == 0 + value = nil + elsif values.size == 1 + value = values[0] + else + value = values + @consecutive = true + @expected_received_count = values.size if @expected_received_count != :any && + @expected_received_count < values.size + end + @return_block = block_given? ? return_block : lambda { value } + end + + # :call-seq: + # and_raise() + # and_raise(Exception) #any exception class + # and_raise(exception) #any exception object + # + # == Warning + # + # When you pass an exception class, the MessageExpectation will + # raise an instance of it, creating it with +new+. If the exception + # class initializer requires any parameters, you must pass in an + # instance and not the class. + def and_raise(exception=Exception) + @exception_to_raise = exception + end + + def and_throw(symbol) + @symbol_to_throw = symbol + end + + def and_yield(*args) + @args_to_yield = args + end + + def matches(sym, args) + @sym == sym and @args_expectation.check_args(args) + end + + def invoke(args, block) + @order_group.handle_order_constraint self + + begin + if @exception_to_raise.class == Class + @exception_instance_to_raise = @exception_to_raise.new + else + @exception_instance_to_raise = @exception_to_raise + end + Kernel::raise @exception_to_raise unless @exception_to_raise.nil? + Kernel::throw @symbol_to_throw unless @symbol_to_throw.nil? + + if !@method_block.nil? + return invoke_method_block(args) + elsif !@args_to_yield.nil? + return invoke_with_yield(block) + elsif @consecutive + return invoke_consecutive_return_block(args, block) + else + return invoke_return_block(args, block) + end + ensure + @received_count += 1 + end + end + + protected + + def invoke_method_block(args) + begin + @method_block.call(*args) + rescue => detail + @error_generator.raise_block_failed_error @sym, detail.message + end + end + + def invoke_with_yield(block) + if block.nil? + @error_generator.raise_missing_block_error @args_to_yield + end + if block.arity > -1 && @args_to_yield.length != block.arity + @error_generator.raise_wrong_arity_error @args_to_yield, block.arity + end + block.call(*@args_to_yield) + end + + def invoke_consecutive_return_block(args, block) + args << block unless block.nil? + value = @return_block.call(*args) + + index = [@received_count, value.size-1].min + value[index] + end + + def invoke_return_block(args, block) + args << block unless block.nil? + value = @return_block.call(*args) + + value + end + end + + class MessageExpectation < BaseExpectation + + def matches_name_but_not_args(sym, args) + @sym == sym and not @args_expectation.check_args(args) + end + + def verify_messages_received + return if @expected_received_count == :any + return if (@at_least) && (@received_count >= @expected_received_count) + return if (@at_most) && (@received_count <= @expected_received_count) + return if @expected_received_count == @received_count + + begin + @error_generator.raise_expectation_error(@sym, @expected_received_count, @received_count, *@args_expectation.args) + rescue => error + error.backtrace.insert(0, @expected_from) + Kernel::raise error + end + end + + def with(*args, &block) + @method_block = block if block + @args_expectation = ArgumentExpectation.new(args) + self + end + + def exactly(n) + set_expected_received_count :exactly, n + self + end + + def at_least(n) + set_expected_received_count :at_least, n + self + end + + def at_most(n) + set_expected_received_count :at_most, n + self + end + + def times(&block) + @method_block = block if block + self + end + + def any_number_of_times(&block) + @method_block = block if block + @expected_received_count = :any + self + end + + def never + @expected_received_count = 0 + self + end + + def once(&block) + @method_block = block if block + @expected_received_count = 1 + self + end + + def twice(&block) + @method_block = block if block + @expected_received_count = 2 + self + end + + def ordered(&block) + @method_block = block if block + @order_group.register(self) + @ordered = true + self + end + + def negative_expectation_for?(sym) + return false + end + + protected + def set_expected_received_count(relativity, n) + @at_least = (relativity == :at_least) + @at_most = (relativity == :at_most) + @expected_received_count = 1 if n == :once + @expected_received_count = 2 if n == :twice + @expected_received_count = n if n.kind_of? Numeric + end + + end + + class NegativeMessageExpectation < MessageExpectation + def initialize(message, expectation_ordering, expected_from, sym, method_block) + super(message, expectation_ordering, expected_from, sym, method_block, 0) + end + + def negative_expectation_for?(sym) + return @sym == sym + end + end + + class MethodStub < BaseExpectation + def initialize(message, expectation_ordering, expected_from, sym, method_block) + super(message, expectation_ordering, expected_from, sym, method_block, 0) + @expected_received_count = :any + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/methods.rb b/vendor/plugins/rspec/lib/spec/mocks/methods.rb new file mode 100644 index 000000000..3d898cf31 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/methods.rb @@ -0,0 +1,39 @@ +module Spec + module Mocks + module Methods + def should_receive(sym, opts={}, &block) + __mock_proxy.add_message_expectation(opts[:expected_from] || caller(1)[0], sym.to_sym, opts, &block) + end + + def should_not_receive(sym, &block) + __mock_proxy.add_negative_message_expectation(caller(1)[0], sym.to_sym, &block) + end + + def stub!(sym) + __mock_proxy.add_stub(caller(1)[0], sym.to_sym) + end + + def received_message?(sym, *args, &block) #:nodoc: + __mock_proxy.received_message?(sym.to_sym, *args, &block) + end + + def rspec_verify #:nodoc: + __mock_proxy.verify + end + + def rspec_reset #:nodoc: + __mock_proxy.reset + end + + private + + def __mock_proxy + if Mock === self + @mock_proxy ||= Proxy.new(self, @name, @options) + else + @mock_proxy ||= Proxy.new(self, self.class.name) + end + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/mock.rb b/vendor/plugins/rspec/lib/spec/mocks/mock.rb new file mode 100644 index 000000000..aa380e0af --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/mock.rb @@ -0,0 +1,29 @@ +module Spec + module Mocks + class Mock + include Methods + + # Creates a new mock with a +name+ (that will be used in error messages only) + # == Options: + # * <tt>:null_object</tt> - if true, the mock object acts as a forgiving null object allowing any message to be sent to it. + def initialize(name, options={}) + @name = name + @options = options + end + + def method_missing(sym, *args, &block) + __mock_proxy.instance_eval {@messages_received << [sym, args, block]} + begin + return self if __mock_proxy.null_object? + super(sym, *args, &block) + rescue NoMethodError + __mock_proxy.raise_unexpected_message_error sym, *args + end + end + + def inspect + "#<#{self.class}:#{sprintf '0x%x', self.object_id} @name=#{@name.inspect}>" + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/order_group.rb b/vendor/plugins/rspec/lib/spec/mocks/order_group.rb new file mode 100644 index 000000000..9983207eb --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/order_group.rb @@ -0,0 +1,29 @@ +module Spec + module Mocks + class OrderGroup + def initialize error_generator + @error_generator = error_generator + @ordering = Array.new + end + + def register(expectation) + @ordering << expectation + end + + def ready_for?(expectation) + return @ordering.first == expectation + end + + def consume + @ordering.shift + end + + def handle_order_constraint expectation + return unless @ordering.include? expectation + return consume if ready_for?(expectation) + @error_generator.raise_out_of_order_error expectation.sym + end + + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/proxy.rb b/vendor/plugins/rspec/lib/spec/mocks/proxy.rb new file mode 100644 index 000000000..6c79d1068 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/proxy.rb @@ -0,0 +1,167 @@ +module Spec + module Mocks + class Proxy + DEFAULT_OPTIONS = { + :null_object => false, + } + + def initialize(target, name, options={}) + @target = target + @name = name + @error_generator = ErrorGenerator.new target, name + @expectation_ordering = OrderGroup.new @error_generator + @expectations = [] + @messages_received = [] + @stubs = [] + @proxied_methods = [] + @options = options ? DEFAULT_OPTIONS.dup.merge(options) : DEFAULT_OPTIONS + end + + def null_object? + @options[:null_object] + end + + def add_message_expectation(expected_from, sym, opts={}, &block) + __add sym, block + @expectations << MessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil, 1, opts) + @expectations.last + end + + def add_negative_message_expectation(expected_from, sym, &block) + __add sym, block + @expectations << NegativeMessageExpectation.new(@error_generator, @expectation_ordering, expected_from, sym, block_given? ? block : nil) + @expectations.last + end + + def add_stub(expected_from, sym) + __add sym, nil + @stubs.unshift MethodStub.new(@error_generator, @expectation_ordering, expected_from, sym, nil) + @stubs.first + end + + def verify #:nodoc: + begin + verify_expectations + ensure + reset + end + end + + def reset + clear_expectations + clear_stubs + reset_proxied_methods + clear_proxied_methods + end + + def received_message?(sym, *args, &block) + return true if @messages_received.find {|array| array == [sym, args, block]} + return false + end + + def has_negative_expectation?(sym) + @expectations.detect {|expectation| expectation.negative_expectation_for?(sym)} + end + + def message_received(sym, *args, &block) + if expectation = find_matching_expectation(sym, *args) + expectation.invoke(args, block) + elsif stub = find_matching_method_stub(sym) + stub.invoke([], block) + elsif expectation = find_almost_matching_expectation(sym, *args) + raise_unexpected_message_args_error(expectation, *args) unless has_negative_expectation?(sym) unless null_object? + else + @target.send :method_missing, sym, *args, &block + end + end + + def raise_unexpected_message_args_error(expectation, *args) + @error_generator.raise_unexpected_message_args_error expectation, *args + end + + def raise_unexpected_message_error(sym, *args) + @error_generator.raise_unexpected_message_error sym, *args + end + + private + + def __add(sym, block) + $rspec_mocks.add(@target) unless $rspec_mocks.nil? + define_expected_method(sym) + end + + def define_expected_method(sym) + if target_responds_to?(sym) && !@proxied_methods.include?(sym) + metaclass.__send__(:alias_method, munge(sym), sym) if metaclass.instance_methods.include?(sym.to_s) + @proxied_methods << sym + end + + metaclass_eval(<<-EOF, __FILE__, __LINE__) + def #{sym}(*args, &block) + __mock_proxy.message_received :#{sym}, *args, &block + end + EOF + end + + def target_responds_to?(sym) + return @target.send(munge(:respond_to?),sym) if @already_proxied_respond_to + return @already_proxied_respond_to = true if sym == :respond_to? + return @target.respond_to?(sym) + end + + def munge(sym) + "proxied_by_rspec__#{sym.to_s}".to_sym + end + + def clear_expectations + @expectations.clear + end + + def clear_stubs + @stubs.clear + end + + def clear_proxied_methods + @proxied_methods.clear + end + + def metaclass_eval(str, filename, lineno) + metaclass.class_eval(str, filename, lineno) + end + + def metaclass + (class << @target; self; end) + end + + def verify_expectations + @expectations.each do |expectation| + expectation.verify_messages_received + end + end + + def reset_proxied_methods + @proxied_methods.each do |sym| + if metaclass.instance_methods.include?(munge(sym).to_s) + metaclass.__send__(:alias_method, sym, munge(sym)) + metaclass.__send__(:undef_method, munge(sym)) + else + metaclass.__send__(:undef_method, sym) + end + end + end + + def find_matching_expectation(sym, *args) + @expectations.find {|expectation| expectation.matches(sym, args)} + end + + def find_almost_matching_expectation(sym, *args) + @expectations.find {|expectation| expectation.matches_name_but_not_args(sym, args)} + end + + def find_matching_method_stub(sym) + @stubs.find {|stub| stub.matches(sym, [])} + end + + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/space.rb b/vendor/plugins/rspec/lib/spec/mocks/space.rb new file mode 100644 index 000000000..e04bc5ccb --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/space.rb @@ -0,0 +1,28 @@ +module Spec + module Mocks + class Space + def add(obj) + mocks << obj unless mocks.include?(obj) + end + + def verify_all + mocks.each do |mock| + mock.rspec_verify + end + end + + def reset_all + mocks.each do |mock| + mock.rspec_reset + end + mocks.clear + end + + private + + def mocks + @mocks ||= [] + end + end + end +end diff --git a/vendor/plugins/rspec/lib/spec/mocks/spec_methods.rb b/vendor/plugins/rspec/lib/spec/mocks/spec_methods.rb new file mode 100644 index 000000000..fd67fd210 --- /dev/null +++ b/vendor/plugins/rspec/lib/spec/mocks/spec_methods.rb @@ -0,0 +1,30 @@ +module Spec + module Mocks + module SpecMethods + include Spec::Mocks::ArgumentConstraintMatchers + + # Shortcut for creating an instance of Spec::Mocks::Mock. + def mock(name, options={}) + Spec::Mocks::Mock.new(name, options) + end + + # Shortcut for creating an instance of Spec::Mocks::Mock with + # predefined method stubs. + # + # == Examples + # + # stub_thing = stub("thing", :a => "A") + # stub_thing.a == "A" => true + # + # stub_person = stub("thing", :name => "Joe", :email => "joe@domain.com") + # stub_person.name => "Joe" + # stub_person.email => "joe@domain.com" + def stub(name, stubs={}) + object_stub = mock(name) + stubs.each { |key, value| object_stub.stub!(key).and_return(value) } + object_stub + end + + end + end +end |