Class: Whitestone::Assertion::Custom

Inherits:
Base show all
Defined in:
lib/whitestone/custom_assertions.rb

Overview

Whitestone::Assertion::Custom – custom assertions

This class is responsible for creating and running custom assertions.

Creating:

Whitestone.custom :circle, {
  :description => "Circle equality",
  :parameters  => [ [:circle, Circle], [:values, Array] ],
  :run => lambda {
    x, y, r, label = values
    test('x')     { Ft circle.centre.x,  x            }
    test('y')     { Ft circle.centre.y,  y            }
    test('r')     { Ft circle.radius,    r            }
    test('label') { Eq circle.label,     Label[label] }
  }
}
  • (Whitestone.custom passes its arguments straight through to Custom.define, which is surprisingly a very lightweight method.)

Running:

T :circle, circle, [4,1, 10, nil]
  --> assertion = Custom.new(:custom, :assert, :circle, circle, [4,1, 10, nil]
  --> assertion.run

Custom is an assertion (Assertion::Base) object, just like True, Equality, Catch, etc. It follows the same methods and life-cycle:

  • initialize: check arguments are sound; store instance variables for later

  • run: use the instance variables to perform the necessary assertion

  • message: return a message to be displayed upon failure

run is a lot more complicated than a normal assertion because all the logic is in the Config object (compare Equality#run: == @expected). The block that is specified (the lambda above) needs to be run in a special context for those test calls to work.

Defined Under Namespace

Classes: Config, CustomTestContext

Constant Summary collapse

@@config =

{ :circle => Config.new(…), :square => Config.new(…) }

Hash.new

Class Method Summary collapse

Instance Method Summary collapse

Methods inherited from Base

#block

Methods included from Guards

#args_or_block_one_only, #block_required, #no_block_allowed, #one_argument, #two_arguments, #two_or_three_arguments, #type_check

Constructor Details

#initialize(mode, *args, &block) ⇒ Custom

Custom#initialize

Retrieves the config for the named custom assertion and checks the arguments against the configured parameters.

Sets up a context (CustomTestContext) for running the assertion when #run is called.



89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/whitestone/custom_assertions.rb', line 89

def initialize(mode, *args, &block)
  name = args.shift
  super(mode, *args, &block)
  no_block_allowed
  @config = @@config[name]
  if @config.nil?
    message = "Non-existent custom assertion: #{name.inspect}"
    raise AssertionSpecificationError, message
  end
  check_args_against_parameters(args)
  @context = CustomTestContext.new(@config.parameters, args)
end

Class Method Details

.define(name, definition) ⇒ Object

Custom.define

Defines a new custom assertion – just stores the configuration away for retrieval when the assertion is run.



78
79
80
# File 'lib/whitestone/custom_assertions.rb', line 78

def self.define(name, definition)
  @@config[name] = Config.new(name, definition)
end

Instance Method Details

#messageObject

Custom#message

If a failure occurred, a failure message was prepared when the exception was caught in #run.



151
152
153
# File 'lib/whitestone/custom_assertions.rb', line 151

def message
  @message
end

#runObject

Custom#run

Returns true or false for pass or fail, just like other assertions.

The Config object provides the block to run, while @context provides the context in which to run it.

We trap FailureOccurred errors because as a custom assertion we need to take responsibility for the errors, and wrap some information around the error message.



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
# File 'lib/whitestone/custom_assertions.rb', line 112

def run
  test_code = @config.run_block
  @context.instance_eval &test_code
    # ^^^ This gives the test code access to the 'test' method that is so
    #     important for running a custom assertion.
    #     See the notes on CustomTestContext for an example.
  return true  # the custom test passed
rescue FailureOccurred => f
  # We are here because an assertion failed.  That means _this_ (custom)
  # assertion has failed.  We need to build an error message and raise
  # FailureOccurred ourselves.
  @message = String.new.tap { |str|
	str << Col["#{@config.description} test failed: "].yb
	str << Col[@context.context_label].cb
	str << Col[" (details below)\n", f.message.___indent(4)].fmt(:yb, :yb)
  }
  return false
rescue AssertionSpecificationError => e
  # While running the test block, we got an AssertionSpecificationError.
  # This probably means some bad data was put in, like
  #    T :circle, c, [4,1, "radius", nil]
  # (The radius needs to be a number, not a string.)
  # We will still raise the AssertionSpecificationError but we want it to
  # look like it comes from the _custom_ assertion, not the _primitive_
  # one.  Essentially, we are acting like it's a failure: constructing the
  # message that includes the context label (in this case, 'r' for
  # radius).
  message = String.new.tap { |str|
	str << Col["#{@config.description} test -- error: "].yb
	str << Col[@context.context_label].cb
	str << Col[" details below\n", e.message.___indent(4)].fmt(:yb, :yb)
  }
  raise AssertionSpecificationError, message
end