Merge branch '0.1.5'

This commit is contained in:
anatoly 2015-12-01 08:49:14 -05:00
commit fd4d846c3f
14 changed files with 227 additions and 73 deletions

View file

@ -22,6 +22,7 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
- [Using State](#using-state) - [Using State](#using-state)
- [Dependencies](#dependencies) - [Dependencies](#dependencies)
- [Talking States](#talking-states) - [Talking States](#talking-states)
- [Value of Values](#value-of-values)
- [The Importance of Being Reloadable](#the-importance-of-being-reloadable) - [The Importance of Being Reloadable](#the-importance-of-being-reloadable)
- [Start and Stop Order](#start-and-stop-order) - [Start and Stop Order](#start-and-stop-order)
- [Start and Stop Parts of Application](#start-and-stop-parts-of-application) - [Start and Stop Parts of Application](#start-and-stop-parts-of-application)
@ -80,15 +81,15 @@ mount is an alternative to the [component](https://github.com/stuartsierra/compo
Creating state is easy: Creating state is easy:
```clojure ```clojure
(defstate conn :start (create-conn)) (defstate conn :start create-conn)
``` ```
where `(create-conn)` is defined elsewhere, can be right above it. where the `create-conn` function is defined elsewhere, can be right above it.
In case this state needs to be cleaned / destryed between reloads, there is also `:stop` In case this state needs to be cleaned / destryed between reloads, there is also `:stop`
```clojure ```clojure
(defstate conn :start (create-conn) (defstate conn :start create-conn
:stop (disconnect conn)) :stop (disconnect conn))
``` ```
@ -153,6 +154,44 @@ this `app-config`, being top level, can be used in other namespaces, including t
[here](https://github.com/tolitius/mount/blob/master/test/app/nyse.clj) [here](https://github.com/tolitius/mount/blob/master/test/app/nyse.clj)
is an example of a Datomic connection that "depends" on a similar `app-config`. is an example of a Datomic connection that "depends" on a similar `app-config`.
## Value of values
Lifecycle functions start/stop/suspend/resume 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)
```
Besides scalar values, lifecycle functions can take anonymous functions, partial functions, function references, etc.. Here are some examples:
```clojure
(defn f [n]
(fn [m]
(+ n m)))
(defn g [a b]
(+ a b))
(defn- pf [n]
(+ 41 n))
(defn fna []
42)
(defstate scalar :start 42)
(defstate fun :start #(inc 41))
(defstate with-fun :start (inc 41))
(defstate with-partial :start (partial g 41))
(defstate f-in-f :start (f 41))
(defstate f-no-args-value :start (fna))
(defstate f-no-args :start fna)
(defstate f-args :start g)
(defstate f-value :start (g 41 1))
(defstate private-f :start pf)
```
Check out [fun-with-values-test](https://github.com/tolitius/mount/blob/0.1.5/test/check/fun_with_values_test.clj) for more details.
## The Importance of Being Reloadable ## The Importance of Being Reloadable
`mount` has start and stop functions that will walk all the states created with `defstate` and start / stop them `mount` has start and stop functions that will walk all the states created with `defstate` and start / stop them
@ -311,9 +350,9 @@ and some other use cases.
In additiong to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones: In additiong to `start` / `stop` functions, a state can also have `resume` and, if needed, `suspend` ones:
```clojure ```clojure
(defstate web-server :start (start-server ...) (defstate web-server :start start-server
:resume (resume-server ...) :resume resume-server
:stop (stop-server ...)) :stop stop-server)
``` ```

View file

@ -19,11 +19,9 @@
(defn start [] (defn start []
(with-logging-status) (with-logging-status)
(mount/start-without #'check.start-with-test/test-conn (mount/start #'app.config/app-config
#'check.start-with-test/test-nrepl #'app.nyse/conn
#'check.parts-test/should-not-start #'app/nrepl)) ;; example on how to start app with certain states
#'check.suspend-resume-test/web-server
#'check.suspend-resume-test/q-listener)) ;; example on how to start app without certain states
(defn stop [] (defn stop []
(mount/stop)) (mount/stop))

View file

@ -137,8 +137,8 @@ Depending on the number of application components the "extra" size may vary.
Mount is pretty much: Mount is pretty much:
```clojure ```clojure
(defstate name :start (fn) (defstate name :start fn
:stop (fn)) :stop fn)
``` ```
no "ceremony". no "ceremony".

View file

@ -1,3 +0,0 @@
# Introduction to statuo
TODO: write [great documentation](http://jacobian.org/writing/what-to-write/)

View file

@ -84,13 +84,13 @@ In order to demo all of the above, we'll build an uberjar:
```bash ```bash
$ lein do clean, uberjar $ lein do clean, uberjar
... ...
Created .. mount/target/mount-0.2.0-SNAPSHOT-standalone.jar Created .. mount/target/mount-0.1.5-SNAPSHOT-standalone.jar
``` ```
Since we have a default for a Datomic URI, it'll work with no arguments: Since we have a default for a Datomic URI, it'll work with no arguments:
```bash ```bash
$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar $ java -jar target/mount-0.1.5-SNAPSHOT-standalone.jar
22:12:03.290 [main] INFO mount - >> starting.. app-config 22:12:03.290 [main] INFO mount - >> starting.. app-config
22:12:03.293 [main] INFO mount - >> starting.. conn 22:12:03.293 [main] INFO mount - >> starting.. conn
@ -101,7 +101,7 @@ $ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar
Now let's ask it to help us: Now let's ask it to help us:
```bash ```bash
$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar --help $ java -jar target/mount-0.1.5-SNAPSHOT-standalone.jar --help
22:13:48.798 [main] INFO mount - >> starting.. app-config 22:13:48.798 [main] INFO mount - >> starting.. app-config
22:13:48.799 [main] INFO app.config - 22:13:48.799 [main] INFO app.config -
@ -116,7 +116,7 @@ this is a sample mount app to demo how to pass and read runtime arguments
And finally let's connect to the Single Malt Database. It's Friday.. And finally let's connect to the Single Malt Database. It's Friday..
```bash ```bash
$ java -jar target/mount-0.2.0-SNAPSHOT-standalone.jar -d datomic:mem://single-malt-database $ java -jar target/mount-0.1.5-SNAPSHOT-standalone.jar -d datomic:mem://single-malt-database
22:16:10.733 [main] INFO mount - >> starting.. app-config 22:16:10.733 [main] INFO mount - >> starting.. app-config
22:16:10.737 [main] INFO mount - >> starting.. conn 22:16:10.737 [main] INFO mount - >> starting.. conn

View file

@ -38,15 +38,14 @@ where `nyse-app` is _the_ app. It has the usual routes:
and the reloadable state: and the reloadable state:
```clojure ```clojure
(defn start-nyse [] (defn start-nyse [{:keys [www]}]
(create-nyse-schema) ;; creating schema (usually done long before the app is started..)
(-> (routes mount-example-routes) (-> (routes mount-example-routes)
(handler/site) (handler/site)
(run-jetty {:join? false (run-jetty {:join? false
:port (get-in app-config [:www :port])}))) :port (:port www)})))
(defstate nyse-app :start (start-nyse) (defstate nyse-app :start (start-nyse app-config)
:stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point :stop (.stop nyse-app)) ;; it's a "org.eclipse.jetty.server.Server" at this point
``` ```
In order not to block, and being reloadable, the Jetty server is started in the "`:join? false`" mode which starts the server, In order not to block, and being reloadable, the Jetty server is started in the "`:join? false`" mode which starts the server,

View file

@ -1,11 +1,11 @@
(ns mount.core (ns mount.core
(:require [clojure.tools.macro :as macro])) (:require [clojure.tools.macro :as macro]))
;; (defonce ^:private session-id (System/currentTimeMillis))
(defonce ^:private mount-state 42) (defonce ^:private mount-state 42)
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files (defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
(defonce ^:private state-seq (atom 0)) (defonce ^:private state-seq (atom 0))
(defonce ^:private state-order (atom {})) (defonce ^:private state-order (atom {}))
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
;; supporting tools.namespace: (disable-reload!) ;; supporting tools.namespace: (disable-reload!)
(alter-meta! *ns* assoc ::load false) ;; to exclude the dependency (alter-meta! *ns* assoc ::load false) ;; to exclude the dependency
@ -29,17 +29,39 @@
(and suspend (not resume)) (throw (and suspend (not resume)) (throw
(IllegalArgumentException. "suspendable state should have a resume function (i.e. missing :resume fn)")))) (IllegalArgumentException. "suspendable state should have a resume function (i.e. missing :resume fn)"))))
(defn- with-ns [ns name]
(str ns "/" name))
(defn- pounded? [f]
(let [pound "(fn* [] "] ;;TODO: think of a better (i.e. typed) way to distinguish #(f params) from (fn [params] (...)))
(.startsWith (str f) pound)))
(defn- unpound [f]
(if (pounded? f)
(nth f 2) ;; magic 2 is to get the body => ["fn*" "[]" "(fn body)"]
f))
(defn- cleanup-if-dirty
"in case a namespace is recompiled without calling (mount/stop),
a running state instance will still be running.
this function stops this 'lost' state instance.
it is meant to be called by defstate before defining a new state"
[state]
(when-let [stop (@running state)]
(stop)))
(defmacro defstate [state & body] (defmacro defstate [state & body]
(let [[state params] (macro/name-with-attributes 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 suspend resume] :as lifecycle} (apply hash-map params)]
(validate lifecycle) (validate lifecycle)
(cleanup-if-dirty (with-ns *ns* state))
(let [s-meta (cond-> {:mount-state mount-state (let [s-meta (cond-> {:mount-state mount-state
:order (make-state-seq state) :order (make-state-seq (with-ns *ns* state))
:start `(fn [] (~@start)) :start `(fn [] ~start)
:started? false} :status #{:stopped}}
stop (assoc :stop `(fn [] (~@stop))) stop (assoc :stop `(fn [] ~(unpound stop)))
suspend (assoc :suspend `(fn [] (~@suspend))) suspend (assoc :suspend `(fn [] ~suspend))
resume (assoc :resume `(fn [] (~@resume))))] resume (assoc :resume `(fn [] ~resume)))]
`(defonce ~(with-meta state (merge (meta state) s-meta)) `(defonce ~(with-meta state (merge (meta state) s-meta))
(NotStartedState. ~(str state)))))) (NotStartedState. ~(str state))))))
@ -48,44 +70,46 @@
(swap! done conj (ns-resolve ns name)) (swap! done conj (ns-resolve ns name))
state)) state))
(defn- up [var {:keys [ns name start started? resume suspended?] :as state} done] (defn- up [var {:keys [ns name start stop resume status] :as state} done]
(when-not started? (when-not (:started status)
(let [s (try (if suspended? (let [s (try (if (:suspended status)
(record! state resume done) (record! state resume done)
(record! state start done)) (record! state start done))
(catch Throwable t (catch Throwable t
(throw (RuntimeException. (str "could not start [" name "] due to") t))))] (throw (RuntimeException. (str "could not start [" name "] due to") t))))]
(intern ns (symbol name) s) (intern ns (symbol name) s)
(alter-meta! var assoc :started? true :suspended? false)))) (swap! running assoc (with-ns ns name) stop)
(alter-meta! var assoc :status #{:started}))))
(defn- down [var {:keys [ns name stop started? suspended?] :as state} done] (defn- down [var {:keys [ns name stop status] :as state} done]
(when (or started? suspended?) (when (some status #{:started :suspended})
(when stop (when stop
(try (try
(record! state stop done) (record! state stop done)
(catch Throwable t (catch Throwable t
(throw (RuntimeException. (str "could not stop [" name "] due to") t))))) (throw (RuntimeException. (str "could not stop [" name "] due to") t)))))
(intern ns (symbol name) (NotStartedState. name)) ;; (!) if a state does not have :stop when _should_ this might leak (intern ns (symbol name) (NotStartedState. name)) ;; (!) if a state does not have :stop when _should_ this might leak
(alter-meta! var assoc :started? false :suspended? false))) (swap! running dissoc (with-ns ns name))
(alter-meta! var assoc :status #{:stopped})))
(defn- sigstop [var {:keys [ns name started? suspend resume] :as state} done] (defn- sigstop [var {:keys [ns name suspend resume status] :as state} done]
(when (and started? resume) ;; can't have suspend without resume, but the reverse is possible (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?) (when suspend ;; don't suspend if there is only resume function (just mark it :suspended?)
(let [s (try (record! state suspend done) (let [s (try (record! state suspend done)
(catch Throwable t (catch Throwable t
(throw (RuntimeException. (str "could not suspend [" name "] due to") t))))] (throw (RuntimeException. (str "could not suspend [" name "] due to") t))))]
(intern ns (symbol name) s))) (intern ns (symbol name) s)))
(alter-meta! var assoc :started? false :suspended? true))) (alter-meta! var assoc :status #{:suspended})))
(defn- sigcont [var {:keys [ns name start started? resume suspended?] :as state} done] (defn- sigcont [var {:keys [ns name start resume status] :as state} done]
(when (instance? NotStartedState var) (when (instance? NotStartedState var)
(throw (RuntimeException. (str "could not resume [" name "] since it is stoppped (i.e. not suspended)")))) (throw (RuntimeException. (str "could not resume [" name "] since it is stoppped (i.e. not suspended)"))))
(when suspended? (when (:suspended status)
(let [s (try (record! state resume done) (let [s (try (record! state resume done)
(catch Throwable t (catch Throwable t
(throw (RuntimeException. (str "could not resume [" name "] due to") t))))] (throw (RuntimeException. (str "could not resume [" name "] due to") t))))]
(intern ns (symbol name) s) (intern ns (symbol name) s)
(alter-meta! var assoc :started? true :suspended? false)))) (alter-meta! var assoc :status #{:started}))))
;;TODO args might need more thinking ;;TODO args might need more thinking
(defn args [] @-args) (defn args [] @-args)
@ -110,7 +134,7 @@
(defn states-with-deps [] (defn states-with-deps []
(let [all (find-all-states)] (let [all (find-all-states)]
(->> (map (comp #(add-deps % all) (->> (map (comp #(add-deps % all)
#(select-keys % [:name :order :ns :started? :suspended?]) #(select-keys % [:name :order :ns :status])
meta) meta)
all) all)
(sort-by :order)))) (sort-by :order))))
@ -129,9 +153,9 @@
however other keys of 'state' (such as :ns,:name,:order) should not be overriden" however other keys of 'state' (such as :ns,:name,:order) should not be overriden"
([state sub] ([state sub]
(merge-lifecycles state nil sub)) (merge-lifecycles state nil sub))
([state origin {:keys [start stop suspend resume suspended?]}] ([state origin {:keys [start stop suspend resume status]}]
(assoc state :origin origin (assoc state :origin origin
:suspended? suspended? :status status
:start start :stop stop :suspend suspend :resume resume))) :start start :stop stop :suspend suspend :resume resume)))
(defn- rollback! [state] (defn- rollback! [state]
@ -140,7 +164,7 @@
(alter-meta! state #(merge-lifecycles % origin))))) (alter-meta! state #(merge-lifecycles % origin)))))
(defn- substitute! [state with] (defn- substitute! [state with]
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :suspended?]) (let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
origin (meta state) origin (meta state)
sub (meta with)] sub (meta with)]
(alter-meta! with assoc :sub? true) (alter-meta! with assoc :sub? true)
@ -148,8 +172,7 @@
(defn- unsub [state] (defn- unsub [state]
(when (-> (meta state) :sub?) (when (-> (meta state) :sub?)
(alter-meta! state assoc :sub? nil (alter-meta! state dissoc :sub?)))
:started false)))
(defn- all-without-subs [] (defn- all-without-subs []
(remove (comp :sub? meta) (find-all-states))) (remove (comp :sub? meta) (find-all-states)))
@ -167,11 +190,8 @@
(defn stop-except [& states] (defn stop-except [& states]
(let [all (set (find-all-states)) (let [all (set (find-all-states))
states (remove (set states) all) states (remove (set states) all)]
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with" (apply stop states)))
stopped (bring states down >)]
(dorun (map rollback! states)) ;; restore to origin from "start-with"
{:stopped stopped}))
(defn start-with-args [xs & states] (defn start-with-args [xs & states]
(reset! -args xs) (reset! -args xs)

View file

@ -17,13 +17,13 @@
"mount.core$sigcont" :resume "mount.core$sigcont" :resume
:noop))) :noop)))
(defn whatcha-doing? [{:keys [started? suspended? suspend]} action] (defn whatcha-doing? [{:keys [status suspend]} action]
(case action (case action
:up (if suspended? ">> resuming" :up (if (status :suspended) ">> resuming"
(if-not started? ">> starting")) (if-not (status :started) ">> starting"))
:down (if (or started? suspended?) "<< stopping") :down (if (or (status :started) (status :suspended)) "<< stopping")
:suspend (if (and started? suspend) "<< suspending") :suspend (if (and (status :started) suspend) "<< suspending")
:resume (if suspended? ">> resuming"))) :resume (if (status :suspended) ">> resuming")))
(defn log-status [f & args] (defn log-status [f & args]
(let [{:keys [ns name] :as state} (second args) (let [{:keys [ns name] :as state} (second args)

View file

@ -0,0 +1,13 @@
(ns check.cleanup_dirty_states_test
(:require [mount.core :as mount]
[app]
[clojure.test :refer :all]))
(deftest cleanup-dirty-states
(let [_ (mount/start)]
(is (not (.isClosed (:server-socket app/nrepl))))
(require 'app :reload)
(mount/start) ;; should not result in "BindException Address already in use" since the clean up will stop the previous instance
(is (not (.isClosed (:server-socket app/nrepl))))
(mount/stop)
(is (instance? mount.core.NotStartedState app/nrepl))))

View file

@ -0,0 +1,55 @@
(ns check.fun-with-values-test
(:require [mount.core :as mount :refer [defstate]]
[clojure.test :refer :all]))
(defn f [n]
(fn [m]
(+ n m)))
(defn g [a b]
(+ a b))
(defn- pf [n]
(+ 41 n))
(defn fna []
42)
(defstate scalar :start 42)
(defstate fun :start #(inc 41))
(defstate with-fun :start (inc 41))
(defstate with-partial :start (partial g 41))
(defstate f-in-f :start (f 41))
(defstate f-no-args-value :start (fna))
(defstate f-no-args :start fna)
(defstate f-args :start g)
(defstate f-value :start (g 41 1))
(defstate private-f :start pf)
(defn with-fun-and-values [f]
(mount/start #'check.fun-with-values-test/scalar
#'check.fun-with-values-test/fun
#'check.fun-with-values-test/with-fun
#'check.fun-with-values-test/with-partial
#'check.fun-with-values-test/f-in-f
#'check.fun-with-values-test/f-args
#'check.fun-with-values-test/f-no-args-value
#'check.fun-with-values-test/f-no-args
#'check.fun-with-values-test/private-f
#'check.fun-with-values-test/f-value)
(f)
(mount/stop))
(use-fixtures :each with-fun-and-values)
(deftest fun-with-values
(is (= scalar 42))
(is (= (fun) 42))
(is (= with-fun 42))
(is (= (with-partial 1) 42))
(is (= (f-in-f 1) 42))
(is (= f-no-args-value 42))
(is (= (f-no-args) 42))
(is (= (f-args 41 1) 42))
(is (= (private-f 1) 42))
(is (= f-value 42)))

View file

@ -3,7 +3,7 @@
[app.nyse :refer [conn]] [app.nyse :refer [conn]]
[clojure.test :refer :all])) [clojure.test :refer :all]))
(defstate should-not-start :start (constantly 42)) (defstate should-not-start :start #(constantly 42))
(defn with-parts [f] (defn with-parts [f]
(m/start #'app.config/app-config #'app.nyse/conn) (m/start #'app.config/app-config #'app.nyse/conn)

View file

@ -0,0 +1,14 @@
(ns check.private-fun-test
(:require [mount.core :as mount :refer [defstate]]
[check.fun-with-values-test :refer [private-f]]
[clojure.test :refer :all]))
(defn with-fun-and-values [f]
(mount/start #'check.fun-with-values-test/private-f)
(f)
(mount/stop))
(use-fixtures :each with-fun-and-values)
(deftest fun-with-valuesj
(is (= (private-f 1) 42)))

View file

@ -5,10 +5,10 @@
[app :refer [nrepl]] [app :refer [nrepl]]
[clojure.test :refer :all])) [clojure.test :refer :all]))
(defstate test-conn :start (long 42) (defstate test-conn :start 42
:stop (constantly 0)) :stop #(constantly 0))
(defstate test-nrepl :start (vector)) (defstate test-nrepl :start [])
(deftest start-with (deftest start-with
@ -44,3 +44,4 @@
(is (instance? mount.core.NotStartedState test-conn)) (is (instance? mount.core.NotStartedState test-conn))
(is (instance? mount.core.NotStartedState test-nrepl)) (is (instance? mount.core.NotStartedState test-nrepl))
(mount/stop)))) (mount/stop))))

View file

@ -25,9 +25,9 @@
:suspend (suspend :q) :suspend (suspend :q)
:resume (resume :q)) :resume (resume :q))
(deftest suspendable (defstate randomizer :start (rand-int 42))
;; lifecycle (deftest suspendable-lifecycle
(testing "should suspend _only suspendable_ states that are currently started" (testing "should suspend _only suspendable_ states that are currently started"
(let [_ (mount/start) (let [_ (mount/start)
@ -66,9 +66,10 @@
(is (instance? mount.core.NotStartedState app-config)) (is (instance? mount.core.NotStartedState app-config))
(is (instance? mount.core.NotStartedState nrepl)) (is (instance? mount.core.NotStartedState nrepl))
(is (instance? mount.core.NotStartedState conn)) (is (instance? mount.core.NotStartedState conn))
(is (instance? mount.core.NotStartedState web-server)))) (is (instance? mount.core.NotStartedState web-server)))))
;; start-with
(deftest suspendable-start-with
(testing "when replacing a non suspendable state with a suspendable one, (testing "when replacing a non suspendable state with a suspendable one,
the later should be able to suspend/resume, the later should be able to suspend/resume,
@ -85,7 +86,24 @@
(mount/stop))) (mount/stop)))
;; this is a messy use case, but can still happen especially at REPL time ;; this is a messy use case, but can still happen especially at REPL time
(testing "when replacing a suspended state with a non suspendable one, ;; 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 {#'check.suspend-resume-test/web-server #'check.suspend-resume-test/randomizer})
_ (mount/suspend)]
(is (integer? web-server))
(is (instance? mount.core.NotStartedState randomizer))
(mount/stop)
(mount/start)
(mount/suspend)
(is (integer? randomizer))
(is (= 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 later should not be suspendable,
the original should still be suspended and preserve its lifecycle fns after the rollback/stop" the original should still be suspended and preserve its lifecycle fns after the rollback/stop"
(let [_ (mount/start) (let [_ (mount/start)
@ -93,7 +111,7 @@
_ (mount/start-with {#'check.suspend-resume-test/web-server #'app.nyse/conn}) ;; TODO: good to WARN on started states during "start-with" _ (mount/start-with {#'check.suspend-resume-test/web-server #'app.nyse/conn}) ;; TODO: good to WARN on started states during "start-with"
_ (mount/suspend)] _ (mount/suspend)]
(is (instance? datomic.peer.LocalConnection conn)) (is (instance? datomic.peer.LocalConnection conn))
(is (instance? datomic.peer.LocalConnection web-server)) (is (= web-server :w-suspended)) ;; since the "conn" does not have a resume method, so web-server was not started
(mount/stop) (mount/stop)
(mount/start) (mount/start)
(mount/suspend) (mount/suspend)