Skip to content

Yadriggy PowerAssert

chibash edited this page Jul 7, 2018 · 2 revisions

A simple power-assert library built with Yadriggy.

Overview

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

Writing your own power assert

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

Implementation

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.

Is this a DSL?

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.

Clone this wiki locally