tolitius / mount

managing Clojure and ClojureScript app state since (reset)
Eclipse Public License 1.0
1.22k stars 88 forks source link

Auto-reloading and stest/instrument #92

Closed conan closed 3 years ago

conan commented 6 years ago

In development, I like always to have clojure.spec.test.alpha/instrument in effect, which provides spec validation for function calls in the REPL by instrumenting all loaded namespaces.

When mount auto-reloads a namespace which has been recompiled, the recompiled version is not instrumented, meaning it does not perform spec validation. It is possible to achieve validation by including :on-reload metadata in all my mount state definitions, but this ends up being a lot of boilerplate and dependent on me remembering to include it for every state; in addition, I don't feel comfortable having to include this in my production namespaces, when my initial call to instrument lives in a user namespace that is only loaded for development purposes.

Is there any way around this problem?

tolitius commented 6 years ago

When mount auto-reloads a namespace which has been recompiled, the recompiled version is not instrumented, meaning it does not perform spec validation

Can you give an example of what does not go through the validation after reload?

When a mount state is reloaded, it calls its :stop function followed by its :start function. It could be that instrumentation is weaved in once, and when mount calls :start it gets the real function definition rather than the one that was "replaced" by spec's instrumentation.

conan commented 6 years ago

It's the reloading that causes the problem rather than anything to do with the mount lifecycle. If you reload a namespace, it loses its instrumentation. Namespaces that contain a defstate get reloaded, which is fantastic, but it breaks the instrumentation.

conan commented 6 years ago

In another loaded namespace I've called (clojure.spec.test.alpha/instrument). I have the following namespace:

(ns stuff
  (:require [clojure.spec.alpha :as s]))

(defn my-inc [x]
  (+ x 1))

(s/fdef my-inc
  :args (s/cat :x int?))

(my-inc :1)

I see this error:

ExceptionInfo Call to #'user/my-inc did not conform to spec:
In: [0] val: :1 fails at: [:args :x] predicate: int?
  clojure.core/ex-info (core.clj:4739)

If I modify the namespace (adding a comment or something semantically meaningless) and it gets reloaded, I see this error:

ClassCastException clojure.lang.Keyword cannot be cast to java.lang.Number  clojure.lang.Numbers.add (Numbers.java:128)

In this example it's clear what happened in both cases, but in many instances the spec error is much more informative.

tolitius commented 6 years ago

I believe you, but I don't use spec/instrument, so I just want to look at the example with a mount state that has / causes problem reloading to understand the problem better.

conan commented 3 years ago

I've developed a way of working around this in my user namespace so I'm closing this. thanks!