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

assert(pass, error=nil, negated=nil, backtrace=nil) click to toggle source

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
assertion_error() click to toggle source

Returns the Exception class to be raised when an assertion fails.

# File lib/ae/assertor.rb, line 85
def self.assertion_error
  ::Assertion
end
counts() click to toggle source

Returns Hash used to track assertion counts.

# File lib/ae/assertor.rb, line 25
def self.counts
  $ASSERTION_COUNTS
end
increment_counts(pass) click to toggle source

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(delegate, opts={}) click to toggle source

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
raise_assertion(error, negated, backtrace=nil) click to toggle source

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
recount(reset=nil) click to toggle source

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

const_missing(const) click to toggle source

@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

=~(match) click to toggle source

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
assert(*args, &block) click to toggle source

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
expect(*args, &block) click to toggle source

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
flunk(message=nil, backtrace=nil) click to toggle source
# File lib/ae/assertor.rb, line 237
def flunk(message=nil, backtrace=nil)
  __assert__(false, message || @message)
end
inspect() click to toggle source
# File lib/ae/assertor.rb, line 254
def inspect
  @delegate.inspect
end
not(msg=nil) click to toggle source

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
send(op, *a, &b) click to toggle source
# File lib/ae/assertor.rb, line 249
def send(op, *a, &b)
  method_missing(op, *a, &b)
end

Private Instance Methods

__assert__(pass, error=nil) click to toggle source

Simple assert.

# File lib/ae/assertor.rb, line 357
def __assert__(pass, error=nil)
  Assertor.assert(pass, error, @negated, @backtrace)
end
assay_assertion?(assertion) click to toggle source

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
assay_assertion_apply(assay) click to toggle source
# 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
compare_message(operator, *args, &blk) click to toggle source

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
exception?(object) click to toggle source

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
generic_message(op, *a, &b) click to toggle source

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
method_missing(sym, *args, &block) click to toggle source

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
proc_apply(target) click to toggle source
# 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
proc_assertion?(target) click to toggle source
# File lib/ae/assertor.rb, line 263
def proc_assertion?(target) 
  ::Proc === target || target.respond_to?(:call) || target.respond_to?(:to_proc)
end
rspec_matcher?(target) click to toggle source

Is `target` an Rspec-style Matcher?

# File lib/ae/assertor.rb, line 301
def rspec_matcher?(target)
  target.respond_to?(:matches?)
end
rspec_matcher_apply(matcher) click to toggle source
# 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
rspec_matcher_message(matcher) click to toggle source
# 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