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