Skip to content

Instantly share code, notes, and snippets.

@ul
Forked from olivergeorge/Nicer Assert README.md
Created March 29, 2016 03:05
Show Gist options
  • Save ul/d2c28bb4b3a11e26f3f9 to your computer and use it in GitHub Desktop.
Save ul/d2c28bb4b3a11e26f3f9 to your computer and use it in GitHub Desktop.
Assert macro patch to show details about failed assertions.

This is an attempt to make assert more friendly by including more details in the assertion error.

  • show the result of the assertion expression
  • show local variable values used in assertion form

In other words, this should make assertion errors easier to reason about:

AssertionError Assert failed: Should be equal
(= x y) => false
 where x is 222
 where y is 1

This approach handles form pre/post conditions as well as inline asserts.

An easy way to include in your code is:

(defproject my-proj "0.1.0-SNAPSHOT"
  ...
  :injections [(load-file "scripts/patch_assert.clj")])

Examples of use:

(defn boom [s]
  {:pre [(string? s)]}
  (str s "!!!"))
=> #'user/boom
(boom "ROAR")
=> "ROAR!!!"
(boom 123)
AssertionError Assert failed: (string? s) => false
 where s is 123
  user/boom (form-init1236645087160261984.clj:1)

(let [x 222 y 1 z (range 10)]
  (assert (= x y ) "Should be equal"))
=> #'clojure.core/assert
AssertionError Assert failed: Should be equal
(= x y) => false
 where x is 222
 where y is 1
  user/eval9905 (form-init1236645087160261984.clj:3)
(let [x 222 y 1 z (range 10)]
  (assert (= 3 (count z)) "Should be 3 long"))
=> #'clojure.core/assert
AssertionError Assert failed: Should be 3 long
(= 3 (count z)) => false
 where z is (0 1 2 3 4 5 6 7 8 9)
  user/eval9806 (form-init1236645087160261984.clj:3)

Test in a repl with something like this:

(load-file "scripts/patch_assert.clj")
(let [x 222 y 1 z (range 10)]
  (assert (= x y ) "Should be equal"))

How it works...

  • defmacro has access to &env which is a map of variables. Using this and the assert form we can lookup relevant variables.
  • lein :injections allows code to be run before your project code. With this we can patch the assert macro before loading project code.
(in-ns 'clojure.core)
(defn show-env
"Macro helper. Show env with walk based form scanner.
NOTE: Could have false positives: any form matching an env key gets printed."
[form env]
(let [xs (tree-seq seq? identity form)
ks (set (keys env))]
(for [k (filter ks xs)]
`(str " where " '~k " is " ~k "\n"))))
(defmacro clojure.core/assert
"Evaluates expr and throws an exception if it does not evaluate to
logical true."
{:added "1.0"}
([x]
`(when-not ~x
(throw (new AssertionError (str "Assert failed: "
(pr-str '~x) " => " (pr-str ~x) "\n"
~@(show-env x &env))))))
([x message]
`(when-not ~x
(throw (new AssertionError (str "Assert failed: " ~message "\n"
(pr-str '~x) " => " (pr-str ~x) "\n"
~@(show-env x &env)))))))
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment