lib/ae/assertor.rb in ae-1.6.1 vs lib/ae/assertor.rb in ae-1.7.0
- old
+ new
@@ -1,216 +1,288 @@
require 'ae/assertion'
require 'ae/basic_object'
-# = Assertor (Assertion Functor)
-#
-# == What is a Functor?
-#
-# A Functor is a succinct name for what is also know as a
-# Higher Order Function. In other words, it is a function
-# that acts on a function. It is very similiar to a delegator
-# in most respects, but is conditioned on the operation applied,
-# rather then simply passing-off to an alternate reciever.
-#
-class Assertor < AE::BasicObject
+module AE
+ # Assertor is the underlying class of the whole system. It implements
+ # the flutent assertion notation.
#
- #instance_methods.each{ |m| protected m unless /^(__|object_id$)/ =~ m.to_s }
-
- if ::RUBY_VERSION >= '1.9'
- eval "private :==, :!, :!=" # using eval here b/c it's a syntax error in 1.8-
- end
-
- # New Assertor.
+ # An Assertor is an Assertion Functor. A Functor is a succinct name for what
+ # is also known as Higher Order Function. In other words, it is a function
+ # that acts on a function. It is very similiar to a delegator in most
+ # respects, but is conditioned on the operation applied, rather then simply
+ # passing-off to an alternate reciever.
#
- def initialize(delegate, opts={}) #, backtrace)
- @delegate = delegate
- @message = opts[:message]
- @backtrace = opts[:backtrace] || caller #[1..-1]
- @negated = !!opts[:negated]
- end
+ class Assertor < AE::BasicObject
- # Negate the meaning of the assertion.
- #
- # TODO: Should this return a new Assertor instead of inplace negation?
- def not(msg=nil)
- @negated = !@negated
- @message = msg if msg
- self
- end
+ # Initial settings of assertion counts.
+ ZERO_COUNTS = {:total=>0,:pass=>0,:fail=>0}
- # Internal assert, provides all functionality associated
- # with external #assert method. (See Assert#assert)
- #
- # NOTE: I'm calling YAGNI on using extra arguments to pass
- # to the block. The interface is much nicer if a macro is
- # created to handle any neccessry arguments. Eg.
- #
- # assert something(parameter)
- #
- # instead of
- #
- # assert something, parameter
- #
- def assert(*args, &block)
- return self if args.empty? && !block
+ # Initialize assertion counts global variable.
+ $assertion_counts = ZERO_COUNTS.dup
- target = block || args.shift
+ # Returns Hash used to track assertion counts.
+ def self.counts
+ $assertion_counts
+ end
- if ::Proc === target || target.respond_to?(:to_proc)
- block = target.to_proc
- match = args.shift
- result = block.arity > 0 ? block.call(@delegate) : block.call
- if match
- pass = (match == result)
- msg = @message || "#{match.inspect} == #{result.inspect}"
+ # Reset assertion counts.
+ #
+ # reset - Hash which will be used to set counts manually (optional).
+ #
+ # Returns the Hash of previous counts.
+ def self.recount(reset={})
+ old_counts = counts.dup
+ if reset.empty?
+ counts.replace(ZERO_COUNTS.dup)
else
- pass = result
- msg = @message || block.inspect # "#{result.inspect}"
+ reset.each do |type, value|
+ counts[type.to_sym] = value
+ end
end
- elsif target.respond_to?(:matches?)
- pass = target.matches?(@delegate)
- msg = @message || matcher_message(target) || target.inspect
- else
- pass = target # truthiness
- msg = args.shift # optional mesage for TestUnit compatiability
+ return old_counts
end
- __assert__(pass, msg)
- end
+ # Increment assertion counts. If +pass+ is +true+ then +:total+
+ # and +:pass+ are increased. If +pass+ if +false+ then +:total+
+ # and +:fail+ are incremented.
+ def self.increment_counts(pass)
+ counts[:total] += 1
+ if pass
+ counts[:pass] += 1
+ else
+ counts[:fail] += 1
+ end
+ return counts
+ end
- # Internal expect, provides all functionality associated
- # with external #expect method. (See Expect#expect)
- #
- #--
- # TODO: Should we deprecate the receiver matches in favor of #expected ?
- # In other words, should the <code>|| @delegate</code> be dropped?
- #++
- def expect(*args, &block)
- return self if args.empty? && !block # same as #assert
+ # Basic assertion. This method by-passes all the Assertor fluent
+ # constructs and performs the underlying assertion procedure. It
+ # is used by Assertor as the end result of an assertion.
+ def self.assert(pass, message=nil, backtrace=nil)
+ increment_counts(pass)
+ if !pass
+ backtrace = backtrace || caller
+ message = message || 'flunk'
+ raise_assertion(message, backtrace)
+ end
+ return pass
+ end
- target = block || args.shift
+ # This method can be replaced to support alternate frameworks.
+ # The intent of the method is to raise the assertion failure
+ # class that the framework uses.
+ def self.raise_assertion(message, backtrace=nil)
+ backtrace = backtrace || caller
- if ::Proc === target #|| target.respond_to?(:to_proc)
- #block = target.to_proc
- match = args.shift || @delegate
- if exception?(match)
- $DEBUG, debug = false, $DEBUG # b/c it always spits-out a NameError
- begin
- block.arity > 0 ? block.call(@delegate) : block.call
- pass = false
- msg = "#{match} not raised"
- rescue match => error
- pass = true
- msg = "#{match} raised"
- rescue ::Exception => error
- pass = false
- msg = "#{match} expected but #{error.class} was raised"
- ensure
- $DEBUG = debug
+ error = assertion_error.new(message)
+ error.set_backtrace(backtrace)
+ error.set_assertion(true)
+ fail error
+ end
+
+ # Returns the Exception class to be raised when an assertion fails.
+ def self.assertion_error
+ ::Assertion
+ end
+
+ #
+ if ::RUBY_VERSION >= '1.9'
+ eval "private :==, :!, :!=" # using eval here b/c it's a syntax error in 1.8-
+ end
+
+ # New Assertor.
+ #
+ def initialize(delegate, opts={}) #, backtrace)
+ @delegate = delegate
+ @message = opts[:message]
+ @backtrace = opts[:backtrace] || caller #[1..-1]
+ @negated = !!opts[:negated]
+ end
+
+ # Negate the meaning of the assertion.
+ #
+ #--
+ # TODO: Should this return a new Assertor instead of in place negation?
+ #++
+ def not(msg=nil)
+ @negated = !@negated
+ @message = msg if msg
+ self
+ end
+
+ # Internal assert, provides all functionality associated
+ # with external #assert method. (See Assert#assert)
+ #
+ # NOTE: I'm calling YAGNI on using extra arguments to pass
+ # to the block. The interface is much nicer if a macro is
+ # created to handle any neccessry arguments. Eg.
+ #
+ # assert something(parameter)
+ #
+ # instead of
+ #
+ # assert something, parameter
+ #
+ # Returns +true+ or +false+ based on assertions success.
+ def assert(*args, &block)
+ return self if args.empty? && !block
+
+ target = block || args.shift
+
+ if ::Proc === target || target.respond_to?(:to_proc)
+ block = target.to_proc
+ match = args.shift
+ result = block.arity > 0 ? block.call(@delegate) : block.call
+ if match
+ pass = (match == result)
+ msg = @message || "#{match.inspect} == #{result.inspect}"
+ else
+ pass = result
+ msg = @message || block.inspect # "#{result.inspect}"
end
+ elsif target.respond_to?(:matches?)
+ pass = target.matches?(@delegate)
+ msg = @message || matcher_message(target) || target.inspect
else
- result = block.arity > 0 ? block.call(@delegte) : block.call
- pass = (match === result)
- msg = @message || "#{match.inspect} === #{result.inspect}"
+ pass = target # truthiness
+ msg = args.shift # optional mesage for TestUnit compatiability
end
- elsif target.respond_to?(:matches?)
- pass = target.matches?(@delegate)
- msg = @message || matcher_message(target) || target.inspect
- else
- pass = (target === @delegate)
- msg = @message || "#{target.inspect} === #{@delegate.inspect}"
+
+ __assert__(pass, msg)
end
- __assert__(pass, msg)
- end
+ # Internal expect, provides all functionality associated
+ # with external #expect method. (See Expect#expect)
+ #
+ #--
+ # TODO: Should we deprecate the receiver matches in favor of #expected ?
+ # In other words, should the <code>|| @delegate</code> be dropped?
+ #++
+ def expect(*args, &block)
+ return self if args.empty? && !block # same as #assert
- # Is the +object+ and Exception or an instance of one.
- #--
- # TODO: Should we use a more libreral determination of exception.
- # e.g. <code>respond_to?(:exception)</code>.
- #++
- def exception?(object)
- ::Exception === object or ::Class === object and object.ancestors.include?(::Exception)
- end
+ target = block || args.shift
- #
- def flunk(message=nil)
- __assert__(false, message || @message)
- end
+ if ::Proc === target #|| target.respond_to?(:to_proc)
+ #block = target.to_proc
+ match = args.shift || @delegate
+ if exception?(match)
+ $DEBUG, debug = false, $DEBUG # b/c it always spits-out a NameError
+ begin
+ block.arity > 0 ? block.call(@delegate) : block.call
+ pass = false
+ msg = "#{match} not raised"
+ rescue match => error
+ pass = true
+ msg = "#{match} raised"
+ rescue ::Exception => error
+ pass = false
+ msg = "#{match} expected but #{error.class} was raised"
+ ensure
+ $DEBUG = debug
+ end
+ else
+ result = block.arity > 0 ? block.call(@delegte) : block.call
+ pass = (match === result)
+ msg = @message || "#{match.inspect} === #{result.inspect}"
+ end
+ elsif target.respond_to?(:matches?)
+ pass = target.matches?(@delegate)
+ msg = @message || matcher_message(target) || target.inspect
+ else
+ pass = (target === @delegate)
+ msg = @message || "#{target.inspect} === #{@delegate.inspect}"
+ end
- # Ruby seems to have a quark in it's implementation whereby
- # this must be defined explicitly, otherwise it somehow
- # skips #method_missing.
- def =~(match)
- method_missing(:"=~", match)
- end
+ __assert__(pass, msg)
+ end
- #
- def send(op, *a, &b)
- method_missing(op, *a, &b)
- end
+ #
+ def flunk(message=nil, backtrace=nil)
+ __assert__(false, message || @message)
+ end
- private
+ # Ruby seems to have a quark in it's implementation whereby
+ # this must be defined explicitly, otherwise it somehow
+ # skips #method_missing.
+ def =~(match)
+ method_missing(:"=~", match)
+ end
- # Converts a missing methods into an Assertion.
- #
- def method_missing(sym, *a, &b)
- pass = @delegate.__send__(sym, *a, &b)
- #pass = @delegate.public_send(sym, *a, &b)
- __assert__(pass, @message || __msg__(sym, *a, &b))
- end
+ #
+ def send(op, *a, &b)
+ method_missing(op, *a, &b)
+ end
- # Puts together a suitable error message.
- #
- def __msg__(m, *a, &b)
- inspection = @delegate.send(:inspect)
- if @negated
- "! #{inspection} #{m} #{a.collect{|x| x.inspect}.join(',')}"
- else
- "#{inspection} #{m} #{a.collect{|x| x.inspect}.join(',')}"
+ #
+ def inspect
+ @delegate.inspect
end
- #self.class.message(m)[@delegate, *a] )
- end
- # Pure old simple assert.
- #--
- # TODO: Can the handling of the message be simplified/improved?
- #++
- def __assert__(pass, message=nil)
- pass = @negated ^ pass
- # msg = message || @message
- ::Assertion.test(pass, :message=>message, :backtrace=>@backtrace)
- return pass
- end
+ private
- # This method can be replaced to support alternate frameworks.
- # The idea is to use to record that an assertion took place.
- # def framework_assert(pass, message)
- # # by default nothing needed
- # end
+ # Is the +object+ an Exception or an instance of one?
+ #--
+ # TODO: Should we use a more libreral determination of exception.
+ # e.g. <code>respond_to?(:exception)</code>.
+ #++
+ def exception?(object)
+ ::Exception === object or ::Class === object and object.ancestors.include?(::Exception)
+ end
- #
- def matcher_message(matcher)
- if @negated
- if matcher.respond_to?(:negative_failure_message)
- return matcher.failure_message
+ # Converts a missing method into an Assertion.
+ #
+ # TODO: In future should probably be `@delegate.public_send(sym, *a, &b)`.
+ def method_missing(sym, *a, &b)
+ pass = @delegate.__send__(sym, *a, &b)
+ __assert__(pass, @message || __msg__(sym, *a, &b))
+ end
+
+ # Puts together a suitable error message.
+ #
+ def __msg__(m, *a, &b)
+ inspection = @delegate.send(:inspect)
+ if @negated
+ "! #{inspection} #{m} #{a.collect{|x| x.inspect}.join(',')}"
+ else
+ "#{inspection} #{m} #{a.collect{|x| x.inspect}.join(',')}"
end
+ #self.class.message(m)[@delegate, *a] )
end
- if matcher.respond_to?(:failure_message)
- return matcher.failure_message
+
+ # Simple assert.
+ #--
+ # TODO: Can the handling of the message be simplified/improved?
+ #++
+ def __assert__(pass, message=nil)
+ pass = @negated ^ pass
+ Assertor.assert(pass, message, @backtrace)
end
- false
+
+ #
+ def matcher_message(matcher)
+ if @negated
+ if matcher.respond_to?(:negative_failure_message)
+ return matcher.failure_message
+ end
+ end
+ if matcher.respond_to?(:failure_message)
+ return matcher.failure_message
+ end
+ false
+ end
+
+ # TODO: Ultimately better messages might be nice.
+ #
+ #def self.message(op,&block)
+ # @message ||= {}
+ # block ? @message[op.to_sym] = block : @message[op.to_sym]
+ #end
+ #
+ #message(:==){ |*a| "Expected #{a[0].inspect} to be equal to #{a[1].inspect}" }
end
- # TODO: Ultimately better messages might be nice.
- #
- #def self.message(op,&block)
- # @message ||= {}
- # block ? @message[op.to_sym] = block : @message[op.to_sym]
- #end
- #
- #message(:==){ |*a| "Expected #{a[0].inspect} to be equal to #{a[1].inspect}" }
end
# DO WE MAKE THESE EXCEPTIONS?
#class BasicObject
# def assert