Class: AE::Assertor

Inherits:
BasicObject
Includes:
Subjunctive
Defined in:
lib/ae/assertor.rb,
lib/ae/subjunctive.rb,
lib/ae/adapters/testunit.rb

Overview

:nodoc:

Constant Summary collapse

ZERO_COUNTS =

Initial settings of assertion counts.

{:total=>0,:pass=>0,:fail=>0}

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Subjunctive

#a, #be

Methods inherited from BasicObject

find_hidden_method, hide, reveal

Constructor Details

#initialize(delegate, opts = {}) ⇒ Assertor

New Assertor.



112
113
114
115
116
117
# File 'lib/ae/assertor.rb', line 112

def initialize(delegate, opts={}) #, backtrace)
  @delegate  = delegate
  @message   = opts[:message]
  @backtrace = opts[:backtrace] || caller #[1..-1]
  @negated   = !!opts[:negated]
end

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

#method_missing(sym, *args, &block) ⇒ Object (private)

Converts a missing method into an Assertion.

TODO: In future should probably be ‘@delegate.public_send(sym, *a, &b)`.



277
278
279
280
281
282
283
# File 'lib/ae/assertor.rb', line 277

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

Class Method Details

.assert(pass, error = nil, negated = nil, backtrace = nil) ⇒ Object

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.



62
63
64
65
66
67
68
69
70
# 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_errorObject

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



85
86
87
# File 'lib/ae/assertor.rb', line 85

def self.assertion_error
  ::Assertion
end

.countsObject

Returns Hash used to track assertion counts.



25
26
27
# File 'lib/ae/assertor.rb', line 25

def self.counts
  $assertion_counts
end

.increment_counts(pass) ⇒ Object

Increment assertion counts. If pass is true then :total and :pass are increased. If pass if false then :total and :fail are incremented.



49
50
51
52
53
54
55
56
57
# 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

.raise_assertion(error, negated, backtrace = nil) ⇒ Object

The intent of the method is to raise an assertion failure class that the test framework supports.



74
75
76
77
78
79
80
81
82
# 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) ⇒ Object

Reset assertion counts.

reset - Hash which can be used to set counts manually (optional).

Returns the Hash of previous counts.



34
35
36
37
38
39
40
41
42
43
44
# 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

Instance Method Details

#=~(match) ⇒ Object

Ruby seems to have a quark in it’s implementation whereby this must be defined explicitly, otherwise it somehow skips #method_missing.



249
250
251
# File 'lib/ae/assertor.rb', line 249

def =~(match)
  method_missing(:"=~", match)
end

#assert(*args, &block) ⇒ Object

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. – The use of #to_proc and #matches? as sepcial cases is not a robust solution. ++



148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
# File 'lib/ae/assertor.rb', line 148

def assert(*args, &block)
  return self if args.empty? && !block

  target = block || args.shift
  error  = nil

  # Lambda
  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)
      error = @message || "#{match.inspect} == #{result.inspect}"
    else
      pass  = result
      error = @message || block.inspect  # "#{result.inspect}"
    end

  # Matcher
  elsif target.respond_to?(:matches?) # Matchers
    pass  = target.matches?(@delegate)
    error = @message || matcher_message(target) #|| target.inspect
    if target.respond_to?(:exception)
      #error_class = target.failure_class
      error = target.exception #(:backtrace=>@backtrace, :negated=>@negated)
    end

  # Truthiness
  else
    pass  = target     # truthiness
    error = args.shift # optional message for TestUnit compatiability
  end

  __assert__(pass, error)
end

#expect(*args, &block) ⇒ Object

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? ++



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/ae/assertor.rb', line 192

def expect(*args, &block)
  return self if args.empty? && !block  # same as #assert

  target = block || args.shift
  error  = nil

  # Lambda
  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
        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 Equals
  else
    pass  = (target === @delegate)
    error = @message || "#{target.inspect} === #{@delegate.inspect}"
  end

  __assert__(pass, error)
end

#flunk(message = nil, backtrace = nil) ⇒ Object



242
243
244
# File 'lib/ae/assertor.rb', line 242

def flunk(message=nil, backtrace=nil)
  __assert__(false, message || @message)
end

#inspectObject



259
260
261
# File 'lib/ae/assertor.rb', line 259

def inspect
  @delegate.inspect
end

#not(msg = nil) ⇒ Object

Negate the meaning of the assertion.

– TODO: Should this return a new Assertor instead of in place negation? ++



124
125
126
127
128
# File 'lib/ae/assertor.rb', line 124

def not(msg=nil)
  @negated = !@negated
  @message = msg if msg
  self
end

#send(op, *a, &b) ⇒ Object



254
255
256
# File 'lib/ae/assertor.rb', line 254

def send(op, *a, &b)
  method_missing(op, *a, &b)
end