class AE::Assertor
Assertor
is the underlying class of the whole system. It implements the flutent assertion notation.
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.
Constants
- COMPARISON_OPERATORS
- ZERO_COUNTS
Initial settings of assertion counts.
Public Class Methods
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 call of an assertion.
# File lib/ae/assertor.rb, line 62 def self.assert(pass, error=nil, negated=nil, backtrace=nil) pass = negated ^ !!pass increment_counts(pass) if !pass backtrace = backtrace || caller raise_assertion(error, negated, backtrace) end return pass end
Returns the Exception
class to be raised when an assertion fails.
# File lib/ae/assertor.rb, line 85 def self.assertion_error ::Assertion end
Returns Hash used to track assertion counts.
# File lib/ae/assertor.rb, line 25 def self.counts $ASSERTION_COUNTS end
Increment assertion counts. If pass
is true
then :total
and :pass
are increased. If pass
if false
then :total
and :fail
are incremented.
# File lib/ae/assertor.rb, line 49 def self.increment_counts(pass) counts[:total] += 1 if pass counts[:pass] += 1 else counts[:fail] += 1 end return counts end
New Assertor
.
# File lib/ae/assertor.rb, line 111 def initialize(delegate, opts={}) #, backtrace) @delegate = delegate @message = opts[:message] @backtrace = opts[:backtrace] || caller #[1..-1] @negated = !!opts[:negated] end
The intent of the method is to raise an assertion failure class that the test framework supports.
# File lib/ae/assertor.rb, line 74 def self.raise_assertion(error, negated, backtrace=nil) if not ::Exception === error error = assertion_error.new(error) end error.set_negative(negated) error.set_backtrace(backtrace || caller) error.set_assertion(true) fail error end
Reset assertion counts.
reset - Hash which can be used to set counts manually (optional).
Returns the Hash of previous counts.
# File lib/ae/assertor.rb, line 34 def self.recount(reset=nil) old_counts = counts.dup if reset reset.each do |type, value| counts[type.to_sym] = value end else counts.replace(ZERO_COUNTS.dup) end return old_counts end
Private Class Methods
@see redmine.ruby-lang.org/issues/3768
# File lib/ae/assertor.rb, line 415 def self.const_missing(const) ::Object.const_get(const) end
Public Instance Methods
Ruby seems to have a quark in it's implementation whereby this must be defined explicitly, otherwise it somehow skips method_missing
.
# File lib/ae/assertor.rb, line 244 def =~(match) method_missing(:"=~", match) 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.
# File lib/ae/assertor.rb, line 143 def assert(*args, &block) return self if !block && args.empty? target = args.shift unless block error = nil # Block if block match = args.shift result = block.arity > 0 ? block.call(@delegate) : block.call if match pass = (match == result) error = @message || "#{match.inspect} == #{result.inspect}" else pass = result error = @message || block.inspect # "#{result.inspect}" end # Proc-style elsif proc_assertion?(target) pass, error = proc_apply(target) # Assay-style assertions #elsif assay_assertion?(target) # pass, error = assay_assertion_apply(target) # RSpec-style matchers elsif rspec_matcher?(target) pass, error = rspec_matcher_apply(target) # Truthiness else pass = target # truthiness error = args.shift # optional message for TestUnit compatiability end __assert__(pass, error) end
Internal expect, provides all functionality associated with external expect
method. (See Expect#expect)
# File lib/ae/assertor.rb, line 188 def expect(*args, &block) return self if !block && args.empty? # same as #assert pass = false error = nil if block match = args.shift || @delegate # TODO: see above 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 error = "#{match} not raised" rescue match => error pass = true error = "#{match} raised" rescue ::Exception => error pass = false error = "#{match} expected but #{error.class} was raised" ensure $DEBUG = debug end else result = block.arity > 0 ? block.call(@delegte) : block.call pass = (match === result) error = @message || "#{match.inspect} === #{result.inspect}" end ## Matcher #elsif target.respond_to?(:matches?) # pass = target.matches?(@delegate) # error = @message || matcher_message(target) #|| target.inspect # if target.respond_to?(:exception) # #error_class = target.failure_class # error = target.exception #failure(:backtrace=>@backtrace, :negated=>@negated) # end # Case Equality else target = args.shift pass = (target === @delegate) error = @message || "#{target.inspect} === #{@delegate.inspect}" end __assert__(pass, error) end
# File lib/ae/assertor.rb, line 237 def flunk(message=nil, backtrace=nil) __assert__(false, message || @message) end
# File lib/ae/assertor.rb, line 254 def inspect @delegate.inspect end
Negate the meaning of the assertion.
# File lib/ae/assertor.rb, line 122 def not(msg=nil) @negated = !@negated @message = msg if msg self end
# File lib/ae/assertor.rb, line 249 def send(op, *a, &b) method_missing(op, *a, &b) end
Private Instance Methods
Simple assert.
# File lib/ae/assertor.rb, line 357 def __assert__(pass, error=nil) Assertor.assert(pass, error, @negated, @backtrace) end
Is the `assertion` object an assay-style assertion?
# File lib/ae/assertor.rb, line 282 def assay_assertion?(assertion) assertion.respond_to?(:exception) && assertion.respond_to?(:pass?) end
# File lib/ae/assertor.rb, line 287 def assay_assertion_apply(assay) if @negated pass = assay.fail?(@delegate) error = assay #.exception(@message || ) else pass = assay.pass?(@delegate) error = assay #.exception(@message || ) end return pass, error end
Message to use when making a comparion assertion.
NOTE: This message utilizes the ANSI gem to produce colorized comparisons. If you need to remove color output (for non-ANSI terminals) you can either set `AE.ansi = false` or use the ANSI library's master switch to deactive all ANSI codes, which can be set in your test helper.
@param operator [Symbol] operator/method
@see rubyworks.github.com/ansi
# File lib/ae/assertor.rb, line 375 def compare_message(operator, *args, &blk) return nil unless COMPARISON_OPERATORS.key?(operator) prefix = "" a, b = @delegate.inspect, args.first.inspect if @negated op = COMPARISON_OPERATORS[operator] if op operator = op else prefix = "NOT " end end if AE.ansi? diff = ::ANSI::Diff.new(a,b) a = diff.diff1 b = diff.diff2 end if a.size > 13 or b.size > 13 prefix + "a #{operator} b\na) " + a + "\nb) " + b else prefix + "#{a} #{operator} #{b}" end end
Is the object
an Exception
or an instance of one?
# File lib/ae/assertor.rb, line 339 def exception?(object) ::Exception === object or ::Class === object and object.ancestors.include?(::Exception) end
Puts together a suitable error message.
@param op [Symbol] operator/method
@return [String] message
# File lib/ae/assertor.rb, line 404 def generic_message(op, *a, &b) inspection = @delegate.send(:inspect) if @negated "! #{inspection} #{op} #{a.collect{|x| x.inspect}.join(',')}" else "#{inspection} #{op} #{a.collect{|x| x.inspect}.join(',')}" end #self.class.message(m)[@delegate, *a] ) end
Converts a missing method into an Assertion.
# File lib/ae/assertor.rb, line 346 def method_missing(sym, *args, &block) error = @message || compare_message(sym, *args, &block) || generic_message(sym, *args, &block) pass = @delegate.__send__(sym, *args, &block) __assert__(pass, error) end
# File lib/ae/assertor.rb, line 268 def proc_apply(target) call = target.method(:call) rescue target.to_proc pass = call.arity != 0 ? call.call(@delegate) : call.call error = @message || ( to_s = target.method(:to_s) to_s.arity == 0 ? to_s.call : to_s.call(@negated) ) return pass, error end
# File lib/ae/assertor.rb, line 263 def proc_assertion?(target) ::Proc === target || target.respond_to?(:call) || target.respond_to?(:to_proc) end
Is `target` an Rspec-style Matcher?
# File lib/ae/assertor.rb, line 301 def rspec_matcher?(target) target.respond_to?(:matches?) end
# File lib/ae/assertor.rb, line 306 def rspec_matcher_apply(matcher) pass = matcher.matches?(@delegate) error = @message || rspec_matcher_message(matcher) return pass, error end
# File lib/ae/assertor.rb, line 315 def rspec_matcher_message(matcher) if @negated if matcher.respond_to?(:failure_message_for_should_not) return matcher.failure_message_for_should_not end if matcher.respond_to?(:negative_failure_message) return matcher.negative_failure_message end end if matcher.respond_to?(:failure_message_for_should) return matcher.failure_message_for_should end if matcher.respond_to?(:failure_message) return matcher.failure_message end return matcher.to_s # TODO: or just `nil` ? end