releasing 0.1.10
This commit is contained in:
commit
8026a7b69c
11 changed files with 300 additions and 359 deletions
168
README.md
168
README.md
|
|
@ -29,16 +29,13 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
|
|||
- [Value of Values](#value-of-values)
|
||||
- [The Importance of Being Reloadable](#the-importance-of-being-reloadable)
|
||||
- [Start and Stop Order](#start-and-stop-order)
|
||||
- [Composing States](#composing-states)
|
||||
- [Start and Stop Parts of Application](#start-and-stop-parts-of-application)
|
||||
- [Start an Application Without Certain States](#start-an-application-without-certain-states)
|
||||
- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
|
||||
- [Swapping Alternate Implementations](#swapping-alternate-implementations)
|
||||
- [Swapping States with Values](#swapping-states-with-values)
|
||||
- [Swapping States with States](#swapping-states-with-states)
|
||||
- [Suspending and Resuming](#suspending-and-resuming)
|
||||
- [Suspendable Lifecycle](#suspendable-lifecycle)
|
||||
- [Plugging into (reset)](#plugging-into-reset)
|
||||
- [Suspendable Example Application](#suspendable-example-application)
|
||||
- [Stop an Application Except Certain States](#stop-an-application-except-certain-states)
|
||||
- [ClojureScript is Clojure](doc/clojurescript.md#managing-state-in-clojurescript)
|
||||
- [Packaging](#packaging)
|
||||
- [Affected States](#affected-states)
|
||||
|
|
@ -168,7 +165,7 @@ is an example of a web server that "depends" on a similar `config`.
|
|||
|
||||
## Value of values
|
||||
|
||||
Lifecycle functions start/stop/suspend/resume can take both functions and values. This is "valuable" and also works:
|
||||
Lifecycle functions start/stop can take both functions and values. This is "valuable" and also works:
|
||||
|
||||
```clojure
|
||||
(defstate answer-to-the-ultimate-question-of-life-the-universe-and-everything :start 42)
|
||||
|
|
@ -259,11 +256,74 @@ dev=> (reset)
|
|||
|
||||
You can see examples of start and stop flows in the [example app](README.md#mount-and-develop).
|
||||
|
||||
## Composing States
|
||||
|
||||
Besides calling `(mount/start)` there are other useful ways to start an application:
|
||||
|
||||
* [starting parts of an application](README.md#start-and-stop-parts-of-application)
|
||||
* [starting an application without certain states](README.md#start-an-application-without-certain-states)
|
||||
* [swapping alternate implementations](README.md#swapping-alternate-implementations)
|
||||
* [passing runtime arguments](README.md#runtime-arguments)
|
||||
|
||||
While all of these are great by themselves, sometimes it is really handy to compose these super powers. For example to start an application with _only_ certain states, _swapping_ a couple of them for new values, while passing runtime _arguments_.
|
||||
|
||||
### Composer's Toolbox
|
||||
|
||||
Each "tool" has a single responsibility and can be composed with other tools in _any_ combination and order.
|
||||
|
||||
* `only` will return _only_ states that it is given + exist (seen by mount) in the application
|
||||
* `except` will return all the states that it is given _except_ a given set
|
||||
* `swap` will take a map with keys as states and values as their substitute values
|
||||
* `swap-states` will take a map with keys as states and values as their substitute states
|
||||
* `with-args` will take a map that could later be accessed by `(mount/args)`
|
||||
|
||||
All these functions take one or two arguments. If called with two arguments, the first one will be treated as the universe of states to work with. If called with one argument, it will work with _all known_ to mount states.
|
||||
|
||||
None of these functions start or stop the application states, they merely serve as transformations from the initial set of states to the one that will later be passed to `(mount/start)`.
|
||||
|
||||
### Be Composing
|
||||
|
||||
All of the above is much easier to understand by looking at examples:
|
||||
|
||||
```clojure
|
||||
(-> (only #{#'foo/a
|
||||
#'foo/b
|
||||
#'foo/c
|
||||
#'bar/d
|
||||
#'baz/e})
|
||||
(except [#'foo/c
|
||||
#'bar/d])
|
||||
(with-args {:a 42})
|
||||
mount/start)
|
||||
```
|
||||
|
||||
This would start off from 5 states, even though the whole application may have many more states available. It would then exclude two states (i.e. `#'foo/c` and `#'bar/d`), then it will pass runtime arguments `{:a 42}`, and finally it will start the remaining three states: `#'foo/a`, `#'foo/b`, `#'baz/e`.
|
||||
|
||||
You may notice that `only` takes a set, while `except` takes a vector in this example. This is done intentionally to demonstraate that both these functions can take any collection of states. `set` would make more sense for most cases though.
|
||||
|
||||
Here is a more "involved" example:
|
||||
|
||||
```clojure
|
||||
(-> (only #{#'foo/a
|
||||
#'foo/b
|
||||
#'foo/c
|
||||
#'bar/d
|
||||
#'baz/e})
|
||||
(with-args {:a 42})
|
||||
(except [#'foo/c
|
||||
#'bar/d])
|
||||
(swap-states {#'foo/a #'test/a})
|
||||
(swap {#'baz/e {:datomic {:uri "datomic:mem://composable-mount"}}})
|
||||
mount/start)
|
||||
```
|
||||
|
||||
This will do the same thing as the previous example plus it would swap `#'foo/a` with `#'test/a` state and `#'baz/e` with `{:datomic {:uri "datomic:mem://composable-mount"}}` value before starting the application.
|
||||
|
||||
## Start and Stop Parts of Application
|
||||
|
||||
In REPL or during testing it is often very useful to work with / start / stop _only a part_ of an application, i.e. "only these two states".
|
||||
|
||||
`mount`'s lifecycle functions, i.e. start/stop/suspend/resume, can _optionally_ take states as vars (i.e. prefixed with their namespaces):
|
||||
`mount`'s lifecycle functions, i.e. start/stop, can _optionally_ take states as vars (i.e. prefixed with their namespaces):
|
||||
|
||||
```clojure
|
||||
(mount/start #'app.config/config #'app.nyse/conn)
|
||||
|
|
@ -378,80 +438,6 @@ dev=> (mount/start)
|
|||
|
||||
Notice that the `nyse-app` is not started the second time (hence no more accidental `java.net.BindException: Address already in use`). It is already up and running.
|
||||
|
||||
## Suspending and Resuming
|
||||
|
||||
Besides starting and stopping states can also be suspended and resumed. While this is not needed most of the time, it does comes really handy _when_ this need is there. For example:
|
||||
|
||||
* while working in REPL, you only want to truly restart a web server/queue listener/db connection _iff_ something changed, all other times `(mount/stop)` / `(mount/start)` or `(reset)` is called, these states should not be restarted. This might have to do with time to connect / bound ports / connection timeouts, etc..
|
||||
|
||||
* when taking an application out of rotation in a data center, and then phasing it back in, it might be handy to still keep it _up_, but suspend all the client / novelty facing components in between.
|
||||
|
||||
and some other use cases.
|
||||
|
||||
### Suspendable Lifecycle
|
||||
|
||||
In addition to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones:
|
||||
|
||||
```clojure
|
||||
(defstate web-server :start start-server
|
||||
:resume resume-server
|
||||
:stop stop-server)
|
||||
|
||||
```
|
||||
|
||||
`suspend` function is optional. Combining this with [(mount/stop-except)](#stop-an-application-except-certain-states), can result in an interesting restart behavior where everything is restared, but this `web-server` is _resumed_ instead (in this case `#'app.www/nyse-app` is an example of the above `web-server`):
|
||||
|
||||
```clojure
|
||||
dev=> (mount/stop-except #'app.www/nyse-app)
|
||||
14:44:33.991 [nREPL-worker-1] INFO mount.core - << stopping.. nrepl
|
||||
14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. conn
|
||||
14:44:33.992 [nREPL-worker-1] INFO app.db - disconnecting from datomic:mem://mount
|
||||
14:44:33.992 [nREPL-worker-1] INFO mount.core - << stopping.. config
|
||||
:stopped
|
||||
dev=>
|
||||
|
||||
dev=> (mount/suspend)
|
||||
14:44:52.467 [nREPL-worker-1] INFO mount.core - >> suspending.. nyse-app
|
||||
:suspended
|
||||
dev=>
|
||||
|
||||
dev=> (mount/start)
|
||||
14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. config
|
||||
14:45:00.297 [nREPL-worker-1] INFO mount.core - >> starting.. conn
|
||||
14:45:00.298 [nREPL-worker-1] INFO app.db - creating a connection to datomic: datomic:mem://mount
|
||||
14:45:00.315 [nREPL-worker-1] INFO mount.core - >> resuming.. nyse-app
|
||||
14:45:00.316 [nREPL-worker-1] INFO mount.core - >> starting.. nrepl
|
||||
:started
|
||||
```
|
||||
|
||||
Notice `>> resuming.. nyse-app`, which in [this case](https://github.com/tolitius/mount/blob/suspendable/test/app/www.clj#L32) just recreates Datomic schema vs. doing that _and_ starting the actual web server.
|
||||
|
||||
### Plugging into (reset)
|
||||
|
||||
In case `tools.namespace` is used, this lifecycle can be easily hooked up with `dev.clj`:
|
||||
|
||||
```clojure
|
||||
(defn start []
|
||||
(mount/start))
|
||||
|
||||
(defn stop []
|
||||
(mount/suspend)
|
||||
(mount/stop-except #'app.www/nyse-app))
|
||||
|
||||
(defn reset []
|
||||
(stop)
|
||||
(tn/refresh :after 'dev/start))
|
||||
```
|
||||
|
||||
### Suspendable Example Application
|
||||
|
||||
An [example application](https://github.com/tolitius/mount/tree/suspendable/test/app) with a suspendable web server and `dev.clj` lives in the `suspendable` branch. You can clone mount and try it out:
|
||||
|
||||
```
|
||||
$ git checkout suspendable
|
||||
Switched to branch 'suspendable'
|
||||
```
|
||||
|
||||
## Recompiling Namespaces with Running States
|
||||
|
||||
Mount will detect when a namespace with states (i.e. with `(defstate ...)`) was reloaded/recompiled,
|
||||
|
|
@ -566,28 +552,22 @@ In practice only a few namespaces need to be `:require`d, since others will be b
|
|||
|
||||
## Affected States
|
||||
|
||||
Every time a lifecycle function (start/stop/suspend/resume) is called mount will return all the states that were affected:
|
||||
Every time a lifecycle function (start/stop) is called mount will return all the states that were affected:
|
||||
|
||||
```clojure
|
||||
dev=> (mount/start)
|
||||
{:started [#'app.config/config
|
||||
#'app.nyse/conn
|
||||
#'app/nrepl
|
||||
#'check.suspend-resume-test/web-server
|
||||
#'check.suspend-resume-test/q-listener]}
|
||||
#'app/nrepl]}
|
||||
```
|
||||
```clojure
|
||||
dev=> (mount/suspend)
|
||||
{:suspended [#'check.suspend-resume-test/web-server
|
||||
#'check.suspend-resume-test/q-listener]}
|
||||
```
|
||||
```clojure
|
||||
dev=> (mount/start)
|
||||
{:started [#'check.suspend-resume-test/web-server
|
||||
#'check.suspend-resume-test/q-listener]}
|
||||
dev=> (mount/stop)
|
||||
{:started [#'app/nrepl
|
||||
#'app.nyse/conn
|
||||
#'app.config/config]}
|
||||
```
|
||||
|
||||
An interesting bit here is a vector vs. a set: all the states are returned _in the order they were changed_.
|
||||
An interesting bit here is a vector vs. a set: all the states are returned _in the order they were affected_.
|
||||
|
||||
## Logging
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(def +version+ "0.1.9")
|
||||
(def +version+ "0.1.10")
|
||||
|
||||
(set-env!
|
||||
:source-paths #{"src"}
|
||||
|
|
|
|||
|
|
@ -12,4 +12,4 @@ test:
|
|||
- boot test
|
||||
- boot test-cljs
|
||||
- boot test-cljs-advanced
|
||||
- lein test2junit
|
||||
- lein do clean, test2junit
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@
|
|||
|
||||
(defonce lifecycle-fns
|
||||
#{#'mount.core/up
|
||||
#'mount.core/down
|
||||
#'mount.core/sigstop
|
||||
#'mount.core/sigcont})
|
||||
#'mount.core/down})
|
||||
|
||||
(defn without-logging-status []
|
||||
(doall (map #(clear-hooks %) lifecycle-fns)))
|
||||
|
|
|
|||
|
|
@ -197,7 +197,6 @@ Testing is not alien to Mount and it knows how to do a thing or two:
|
|||
* [start an application without certain states](https://github.com/tolitius/mount#start-an-application-without-certain-states)
|
||||
* [swapping alternate implementations](https://github.com/tolitius/mount#swapping-alternate-implementations)
|
||||
* [stop an application except certain states](https://github.com/tolitius/mount#stop-an-application-except-certain-states)
|
||||
* [suspending and resuming](https://github.com/tolitius/mount#suspending-and-resuming)
|
||||
|
||||
After [booting mount](http://www.dotkam.com/2015/12/22/the-story-of-booting-mount/) I was secretly thinking of achieving multiple separate systems by running them in different [Boot Pods](https://github.com/boot-clj/boot/wiki/Pods).
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
(defproject mount "0.1.9"
|
||||
(defproject mount "0.1.10"
|
||||
:description "managing Clojure and ClojureScript app state since (reset)"
|
||||
:url "https://github.com/tolitius/mount"
|
||||
:license {:name "Eclipse Public License"
|
||||
|
|
|
|||
|
|
@ -1,13 +1,15 @@
|
|||
(ns mount.core
|
||||
#?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro]
|
||||
[mount.tools.logger :refer [log]]
|
||||
[clojure.set :refer [intersection]]
|
||||
[clojure.string :as s])
|
||||
:cljs (:require [mount.tools.macro :as macro]
|
||||
[clojure.set :refer [intersection]]
|
||||
[mount.tools.logger :refer [log]]))
|
||||
#?(:cljs (:require-macros [mount.core]
|
||||
[mount.tools.macro :refer [if-clj on-error throw-runtime]])))
|
||||
|
||||
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
|
||||
(defonce ^:private -args (atom {})) ;; mostly for command line args and external files
|
||||
(defonce ^:private state-seq (atom 0))
|
||||
(defonce ^:private mode (atom :clj))
|
||||
(defonce ^:private meta-state (atom {}))
|
||||
|
|
@ -30,8 +32,7 @@
|
|||
(defn- validate [{:keys [start stop suspend resume] :as lifecycle}]
|
||||
(cond
|
||||
(not start) (throw-runtime "can't start a stateful thing without a start function. (i.e. missing :start fn)")
|
||||
(and suspend
|
||||
(not resume)) (throw-runtime "suspendable state should have a resume function (i.e. missing :resume fn)")))
|
||||
(or suspend resume) (throw-runtime "suspend / resume lifecycle support was removed in \"0.1.10\" in favor of (mount/stop-except)")))
|
||||
|
||||
(defn- with-ns [ns name]
|
||||
(str "#'" ns "/" name))
|
||||
|
|
@ -86,18 +87,16 @@
|
|||
(swap! done conj state-name)
|
||||
state))
|
||||
|
||||
(defn- up [state {:keys [start stop resume status] :as current} done]
|
||||
(defn- up [state {:keys [start stop status] :as current} done]
|
||||
(when-not (:started status)
|
||||
(let [s (on-error (str "could not start [" state "] due to")
|
||||
(if (:suspended status)
|
||||
(record! state resume done)
|
||||
(record! state start done)))]
|
||||
(record! state start done))]
|
||||
(alter-state! current s)
|
||||
(swap! running assoc state {:stop stop})
|
||||
(update-meta! [state :status] #{:started}))))
|
||||
|
||||
(defn- down [state {:keys [stop status] :as current} done]
|
||||
(when (some status #{:started :suspended})
|
||||
(when (some status #{:started})
|
||||
(when stop
|
||||
(on-error (str "could not stop [" state "] due to")
|
||||
(record! state stop done)))
|
||||
|
|
@ -105,21 +104,6 @@
|
|||
(swap! running dissoc state)
|
||||
(update-meta! [state :status] #{:stopped})))
|
||||
|
||||
(defn- sigstop [state {:keys [resume suspend status] :as current} done]
|
||||
(when (and (:started status) resume) ;; can't have suspend without resume, but the reverse is possible
|
||||
(when suspend ;; don't suspend if there is only resume function (just mark it :suspended?)
|
||||
(let [s (on-error (str "could not suspend [" state "] due to")
|
||||
(record! state suspend done))]
|
||||
(alter-state! current s)))
|
||||
(update-meta! [state :status] #{:suspended})))
|
||||
|
||||
(defn- sigcont [state {:keys [resume status] :as current} done]
|
||||
(when (:suspended status)
|
||||
(let [s (on-error (str "could not resume [" state "] due to")
|
||||
(record! state resume done))]
|
||||
(alter-state! current s)
|
||||
(update-meta! [state :status] #{:started}))))
|
||||
|
||||
(deftype DerefableState [name]
|
||||
#?(:clj clojure.lang.IDeref
|
||||
:cljs IDeref)
|
||||
|
|
@ -151,16 +135,14 @@
|
|||
#?(:clj
|
||||
(defmacro defstate [state & body]
|
||||
(let [[state params] (macro/name-with-attributes state body)
|
||||
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)
|
||||
{:keys [start stop] :as lifecycle} (apply hash-map params)
|
||||
state-name (with-ns *ns* state)
|
||||
order (make-state-seq state-name)]
|
||||
(validate lifecycle)
|
||||
(let [s-meta (cond-> {:order order
|
||||
:start `(fn [] ~start)
|
||||
:status #{:stopped}}
|
||||
stop (assoc :stop `(fn [] ~stop))
|
||||
suspend (assoc :suspend `(fn [] ~suspend))
|
||||
resume (assoc :resume `(fn [] ~resume)))]
|
||||
stop (assoc :stop `(fn [] ~stop)))]
|
||||
`(do
|
||||
(~'defonce ~state (DerefableState. ~state-name))
|
||||
(mount-it (~'var ~state) ~state-name ~s-meta)
|
||||
|
|
@ -226,14 +208,14 @@
|
|||
|
||||
(defn- merge-lifecycles
|
||||
"merges with overriding _certain_ non existing keys.
|
||||
i.e. :suspend is in a 'state', but not in a 'substitute': it should be overriden with nil
|
||||
i.e. :stop is in a 'state', but not in a 'substitute': it should be overriden with nil
|
||||
however other keys of 'state' (such as :ns,:name,:order) should not be overriden"
|
||||
([state sub]
|
||||
(merge-lifecycles state nil sub))
|
||||
([state origin {:keys [start stop suspend resume status]}]
|
||||
([state origin {:keys [start stop status]}]
|
||||
(assoc state :origin origin
|
||||
:status status
|
||||
:start start :stop stop :suspend suspend :resume resume)))
|
||||
:start start :stop stop)))
|
||||
|
||||
(defn- rollback! [state]
|
||||
(let [{:keys [origin] :as sub} (@meta-state state)]
|
||||
|
|
@ -241,7 +223,7 @@
|
|||
(update-meta! [state] (merge-lifecycles sub origin)))))
|
||||
|
||||
(defn- substitute! [state with mode]
|
||||
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
|
||||
(let [lifecycle-fns #(select-keys % [:start :stop :status])
|
||||
origin (@meta-state state)
|
||||
sub (if (= :value mode)
|
||||
{:start (fn [] with) :status :stopped}
|
||||
|
|
@ -258,8 +240,10 @@
|
|||
(remove (comp :sub? @meta-state) (find-all-states)))
|
||||
|
||||
(defn start [& states]
|
||||
(let [states (or (seq states) (all-without-subs))]
|
||||
{:started (bring states up <)}))
|
||||
(if (-> states first coll?)
|
||||
(apply start (first states))
|
||||
(let [states (or (seq states) (all-without-subs))]
|
||||
{:started (bring states up <)})))
|
||||
|
||||
(defn stop [& states]
|
||||
(let [states (or states (find-all-states))
|
||||
|
|
@ -268,6 +252,53 @@
|
|||
(dorun (map rollback! states)) ;; restore to origin from "start-with"
|
||||
{:stopped stopped}))
|
||||
|
||||
;; composable set of states
|
||||
|
||||
(defn- mapset [f xs]
|
||||
(-> (map f xs)
|
||||
set))
|
||||
|
||||
(defn only
|
||||
([states]
|
||||
(only (find-all-states) states))
|
||||
([states these]
|
||||
(intersection (mapset var-to-str these)
|
||||
(mapset var-to-str states))))
|
||||
|
||||
(defn with-args
|
||||
([args]
|
||||
(with-args (find-all-states) args))
|
||||
([states args]
|
||||
(reset! -args args) ;; TODO localize
|
||||
states))
|
||||
|
||||
(defn except
|
||||
([states]
|
||||
(except (find-all-states) states))
|
||||
([states these]
|
||||
(remove (mapset var-to-str these)
|
||||
(mapset var-to-str states))))
|
||||
|
||||
(defn swap
|
||||
([with]
|
||||
(swap (find-all-states) with))
|
||||
([states with]
|
||||
(doseq [[from to] with]
|
||||
(substitute! (var-to-str from)
|
||||
to :value))
|
||||
states))
|
||||
|
||||
(defn swap-states
|
||||
([with]
|
||||
(swap-states (find-all-states) with))
|
||||
([states with]
|
||||
(doseq [[from to] with]
|
||||
(substitute! (var-to-str from)
|
||||
(var-to-str to) :state))
|
||||
states))
|
||||
|
||||
;; explicit, not composable (subject to depreciate?)
|
||||
|
||||
(defn stop-except [& states]
|
||||
(let [all (set (find-all-states))
|
||||
states (map var-to-str states)
|
||||
|
|
@ -299,11 +330,3 @@
|
|||
without (remove (set states) app)]
|
||||
(apply start without))
|
||||
(start)))
|
||||
|
||||
(defn suspend [& states]
|
||||
(let [states (or (seq states) (all-without-subs))]
|
||||
{:suspended (bring states sigstop <)}))
|
||||
|
||||
(defn resume [& states]
|
||||
(let [states (or (seq states) (all-without-subs))]
|
||||
{:resumed (bring states sigcont <)}))
|
||||
|
|
|
|||
|
|
@ -34,9 +34,7 @@
|
|||
|
||||
(defonce lifecycle-fns
|
||||
#{#'mount.core/up
|
||||
#'mount.core/down
|
||||
#'mount.core/sigstop
|
||||
#'mount.core/sigcont})
|
||||
#'mount.core/down})
|
||||
|
||||
(defn without-logging-status []
|
||||
(doall (map clear-hooks lifecycle-fns)))
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@
|
|||
mount.test.start-without
|
||||
mount.test.start-with
|
||||
mount.test.start-with-states
|
||||
mount.test.suspend-resume
|
||||
))
|
||||
|
||||
#?(:clj (alter-meta! *ns* assoc ::load false))
|
||||
|
|
@ -32,7 +31,6 @@
|
|||
'mount.test.start-without
|
||||
'mount.test.start-with
|
||||
'mount.test.start-with-states
|
||||
'mount.test.suspend-resume
|
||||
))
|
||||
|
||||
(defn run-tests []
|
||||
|
|
|
|||
157
test/core/mount/test/composable_fns.cljc
Normal file
157
test/core/mount/test/composable_fns.cljc
Normal file
|
|
@ -0,0 +1,157 @@
|
|||
(ns mount.test.composable-fns
|
||||
(:require
|
||||
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||
[clojure.set :refer [intersection]]
|
||||
[mount.core :refer [only except swap swap-states with-args] :as mount :refer-macros [defstate]]
|
||||
[tapp.websockets :refer [system-a]]
|
||||
[tapp.conf :refer [config]]
|
||||
[tapp.audit-log :refer [log]]]
|
||||
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||
[clojure.set :refer [intersection]]
|
||||
[mount.core :as mount :refer [defstate only except swap swap-states with-args]]
|
||||
[tapp.conf :refer [config]]
|
||||
[tapp.nyse :refer [conn]]
|
||||
[tapp.example :refer [nrepl]]])
|
||||
[mount.test.helper :refer [dval helper]]))
|
||||
|
||||
#?(:clj (alter-meta! *ns* assoc ::load false))
|
||||
|
||||
(defstate test-conn :start 42
|
||||
:stop (constantly 0))
|
||||
|
||||
(defstate test-nrepl :start [])
|
||||
|
||||
#?(:clj
|
||||
(deftest only-states
|
||||
|
||||
(testing "only should only return given states.
|
||||
if source set of states is not provided, it should use all the states to select from"
|
||||
(is (= #{"#'mount.test.composable-fns/test-conn" "#'tapp.example/nrepl" "#'tapp.nyse/conn"}
|
||||
(only #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn}))))
|
||||
|
||||
(testing "only should only return given states"
|
||||
(is (= #{"#'mount.test.composable-fns/test-conn" "#'tapp.example/nrepl"}
|
||||
(only [#'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn]
|
||||
#{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl}))))))
|
||||
|
||||
#?(:clj
|
||||
(deftest except-states
|
||||
|
||||
(testing "except should exclude given states.
|
||||
if source set of states is not provided, it should use all the states to exclude from"
|
||||
(let [states (except #{"#'is.not/here" #'tapp.example/nrepl #'tapp.nyse/conn})]
|
||||
(is (coll? states))
|
||||
(is (pos? (count states)))
|
||||
(is (zero? (count (intersection (set states)
|
||||
#{"#'tapp.example/nrepl" "#'tapp.nyse/conn" "#'is.not/here"}))))))
|
||||
|
||||
(testing "except should exclude given states"
|
||||
(is (= #{"#'tapp.conf/config" "#'mount.test.composable-fns/test-conn"}
|
||||
(set (except #{#'tapp.example/nrepl #'tapp.conf/config #'mount.test.composable-fns/test-conn}
|
||||
#{"#'is.not/here" #'tapp.example/nrepl #'tapp.nyse/conn})))))))
|
||||
|
||||
#?(:clj
|
||||
(deftest states-with-args
|
||||
|
||||
(testing "with-args should set args and return all states if none provided"
|
||||
(let [states (with-args {:a 42})]
|
||||
(is (= {:a 42} (mount/args)))
|
||||
(is (= states (#'mount.core/find-all-states)))))
|
||||
|
||||
(testing "with-args should set args and thread states if provided"
|
||||
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn}
|
||||
states (with-args t-states {:a 42})]
|
||||
(is (= {:a 42} (mount/args)))
|
||||
(is (= states t-states))))))
|
||||
|
||||
#?(:clj
|
||||
(deftest swap-states-with-values
|
||||
|
||||
(testing "swap should swap states with values and return all states if none is given"
|
||||
(let [states (swap {#'tapp.nyse/conn "conn-sub"
|
||||
#'tapp.example/nrepl :nrepl-sub})]
|
||||
(is (= states (#'mount.core/find-all-states)))
|
||||
(mount/start)
|
||||
(is (map? (dval config)))
|
||||
(is (= :nrepl-sub (dval nrepl)))
|
||||
(is (= "conn-sub" (dval conn)))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "swap should swap states with values and return only states that it is given"
|
||||
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.example/nrepl #'tapp.nyse/conn}
|
||||
states (swap t-states {#'tapp.nyse/conn "conn-sub"
|
||||
#'tapp.example/nrepl :nrepl-sub})]
|
||||
(is (= states t-states))
|
||||
(mount/start)
|
||||
(is (map? (dval config)))
|
||||
(is (= :nrepl-sub (dval nrepl)))
|
||||
(is (= "conn-sub" (dval conn)))
|
||||
(is (= 42 (dval test-conn)))
|
||||
(mount/stop)))))
|
||||
|
||||
#?(:clj
|
||||
(deftest swap-states-with-states
|
||||
|
||||
(testing "swap-states should swap states with states and return all mount states if none is given"
|
||||
(let [states (swap-states {#'tapp.nyse/conn #'mount.test.composable-fns/test-conn
|
||||
#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})]
|
||||
(is (= states (#'mount.core/find-all-states)))
|
||||
(mount/start)
|
||||
(is (map? (dval config)))
|
||||
(is (vector? (dval nrepl)))
|
||||
(is (= 42 (dval conn)))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "swap-states should swap states with states and return only states that it is given"
|
||||
(let [t-states #{"#'is.not/here" #'mount.test.composable-fns/test-conn #'tapp.nyse/conn}
|
||||
states (swap-states t-states {#'tapp.nyse/conn #'mount.test.composable-fns/test-conn
|
||||
#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})]
|
||||
(is (= states t-states))
|
||||
(apply mount/start states)
|
||||
(is (instance? mount.core.NotStartedState (dval config)))
|
||||
(is (instance? mount.core.NotStartedState (dval nrepl)))
|
||||
(is (= 42 (dval conn)))
|
||||
(is (= 42 (dval test-conn))) ;; test-conn is explicitly started via "t-states"
|
||||
(mount/stop)))))
|
||||
|
||||
#?(:clj
|
||||
(deftest composing
|
||||
|
||||
(testing "states provided to the top level should narrow down the scope for the whole composition"
|
||||
(let [scope [#'tapp.conf/config
|
||||
#'tapp.example/nrepl
|
||||
#'tapp.nyse/conn
|
||||
#'mount.test.composable-fns/test-nrepl
|
||||
#'mount.test.composable-fns/test-conn]
|
||||
states (-> (only scope)
|
||||
(with-args {:a 42})
|
||||
(except [#'mount.test.composable-fns/test-nrepl
|
||||
#'mount.test.composable-fns/test-conn])
|
||||
(swap-states {#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})
|
||||
(swap {#'tapp.conf/config {:datomic {:uri "datomic:mem://composable-mount"}}}))]
|
||||
(is (= #{"#'tapp.nyse/conn" "#'tapp.conf/config" "#'tapp.example/nrepl"} (set states)))
|
||||
(mount/start states)
|
||||
(is (= {:a 42} (mount/args)))
|
||||
(is (= {:datomic {:uri "datomic:mem://composable-mount"}} (dval config)))
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (vector? (dval nrepl)))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should compose and start in a single composition"
|
||||
(let [scope [#'tapp.conf/config
|
||||
#'tapp.example/nrepl
|
||||
#'tapp.nyse/conn
|
||||
#'mount.test.composable-fns/test-nrepl
|
||||
#'mount.test.composable-fns/test-conn]]
|
||||
(-> (only scope)
|
||||
(with-args {:a 42})
|
||||
(except [#'mount.test.composable-fns/test-nrepl
|
||||
#'mount.test.composable-fns/test-conn])
|
||||
(swap-states {#'tapp.example/nrepl #'mount.test.composable-fns/test-nrepl})
|
||||
(swap {#'tapp.conf/config {:datomic {:uri "datomic:mem://composable-mount"}}})
|
||||
mount/start)
|
||||
(is (= {:a 42} (mount/args)))
|
||||
(is (= {:datomic {:uri "datomic:mem://composable-mount"}} (dval config)))
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (vector? (dval nrepl)))
|
||||
(mount/stop)))))
|
||||
|
|
@ -1,212 +0,0 @@
|
|||
(ns mount.test.suspend-resume
|
||||
(:require
|
||||
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||
[mount.core :as mount :refer-macros [defstate]]
|
||||
[tapp.websockets :refer [system-a]]
|
||||
[tapp.conf :refer [config]]
|
||||
[tapp.audit-log :refer [log]]]
|
||||
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||
[mount.core :as mount :refer [defstate]]
|
||||
[tapp.conf :refer [config]]
|
||||
[tapp.nyse :refer [conn]]
|
||||
[tapp.example :refer [nrepl]]])
|
||||
[mount.test.helper :refer [dval]]))
|
||||
|
||||
#?(:clj (alter-meta! *ns* assoc ::load false))
|
||||
|
||||
(defn koncat [k s]
|
||||
(-> (name k)
|
||||
(str "-" (name s))
|
||||
keyword))
|
||||
|
||||
(defn start [s] (koncat s :started))
|
||||
(defn stop [s] (koncat s :stopped))
|
||||
(defn suspend [s] (koncat s :suspended))
|
||||
(defn resume [s] (koncat s :resumed))
|
||||
|
||||
(defstate web-server :start (start :w)
|
||||
:stop (stop :w)
|
||||
:suspend (suspend :w)
|
||||
:resume (resume :w))
|
||||
|
||||
(defstate q-listener :start (start :q)
|
||||
:stop (stop :q)
|
||||
:suspend (suspend :q)
|
||||
:resume (resume :q))
|
||||
|
||||
(defstate randomizer :start (rand-int 42))
|
||||
|
||||
#?(:cljs
|
||||
(deftest suspendable-lifecycle
|
||||
|
||||
(testing "should suspend _only suspendable_ states that are currently started"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)]
|
||||
(is (map? (dval config)))
|
||||
(is (instance? datascript.db/DB @(dval log)))
|
||||
(is (instance? js/WebSocket (dval system-a)))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should resume _only suspendable_ states that are currently suspended"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/stop #'tapp.websockets/system-a)
|
||||
_ (mount/suspend)
|
||||
_ (mount/resume)]
|
||||
(is (map? (dval config)))
|
||||
(is (instance? mount.core.NotStartedState (dval system-a)))
|
||||
(is (instance? datascript.db/DB @(dval log)))
|
||||
(is (= (dval web-server) :w-resumed))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should start all the states, except the ones that are currently suspended, should resume them instead"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)
|
||||
_ (mount/start)]
|
||||
(is (map? (dval config)))
|
||||
(is (instance? js/WebSocket (dval system-a)))
|
||||
(is (instance? datascript.db/DB @(dval log)))
|
||||
(is (= (dval web-server) :w-resumed))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should stop all: started and suspended"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)
|
||||
_ (mount/stop)]
|
||||
(is (instance? mount.core.NotStartedState (dval config)))
|
||||
(is (instance? mount.core.NotStartedState (dval system-a)))
|
||||
(is (instance? mount.core.NotStartedState (dval log)))
|
||||
(is (instance? mount.core.NotStartedState (dval web-server)))))))
|
||||
|
||||
#?(:cljs
|
||||
(deftest suspendable-start-with-states
|
||||
|
||||
(testing "when replacing a non suspendable state with a suspendable one,
|
||||
the later should be able to suspend/resume,
|
||||
the original should not be suspendable after resume and preserve its lifecycle fns after rollback/stop"
|
||||
(let [_ (mount/start-with-states {#'tapp.websockets/system-a #'mount.test.suspend-resume/web-server})
|
||||
_ (mount/suspend)]
|
||||
(is (= (dval system-a) :w-suspended))
|
||||
(is (instance? mount.core.NotStartedState (dval web-server)))
|
||||
(mount/stop)
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (instance? js/WebSocket (dval system-a)))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))))
|
||||
|
||||
#?(:clj
|
||||
(deftest suspendable-lifecycle
|
||||
|
||||
(testing "should suspend _only suspendable_ states that are currently started"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)]
|
||||
(is (map? (dval config)))
|
||||
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should resume _only suspendable_ states that are currently suspended"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/stop #'tapp.example/nrepl)
|
||||
_ (mount/suspend)
|
||||
_ (mount/resume)]
|
||||
(is (map? (dval config)))
|
||||
(is (instance? mount.core.NotStartedState (dval nrepl)))
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (= (dval web-server) :w-resumed))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should start all the states, except the ones that are currently suspended, should resume them instead"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)
|
||||
_ (mount/start)]
|
||||
(is (map? (dval config)))
|
||||
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (= (dval web-server) :w-resumed))
|
||||
(mount/stop)))
|
||||
|
||||
(testing "should stop all: started and suspended"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)
|
||||
_ (mount/stop)]
|
||||
(is (instance? mount.core.NotStartedState (dval config)))
|
||||
(is (instance? mount.core.NotStartedState (dval nrepl)))
|
||||
(is (instance? mount.core.NotStartedState (dval conn)))
|
||||
(is (instance? mount.core.NotStartedState (dval web-server)))))))
|
||||
|
||||
|
||||
#?(:clj
|
||||
(deftest suspendable-start-with-states
|
||||
|
||||
(testing "when replacing a non suspendable state with a suspendable one,
|
||||
the later should be able to suspend/resume,
|
||||
the original should not be suspendable after resume and preserve its lifecycle fns after rollback/stop"
|
||||
(let [_ (mount/start-with-states {#'tapp.example/nrepl #'mount.test.suspend-resume/web-server})
|
||||
_ (mount/suspend)]
|
||||
(is (= (dval nrepl) :w-suspended))
|
||||
(is (instance? mount.core.NotStartedState (dval web-server)))
|
||||
(mount/stop)
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))
|
||||
|
||||
;; this is a messy use case, but can still happen especially at REPL time
|
||||
;; it also messy, because usually :stop function refers the _original_ state by name (i.e. #(disconnect conn))
|
||||
;; (unchanged/not substituted in its lexical scope), and original state won't be started
|
||||
(testing "when replacing a suspendable state with a non suspendable one,
|
||||
the later should not be suspendable,
|
||||
the original should still be suspendable and preserve its lifecycle fns after the rollback/stop"
|
||||
(let [_ (mount/start-with-states {#'mount.test.suspend-resume/web-server #'mount.test.suspend-resume/randomizer})
|
||||
_ (mount/suspend)]
|
||||
(is (integer? (dval web-server)))
|
||||
(is (instance? mount.core.NotStartedState (dval randomizer)))
|
||||
(mount/stop)
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (integer? (dval randomizer)))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))
|
||||
|
||||
;; this is a messy use case, but can still happen especially at REPL time
|
||||
(testing "when replacing a suspended state with a non suspendable started one,
|
||||
the later should not be suspendable,
|
||||
the original should still be suspended and preserve its lifecycle fns after the rollback/stop"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)
|
||||
_ (mount/start-with-states {#'mount.test.suspend-resume/web-server #'tapp.nyse/conn}) ;; TODO: good to WARN on started states during "start-with-states"
|
||||
_ (mount/suspend)]
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (= (dval web-server) :w-suspended)) ;; since the "conn" does not have a resume method, so web-server was not started
|
||||
(mount/stop)
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))
|
||||
|
||||
;; this is a messy use case, but can still happen especially at REPL time
|
||||
(testing "when replacing a suspended state with a suspendable one,
|
||||
the later should be suspendable,
|
||||
the original should still be suspended and preserve its lifecycle fns after the rollback/stop"
|
||||
(let [_ (mount/start)
|
||||
_ (mount/suspend)
|
||||
_ (mount/start-with-states {#'mount.test.suspend-resume/web-server
|
||||
#'mount.test.suspend-resume/q-listener})] ;; TODO: good to WARN on started states during "start-with-states"
|
||||
(is (= (dval q-listener) :q-suspended))
|
||||
(is (= (dval web-server) :q-resumed))
|
||||
(mount/suspend)
|
||||
(is (= (dval q-listener) :q-suspended))
|
||||
(is (= (dval web-server) :q-suspended))
|
||||
(mount/stop)
|
||||
(is (instance? mount.core.NotStartedState (dval web-server)))
|
||||
(is (instance? mount.core.NotStartedState (dval q-listener)))
|
||||
(mount/start)
|
||||
(mount/suspend)
|
||||
(is (= (dval q-listener) :q-suspended))
|
||||
(is (= (dval web-server) :w-suspended))
|
||||
(mount/stop)))))
|
||||
Loading…
Reference in a new issue