assert
macro and a few other macros which you can derive from it.The assert
macro
This new macro, assert
, was added to SKILL in SKILL
version 32. You can find out which version of SKILL you are using
with the SKILL functiongetSkillVersion
, which returns a string such as"SKILL32.00"
or "SKILL33.00"
.Using this macro is simple. In its simplest form, you wrap
any single expression within (assert ...)
. At evaluation
time this will trigger an error if the expression evaluates to nil
.
CIW> (assert 1 + 1 == 2)nil CIW> (assert 2 + 2 == 5)*Error* ASSERT FAILED: ((2 + 2) == 5)<<< Stack Trace >>> error("ASSERT FAILED: %L\n" '((2 + 2) == 5)) unless(((2 + 2) == 5) error("ASSERT FAILED: %L\n" '(& == 5)))
You can also specify the error message using printf-style arguments.
CIW> (defun testit (x "n") (assert x > 3 "expecting x > 3, not %L" x) x-3) CIW> (testit 12)9 CIW> (testit 2)*Error* expecting x > 3, not 2<<< Stack Trace >>> error("expecting x > 3, not %L" x) unless((x > 3) error("expecting x > 3, not %L" x)) testit(2)
What is a macro?
Macros are a feature in many lisps including emacs lisp, common lisp, and SKILL. Consequently, you can find tons of information on the Internet explaining lisp macros. A quick Google search for "lisp macro" returns pages of useful results.In particular, a SKILL macro is a special type of function which computes and returns another piece of SKILL code in the form of an s-expression. This capability allows SKILL programs to write SKILL programs as a function of their raw, unevaluated, operands at the call-site. Although macros can certainly be abused, when used correctly SKILL macros can greatly enhance readability by abstracting away certain details, or by automating repetitive patterns.
You can find out more about SKILL macros by consulting the Cadence documentation. There is also an Advanced SKILL Programming class which Cadence Educational Services offers.
In-line tests
You can useassert
inside your
functions for run-time checks. You can also use assert
at
the top level of your SKILL files for load-time checks. This has an
added advantage of helping the person who reads your code to
understand how the function you are defining is used. In the
following example anyone who reads your function definition can
immediately see some examples of how to use the function.;; Sort a list of strings case independently into alphabetical order. (defun sort_case_independent (words "l") (sort words (lambda (w1 w2) (alphalessp (lowerCase w1) (lowerCase w2))))) (assert nil ; works for empty list? == (sort_case_independent nil)) (assert '("a"); works for singleton list? == (sort_case_independent '("a")))) (assert '("a" "B") == (sort_case_independent '("B" "a"))) (assert '("a" "B") == (sort_case_independent '("a" "B"))) (assert '("A" "b") == (sort_case_independent '("b" "A"))) (assert '("A" "b") == (sort_case_independent '("A" "b"))) (assert '("A" "b" "c" "D") == (sort_case_independent '("c" "D" "A" "b")))Writing your SKILL files to include these top-level assertions has yet another advantage: if someone later modifies your function,
sort_case_independent
, the tests will run the
next time anyone loads the file. This means if an error has been
introduced in the function, some sanity testing happens at load time.
Furthermore, if someone enhances the function in a way that
breaks backward compatibility, the assertion will fail at load time.Defining assert
if it is missing
If you are using a version of Virtuoso, Allegro, etc, which does not
contain a definition of assert, you can define it yourself.
(unless (isCallable 'assert) (defmacro assert (expression @rest printf_style_args) (if printf_style_args `(unless ,expression (error ,@printf_style_args)) `(unless ,expression (error "ASSERTION FAILED: %L\n" ',expression)))))
The assert_test
macro
Some unit testing frameworks supply assertion functions such as
assert_less, assert_greater, assert_equal, assert_not_equal. It is
possible in SKILL to define a single assertion macro called,
assert_test
, which provides all these capabilities in
one. You don't really
need assert_equal
, assert_not_equal
, asset_lessp
etc...This macro is useful for building test cases. This macro attempts to output a helpful message if the assertion fails. The message includes the parameters to the testing expression, and the values they evaluate to. For example:
CIW> A = 33 CIW> B = 22 CIW> (assert_test A+B == B+2) *Error* (A + B) --> 3 (B + 2) --> 4 FAILED ASSERTION: ((A + B) == (B + 2))<<< Stack Trace >>> ... CIW> (assert_test A+B > B+2) *Error* (A + B) --> 3 (B + 2) --> 4 FAILED ASSERTION: ((A + B) > (B + 2))<<< Stack Trace >>> ...
The intelligent thing about assert_test
as can be
seen from the above example, is that it constructs an error message
which tells you the text of the assertion that failed: ((A + B)
== (B + 2))
. It also tells you the arguments to the testing
function in both raw and evaluated form: (A + B) --> 3
and (B + 2) --> 4
The macro definition is not trivial, but the code is given here. You don't really need to understand how it works in order to use it.
;; ARGUMENTS: ;; expr - an expression to evaluate, asserting that it does not return nil ;; ?ident ident - specifies an optional identifier which will be printed with [%L] in ;; the output if the assertion fails. This will help you identify the ;; exact assertion that failed when scanning a testing log file. ;; printf_style_args - additional printed information which will be output if the ;; assertion fails. (defmacro assert_test (expr @key ident @rest printf_style_args) (if (atom expr) `(assert ,expr) (let ((extra (if printf_style_args `(strcat "\n" (sprintf nil ,@printf_style_args))""))) (destructuringBind (operator @rest operands) expr (letseq ((vars (foreach mapcar _operand operands (gensym))) (bindings (foreach mapcar (var operand) vars operands (list var operand))) (assertion `(,operator ,@vars)) (errors (foreach mapcar (var operand) vars operands `(sprintf nil "%L\n --> %L" ',operand ,var)))) `(let ,bindings (unless ,assertion (error "%s%s%s" (if ',ident (sprintf nil "[%L] " ,ident)"") (buildString (list ,@errors (sprintf nil "FAILED ASSERTION: %L" ',expr))"\n") ,extra))))))))
The assert_fails
macro
With the assertion macros presented above you can pretty robustly make
assertions about the return values of functions. A limitation,
however, is you cannot easily make assertions about the corner cases
where your function triggers an error. The following macro,assert_fails
, provides the ability to assert that an
expression triggers an error. For example, thesort_case_independent
function defined above will fail,
triggering an error, if given a list containing a non-string.
CIW> (sort_case_independent '("a" "b" 42 "c" "d"))*Error* lowerCase: argument #1 should be either a string or a symbol (type template = "S") at line 112 of file "*ciwInPort*" - 42<<< Stack Trace >>> lowerCase(w2) alphalessp(lowerCase(w1) lowerCase(w2)) funobj@0x2cac49a8("a" 42) sort(words lambda((w1 w2) alphalessp(lowerCase(w1) lowerCase(w2)))) sort_case_independent('("a" "b"))
You could fix this by enhancing the function to do something reasonable in such a situation. Or you could simply document the limitation, in which case you might want to extend the in-line test cases as well.
(assert_fails (sort_case_independent '("a" "b" 42 "c" "d")))
Here is an implementation of such a assert_fails
macro.
(defmacro assert_fails (expression) `(assert (not (errset ,expression))"EXPECTING FAILURE: %L\n"',expression))
Summary
In this article we looked at theassert
macro, which is
probably in the version of Virtuoso or Allegro you are using, and if
not you can easily define it yourself. We also looked
at assert_test
and assert_fails
which you
can define yourself. You can use these three macros to easily improve
the robustness of your SKILL programs.