babashka/test-resources/lib_tests/com/stuartsierra/component_test.clj
Gabriel Horner 665ae4dd97
Finish up library tests (#1120)
* Add tests for markdown-clj and tools.namespace

See comment for why only one markdown test could be run.
Closes #1069 and #1064

* Convert 10 test libs using add-libtest

Also improved add-libtest to only require maven artifact
and rely on clojars for getting git-url most of the time

* Convert 8 more test libs using add-libtest

Also updated table and added comment for newline test

* Fix doric test

* Disable tools.namespace test that fails on windows

* Added dozen manual test libs and converted 2 test libs

add-libtest.clj supports manually-added and test-directories options

* Converts last tests to test namespaces and write libraries.csv

* Add a number of library tests from projects.md

Also add more docs around adding test libs and tweak add script

* Use :sha for gitlib and older clojure cli

* Revert "Use :sha for gitlib and older clojure cli"

This reverts commit c663ab8368.

* Fix and disable failing tests

Disabled tests that fail consistently and fixed windows one
2021-12-29 16:35:14 +01:00

307 lines
8.7 KiB
Clojure

(ns com.stuartsierra.component-test
(:require [clojure.test :refer (deftest is are)]
[clojure.set :refer (map-invert)]
[com.stuartsierra.component :as component]))
(def ^:dynamic *log* nil)
(defn- log [& args]
(when (thread-bound? #'*log*)
(set! *log* (conj *log* args))))
(defn- ordering
"Given an ordered collection of messages, returns a map from the
head of each message to its index position in the collection."
[log]
(into {} (map-indexed (fn [i [message & _]] [message i]) log)))
(defn before?
"In the collection of messages, does the message beginning with
symbol a come before the message begging with symbol b?"
[log sym-a sym-b]
(let [order (ordering log)]
(< (get order sym-a) (get order sym-b))))
(defn started? [component]
(true? (::started? component)))
(defn stopped? [component]
(false? (::started? component)))
(defrecord ComponentA [state]
component/Lifecycle
(start [this]
(log 'ComponentA.start this)
(assoc this ::started? true))
(stop [this]
(log 'ComponentA.stop this)
(assoc this ::started? false)))
(defn component-a []
(->ComponentA (rand-int Integer/MAX_VALUE)))
(defrecord ComponentB [state a]
component/Lifecycle
(start [this]
(log 'ComponentB.start this)
(assert (started? a))
(assoc this ::started? true))
(stop [this]
(log 'ComponentB.stop this)
(assert (started? a))
(assoc this ::started? false)))
(defn component-b []
(component/using
(map->ComponentB {:state (rand-int Integer/MAX_VALUE)})
[:a]))
(defrecord ComponentC [state a b]
component/Lifecycle
(start [this]
(log 'ComponentC.start this)
(assert (started? a))
(assert (started? b))
(assoc this ::started? true))
(stop [this]
(log 'ComponentC.stop this)
(assert (started? a))
(assert (started? b))
(assoc this ::started? false)))
(defn component-c []
(component/using
(map->ComponentC {:state (rand-int Integer/MAX_VALUE)})
[:a :b]))
(defrecord ComponentD [state my-c b]
component/Lifecycle
(start [this]
(log 'ComponentD.start this)
(assert (started? b))
(assert (started? my-c))
(assoc this ::started? true))
(stop [this]
(log 'ComponentD.stop this)
(assert (started? b))
(assert (started? my-c))
(assoc this ::started? false)))
(defn component-d []
(map->ComponentD {:state (rand-int Integer/MAX_VALUE)}))
(defrecord ComponentE [state]
component/Lifecycle
(start [this]
(log 'ComponentE.start this)
(assoc this ::started? true))
(stop [this]
(log 'ComponentE.stop this)
(assoc this ::started? false)))
(defn component-e []
(map->ComponentE {:state (rand-int Integer/MAX_VALUE)}))
(defrecord System1 [d a e c b] ; deliberately scrambled order
component/Lifecycle
(start [this]
(log 'System1.start this)
(component/start-system this))
(stop [this]
(log 'System1.stop this)
(component/stop-system this)))
(defn system-1 []
(map->System1 {:a (component-a)
:b (component-b)
:c (component-c)
:d (component/using (component-d)
{:b :b
:my-c :c})
:e (component-e)}))
(defmacro with-log [& body]
`(binding [*log* []]
~@body
*log*))
(deftest components-start-in-order
(let [log (with-log (component/start (system-1)))]
(are [k1 k2] (before? log k1 k2)
'ComponentA.start 'ComponentB.start
'ComponentA.start 'ComponentC.start
'ComponentB.start 'ComponentC.start
'ComponentC.start 'ComponentD.start
'ComponentB.start 'ComponentD.start)))
(deftest all-components-started
(let [system (component/start (system-1))]
(doseq [component (vals system)]
(is (started? component)))))
(deftest all-components-stopped
(let [system (component/stop (component/start (system-1)))]
(doseq [component (vals system)]
(is (stopped? component)))))
(deftest dependencies-satisfied
(let [system (component/start (component/start (system-1)))]
(are [keys] (started? (get-in system keys))
[:b :a]
[:c :a]
[:c :b]
[:d :my-c])))
(defrecord ErrorStartComponentC [state error a b]
component/Lifecycle
(start [this]
(throw error))
(stop [this]
this))
(defn error-start-c [error]
(component/using
(map->ErrorStartComponentC {:error error})
[:a :b]))
(defn setup-error
([]
(setup-error (ex-info "Boom!" {})))
([error]
(try (component/start
(assoc (system-1) :c (error-start-c error)))
(catch Exception e e))))
(deftest error-thrown-with-partial-system
(let [ex (setup-error)]
(is (started? (-> ex ex-data :system :b :a)))))
(deftest error-thrown-with-component-dependencies
(let [ex (setup-error)]
(is (started? (-> ex ex-data :component :a)))
(is (started? (-> ex ex-data :component :b)))))
(deftest error-thrown-with-cause
(let [error (ex-info "Boom!" {})
ex (setup-error error)]
(is (identical? error (.getCause ^Exception ex)))))
(deftest error-is-from-component
(let [error (ex-info "Boom!" {})
ex (setup-error error)]
(is (component/ex-component? ex))))
(deftest error-is-not-from-component
(is (not (component/ex-component? (ex-info "Boom!" {})))))
(deftest remove-components-from-error
(let [error (ex-info (str (rand-int Integer/MAX_VALUE)) {})
^Exception ex (setup-error error)
^Exception ex-without (component/ex-without-components ex)]
(is (contains? (ex-data ex) :component))
(is (contains? (ex-data ex) :system))
(is (not (contains? (ex-data ex-without) :component)))
(is (not (contains? (ex-data ex-without) :system)))
(is (= (.getMessage ex)
(.getMessage ex-without)))
(is (= (.getCause ex)
(.getCause ex-without)))
(is (java.util.Arrays/equals
(.getStackTrace ex)
(.getStackTrace ex-without)))))
(defrecord System2b [one]
component/Lifecycle
(start [this]
(assert (started? (get-in one [:b :a])))
this)
(stop [this]
(assert (started? (get-in one [:b :a])))
this))
(defn system-2 []
(component/system-map :alpha (system-1)
:beta (component/using (->System2b nil)
{:one :alpha})))
(deftest composed-systems
(let [system (component/start (system-2))]
(is (started? (get-in system [:beta :one :d :my-c])))))
(defn increment-all-components [system]
(component/update-system
system (keys system) update-in [:n] inc))
(defn assert-increments [system]
(are [n keys] (= n (get-in system keys))
11 [:a :n]
11 [:b :a :n]
11 [:c :a :n]
11 [:c :b :a :n]
11 [:e :d :b :a :n]
21 [:b :n]
21 [:c :b :n]
21 [:d :b :n]
31 [:c :n]
41 [:d :n]
51 [:e :n]))
(deftest update-with-custom-function-on-maps
(let [system {:a {:n 10}
:b (component/using {:n 20} [:a])
:c (component/using {:n 30} [:a :b])
:d (component/using {:n 40} [:a :b])
:e (component/using {:n 50} [:b :c :d])}]
(assert-increments (increment-all-components system))))
(deftest t-system-using
(let [dependency-map {:b [:a]
:c [:a :b]
:d {:a :a :b :b}
:e [:b :c :d]}
system {:a {:n 10}
:b {:n 20}
:c {:n 30}
:d {:n 40}
:e {:n 50}}
system (component/system-using system dependency-map)]
(assert-increments (increment-all-components system))))
(defrecord ComponentWithoutLifecycle [state])
;; BB-TEST-PATCH: No implementation of method errors for start and stop
#_(deftest component-without-lifecycle
(let [c (->ComponentWithoutLifecycle nil)]
(is (= c (component/start c)))
(is (= c (component/stop c)))))
(defrecord ComponentReturningNil [state]
component/Lifecycle
(start [this]
nil)
(stop [this]
nil))
(deftest component-returning-nil
(let [a (->ComponentReturningNil nil)
s (component/system-map :a a :b (component-b))
e (try (component/start s)
false
(catch Exception e e))]
(is (= ::component/nil-component (:reason (ex-data e))))))
(deftest missing-dependency-error
(let [system-key ::system-b
local-key ::local-b
a (component/using (component-a) {local-key system-key})
system (component/system-map
:a a)
e (try (component/start system)
false
(catch Exception e e))
data (ex-data e)]
(is (= ::component/missing-dependency (:reason data)))
(is (= system-key (:system-key data)))
(is (= local-key (:dependency-key data)))
(is (= a (:component data)))
(is (= system (:system data)))))