clojure

Catch a compiler exception in macroexpand


I have an assertion that checks key correctness at compile time:

(defn assert-keys [check-map valid-keys]
  (let [k (set (keys check-map))]
    (when-not (set/subset? k valid-keys)
      (throw (ex-info "Function call contains invalid key." {:submitted-keys k
                                                             :valid-keys     valid-keys})))))

It is invoked in a macro:

(defmacro with-hooks [{:keys [read write] :as options} & body]
  (assert-keys options #{:read :write})
  `(binding [*read-hook*  (or ~read  *read-hook*)
             *write-hook* (or ~write *write-hook*)]
     ~@body))

In this case it will check that the options map may contain the keys :read and :write, and nothing more.

The thing is, I'd like to write a test for this. The code will cause a compiler error on macro expand if there's an invalid key. Is there any way of catching this error in a test?


Solution

  • This is the solution I found. Create a macro for testing:

    (defmacro catch-assert [body]
      (try
        (eval body)
        0
        (catch clojure.lang.Compiler$CompilerException e
          (if (re-find #"Function call contains invalid key"
                       (str e))
            1
            -1))
        (catch Exception _e
          -1)))
    

    It returns: 0, call didn't produce exception. 1, compiler exception being tested for. -1, other exceptions.

    Then I call in my test:

    (deftest valid-keys
      (testing "Valid hook keys"
        (is (= 0 (catch-assert
                  (with-hooks {:read ()
                               :write ()})))))
      (testing "Invalid hook key"
        (is (= 1 (catch-assert
                  (with-hooks {:rea ()}))))))