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
#
#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.
#
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 inplace 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
#
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
pass = target # truthiness
msg = args.shift # optional mesage for TestUnit compatiability
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 || @delegate
be dropped?
#++
def expect(*args, &block)
return self if args.empty? && !block # same as #assert
target = block || args.shift
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
__assert__(pass, msg)
end
# Is the +object+ and Exception or an instance of one.
#--
# TODO: Should we use a more libreral determination of exception.
# e.g. respond_to?(:exception)
.
#++
def exception?(object)
::Exception === object or ::Class === object and object.ancestors.include?(::Exception)
end
#
def flunk(message=nil)
__assert__(false, message || @message)
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
#
def send(op, *a, &b)
method_missing(op, *a, &b)
end
private
# 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
# 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
# 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
# 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
#
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
# DO WE MAKE THESE EXCEPTIONS?
#class BasicObject
# def assert
# end
#end
# Copyright (c) 2008,2009 Thomas Sawyer