Method: Whitestone.action

Defined in:
lib/whitestone.rb

.action(base, assert_negate_query, *args, &block) ⇒ Object

Whitestone.action

This is an absolutely key method. It implements T, F, Eq, T!, F?, Eq?, etc. After some sanity checking, it creates an assertion object, runs it, and sees whether it passed or failed.

If the assertion fails, we raise FailureOccurred, with the necessary information about the failure. If an error happens while the assertion is run, we don’t catch it. Both the error and the failure are handled upstream, in Whitestone.call.

It’s worth noting that errors can occur while tests are run that are unconnected to this method. Consider these two examples:

T { "foo".frobnosticate? }     -- error occurs on our watch
T "foo".frobnosticate?         -- error occurs before T() is called

By letting errors from here escape, the two cases can be dealt with together.

T and F are special cases: they can be called with custom assertions.

T :circle, c, [4,1, 10, :H]
  -> run_custom_test(:circle, :assert, [4,1,10,:H])


317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
# File 'lib/whitestone.rb', line 317

def action(base, assert_negate_query, *args, &block)
  mode = assert_negate_query    # :assert, :negate or :query

  # Sanity checks: these should never fail!
  unless [:assert, :negate, :query].include? mode
    raise AssertionSpecificationError, "Invalid mode: #{mode.inspect}"
  end
  unless ASSERTION_CLASSES.key? base
    raise AssertionSpecificationError, "Invalid base: #{base.inspect}"
  end

  # Special case: T may be used to invoke custom assertions.
  # We catch the use of F as well, even though it's disallowed, so that
  # we can give an appropriate error message.
  if base == :T or base == :F and args.size > 1 and args.first.is_a? Symbol
    if base == :T and mode == :assert
      # Run a custom assertion.
      inside_custom_assertion do
        action(:custom, :assert, *args)
      end
      return nil
    else
      message =  "You are attempting to run a custom assertion.\n"
      message << "These can only be run with T, not F, T?, T!, F? etc."
      raise AssertionSpecificationError, message
    end
  end

  assertion = ASSERTION_CLASSES[base].new(mode, *args, &block)
    # e.g. assertion = Assertion::Equality(:assert, 4, 4)   # no block
    #      assertion = Assertion::Nil(:query) { names.find "Tobias" }
    #      assertion = Assertion::Custom(...)

  stats[:assertions] += 1 unless @inside_custom_assertion

  # We run the assertion (returns true for pass and false for fail).
  passed = assertion.run

  # We negate the result if neccesary...
  case mode
  when :negate then passed = ! passed
  when :query  then return passed
  end
  # ...and report a failure if necessary.
  if passed
    # We do this here because we only want the test to pass if it actually
    # runs an assertion; otherwise its result is 'blank'.  If a later
    # assertion in the test fails or errors, the result will be rewritten.
    @current_test.result = :pass if @current_test
  else
    calling_context = assertion.block || @calls.last
    backtrace = caller
    raise FailureOccurred.new(calling_context, assertion.message, backtrace)
  end
end