-
Notifications
You must be signed in to change notification settings - Fork 2
Yadriggy PowerAssert
A simple power-assert library built with Yadriggy.
Power assert is a function that shows the values of every sub expression when the given assertion fails. It is available in various programming languages such as Groovy, .NET, JavaScript, and Ruby. Yadriggy simplifies the implementation of the power assert.
Using the power assert by Yadriggy is easy.
require 'yadriggy/assert'
arr = [2, 4, 6]
Yadriggy::Assert::assert { arr[1] % 2 != 0 }
The assertion is a block passed to the method assert
.
When the given assertion fails, assert
prints the results
of the sub expressions in the assertion:
--- Yadriggy::Assert ---
arr[1] % 2 != 0
| | | | |
| | | | 0
| | | false
| | 2
| 0
4
------------------------
It might be convenient to define your own version of the
power assert method.
The implementation of the assert
method above is as follows:
def self.assert(&block)
reason = Reason.new
begin
res = assertion(reason, block)
puts_reason(reason) unless res
return res
rescue AssertFailure => evar
puts_reason(evar.reason, evar)
raise evar.cause
end
end
The main part of this method is the call to assertion
:
res = assertion(reason, block)
assertion
returns the result of evaluating the assertion.
The first argument reason
is the object where the detailed logs
of the evaluation of the assertion will be recorded.
The second argument block
is a block containing the assertion.
So you might want to define the following convenient method:
def my_assert(&block)
reason = Yadriggy::Assert::Reason.new
unless Yadriggy::Assert::assertion(reason, block)
puts(reason.show) # print the log
binding.pry # for further investigation of reason
end
end
reason.show
returns an array of String
, which presents the result of the
evaluation. reason.ast
is the abstract syntax tree of the assertion.
reason.results
is a hash map from sub expressions to their source code
and their values.
For example, reason.results[reason.ast]
returns an array.
The first element is the source code for ast
while the second element is
the value that ast
evaluates to. So, when the value of a sub expression
is a large object, instead of doing reason.show
,
you can inspect the value as follows:
ast = reason.ast
results = reason.results
results[ast][1] # the value of the assertion
# If ast is a binary expression
results[ast.left][1] # the value of the left operand
results[ast.right][1] # the value of the left operand
# If ast is a unary expression
results[ast.operand][1] # the value of the operand
# If ast is a method call
results[ast.receiver][1] # the value of the receiver
results[ast.args[0]][1] # the value of the first argument
# If ast is a expression surrounded with ()
results[ast.expression][1] # the value of the expression
The implementation using Yadriggy is simple. It does not need source-code preprocessing or a dedicated virtual machine.
It first obtains the abstract syntax tree of the assertion:
ast = Yadriggy::reify(block)
Then it traverses the tree. When it reaches a variable or a complex expression that it cannot directly interpret, it converts the tree node into the source code:
src = Yadriggy::PrettyPrinter.ast_to_s(ast)
Here, ast
is a tree node (a leaf or intermediate node).
ast_to_s
returns a String
object. Then, eval
is called
to evaluate the source code:
file_name, lineno = ast.source_location
eval(src, block.binding, file_name, lineno)
ast.source_location
returns the file name and the line number
of the abstract syntax tree.
Yes, you can regard this power assert implementation as a domain specific
language with slightly extended Ruby's semantics. The semantics is mostly
the same as Ruby's except every evaluation of a sub expression is logged in
a Reason
object.