Merge branch '0.1.6'
This commit is contained in:
commit
e36d524176
48 changed files with 1425 additions and 588 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
|
@ -4,8 +4,9 @@
|
||||||
pom.xml
|
pom.xml
|
||||||
pom.xml.asc
|
pom.xml.asc
|
||||||
.repl*
|
.repl*
|
||||||
dev-resources/
|
dev/resources/public/js/*
|
||||||
figwheel_server.log
|
figwheel_server.log
|
||||||
|
build.xml
|
||||||
*.jar
|
*.jar
|
||||||
*.class
|
*.class
|
||||||
/.lein-*
|
/.lein-*
|
||||||
|
|
|
||||||
19
README.md
19
README.md
|
|
@ -35,6 +35,9 @@ _**Alan J. Perlis** from [Structure and Interpretation of Computer Programs](htt
|
||||||
- [Plugging into (reset)](#plugging-into-reset)
|
- [Plugging into (reset)](#plugging-into-reset)
|
||||||
- [Suspendable Example Application](#suspendable-example-application)
|
- [Suspendable Example Application](#suspendable-example-application)
|
||||||
- [Affected States](#affected-states)
|
- [Affected States](#affected-states)
|
||||||
|
|
||||||
|
- [ClojureScript is Clojure](doc/clojurescript.md)
|
||||||
|
|
||||||
- [Logging](#logging)
|
- [Logging](#logging)
|
||||||
- [Mount and Develop!](#mount-and-develop)
|
- [Mount and Develop!](#mount-and-develop)
|
||||||
- [Running New York Stock Exchange](#running-new-york-stock-exchange)
|
- [Running New York Stock Exchange](#running-new-york-stock-exchange)
|
||||||
|
|
@ -152,7 +155,7 @@ this `app-config`, being top level, can be used in other namespaces, including t
|
||||||
(defstate conn :start (create-connection app-config))
|
(defstate conn :start (create-connection app-config))
|
||||||
```
|
```
|
||||||
|
|
||||||
[here](https://github.com/tolitius/mount/blob/master/test/app/nyse.clj)
|
[here](dev/clj/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
|
## Value of values
|
||||||
|
|
@ -191,7 +194,7 @@ Besides scalar values, lifecycle functions can take anonymous functions, partial
|
||||||
(defstate private-f :start pf)
|
(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.
|
Check out [fun-with-values-test](test/mount/test/fun_with_values.cljc) for more details.
|
||||||
|
|
||||||
## The Importance of Being Reloadable
|
## The Importance of Being Reloadable
|
||||||
|
|
||||||
|
|
@ -207,7 +210,7 @@ dev=> (mount/start)
|
||||||
|
|
||||||
While it is not always necessary, mount lificycle can be easily hooked up to [tools.namespace](https://github.com/clojure/tools.namespace),
|
While it is not always necessary, mount lificycle can be easily hooked up to [tools.namespace](https://github.com/clojure/tools.namespace),
|
||||||
to make the whole application reloadable with refreshing the app namespaces.
|
to make the whole application reloadable with refreshing the app namespaces.
|
||||||
Here is a [dev.clj](https://github.com/tolitius/mount/blob/master/dev/dev.clj) as an example, that sums up to:
|
Here is a [dev.clj](dev/dev.clj) as an example, that sums up to:
|
||||||
|
|
||||||
```clojure
|
```clojure
|
||||||
(defn go []
|
(defn go []
|
||||||
|
|
@ -244,7 +247,7 @@ dev=> (reset)
|
||||||
:ready
|
:ready
|
||||||
```
|
```
|
||||||
|
|
||||||
You can see examples of start and stop flows in the [example app](https://github.com/tolitius/mount#mount-and-develop).
|
You can see examples of start and stop flows in the [example app](README.md#mount-and-develop).
|
||||||
|
|
||||||
## Start and Stop Parts of Application
|
## Start and Stop Parts of Application
|
||||||
|
|
||||||
|
|
@ -260,7 +263,7 @@ In REPL or during testing it is often very useful to work with / start / stop _o
|
||||||
|
|
||||||
which will only start/stop `app-config` and `conn` (won't start any other states).
|
which will only start/stop `app-config` and `conn` (won't start any other states).
|
||||||
|
|
||||||
Here is an [example](test/check/parts_test.clj) test that uses only two namespaces checking that the third one is not started.
|
Here is an [example](test/mount/test/parts.cljc) test that uses only two namespaces checking that the third one is not started.
|
||||||
|
|
||||||
## Start an Application Without Certain States
|
## Start an Application Without Certain States
|
||||||
|
|
||||||
|
|
@ -275,7 +278,7 @@ The `start-without` function can do just that:
|
||||||
|
|
||||||
which will start an application without starting `feed-listener` and `nrepl` states.
|
which will start an application without starting `feed-listener` and `nrepl` states.
|
||||||
|
|
||||||
Here is an [example](test/check/start_without_test.clj) test that excludes Datomic connection and nREPL from an application on start.
|
Here is an [example](test/mount/test/start_without.cljc) test that excludes Datomic connection and nREPL from an application on start.
|
||||||
|
|
||||||
## Swapping Alternate Implementations
|
## Swapping Alternate Implementations
|
||||||
|
|
||||||
|
|
@ -298,7 +301,7 @@ One thing to note, whenever
|
||||||
|
|
||||||
is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
|
is run after `start-with`, it rolls back to an original "state of states", i.e. `#'app.nyse/db` is `#'app.nyse/db` again. So subsequent calls to `(mount/start)` or even to `(mount/start-with {something else})` will start from a clean slate.
|
||||||
|
|
||||||
Here is an [example](test/check/start_with_test.clj) test that starts an app with mocking Datomic connection and nREPL.
|
Here is an [example](test/mount/test/start_with.cljc) test that starts an app with mocking Datomic connection and nREPL.
|
||||||
|
|
||||||
## Stop an Application Except Certain States
|
## Stop an Application Except Certain States
|
||||||
|
|
||||||
|
|
@ -449,7 +452,7 @@ The way this is done is via an excellent [robert hooke](https://github.com/techn
|
||||||
|
|
||||||
## Mount and Develop!
|
## Mount and Develop!
|
||||||
|
|
||||||
`mount` comes with an example [app](https://github.com/tolitius/mount/tree/master/test/app)
|
`mount` comes with an example [app](dev/clj/app)
|
||||||
that has 3 states:
|
that has 3 states:
|
||||||
|
|
||||||
* `config`, loaded from the files and refreshed on each `(reset)`
|
* `config`, loaded from the files and refreshed on each `(reset)`
|
||||||
|
|
|
||||||
10
circle.yml
Normal file
10
circle.yml
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
machine:
|
||||||
|
java:
|
||||||
|
version: oraclejdk8
|
||||||
|
|
||||||
|
test:
|
||||||
|
override:
|
||||||
|
- lein do clean, test
|
||||||
|
- lein do clean, doo phantom test once
|
||||||
|
- lein do clean, cljsbuild once prod
|
||||||
|
- lein test2junit
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
(ns app.config
|
(ns app.conf
|
||||||
(:require [mount.core :as mount :refer [defstate]]
|
(:require [mount.core :as mount :refer [defstate]]
|
||||||
[clojure.edn :as edn]
|
[clojure.edn :as edn]
|
||||||
[clojure.tools.logging :refer [info]]))
|
[clojure.tools.logging :refer [info]]))
|
||||||
|
|
@ -9,5 +9,5 @@
|
||||||
slurp
|
slurp
|
||||||
edn/read-string))
|
edn/read-string))
|
||||||
|
|
||||||
(defstate app-config
|
(defstate config
|
||||||
:start (load-config "test/resources/config.edn"))
|
:start (load-config "dev/resources/config.edn"))
|
||||||
|
|
@ -1,9 +1,9 @@
|
||||||
(ns app
|
(ns app.example
|
||||||
(:require [datomic.api :as d]
|
(:require [datomic.api :as d]
|
||||||
[clojure.tools.nrepl.server :refer [start-server stop-server]]
|
[clojure.tools.nrepl.server :refer [start-server stop-server]]
|
||||||
[mount.core :as mount :refer [defstate]]
|
[mount.core :as mount :refer [defstate]]
|
||||||
[app.utils.datomic :refer [touch]]
|
[app.utils.datomic :refer [touch]]
|
||||||
[app.config :refer [app-config]]
|
[app.conf :refer [config]]
|
||||||
[app.nyse :as nyse]))
|
[app.nyse :as nyse]))
|
||||||
|
|
||||||
;; example on creating a network REPL
|
;; example on creating a network REPL
|
||||||
|
|
@ -11,7 +11,7 @@
|
||||||
(start-server :bind host :port port))
|
(start-server :bind host :port port))
|
||||||
|
|
||||||
;; nREPL is just another simple state
|
;; nREPL is just another simple state
|
||||||
(defstate nrepl :start (start-nrepl (:nrepl app-config))
|
(defstate nrepl :start (start-nrepl (:nrepl config))
|
||||||
:stop (stop-server nrepl))
|
:stop (stop-server nrepl))
|
||||||
|
|
||||||
;; datomic schema
|
;; datomic schema
|
||||||
|
|
@ -2,7 +2,7 @@
|
||||||
(:require [mount.core :as mount :refer [defstate]]
|
(:require [mount.core :as mount :refer [defstate]]
|
||||||
[datomic.api :as d]
|
[datomic.api :as d]
|
||||||
[clojure.tools.logging :refer [info]]
|
[clojure.tools.logging :refer [info]]
|
||||||
[app.config :refer [app-config]]))
|
[app.conf :refer [config]]))
|
||||||
|
|
||||||
(defn- new-connection [conf]
|
(defn- new-connection [conf]
|
||||||
(info "conf: " conf)
|
(info "conf: " conf)
|
||||||
|
|
@ -17,5 +17,5 @@
|
||||||
(.release conn) ;; usually it's not released, here just to illustrate the access to connection on (stop)
|
(.release conn) ;; usually it's not released, here just to illustrate the access to connection on (stop)
|
||||||
(d/delete-database uri)))
|
(d/delete-database uri)))
|
||||||
|
|
||||||
(defstate conn :start (new-connection app-config)
|
(defstate conn :start (new-connection config)
|
||||||
:stop (disconnect app-config conn))
|
:stop (disconnect config conn))
|
||||||
|
|
@ -26,10 +26,10 @@
|
||||||
:resume (if (status :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 [var] :as state} (second args)
|
||||||
action (f-to-action f)]
|
action (f-to-action f)]
|
||||||
(when-let [taking-over-the-world (whatcha-doing? state action)]
|
(when-let [taking-over-the-world (whatcha-doing? state action)]
|
||||||
(info (str taking-over-the-world ".. " (ns-resolve ns name))))
|
(info (str taking-over-the-world ".. " var)))
|
||||||
(apply f args)))
|
(apply f args)))
|
||||||
|
|
||||||
(defonce lifecycle-fns
|
(defonce lifecycle-fns
|
||||||
25
dev/cljs/app/audit_log.cljs
Normal file
25
dev/cljs/app/audit_log.cljs
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
(ns app.audit-log
|
||||||
|
(:require [datascript.core :as d]
|
||||||
|
[cljs-time.core :refer [now]])
|
||||||
|
(:require-macros [mount.core :refer [defstate]]))
|
||||||
|
|
||||||
|
(defstate log :start (d/create-conn {}))
|
||||||
|
|
||||||
|
(defn audit [db source & msg]
|
||||||
|
(d/transact! @db [{:db/id -1
|
||||||
|
:source source
|
||||||
|
:timestamp (now)
|
||||||
|
:msg (apply str msg)}]))
|
||||||
|
|
||||||
|
(defn find-source-logs [db source]
|
||||||
|
(d/q '{:find [?t ?msg]
|
||||||
|
:in [$ ?s]
|
||||||
|
:where [[?e :source ?s]
|
||||||
|
[?e :timestamp ?t]
|
||||||
|
[?e :msg ?msg]]}
|
||||||
|
@@db source))
|
||||||
|
|
||||||
|
(defn find-all-logs [db]
|
||||||
|
(->> (map :e (d/datoms @@db :aevt :timestamp))
|
||||||
|
dedupe
|
||||||
|
(d/pull-many @@db '[:timestamp :source :msg])))
|
||||||
9
dev/cljs/app/conf.cljs
Normal file
9
dev/cljs/app/conf.cljs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
(ns app.conf
|
||||||
|
(:require [app.audit-log :refer [audit log]])
|
||||||
|
(:require-macros [mount.core :refer [defstate]]))
|
||||||
|
|
||||||
|
(defn load-config [path]
|
||||||
|
(audit log :app-conf "loading config from '" path "' (at least pretending)")
|
||||||
|
{:system-a {:uri "ws://echo.websocket.org/"}})
|
||||||
|
|
||||||
|
(defstate config :start (load-config "resources/config.end"))
|
||||||
26
dev/cljs/app/example.cljs
Normal file
26
dev/cljs/app/example.cljs
Normal file
|
|
@ -0,0 +1,26 @@
|
||||||
|
(ns app.example
|
||||||
|
(:require [mount.core :as mount]
|
||||||
|
[app.conf]
|
||||||
|
[app.websockets]
|
||||||
|
[app.audit-log :refer [log find-all-logs]]
|
||||||
|
[cljs-time.format :refer [unparse formatters]]
|
||||||
|
[hiccups.runtime :as hiccupsrt])
|
||||||
|
(:require-macros [hiccups.core :as hiccups :refer [html]]))
|
||||||
|
|
||||||
|
(defn format-log-event [{:keys [timestamp source msg]}]
|
||||||
|
(str (unparse (formatters :date-hour-minute-second-fraction) timestamp)
|
||||||
|
" → [" (name source) "]: " msg))
|
||||||
|
|
||||||
|
(defn show-log []
|
||||||
|
(.write js/document
|
||||||
|
(html [:ul (doall (for [e (find-all-logs log)]
|
||||||
|
[:li (format-log-event e)]))])))
|
||||||
|
|
||||||
|
(mount/start)
|
||||||
|
|
||||||
|
;; time to establish a websocket connection before disconnecting
|
||||||
|
(js/setTimeout #(mount/stop-except "#'app.audit-log/log") 500)
|
||||||
|
|
||||||
|
;; time to close a connection to show it in audit
|
||||||
|
(js/setTimeout #(show-log) 1000)
|
||||||
|
|
||||||
22
dev/cljs/app/websockets.cljs
Normal file
22
dev/cljs/app/websockets.cljs
Normal file
|
|
@ -0,0 +1,22 @@
|
||||||
|
(ns app.websockets
|
||||||
|
(:require [app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [audit log]])
|
||||||
|
(:require-macros [mount.core :refer [defstate]]))
|
||||||
|
|
||||||
|
(defn ws-status [ws]
|
||||||
|
{:url (.-url ws) :ready-state (.-readyState ws)})
|
||||||
|
|
||||||
|
(defn connect [uri]
|
||||||
|
(let [ws (js/WebSocket. uri)]
|
||||||
|
(audit log :system-a "connecting to " (ws-status ws))
|
||||||
|
(set! (.-onopen ws) #(audit log :system-a "opened " (ws-status ws)))
|
||||||
|
(set! (.-onclose ws) #(audit log :system-a "closed " (ws-status ws)))
|
||||||
|
ws))
|
||||||
|
|
||||||
|
(defn disconnect [ws]
|
||||||
|
(audit log :system-a "closing " (ws-status @ws))
|
||||||
|
(.close @ws)
|
||||||
|
(audit log :system-a "disconnecting " (ws-status @ws)))
|
||||||
|
|
||||||
|
(defstate system-a :start (connect (get-in @config [:system-a :uri]))
|
||||||
|
:stop (disconnect system-a))
|
||||||
11
dev/dev.clj
11
dev/dev.clj
|
|
@ -10,18 +10,15 @@
|
||||||
[clojure.string :as str]
|
[clojure.string :as str]
|
||||||
[clojure.test :as test]
|
[clojure.test :as test]
|
||||||
[clojure.tools.namespace.repl :as tn]
|
[clojure.tools.namespace.repl :as tn]
|
||||||
[check.parts-test]
|
|
||||||
[check.start-with-test]
|
|
||||||
[check.suspend-resume-test]
|
|
||||||
[mount.core :as mount]
|
[mount.core :as mount]
|
||||||
[app.utils.logging :refer [with-logging-status]]
|
[app.utils.logging :refer [with-logging-status]]
|
||||||
[app :refer [create-nyse-schema find-orders add-order]])) ;; <<<< replace this your "app" namespace(s) you want to be available at REPL time
|
[app.example :refer [create-nyse-schema find-orders add-order]])) ;; <<<< replace this your "app" namespace(s) you want to be available at REPL time
|
||||||
|
|
||||||
(defn start []
|
(defn start []
|
||||||
(with-logging-status)
|
(with-logging-status)
|
||||||
(mount/start #'app.config/app-config
|
(mount/start #'app.conf/config
|
||||||
#'app.nyse/conn
|
#'app.nyse/conn
|
||||||
#'app/nrepl)) ;; example on how to start app with certain states
|
#'app.example/nrepl)) ;; example on how to start app with certain states
|
||||||
|
|
||||||
(defn stop []
|
(defn stop []
|
||||||
(mount/stop))
|
(mount/stop))
|
||||||
|
|
@ -45,3 +42,5 @@
|
||||||
[]
|
[]
|
||||||
(stop)
|
(stop)
|
||||||
(tn/refresh :after 'dev/go))
|
(tn/refresh :after 'dev/go))
|
||||||
|
|
||||||
|
(mount/in-clj-mode)
|
||||||
|
|
|
||||||
6
dev/resources/public/index.html
Normal file
6
dev/resources/public/index.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<body>
|
||||||
|
<script src="js/compiled/mount.js" type="text/javascript"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
|
@ -1,4 +1,3 @@
|
||||||
(defn dev
|
(defn dev []
|
||||||
[]
|
|
||||||
(require 'dev)
|
(require 'dev)
|
||||||
(in-ns 'dev))
|
(in-ns 'dev))
|
||||||
|
|
|
||||||
120
doc/clojurescript.md
Normal file
120
doc/clojurescript.md
Normal file
|
|
@ -0,0 +1,120 @@
|
||||||
|
## Managing state in ClojureScript
|
||||||
|
|
||||||
|
- [The "Why"](#the-why)
|
||||||
|
- [Mount Modes](#mount-modes)
|
||||||
|
- [Just Clojure Mode](#just-clojure-mode)
|
||||||
|
- [Clojure and ClojureScript Mode](#clojure-_and_-clojurescript-mode)
|
||||||
|
- [Mounting that ClojureScript](#mounting-that-clojurescript)
|
||||||
|
- [Using States](#using-states)
|
||||||
|
- [Thanks](#thanks)
|
||||||
|
|
||||||
|
In case you need to manage state in ClojureScript using mount, _all_ the mount Clojure features are supported in ClojureScript.
|
||||||
|
Which means all the mount Clojure [documentation](../README.md) is the mount ClojureScript documentation.
|
||||||
|
|
||||||
|
With a slight change in [_mode_](clojurescript.md#mount-modes) ( no change in _mood_ though, just the _mode_ :)).
|
||||||
|
|
||||||
|
### The "Why"
|
||||||
|
|
||||||
|
Since [reader conditionals](http://clojure.org/reader#The%20Reader--Reader%20Conditionals) were added in Clojure 1.7,
|
||||||
|
it became a lot easier to target both platforms with lots of code reuse. You might have noticed
|
||||||
|
that most of mount code lives in `.cljc` files.
|
||||||
|
|
||||||
|
The way mount is designed it "mounts" itself to a solid Clojure [namespace API](http://clojure.org/namespaces),
|
||||||
|
and while `.cljc` helps a lot with targeting Clojure and ClojureScript, JavaScript VM is vastly different from JVM.
|
||||||
|
Since JavaScript mostly tagrets browsers, mobile devices and IoT,
|
||||||
|
it is quite importand to [compress](https://github.com/clojure/clojurescript/wiki/Advanced-Compilation) the final result.
|
||||||
|
|
||||||
|
Which means that Clojure namespaces API are not that well supported in ClojureScript, since they get renamed and optimized
|
||||||
|
during compilation + of course no native namespace support on the JavaScript side
|
||||||
|
(but that is somewhat solved with [Google Closure](https://closure-library.googlecode.com/git-history/docs/local_closure_goog_base.js.source.html#line428)).
|
||||||
|
|
||||||
|
But. When developing an application in Clojure and ClojureScript, it would only make sense if the API for any library
|
||||||
|
would be _identical_ for both platforms. It should be transparent for developers whether they use a library in Clojure or ClojureScript.
|
||||||
|
It is not possible for all libraries (i.e. concurrency, reified Vars, etc.), but we should try to make it possible for most.
|
||||||
|
|
||||||
|
### Mount Modes
|
||||||
|
|
||||||
|
Mount has two modes `clj` and `cljc`.
|
||||||
|
|
||||||
|
#### Just Clojure Mode
|
||||||
|
|
||||||
|
`clj` mode is _default_, and all the APIs are exactly the same as they are in the mount Clojure [documentation](../README.md).
|
||||||
|
|
||||||
|
#### Clojure _and_ ClojureScript Mode
|
||||||
|
|
||||||
|
`cljc` mode is is not default, but it is easy to switch to it:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
(mount/in-cljc-mode)
|
||||||
|
```
|
||||||
|
|
||||||
|
this sets mount into the `cljc` mode. In this mode mount supports _both_: Clojure and ClojureScript with one difference
|
||||||
|
from the default `clj` mode:
|
||||||
|
|
||||||
|
> all states are "_derefable_"
|
||||||
|
|
||||||
|
which means in order to use them, you'd need to `@` it. That's where the difference between two modes end.
|
||||||
|
|
||||||
|
Again, `cljc` mode API is _consistent across both_ Clojure and ClojureScript.
|
||||||
|
|
||||||
|
While initially it may sound strange, this approach has very nice properties:
|
||||||
|
|
||||||
|
* Mentally something that you defer (`@`) is associated with a state behind it
|
||||||
|
* The whole system may start lazily without an explicit call `(mount/start)`
|
||||||
|
* States may have watchers which is just an idea at this point, but it could be quite useful
|
||||||
|
|
||||||
|
Now as the theory is laid out...
|
||||||
|
|
||||||
|
### Mounting that ClojureScript
|
||||||
|
|
||||||
|
Let's look at the example [ClojureScript app](../dev/cljs/app) that uses mount to manage several states:
|
||||||
|
|
||||||
|
* [Datascript](https://github.com/tonsky/datascript) Database
|
||||||
|
* Websocket Connection
|
||||||
|
* Configuration
|
||||||
|
|
||||||
|
In order to run it, just compile `cljs` (in `:advanced` mode, because why not? :)) with:
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
$ lein do clean, cljsbuild once prod
|
||||||
|
Compiling ClojureScript...
|
||||||
|
Compiling "dev/resources/public/js/compiled/mount.js" from ["src" "dev/cljs"]...
|
||||||
|
Successfully compiled "dev/resources/public/js/compiled/mount.js" in 23.966 seconds.
|
||||||
|
```
|
||||||
|
|
||||||
|
And just open a browser at `file:///[path-to-mount]/mount/dev/resources/public/index.html`:
|
||||||
|
|
||||||
|
<img src="img/mount.cljs.example.png" width="400">
|
||||||
|
|
||||||
|
The flow behind the app is quite simple:
|
||||||
|
|
||||||
|
* load config
|
||||||
|
* open a WebSocket connection
|
||||||
|
* keep an audit log in Datascript
|
||||||
|
* call `(mount/stop)` to disconnect
|
||||||
|
|
||||||
|
#### Using States
|
||||||
|
|
||||||
|
A good example of derefing state is here in [websockets.cljs](https://github.com/tolitius/mount/blob/0825ad2ed085b73b7ae989b4382ce4e0376e4be3/dev/cljs/app/websockets.cljs#L21):
|
||||||
|
|
||||||
|
```clojure
|
||||||
|
|
||||||
|
(ns app.websockets
|
||||||
|
(:require [app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [audit log]])
|
||||||
|
(:require-macros [mount.core :refer [defstate]]))
|
||||||
|
|
||||||
|
;; ...
|
||||||
|
|
||||||
|
(defstate system-a :start (connect (get-in @config [:system-a :uri]))
|
||||||
|
:stop (disconnect system-a))
|
||||||
|
```
|
||||||
|
|
||||||
|
notice how config is deferef'ed `@config` in order to use its state. It of course does not have to be deref'ed here, and
|
||||||
|
can be just passed along to the `connect` function to be `@`ed there instead.
|
||||||
|
|
||||||
|
### Thanks
|
||||||
|
|
||||||
|
I'd like to thank these good people for brainstorming and supporting the idea of Mount in ClojureScript universe:
|
||||||
|
|
||||||
|
[@DomKM](https://github.com/DomKM), [@yogthos](https://github.com/yogthos) and [@edvorg](https://github.com/edvorg)
|
||||||
BIN
doc/img/mount.cljs.example.png
Normal file
BIN
doc/img/mount.cljs.example.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 215 KiB |
51
project.clj
51
project.clj
|
|
@ -6,13 +6,56 @@
|
||||||
|
|
||||||
:source-paths ["src"]
|
:source-paths ["src"]
|
||||||
|
|
||||||
:dependencies [[org.clojure/clojure "1.7.0"]]
|
:dependencies [] ;; for visual clarity
|
||||||
|
|
||||||
:profiles {:dev {:source-paths ["dev" "test/app"]
|
:profiles {:dev {:source-paths ["dev" "dev/clj"]
|
||||||
:dependencies [[yesql "0.5.1"]
|
:dependencies [[org.clojure/clojure "1.7.0"]
|
||||||
|
[org.clojure/clojurescript "1.7.170"]
|
||||||
|
[datascript "0.13.3"]
|
||||||
|
[hiccups "0.3.0"]
|
||||||
|
[com.andrewmcveigh/cljs-time "0.3.14"]
|
||||||
[ch.qos.logback/logback-classic "1.1.3"]
|
[ch.qos.logback/logback-classic "1.1.3"]
|
||||||
[org.clojure/tools.logging "0.3.1"]
|
[org.clojure/tools.logging "0.3.1"]
|
||||||
[robert/hooke "1.3.0"]
|
[robert/hooke "1.3.0"]
|
||||||
[org.clojure/tools.namespace "0.2.11"]
|
[org.clojure/tools.namespace "0.2.11"]
|
||||||
[org.clojure/tools.nrepl "0.2.11"]
|
[org.clojure/tools.nrepl "0.2.11"]
|
||||||
[com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]}})
|
[com.datomic/datomic-free "0.9.5327" :exclusions [joda-time]]]
|
||||||
|
|
||||||
|
:plugins [[lein-cljsbuild "1.1.1"]
|
||||||
|
[lein-doo "0.1.6"]
|
||||||
|
[lein-figwheel "0.5.0-2"]
|
||||||
|
[test2junit "1.1.3"]]
|
||||||
|
|
||||||
|
:test2junit-output-dir ~(or (System/getenv "CIRCLE_TEST_REPORTS") "target/test2junit")
|
||||||
|
|
||||||
|
:clean-targets ^{:protect false} [:target-path
|
||||||
|
[:cljsbuild :builds :dev :compiler :output-dir]
|
||||||
|
[:cljsbuild :builds :prod :compiler :output-to]]
|
||||||
|
:cljsbuild {
|
||||||
|
:builds {:dev
|
||||||
|
{:source-paths ["src" "dev/cljs"]
|
||||||
|
:figwheel true
|
||||||
|
|
||||||
|
:compiler {:main app.example
|
||||||
|
:asset-path "js/compiled/out"
|
||||||
|
:output-to "dev/resources/public/js/compiled/mount.js"
|
||||||
|
:output-dir "dev/resources/public/js/compiled/out"
|
||||||
|
:optimizations :none
|
||||||
|
:source-map true
|
||||||
|
:source-map-timestamp true}}
|
||||||
|
:test
|
||||||
|
{:source-paths ["src" "dev/cljs" "test"]
|
||||||
|
:compiler {:main mount.test
|
||||||
|
;; :asset-path "js/compiled/out"
|
||||||
|
:output-to "dev/resources/public/js/compiled/mount.js"
|
||||||
|
:output-dir "dev/resources/public/js/compiled/test"
|
||||||
|
:optimizations :none
|
||||||
|
:source-map true
|
||||||
|
:source-map-timestamp true}}
|
||||||
|
:prod
|
||||||
|
{:source-paths ["src" "dev/cljs"]
|
||||||
|
:compiler {:output-to "dev/resources/public/js/compiled/mount.js"
|
||||||
|
:optimizations :advanced
|
||||||
|
:pretty-print false}}}}}
|
||||||
|
|
||||||
|
:test {:source-paths ["dev" "test/clj" "test"]}})
|
||||||
|
|
|
||||||
|
|
@ -1,220 +0,0 @@
|
||||||
(ns mount.core
|
|
||||||
(:require [mount.tools.macro :as macro]))
|
|
||||||
|
|
||||||
(defonce ^:private mount-state 42)
|
|
||||||
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
|
|
||||||
(defonce ^:private state-seq (atom 0))
|
|
||||||
(defonce ^:private state-order (atom {}))
|
|
||||||
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
|
|
||||||
|
|
||||||
;; supporting tools.namespace: (disable-reload!)
|
|
||||||
(alter-meta! *ns* assoc ::load false) ;; to exclude the dependency
|
|
||||||
|
|
||||||
(defn- make-state-seq [state]
|
|
||||||
(or (@state-order state)
|
|
||||||
(let [nseq (swap! state-seq inc)]
|
|
||||||
(swap! state-order assoc state nseq)
|
|
||||||
nseq)))
|
|
||||||
|
|
||||||
(deftype NotStartedState [state]
|
|
||||||
Object
|
|
||||||
(toString [this]
|
|
||||||
(str "'" state "' is not started (to start all the states call mount/start)")))
|
|
||||||
|
|
||||||
;;TODO validate the whole lifecycle
|
|
||||||
(defn- validate [{:keys [start stop suspend resume] :as lifecycle}]
|
|
||||||
(cond
|
|
||||||
(not start) (throw
|
|
||||||
(IllegalArgumentException. "can't start a stateful thing without a start function. (i.e. missing :start fn)"))
|
|
||||||
(and suspend (not resume)) (throw
|
|
||||||
(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]
|
|
||||||
(let [[state params] (macro/name-with-attributes state body)
|
|
||||||
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)]
|
|
||||||
(validate lifecycle)
|
|
||||||
(cleanup-if-dirty (with-ns *ns* state))
|
|
||||||
(let [s-meta (cond-> {:mount-state mount-state
|
|
||||||
:order (make-state-seq (with-ns *ns* state))
|
|
||||||
:start `(fn [] ~start)
|
|
||||||
:status #{:stopped}}
|
|
||||||
stop (assoc :stop `(fn [] ~(unpound stop)))
|
|
||||||
suspend (assoc :suspend `(fn [] ~suspend))
|
|
||||||
resume (assoc :resume `(fn [] ~resume)))]
|
|
||||||
`(defonce ~(with-meta state (merge (meta state) s-meta))
|
|
||||||
(NotStartedState. ~(str state))))))
|
|
||||||
|
|
||||||
(defn- record! [{:keys [ns name]} f done]
|
|
||||||
(let [state (f)]
|
|
||||||
(swap! done conj (ns-resolve ns name))
|
|
||||||
state))
|
|
||||||
|
|
||||||
(defn- up [var {:keys [ns name start stop resume status] :as state} done]
|
|
||||||
(when-not (:started status)
|
|
||||||
(let [s (try (if (:suspended status)
|
|
||||||
(record! state resume done)
|
|
||||||
(record! state start done))
|
|
||||||
(catch Throwable t
|
|
||||||
(throw (RuntimeException. (str "could not start [" name "] due to") t))))]
|
|
||||||
(intern ns (symbol name) s)
|
|
||||||
(swap! running assoc (with-ns ns name) stop)
|
|
||||||
(alter-meta! var assoc :status #{:started}))))
|
|
||||||
|
|
||||||
(defn- down [var {:keys [ns name stop status] :as state} done]
|
|
||||||
(when (some status #{:started :suspended})
|
|
||||||
(when stop
|
|
||||||
(try
|
|
||||||
(record! state stop done)
|
|
||||||
(catch Throwable 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
|
|
||||||
(swap! running dissoc (with-ns ns name))
|
|
||||||
(alter-meta! var assoc :status #{:stopped})))
|
|
||||||
|
|
||||||
(defn- sigstop [var {:keys [ns name suspend resume status] :as state} 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 (try (record! state suspend done)
|
|
||||||
(catch Throwable t
|
|
||||||
(throw (RuntimeException. (str "could not suspend [" name "] due to") t))))]
|
|
||||||
(intern ns (symbol name) s)))
|
|
||||||
(alter-meta! var assoc :status #{:suspended})))
|
|
||||||
|
|
||||||
(defn- sigcont [var {:keys [ns name start resume status] :as state} done]
|
|
||||||
(when (instance? NotStartedState var)
|
|
||||||
(throw (RuntimeException. (str "could not resume [" name "] since it is stoppped (i.e. not suspended)"))))
|
|
||||||
(when (:suspended status)
|
|
||||||
(let [s (try (record! state resume done)
|
|
||||||
(catch Throwable t
|
|
||||||
(throw (RuntimeException. (str "could not resume [" name "] due to") t))))]
|
|
||||||
(intern ns (symbol name) s)
|
|
||||||
(alter-meta! var assoc :status #{:started}))))
|
|
||||||
|
|
||||||
;;TODO args might need more thinking
|
|
||||||
(defn args [] @-args)
|
|
||||||
|
|
||||||
(defn mount-state? [var]
|
|
||||||
(= (-> var meta :mount-state)
|
|
||||||
mount-state))
|
|
||||||
|
|
||||||
(defn find-all-states []
|
|
||||||
(->> (all-ns)
|
|
||||||
(mapcat ns-interns)
|
|
||||||
(map second)
|
|
||||||
(filter mount-state?)))
|
|
||||||
|
|
||||||
;;TODO ns based for now. need to be _state_ based
|
|
||||||
(defn- add-deps [{:keys [ns] :as state} all]
|
|
||||||
(let [refers (ns-refers ns)
|
|
||||||
any (set all)
|
|
||||||
deps (filter (comp any val) refers)]
|
|
||||||
(assoc state :deps deps)))
|
|
||||||
|
|
||||||
(defn states-with-deps []
|
|
||||||
(let [all (find-all-states)]
|
|
||||||
(->> (map (comp #(add-deps % all)
|
|
||||||
#(select-keys % [:name :order :ns :status])
|
|
||||||
meta)
|
|
||||||
all)
|
|
||||||
(sort-by :order))))
|
|
||||||
|
|
||||||
(defn- bring [states fun order]
|
|
||||||
(let [done (atom [])]
|
|
||||||
(->> states
|
|
||||||
(sort-by (comp :order meta) order)
|
|
||||||
(map #(fun % (meta %) done))
|
|
||||||
dorun)
|
|
||||||
@done))
|
|
||||||
|
|
||||||
(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
|
|
||||||
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]}]
|
|
||||||
(assoc state :origin origin
|
|
||||||
:status status
|
|
||||||
:start start :stop stop :suspend suspend :resume resume)))
|
|
||||||
|
|
||||||
(defn- rollback! [state]
|
|
||||||
(let [{:keys [origin]} (meta state)]
|
|
||||||
(when origin
|
|
||||||
(alter-meta! state #(merge-lifecycles % origin)))))
|
|
||||||
|
|
||||||
(defn- substitute! [state with]
|
|
||||||
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
|
|
||||||
origin (meta state)
|
|
||||||
sub (meta with)]
|
|
||||||
(alter-meta! with assoc :sub? true)
|
|
||||||
(alter-meta! state #(merge-lifecycles % (lifecycle-fns origin) sub))))
|
|
||||||
|
|
||||||
(defn- unsub [state]
|
|
||||||
(when (-> (meta state) :sub?)
|
|
||||||
(alter-meta! state dissoc :sub?)))
|
|
||||||
|
|
||||||
(defn- all-without-subs []
|
|
||||||
(remove (comp :sub? meta) (find-all-states)))
|
|
||||||
|
|
||||||
(defn start [& states]
|
|
||||||
(let [states (or (seq states) (all-without-subs))]
|
|
||||||
{:started (bring states up <)}))
|
|
||||||
|
|
||||||
(defn stop [& states]
|
|
||||||
(let [states (or states (find-all-states))
|
|
||||||
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with"
|
|
||||||
stopped (bring states down >)]
|
|
||||||
(dorun (map rollback! states)) ;; restore to origin from "start-with"
|
|
||||||
{:stopped stopped}))
|
|
||||||
|
|
||||||
(defn stop-except [& states]
|
|
||||||
(let [all (set (find-all-states))
|
|
||||||
states (remove (set states) all)]
|
|
||||||
(apply stop states)))
|
|
||||||
|
|
||||||
(defn start-with-args [xs & states]
|
|
||||||
(reset! -args xs)
|
|
||||||
(if (first states)
|
|
||||||
(start states)
|
|
||||||
(start)))
|
|
||||||
|
|
||||||
(defn start-with [with]
|
|
||||||
(doseq [[from to] with]
|
|
||||||
(substitute! from to))
|
|
||||||
(start))
|
|
||||||
|
|
||||||
(defn start-without [& states]
|
|
||||||
(if (first states)
|
|
||||||
(let [app (set (all-without-subs))
|
|
||||||
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 <)}))
|
|
||||||
271
src/mount/core.cljc
Normal file
271
src/mount/core.cljc
Normal file
|
|
@ -0,0 +1,271 @@
|
||||||
|
(ns mount.core
|
||||||
|
#?(:clj (:require [mount.tools.macro :refer [on-error throw-runtime] :as macro])
|
||||||
|
:cljs (:require [mount.tools.macro :as macro]
|
||||||
|
[mount.tools.cljs :as cljs]))
|
||||||
|
#?(:cljs (:require-macros [mount.tools.macro :refer [on-error throw-runtime]])))
|
||||||
|
|
||||||
|
(defonce ^:private -args (atom :no-args)) ;; mostly for command line args and external files
|
||||||
|
(defonce ^:private state-seq (atom 0))
|
||||||
|
(defonce ^:private mode (atom :clj))
|
||||||
|
(defonce ^:private meta-state (atom {}))
|
||||||
|
(defonce ^:private running (atom {})) ;; to clean dirty states on redefs
|
||||||
|
|
||||||
|
;; supporting tools.namespace: (disable-reload!)
|
||||||
|
#?(:clj
|
||||||
|
(alter-meta! *ns* assoc ::load false)) ;; to exclude the dependency
|
||||||
|
|
||||||
|
(defn- make-state-seq [state]
|
||||||
|
(or (:order (@meta-state state))
|
||||||
|
(swap! state-seq inc)))
|
||||||
|
|
||||||
|
(deftype NotStartedState [state]
|
||||||
|
Object
|
||||||
|
(toString [this]
|
||||||
|
(str "'" state "' is not started (to start all the states call mount/start)")))
|
||||||
|
|
||||||
|
;;TODO validate the whole lifecycle
|
||||||
|
(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)")))
|
||||||
|
|
||||||
|
(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] (...)))
|
||||||
|
#?(:clj (.startsWith (str f) pound)
|
||||||
|
:cljs (cljs/starts-with? (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)))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn current-state [state]
|
||||||
|
(let [{:keys [inst var]} (@meta-state state)]
|
||||||
|
(if (= @mode :cljc)
|
||||||
|
@inst
|
||||||
|
(var-get var))))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn current-state [state]
|
||||||
|
(-> (@meta-state state) :inst deref)))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn alter-state! [{:keys [var inst]} value]
|
||||||
|
(if (= @mode :cljc)
|
||||||
|
(reset! inst value)
|
||||||
|
(alter-var-root var (constantly value))))
|
||||||
|
|
||||||
|
:cljs
|
||||||
|
(defn alter-state! [{:keys [inst]} value]
|
||||||
|
(reset! inst value)))
|
||||||
|
|
||||||
|
(defn- update-meta! [path v]
|
||||||
|
(swap! meta-state assoc-in path v))
|
||||||
|
|
||||||
|
(defn- record! [state-name f done]
|
||||||
|
(let [state (f)]
|
||||||
|
(swap! done conj state-name)
|
||||||
|
state))
|
||||||
|
|
||||||
|
(defn- up [state {:keys [start stop resume 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)))]
|
||||||
|
(alter-state! current s)
|
||||||
|
(swap! running assoc state stop)
|
||||||
|
(update-meta! [state :status] #{:started}))))
|
||||||
|
|
||||||
|
(defn- down [state {:keys [stop status] :as current} done]
|
||||||
|
(when (some status #{:started :suspended})
|
||||||
|
(when stop
|
||||||
|
(on-error (str "could not stop [" state "] due to")
|
||||||
|
(record! state stop done)))
|
||||||
|
(alter-state! current (NotStartedState. state)) ;; (!) if a state does not have :stop when _should_ this might leak
|
||||||
|
(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)
|
||||||
|
(#?(:clj deref
|
||||||
|
:cljs -deref)
|
||||||
|
[_]
|
||||||
|
(let [{:keys [status inst] :as state} (@meta-state name)]
|
||||||
|
(when-not (:started status)
|
||||||
|
(up name state (atom #{})))
|
||||||
|
@inst)))
|
||||||
|
|
||||||
|
(defmacro defstate [state & body]
|
||||||
|
(let [[state params] (macro/name-with-attributes state body)
|
||||||
|
{:keys [start stop suspend resume] :as lifecycle} (apply hash-map params)
|
||||||
|
state-name (with-ns #?(:clj *ns*
|
||||||
|
:cljs (cljs/this-ns)) state) ;; on cljs side (cljs.analyzer/*cljs-ns*) may do it, but still might not be good for :advanced
|
||||||
|
order (make-state-seq state-name)
|
||||||
|
sym (str state)]
|
||||||
|
(validate lifecycle)
|
||||||
|
(cleanup-if-dirty state-name)
|
||||||
|
(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)))]
|
||||||
|
`(do
|
||||||
|
(def ~state (DerefableState. ~state-name))
|
||||||
|
((var update-meta!) [~state-name] (assoc ~s-meta :inst (atom (NotStartedState. ~state-name))
|
||||||
|
:var (var ~state)))
|
||||||
|
(var ~state)))))
|
||||||
|
|
||||||
|
(defn in-cljc-mode []
|
||||||
|
(reset! mode :cljc))
|
||||||
|
|
||||||
|
(defn in-clj-mode []
|
||||||
|
(reset! mode :clj))
|
||||||
|
|
||||||
|
;;TODO args might need more thinking
|
||||||
|
(defn args [] @-args)
|
||||||
|
|
||||||
|
(defn- find-all-states []
|
||||||
|
(keys @meta-state))
|
||||||
|
|
||||||
|
;;TODO ns based for now. need to be _state_ based
|
||||||
|
#_(defn- add-deps [{:keys [ns] :as state} all]
|
||||||
|
(let [refers (ns-refers ns)
|
||||||
|
any (set all)
|
||||||
|
deps (filter (comp any val) refers)]
|
||||||
|
(assoc state :deps deps)))
|
||||||
|
|
||||||
|
#_(defn states-with-deps []
|
||||||
|
(let [all (find-all-states)]
|
||||||
|
(->> (map (comp #(add-deps % all)
|
||||||
|
#(select-keys % [:name :order :ns :status])
|
||||||
|
meta)
|
||||||
|
all)
|
||||||
|
(sort-by :order))))
|
||||||
|
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn- var-to-str [v]
|
||||||
|
(str v)))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(defn var-to-str [v]
|
||||||
|
(if (var? v)
|
||||||
|
(let [{:keys [ns name]} (meta v)]
|
||||||
|
(with-ns ns name))
|
||||||
|
v)))
|
||||||
|
|
||||||
|
(defn- bring [states fun order]
|
||||||
|
(let [done (atom [])]
|
||||||
|
(as-> states $
|
||||||
|
(map var-to-str $)
|
||||||
|
(select-keys @meta-state $)
|
||||||
|
(sort-by (comp :order val) order $)
|
||||||
|
(doseq [[k v] $] (fun k v done)))
|
||||||
|
@done))
|
||||||
|
|
||||||
|
(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
|
||||||
|
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]}]
|
||||||
|
(assoc state :origin origin
|
||||||
|
:status status
|
||||||
|
:start start :stop stop :suspend suspend :resume resume)))
|
||||||
|
|
||||||
|
(defn- rollback! [state]
|
||||||
|
(let [{:keys [origin] :as sub} (@meta-state state)]
|
||||||
|
(when origin
|
||||||
|
(update-meta! [state] (merge-lifecycles sub origin)))))
|
||||||
|
|
||||||
|
(defn- substitute! [state with]
|
||||||
|
(let [lifecycle-fns #(select-keys % [:start :stop :suspend :resume :status])
|
||||||
|
origin (@meta-state state)
|
||||||
|
sub (@meta-state with)]
|
||||||
|
(update-meta! [with :sub?] true)
|
||||||
|
(update-meta! [state] (merge-lifecycles origin (lifecycle-fns origin) sub))))
|
||||||
|
|
||||||
|
(defn- unsub [state]
|
||||||
|
(when (-> (@meta-state state) :sub?)
|
||||||
|
(update-meta! [state :sub?] nil)))
|
||||||
|
|
||||||
|
(defn- all-without-subs []
|
||||||
|
(remove (comp :sub? @meta-state) (find-all-states)))
|
||||||
|
|
||||||
|
(defn start [& states]
|
||||||
|
(let [states (or (seq states) (all-without-subs))]
|
||||||
|
{:started (bring states up <)}))
|
||||||
|
|
||||||
|
(defn stop [& states]
|
||||||
|
(let [states (or states (find-all-states))
|
||||||
|
_ (dorun (map unsub states)) ;; unmark substitutions marked by "start-with"
|
||||||
|
stopped (bring states down >)]
|
||||||
|
(dorun (map rollback! states)) ;; restore to origin from "start-with"
|
||||||
|
{:stopped stopped}))
|
||||||
|
|
||||||
|
(defn stop-except [& states]
|
||||||
|
(let [all (set (find-all-states))
|
||||||
|
states (map var-to-str states)
|
||||||
|
states (remove (set states) all)]
|
||||||
|
(apply stop states)))
|
||||||
|
|
||||||
|
(defn start-with-args [xs & states]
|
||||||
|
(reset! -args xs)
|
||||||
|
(if (first states)
|
||||||
|
(start states)
|
||||||
|
(start)))
|
||||||
|
|
||||||
|
(defn start-with [with]
|
||||||
|
(doseq [[from to] with]
|
||||||
|
(substitute! (var-to-str from)
|
||||||
|
(var-to-str to)))
|
||||||
|
(start))
|
||||||
|
|
||||||
|
(defn start-without [& states]
|
||||||
|
(if (first states)
|
||||||
|
(let [app (set (all-without-subs))
|
||||||
|
states (map var-to-str states)
|
||||||
|
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 <)}))
|
||||||
9
src/mount/tools/cljs.cljs
Normal file
9
src/mount/tools/cljs.cljs
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
(ns mount.tools.cljs
|
||||||
|
(:require [cljs.analyzer :as ana]
|
||||||
|
[goog.string :as gstring]))
|
||||||
|
|
||||||
|
(defn this-ns []
|
||||||
|
ana/*cljs-ns*)
|
||||||
|
|
||||||
|
(defn starts-with? [s pre]
|
||||||
|
(gstring/startsWith s pre))
|
||||||
|
|
@ -1,5 +1,17 @@
|
||||||
(ns mount.tools.macro)
|
(ns mount.tools.macro)
|
||||||
|
|
||||||
|
(defmacro on-error [msg f]
|
||||||
|
`(try
|
||||||
|
~f
|
||||||
|
(catch #?(:clj Throwable
|
||||||
|
:cljs :default) t#
|
||||||
|
(throw #?(:clj (RuntimeException. ~msg t#)
|
||||||
|
:cljs (js/Error (str ~msg (.-stack t#))))))))
|
||||||
|
|
||||||
|
(defmacro throw-runtime [msg]
|
||||||
|
`(throw #?(:clj (RuntimeException. ~msg)
|
||||||
|
:cljs (js/Error (str ~msg)))))
|
||||||
|
|
||||||
;; this is a one to one copy from https://github.com/clojure/tools.macro
|
;; this is a one to one copy from https://github.com/clojure/tools.macro
|
||||||
;; to avoid a lib dependency for a single function
|
;; to avoid a lib dependency for a single function
|
||||||
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
(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))))
|
|
||||||
|
|
@ -1,55 +0,0 @@
|
||||||
(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)))
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
(ns check.parts-test
|
|
||||||
(:require [mount.core :as mount :refer [defstate] :as m]
|
|
||||||
[app.nyse :refer [conn]]
|
|
||||||
[clojure.test :refer :all]))
|
|
||||||
|
|
||||||
(defstate should-not-start :start #(constantly 42))
|
|
||||||
|
|
||||||
(defn with-parts [f]
|
|
||||||
(m/start #'app.config/app-config #'app.nyse/conn)
|
|
||||||
(f)
|
|
||||||
(m/stop))
|
|
||||||
|
|
||||||
(use-fixtures :each with-parts)
|
|
||||||
|
|
||||||
(deftest start-only-parts
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (instance? mount.core.NotStartedState should-not-start)))
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
(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)))
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
(ns check.start-with-test
|
|
||||||
(:require [mount.core :as mount :refer [defstate]]
|
|
||||||
[app.config :refer [app-config]]
|
|
||||||
[app.nyse :refer [conn]]
|
|
||||||
[app :refer [nrepl]]
|
|
||||||
[clojure.test :refer :all]))
|
|
||||||
|
|
||||||
(defstate test-conn :start 42
|
|
||||||
:stop #(constantly 0))
|
|
||||||
|
|
||||||
(defstate test-nrepl :start [])
|
|
||||||
|
|
||||||
(deftest start-with
|
|
||||||
|
|
||||||
(testing "should start with substitutes"
|
|
||||||
(let [_ (mount/start-with {#'app.nyse/conn #'check.start-with-test/test-conn
|
|
||||||
#'app/nrepl #'check.start-with-test/test-nrepl})]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (vector? nrepl))
|
|
||||||
(is (= conn 42))
|
|
||||||
(mount/stop)))
|
|
||||||
|
|
||||||
(testing "should not start the substitute itself"
|
|
||||||
(let [_ (mount/start-with {#'app.nyse/conn #'check.start-with-test/test-conn})]
|
|
||||||
(is (instance? mount.core.NotStartedState test-conn))
|
|
||||||
(is (= conn 42))
|
|
||||||
(mount/stop)))
|
|
||||||
|
|
||||||
(testing "should start normally after start-with"
|
|
||||||
(let [_ (mount/start)]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (= test-conn 42))
|
|
||||||
(is (vector? test-nrepl))
|
|
||||||
(mount/stop)))
|
|
||||||
|
|
||||||
(testing "should start-without normally after start-with"
|
|
||||||
(let [_ (mount/start-without #'check.start-with-test/test-conn
|
|
||||||
#'check.start-with-test/test-nrepl)]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (instance? mount.core.NotStartedState test-conn))
|
|
||||||
(is (instance? mount.core.NotStartedState test-nrepl))
|
|
||||||
(mount/stop))))
|
|
||||||
|
|
||||||
|
|
@ -1,18 +0,0 @@
|
||||||
(ns check.start-without-test
|
|
||||||
(:require [mount.core :as m]
|
|
||||||
[app.config :refer [app-config]]
|
|
||||||
[app.nyse :refer [conn]]
|
|
||||||
[app :refer [nrepl]]
|
|
||||||
[clojure.test :refer :all]))
|
|
||||||
|
|
||||||
(defn without [f]
|
|
||||||
(m/start-without #'app.nyse/conn #'app/nrepl)
|
|
||||||
(f)
|
|
||||||
(m/stop))
|
|
||||||
|
|
||||||
(use-fixtures :each without)
|
|
||||||
|
|
||||||
(deftest start-without-states
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? mount.core.NotStartedState nrepl))
|
|
||||||
(is (instance? mount.core.NotStartedState conn)))
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
(ns check.stop-except-test
|
|
||||||
(:require [mount.core :as mount :refer [defstate]]
|
|
||||||
[app.config :refer [app-config]]
|
|
||||||
[app.nyse :refer [conn]]
|
|
||||||
[app :refer [nrepl]]
|
|
||||||
[clojure.test :refer :all]))
|
|
||||||
|
|
||||||
(deftest stop-except
|
|
||||||
|
|
||||||
(testing "should stop all except nrepl"
|
|
||||||
(let [_ (mount/start)
|
|
||||||
_ (mount/stop-except #'app.nyse/conn #'app.config/app-config)]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (instance? mount.core.NotStartedState nrepl))
|
|
||||||
(mount/stop)))
|
|
||||||
|
|
||||||
(testing "should start normally after stop-except"
|
|
||||||
(let [_ (mount/start)]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(mount/stop)))
|
|
||||||
|
|
||||||
(testing "should stop all normally after stop-except"
|
|
||||||
(let [_ (mount/start)
|
|
||||||
_ (mount/stop-except #'app.nyse/conn #'app.config/app-config)
|
|
||||||
_ (mount/stop)]
|
|
||||||
(is (instance? mount.core.NotStartedState app-config))
|
|
||||||
(is (instance? mount.core.NotStartedState conn))
|
|
||||||
(is (instance? mount.core.NotStartedState nrepl)))))
|
|
||||||
|
|
@ -1,141 +0,0 @@
|
||||||
(ns check.suspend-resume-test
|
|
||||||
(:require [mount.core :as mount :refer [defstate]]
|
|
||||||
[app.config :refer [app-config]]
|
|
||||||
[app.nyse :refer [conn]]
|
|
||||||
[app :refer [nrepl]]
|
|
||||||
[clojure.test :refer :all]))
|
|
||||||
|
|
||||||
(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))
|
|
||||||
|
|
||||||
(deftest suspendable-lifecycle
|
|
||||||
|
|
||||||
(testing "should suspend _only suspendable_ states that are currently started"
|
|
||||||
(let [_ (mount/start)
|
|
||||||
_ (mount/suspend)]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (= web-server :w-suspended))
|
|
||||||
(mount/stop)))
|
|
||||||
|
|
||||||
(testing "should resume _only suspendable_ states that are currently suspended"
|
|
||||||
(let [_ (mount/start)
|
|
||||||
_ (mount/stop #'app/nrepl)
|
|
||||||
_ (mount/suspend)
|
|
||||||
_ (mount/resume)]
|
|
||||||
(is (map? app-config))
|
|
||||||
(is (instance? mount.core.NotStartedState nrepl))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (= 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? app-config))
|
|
||||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (= 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 app-config))
|
|
||||||
(is (instance? mount.core.NotStartedState nrepl))
|
|
||||||
(is (instance? mount.core.NotStartedState conn))
|
|
||||||
(is (instance? mount.core.NotStartedState web-server)))))
|
|
||||||
|
|
||||||
|
|
||||||
(deftest suspendable-start-with
|
|
||||||
|
|
||||||
(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 {#'app/nrepl #'check.suspend-resume-test/web-server})
|
|
||||||
_ (mount/suspend)]
|
|
||||||
(is (= nrepl :w-suspended))
|
|
||||||
(is (instance? mount.core.NotStartedState web-server))
|
|
||||||
(mount/stop)
|
|
||||||
(mount/start)
|
|
||||||
(mount/suspend)
|
|
||||||
(is (instance? clojure.tools.nrepl.server.Server nrepl))
|
|
||||||
(is (= 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 {#'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 original should still be suspended and preserve its lifecycle fns after the rollback/stop"
|
|
||||||
(let [_ (mount/start)
|
|
||||||
_ (mount/suspend)
|
|
||||||
_ (mount/start-with {#'check.suspend-resume-test/web-server #'app.nyse/conn}) ;; TODO: good to WARN on started states during "start-with"
|
|
||||||
_ (mount/suspend)]
|
|
||||||
(is (instance? datomic.peer.LocalConnection conn))
|
|
||||||
(is (= 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 conn))
|
|
||||||
(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 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 {#'check.suspend-resume-test/web-server #'check.suspend-resume-test/q-listener})] ;; TODO: good to WARN on started states during "start-with"
|
|
||||||
(is (= q-listener :q-suspended))
|
|
||||||
(is (= web-server :q-resumed))
|
|
||||||
(mount/suspend)
|
|
||||||
(is (= q-listener :q-suspended))
|
|
||||||
(is (= web-server :q-suspended))
|
|
||||||
(mount/stop)
|
|
||||||
(is (instance? mount.core.NotStartedState web-server))
|
|
||||||
(is (instance? mount.core.NotStartedState q-listener))
|
|
||||||
(mount/start)
|
|
||||||
(mount/suspend)
|
|
||||||
(is (= q-listener :q-suspended))
|
|
||||||
(is (= web-server :w-suspended))
|
|
||||||
(mount/stop))))
|
|
||||||
13
test/clj/app/conf.clj
Normal file
13
test/clj/app/conf.clj
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
(ns app.conf
|
||||||
|
(:require [mount.core :as mount :refer [defstate]]
|
||||||
|
[clojure.edn :as edn]
|
||||||
|
[clojure.tools.logging :refer [info]]))
|
||||||
|
|
||||||
|
(defn load-config [path]
|
||||||
|
(info "loading config from" path)
|
||||||
|
(-> path
|
||||||
|
slurp
|
||||||
|
edn/read-string))
|
||||||
|
|
||||||
|
(defstate config
|
||||||
|
:start (load-config "dev/resources/config.edn"))
|
||||||
65
test/clj/app/example.clj
Normal file
65
test/clj/app/example.clj
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
(ns app.example
|
||||||
|
(:require [datomic.api :as d]
|
||||||
|
[clojure.tools.nrepl.server :refer [start-server stop-server]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.utils.datomic :refer [touch]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.nyse :as nyse]))
|
||||||
|
|
||||||
|
;; example on creating a network REPL
|
||||||
|
(defn- start-nrepl [{:keys [host port]}]
|
||||||
|
(start-server :bind host :port port))
|
||||||
|
|
||||||
|
;; nREPL is just another simple state
|
||||||
|
(defstate nrepl :start (start-nrepl (:nrepl @config))
|
||||||
|
:stop (stop-server @nrepl))
|
||||||
|
|
||||||
|
;; datomic schema
|
||||||
|
(defn create-schema [conn]
|
||||||
|
(let [schema [{:db/id #db/id [:db.part/db]
|
||||||
|
:db/ident :order/symbol
|
||||||
|
:db/valueType :db.type/string
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db/index true
|
||||||
|
:db.install/_attribute :db.part/db}
|
||||||
|
|
||||||
|
{:db/id #db/id [:db.part/db]
|
||||||
|
:db/ident :order/bid
|
||||||
|
:db/valueType :db.type/bigdec
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db.install/_attribute :db.part/db}
|
||||||
|
|
||||||
|
{:db/id #db/id [:db.part/db]
|
||||||
|
:db/ident :order/qty
|
||||||
|
:db/valueType :db.type/long
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db.install/_attribute :db.part/db}
|
||||||
|
|
||||||
|
{:db/id #db/id [:db.part/db]
|
||||||
|
:db/ident :order/offer
|
||||||
|
:db/valueType :db.type/bigdec
|
||||||
|
:db/cardinality :db.cardinality/one
|
||||||
|
:db.install/_attribute :db.part/db}]]
|
||||||
|
|
||||||
|
@(d/transact conn schema)))
|
||||||
|
|
||||||
|
(defn add-order [ticker bid offer qty] ;; can take connection as param
|
||||||
|
@(d/transact @nyse/conn [{:db/id (d/tempid :db.part/user)
|
||||||
|
:order/symbol ticker
|
||||||
|
:order/bid bid
|
||||||
|
:order/offer offer
|
||||||
|
:order/qty qty}]))
|
||||||
|
|
||||||
|
|
||||||
|
(defn find-orders [ticker] ;; can take connection as param
|
||||||
|
(let [orders (d/q '[:find ?e :in $ ?ticker
|
||||||
|
:where [?e :order/symbol ?ticker]]
|
||||||
|
(d/db @nyse/conn) ticker)]
|
||||||
|
(touch @nyse/conn orders)))
|
||||||
|
|
||||||
|
(defn create-nyse-schema []
|
||||||
|
(create-schema @nyse/conn))
|
||||||
|
|
||||||
|
;; example of an app entry point
|
||||||
|
(defn -main [& args]
|
||||||
|
(mount/start))
|
||||||
21
test/clj/app/nyse.clj
Normal file
21
test/clj/app/nyse.clj
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
(ns app.nyse
|
||||||
|
(:require [mount.core :as mount :refer [defstate]]
|
||||||
|
[datomic.api :as d]
|
||||||
|
[clojure.tools.logging :refer [info]]
|
||||||
|
[app.conf :refer [config]]))
|
||||||
|
|
||||||
|
(defn- new-connection [conf]
|
||||||
|
(info "conf: " conf)
|
||||||
|
(let [uri (get-in @conf [:datomic :uri])]
|
||||||
|
(info "creating a connection to datomic:" uri)
|
||||||
|
(d/create-database uri)
|
||||||
|
(d/connect uri)))
|
||||||
|
|
||||||
|
(defn disconnect [conf conn]
|
||||||
|
(let [uri (get-in @conf [:datomic :uri])]
|
||||||
|
(info "disconnecting from " uri)
|
||||||
|
(.release @conn) ;; usually it's not released, here just to illustrate the access to connection on (stop)
|
||||||
|
(d/delete-database uri)))
|
||||||
|
|
||||||
|
(defstate conn :start (new-connection config)
|
||||||
|
:stop (disconnect config conn))
|
||||||
11
test/clj/app/utils/datomic.clj
Normal file
11
test/clj/app/utils/datomic.clj
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
(ns app.utils.datomic
|
||||||
|
(:require [datomic.api :as d]))
|
||||||
|
|
||||||
|
(defn entity [conn id]
|
||||||
|
(d/entity (d/db conn) id))
|
||||||
|
|
||||||
|
(defn touch [conn results]
|
||||||
|
"takes 'entity ids' results from a query
|
||||||
|
e.g. '#{[272678883689461] [272678883689462] [272678883689459] [272678883689457]}'"
|
||||||
|
(let [e (partial entity conn)]
|
||||||
|
(map #(-> % first e d/touch) results)))
|
||||||
46
test/clj/app/utils/logging.clj
Normal file
46
test/clj/app/utils/logging.clj
Normal file
|
|
@ -0,0 +1,46 @@
|
||||||
|
(ns app.utils.logging ;; << change to your namespace/path
|
||||||
|
(:require [mount.core]
|
||||||
|
[robert.hooke :refer [add-hook clear-hooks]]
|
||||||
|
[clojure.string :refer [split]]
|
||||||
|
[clojure.tools.logging :refer [info]]))
|
||||||
|
|
||||||
|
(alter-meta! *ns* assoc ::load false)
|
||||||
|
|
||||||
|
(defn- f-to-action [f]
|
||||||
|
(let [fname (-> (str f)
|
||||||
|
(split #"@")
|
||||||
|
first)]
|
||||||
|
(case fname
|
||||||
|
"mount.core$up" :up
|
||||||
|
"mount.core$down" :down
|
||||||
|
"mount.core$sigstop" :suspend
|
||||||
|
"mount.core$sigcont" :resume
|
||||||
|
:noop)))
|
||||||
|
|
||||||
|
(defn whatcha-doing? [{:keys [status suspend]} action]
|
||||||
|
(case action
|
||||||
|
:up (if (status :suspended) ">> resuming"
|
||||||
|
(if-not (status :started) ">> starting"))
|
||||||
|
:down (if (or (status :started) (status :suspended)) "<< stopping")
|
||||||
|
:suspend (if (and (status :started) suspend) "<< suspending")
|
||||||
|
:resume (if (status :suspended) ">> resuming")))
|
||||||
|
|
||||||
|
(defn log-status [f & args]
|
||||||
|
(let [{:keys [var] :as state} (second args)
|
||||||
|
action (f-to-action f)]
|
||||||
|
(when-let [taking-over-the-world (whatcha-doing? state action)]
|
||||||
|
(info (str taking-over-the-world ".. " var)))
|
||||||
|
(apply f args)))
|
||||||
|
|
||||||
|
(defonce lifecycle-fns
|
||||||
|
#{#'mount.core/up
|
||||||
|
#'mount.core/down
|
||||||
|
#'mount.core/sigstop
|
||||||
|
#'mount.core/sigcont})
|
||||||
|
|
||||||
|
(defn without-logging-status []
|
||||||
|
(doall (map #(clear-hooks %) lifecycle-fns)))
|
||||||
|
|
||||||
|
(defn with-logging-status []
|
||||||
|
(without-logging-status)
|
||||||
|
(doall (map #(add-hook % log-status) lifecycle-fns)))
|
||||||
35
test/mount/test.cljc
Normal file
35
test/mount/test.cljc
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
(ns mount.test
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t]
|
||||||
|
[doo.runner :refer-macros [doo-tests]]]
|
||||||
|
:clj [[clojure.test :as t]])
|
||||||
|
mount.core
|
||||||
|
|
||||||
|
mount.test.fun-with-values
|
||||||
|
mount.test.private-fun
|
||||||
|
mount.test.parts
|
||||||
|
mount.test.cleanup-dirty-states
|
||||||
|
mount.test.stop-except
|
||||||
|
mount.test.start-without
|
||||||
|
mount.test.start-with
|
||||||
|
mount.test.suspend-resume
|
||||||
|
))
|
||||||
|
|
||||||
|
(mount.core/in-cljc-mode)
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
|
||||||
|
;; (doo.runner/do-all-tests)
|
||||||
|
(doo-tests
|
||||||
|
'mount.test.fun-with-values
|
||||||
|
'mount.test.private-fun
|
||||||
|
'mount.test.parts
|
||||||
|
'mount.test.cleanup-dirty-states
|
||||||
|
'mount.test.stop-except
|
||||||
|
'mount.test.start-without
|
||||||
|
'mount.test.start-with
|
||||||
|
'mount.test.suspend-resume
|
||||||
|
))
|
||||||
|
|
||||||
|
(defn run-tests []
|
||||||
|
(t/run-all-tests #"mount.test.*"))
|
||||||
34
test/mount/test/cleanup_dirty_states.cljc
Normal file
34
test/mount/test/cleanup_dirty_states.cljc
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
(ns mount.test.cleanup-dirty-states
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]
|
||||||
|
[app.websockets :refer [system-a]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [log]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.example]])
|
||||||
|
[mount.test.helper :refer [dval helper forty-two]]))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest cleanup-dirty-states
|
||||||
|
(let [_ (mount/start)]
|
||||||
|
(is (not (.isClosed (:server-socket (dval app.example/nrepl)))))
|
||||||
|
(require 'app.example :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 (dval app.example/nrepl)))))
|
||||||
|
(mount/stop)
|
||||||
|
(is (instance? mount.core.NotStartedState (dval app.example/nrepl))))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(deftest cleanup-dirty-states
|
||||||
|
(let [_ (mount/start #'mount.test.helper/helper)]
|
||||||
|
(is (= :started (dval helper)))
|
||||||
|
(is (= 42 @forty-two))
|
||||||
|
(.require js/goog "mount.test.helper") ;; should have run :stop of `helper`
|
||||||
|
;; (is (= :cleaned @forty-two)) ;; TODO: figure out how to reload a namespace properly
|
||||||
|
;; (is (instance? mount.core.NotStartedState (dval helper)))
|
||||||
|
(mount/start #'mount.test.helper/helper)
|
||||||
|
(is (= :started (dval helper)))
|
||||||
|
(mount/stop)
|
||||||
|
(is (instance? mount.core.NotStartedState (dval helper))))))
|
||||||
59
test/mount/test/fun_with_values.cljc
Normal file
59
test/mount/test/fun_with_values.cljc
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
(ns mount.test.fun-with-values
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]])))
|
||||||
|
|
||||||
|
(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 start-states []
|
||||||
|
(mount/start #'mount.test.fun-with-values/scalar
|
||||||
|
#'mount.test.fun-with-values/fun
|
||||||
|
#'mount.test.fun-with-values/with-fun
|
||||||
|
#'mount.test.fun-with-values/with-partial
|
||||||
|
#'mount.test.fun-with-values/f-in-f
|
||||||
|
#'mount.test.fun-with-values/f-args
|
||||||
|
#'mount.test.fun-with-values/f-no-args-value
|
||||||
|
#'mount.test.fun-with-values/f-no-args
|
||||||
|
#'mount.test.fun-with-values/private-f
|
||||||
|
#'mount.test.fun-with-values/f-value))
|
||||||
|
|
||||||
|
(use-fixtures :once
|
||||||
|
#?(:cljs {:before start-states
|
||||||
|
:after mount/stop}
|
||||||
|
:clj #((start-states) (%) (mount/stop))))
|
||||||
|
|
||||||
|
(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)))
|
||||||
18
test/mount/test/helper.cljc
Normal file
18
test/mount/test/helper.cljc
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
(ns mount.test.helper
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[mount.core :as mount :refer-macros [defstate]]]
|
||||||
|
:clj [[mount.core :as mount :refer [defstate]]])))
|
||||||
|
|
||||||
|
(defn dval
|
||||||
|
"returns a value of DerefableState without deref'ing it"
|
||||||
|
[d]
|
||||||
|
(-> (@@(var mount.core/meta-state)
|
||||||
|
#?(:clj (.name d)
|
||||||
|
:cljs (.-name d)))
|
||||||
|
:inst
|
||||||
|
deref))
|
||||||
|
|
||||||
|
(def forty-two (atom 42))
|
||||||
|
|
||||||
|
(defstate helper :start :started
|
||||||
|
:stop (reset! forty-two :cleaned))
|
||||||
36
test/mount/test/parts.cljc
Normal file
36
test/mount/test/parts.cljc
Normal file
|
|
@ -0,0 +1,36 @@
|
||||||
|
(ns mount.test.parts
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]
|
||||||
|
[app.websockets :refer [system-a]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [log]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.nyse :refer [conn]]])
|
||||||
|
[mount.test.helper :refer [dval]]))
|
||||||
|
|
||||||
|
(defstate should-not-start :start (constantly 42))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn with-parts [f]
|
||||||
|
(mount/start #'app.conf/config #'app.nyse/conn)
|
||||||
|
(f)
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(use-fixtures :once
|
||||||
|
#?(:cljs {:before #(mount/start #'app.conf/config #'app.audit-log/log)
|
||||||
|
:after mount/stop}
|
||||||
|
:clj with-parts))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest start-only-parts
|
||||||
|
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval should-not-start)))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(deftest start-only-parts
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval should-not-start)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval system-a)))))
|
||||||
18
test/mount/test/private_fun.cljc
Normal file
18
test/mount/test/private_fun.cljc
Normal file
|
|
@ -0,0 +1,18 @@
|
||||||
|
(ns mount.test.private-fun
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]])
|
||||||
|
|
||||||
|
[mount.test.fun-with-values :refer [private-f]]))
|
||||||
|
|
||||||
|
(use-fixtures :once
|
||||||
|
#?(:cljs {:before #(mount/start #'mount.test.fun-with-values/private-f)
|
||||||
|
:after mount/stop}
|
||||||
|
:clj #((mount/start #'mount.test.fun-with-values/private-f)
|
||||||
|
(%)
|
||||||
|
(mount/stop))))
|
||||||
|
|
||||||
|
(deftest fun-with-values
|
||||||
|
(is (= (@private-f 1) 42)))
|
||||||
93
test/mount/test/start_with.cljc
Normal file
93
test/mount/test/start_with.cljc
Normal file
|
|
@ -0,0 +1,93 @@
|
||||||
|
(ns mount.test.start-with
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]
|
||||||
|
[app.websockets :refer [system-a]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [log]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.nyse :refer [conn]]
|
||||||
|
[app.example :refer [nrepl]]])
|
||||||
|
[mount.test.helper :refer [dval helper]]))
|
||||||
|
|
||||||
|
(defstate test-conn :start 42
|
||||||
|
:stop (constantly 0))
|
||||||
|
|
||||||
|
(defstate test-nrepl :start [])
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(deftest start-with
|
||||||
|
|
||||||
|
(testing "should start with substitutes"
|
||||||
|
(let [_ (mount/start-with {#'app.websockets/system-a #'mount.test.start-with/test-conn
|
||||||
|
#'mount.test.helper/helper #'mount.test.start-with/test-nrepl})]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (vector? (dval helper)))
|
||||||
|
(is (= (dval system-a) 42))
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should not start the substitute itself"
|
||||||
|
(let [_ (mount/start-with {#'app.websockets/system-a #'mount.test.start-with/test-conn})]
|
||||||
|
(is (instance? mount.core.NotStartedState (dval test-conn)))
|
||||||
|
(is (= 42 (dval system-a)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should start normally after start-with"
|
||||||
|
(let [_ (mount/start)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(is (instance? js/WebSocket (dval system-a)))
|
||||||
|
(is (= 42 (dval test-conn)))
|
||||||
|
(is (vector? (dval test-nrepl)))
|
||||||
|
(is (= :started (dval helper)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should start-without normally after start-with"
|
||||||
|
(let [_ (mount/start-without #'mount.test.start-with/test-conn
|
||||||
|
#'mount.test.start-with/test-nrepl)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(is (instance? js/WebSocket (dval system-a)))
|
||||||
|
(is (= :started (dval helper)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval test-conn)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval test-nrepl)))
|
||||||
|
(mount/stop)))))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest start-with
|
||||||
|
|
||||||
|
(testing "should start with substitutes"
|
||||||
|
(let [_ (mount/start-with {#'app.nyse/conn #'mount.test.start-with/test-conn
|
||||||
|
#'app.example/nrepl #'mount.test.start-with/test-nrepl})]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (vector? (dval nrepl)))
|
||||||
|
(is (= (dval conn) 42))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should not start the substitute itself"
|
||||||
|
(let [_ (mount/start-with {#'app.nyse/conn #'mount.test.start-with/test-conn})]
|
||||||
|
(is (instance? mount.core.NotStartedState (dval test-conn)))
|
||||||
|
(is (= (dval conn) 42))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should start normally after start-with"
|
||||||
|
(let [_ (mount/start)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||||
|
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||||
|
(is (= (dval test-conn) 42))
|
||||||
|
(is (vector? (dval test-nrepl)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should start-without normally after start-with"
|
||||||
|
(let [_ (mount/start-without #'mount.test.start-with/test-conn
|
||||||
|
#'mount.test.start-with/test-nrepl)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||||
|
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval test-conn)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval test-nrepl)))
|
||||||
|
(mount/stop)))))
|
||||||
37
test/mount/test/start_without.cljc
Normal file
37
test/mount/test/start_without.cljc
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
(ns mount.test.start-without
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]
|
||||||
|
[app.websockets :refer [system-a]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [log]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.nyse :refer [conn]]
|
||||||
|
[app.example :refer [nrepl]]])
|
||||||
|
[mount.test.helper :refer [dval helper]]))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(defn without [f]
|
||||||
|
(mount/start-without #'app.nyse/conn #'app.example/nrepl)
|
||||||
|
(f)
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(use-fixtures :once
|
||||||
|
#?(:cljs {:before #(mount/start-without #'mount.test.helper/helper #'app.websockets/system-a)
|
||||||
|
:after mount/stop}
|
||||||
|
:clj without))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest start-without-states
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval nrepl)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval conn)))))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(deftest start-without-states
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval helper)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval system-a)))))
|
||||||
66
test/mount/test/stop_except.cljc
Normal file
66
test/mount/test/stop_except.cljc
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
(ns mount.test.stop-except
|
||||||
|
(:require
|
||||||
|
#?@(:cljs [[cljs.test :as t :refer-macros [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer-macros [defstate]]
|
||||||
|
[app.websockets :refer [system-a]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [log]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.nyse :refer [conn]]
|
||||||
|
[app.example :refer [nrepl]]])
|
||||||
|
[mount.test.helper :refer [dval helper]]))
|
||||||
|
|
||||||
|
#?(:cljs
|
||||||
|
(deftest stop-except
|
||||||
|
|
||||||
|
(testing "should stop all except nrepl"
|
||||||
|
(let [_ (mount/start)
|
||||||
|
_ (mount/stop-except #'app.audit-log/log #'mount.test.helper/helper)]
|
||||||
|
(is (= :started (dval helper)))
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval config)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval system-a)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should start normally after stop-except"
|
||||||
|
(let [_ (mount/start)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? js/WebSocket (dval system-a)))
|
||||||
|
(is (instance? datascript.db/DB @(dval log)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should stop all normally after stop-except"
|
||||||
|
(let [_ (mount/start)
|
||||||
|
_ (mount/stop-except #'app.audit-log/log #'mount.test.helper/helper)
|
||||||
|
_ (mount/stop)]
|
||||||
|
(is (instance? mount.core.NotStartedState (dval config)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval log)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval system-a)))))))
|
||||||
|
|
||||||
|
#?(:clj
|
||||||
|
(deftest stop-except
|
||||||
|
|
||||||
|
(testing "should stop all except nrepl"
|
||||||
|
(let [_ (mount/start)
|
||||||
|
_ (mount/stop-except #'app.nyse/conn #'app.conf/config)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval nrepl)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should start normally after stop-except"
|
||||||
|
(let [_ (mount/start)]
|
||||||
|
(is (map? (dval config)))
|
||||||
|
(is (instance? clojure.tools.nrepl.server.Server (dval nrepl)))
|
||||||
|
(is (instance? datomic.peer.LocalConnection (dval conn)))
|
||||||
|
(mount/stop)))
|
||||||
|
|
||||||
|
(testing "should stop all normally after stop-except"
|
||||||
|
(let [_ (mount/start)
|
||||||
|
_ (mount/stop-except #'app.nyse/conn #'app.conf/config)
|
||||||
|
_ (mount/stop)]
|
||||||
|
(is (instance? mount.core.NotStartedState (dval config)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval conn)))
|
||||||
|
(is (instance? mount.core.NotStartedState (dval nrepl)))))))
|
||||||
210
test/mount/test/suspend_resume.cljc
Normal file
210
test/mount/test/suspend_resume.cljc
Normal file
|
|
@ -0,0 +1,210 @@
|
||||||
|
(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]]
|
||||||
|
[app.websockets :refer [system-a]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.audit-log :refer [log]]]
|
||||||
|
:clj [[clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[app.conf :refer [config]]
|
||||||
|
[app.nyse :refer [conn]]
|
||||||
|
[app.example :refer [nrepl]]])
|
||||||
|
[mount.test.helper :refer [dval]]))
|
||||||
|
|
||||||
|
(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 #'app.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
|
||||||
|
|
||||||
|
(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 {#'app.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 #'app.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
|
||||||
|
|
||||||
|
(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 {#'app.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 {#'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 {#'mount.test.suspend-resume/web-server #'app.nyse/conn}) ;; TODO: good to WARN on started states during "start-with"
|
||||||
|
_ (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 {#'mount.test.suspend-resume/web-server
|
||||||
|
#'mount.test.suspend-resume/q-listener})] ;; TODO: good to WARN on started states during "start-with"
|
||||||
|
(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)))))
|
||||||
59
test/mount/test/var/fun_with_values.clj
Normal file
59
test/mount/test/var/fun_with_values.clj
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
(ns mount.test.var.fun-with-values
|
||||||
|
(:require [clojure.test :as t :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]))
|
||||||
|
|
||||||
|
(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 start-states []
|
||||||
|
(mount/in-clj-mode)
|
||||||
|
(require :reload 'mount.test.var.fun-with-values)
|
||||||
|
(mount/start #'mount.test.var.fun-with-values/scalar
|
||||||
|
#'mount.test.var.fun-with-values/fun
|
||||||
|
#'mount.test.var.fun-with-values/with-fun
|
||||||
|
#'mount.test.var.fun-with-values/with-partial
|
||||||
|
#'mount.test.var.fun-with-values/f-in-f
|
||||||
|
#'mount.test.var.fun-with-values/f-args
|
||||||
|
#'mount.test.var.fun-with-values/f-no-args-value
|
||||||
|
#'mount.test.var.fun-with-values/f-no-args
|
||||||
|
#'mount.test.var.fun-with-values/private-f
|
||||||
|
#'mount.test.var.fun-with-values/f-value))
|
||||||
|
|
||||||
|
(defn stop-states []
|
||||||
|
(mount/stop)
|
||||||
|
(mount/in-cljc-mode))
|
||||||
|
|
||||||
|
(use-fixtures :once #((start-states) (%) (stop-states)))
|
||||||
|
|
||||||
|
(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)))
|
||||||
17
test/mount/test/var/private_fun.clj
Normal file
17
test/mount/test/var/private_fun.clj
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
(ns mount.test.var.private-fun
|
||||||
|
(:require [clojure.test :refer [is are deftest testing use-fixtures]]
|
||||||
|
[mount.core :as mount :refer [defstate]]
|
||||||
|
[mount.test.var.fun-with-values :refer [private-f]]))
|
||||||
|
|
||||||
|
(defn in-clj-mode [f]
|
||||||
|
(mount/in-clj-mode)
|
||||||
|
(require :reload 'mount.test.var.fun-with-values 'mount.test.var.private-fun)
|
||||||
|
(mount/start #'mount.test.var.fun-with-values/private-f)
|
||||||
|
(f)
|
||||||
|
(mount/stop)
|
||||||
|
(mount/in-cljc-mode))
|
||||||
|
|
||||||
|
(use-fixtures :once in-clj-mode)
|
||||||
|
|
||||||
|
(deftest fun-with-values
|
||||||
|
(is (= (private-f 1) 42)))
|
||||||
Loading…
Reference in a new issue