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

ZERO_COUNTS =

Initial settings of assertion counts.

{:total=>0,:pass=>0,:fail=>0}
COMPARISON_OPERATORS =
{ :==" => :!=" }

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

- (Assertor) initialize(delegate, opts = {})

New Assertor.



111
112
113
114
115
116
# 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

Dynamic Method Handling

This class handles dynamic methods through the method_missing method

- (Object) method_missing(sym, *args, &block) (private)

Converts a missing method into an Assertion.



346
347
348
349
350
351
352
# 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

Class Method Details

+ (Object) assert(pass, error = nil, negated = nil, backtrace = nil)

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

+ (Object) assertion_error

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

+ (Object) const_missing(const) (private)



415
416
417
# File 'lib/ae/assertor.rb', line 415

def self.const_missing(const)
 ::Object.const_get(const)
end

+ (Object) counts

Returns Hash used to track assertion counts.



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

def self.counts
  $ASSERTION_COUNTS
end

+ (Object) increment_counts(pass)

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

+ (Object) raise_assertion(error, negated, backtrace = nil)

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

+ (Object) recount(reset = nil)

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

- (Object) =~(match)

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



244
245
246
# File 'lib/ae/assertor.rb', line 244

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

- (Object) __assert__(pass, error = nil) (private)

Simple assert.



357
358
359
# File 'lib/ae/assertor.rb', line 357

def __assert__(pass, error=nil)
  Assertor.assert(pass, error, @negated, @backtrace)
end

- (Boolean) assay_assertion?(assertion) (private)

Is the `assertion` object an assay-style assertion?



282
283
284
# File 'lib/ae/assertor.rb', line 282

def assay_assertion?(assertion)
  assertion.respond_to?(:exception) && assertion.respond_to?(:pass?)
end

- (Object) assay_assertion_apply(assay) (private)



287
288
289
290
291
292
293
294
295
296
# 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

- (Object) assert(*args, &block)

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.



143
144
145
146
147
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
# 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

- (Object) compare_message(operator, *args, &blk) (private)

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.



375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
# 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

- (Boolean) exception?(object) (private)

Is the object an Exception or an instance of one?



339
340
341
# File 'lib/ae/assertor.rb', line 339

def exception?(object)
  ::Exception === object or ::Class === object and object.ancestors.include?(::Exception)
end

- (Object) expect(*args, &block)

Internal expect, provides all functionality associated with external #expect method. (See Expect#expect)



188
189
190
191
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
# 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

- (Object) flunk(message = nil, backtrace = nil)



237
238
239
# File 'lib/ae/assertor.rb', line 237

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

- (String) generic_message(op, *a, &b) (private)

Puts together a suitable error message.



404
405
406
407
408
409
410
411
412
# 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

- (Object) inspect



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

def inspect
  @delegate.inspect
end

- (Object) not(msg = nil)

Negate the meaning of the assertion.



122
123
124
125
126
# File 'lib/ae/assertor.rb', line 122

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

- (Object) proc_apply(target) (private)



268
269
270
271
272
273
274
275
276
# 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

- (Boolean) proc_assertion?(target) (private)



263
264
265
# File 'lib/ae/assertor.rb', line 263

def proc_assertion?(target) 
  ::Proc === target || target.respond_to?(:call) || target.respond_to?(:to_proc)
end

- (Boolean) rspec_matcher?(target) (private)

Is `target` an Rspec-style Matcher?



301
302
303
# File 'lib/ae/assertor.rb', line 301

def rspec_matcher?(target)
  target.respond_to?(:matches?)
end

- (Object) rspec_matcher_apply(matcher) (private)



306
307
308
309
310
# 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

- (Object) rspec_matcher_message(matcher) (private)



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
# 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

- (Object) send(op, *a, &b)



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

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