diff --git a/project.clj b/project.clj index cd4cda5..48df0e8 100644 --- a/project.clj +++ b/project.clj @@ -23,9 +23,9 @@ [org.clojure/test.check "1.1.0"] [org.clojure/tools.namespace "1.0.0"] [org.testcontainers/postgresql "1.14.3"]] - :githooks {:auto-install true - :ci-env-variable "CI" - :pre-commit ["script/pre-commit"]} +; :githooks {:auto-install true +; :ci-env-variable "CI" +; :pre-commit ["script/pre-commit"] :source-paths ["dev-src"]}} :target-path "target/%s") diff --git a/src/clj_test_containers/core.clj b/src/clj_test_containers/core.clj index 7c81c03..598e7c9 100644 --- a/src/clj_test_containers/core.clj +++ b/src/clj_test_containers/core.clj @@ -37,7 +37,7 @@ Example: ```clojure - (wait {:strategy :http + (wait {:wait-strategy :http :port 80 :path \"/\" :status-codes [200 201] @@ -45,14 +45,29 @@ :read-timeout 5 :basic-credentials {:username \"user\" :password \"password\"}} - container)) + container) ``` ## Health Strategy - TBD + The :health strategy only accepts a true or false value. This enables support for Docker's + healthcheck feature, whereby you can directly leverage the healthy state of your container + as your wait condition. + Example: + + ```clojure + (wait {:wait-strategy :health :true} container) + ``` ## Log Strategy - TBD" - :strategy) + The :log strategy accepts a message which simply causes the output of your container's log + to be used in determining if the container is ready or not. The output is `grepped` against + the log message. + Example: + + ```clojure + (wait {:wait-strategy :log + :message \"accept connections\"} container) + ```" + :wait-strategy) (defmethod wait :http [{:keys [path port status-codes tls read-timeout basic-credentials] :as options} container] @@ -96,7 +111,7 @@ (defn init "Sets the properties for a testcontainer instance" - [{:keys [container exposed-ports env-vars command network network-aliases wait-for capture-logs?]}] + [{:keys [container exposed-ports env-vars command network network-aliases wait-for] :as init-options}] (.setExposedPorts container (map int exposed-ports)) @@ -111,12 +126,11 @@ (when network-aliases (.setNetworkAliases container (java.util.ArrayList. network-aliases))) - (merge {:container container - :exposed-ports (vec (.getExposedPorts container)) - :env-vars (into {} (.getEnvMap container)) - :host (.getHost container) - :capture-logs? capture-logs? - :network network} (wait wait-for container))) + (merge init-options {:container container + :exposed-ports (vec (.getExposedPorts container)) + :env-vars (into {} (.getEnvMap container)) + :host (.getHost container) + :network network} (wait wait-for container))) (s/fdef create :args (s/cat :create-options ::cs/create-options) @@ -183,28 +197,58 @@ :stdout (.getStdout result) :stderr (.getStderr result)})) -(defn dump-logs - [container-config] - ((:logs container-config))) +(defmulti log + "Sets a log strategy on the container as a means of accessing the container logs. + It currently only supports a :string as the strategy to use. -(defn capture-logs - [container] + ## String Strategy + The :string strategy sets up a function in the returned map, under the `string-log` + key. This function enables the dumping of the logs when passed to the `dump-logs` + function. + Example: + + ```clojure + {:log-strategy :string} + ``` + + Then, later in your program, you can access the logs thus: + + ```clojure + (def container-config (tc/start! container)) + (tc/dump-logs container-config) + ``` + " + :log-strategy) + +(defmethod log :string + [_ container] (let [to-string-consumer (ToStringConsumer.)] (.followOutput container to-string-consumer) - (fn [] - (-> (.toUtf8String to-string-consumer) - (clojure.string/replace #"\n+" "\n"))))) + {:string-log (fn [] + (-> (.toUtf8String to-string-consumer) + (clojure.string/replace #"\n+" "\n")))})) + +(defmethod log :slf4j [_ _] nil) + +(defmethod log :default [_ _] nil) + +(defn dump-logs + "Dumps the logs found by invoking the function on the :string-log key" + [container-config] + ((:string-log container-config))) (defn start! "Starts the underlying testcontainer instance and adds new values to the response map, e.g. :id and :first-mapped-port" [container-config] - (let [{:keys [container capture-logs?]} container-config] + (let [{:keys [container log-to]} container-config] (.start container) - (-> (if capture-logs? (assoc container-config :logs (capture-logs container)) container-config) - (assoc :id (.getContainerId container)) - (assoc :mapped-ports (into {} - (map (fn [port] [port (.getMappedPort container port)]) - (:exposed-ports container-config))))))) + (-> (merge container-config + {:id (.getContainerId container) + :mapped-ports (into {} + (map (fn [port] [port (.getMappedPort container port)]) + (:exposed-ports container-config)))} + (log log-to container)) + (dissoc :log-to)))) (defn stop! "Stops the underlying container" @@ -212,7 +256,7 @@ (.stop (:container container-config)) (-> container-config (dissoc :id) - (dissoc :logs) + (dissoc :string-log) (dissoc :mapped-ports))) (s/fdef create-network diff --git a/src/clj_test_containers/spec/container.clj b/src/clj_test_containers/spec/container.clj index f782bae..5711afb 100644 --- a/src/clj_test_containers/spec/container.clj +++ b/src/clj_test_containers/spec/container.clj @@ -35,7 +35,9 @@ (s/def ::log keyword?) -(s/def ::strategy #{:http :health :log}) +(s/def ::wait-strategy #{:http :health :log}) + +(s/def ::log-strategy #{:string}) (s/def ::path string?) @@ -46,5 +48,5 @@ (s/def ::check boolean?) -(s/def ::capture-logs? - (s/nilable boolean?)) +(s/def ::string + string?) diff --git a/src/clj_test_containers/spec/core.clj b/src/clj_test_containers/spec/core.clj index 8785456..dc65916 100644 --- a/src/clj_test_containers/spec/core.clj +++ b/src/clj_test_containers/spec/core.clj @@ -5,11 +5,15 @@ [clojure.spec.alpha :as s])) (s/def ::wait-for - (s/keys :req-un [::csc/strategy] + (s/keys :req-un [::csc/wait-strategy] :opt-un [::csc/path ::csc/message ::csc/check])) +(s/def ::log-to + (s/keys :req-un [::csc/log-strategy] + :opt-un [::csc/string])) + (s/def ::network (s/nilable (s/keys :req-un [::csn/network ::csn/name @@ -23,7 +27,8 @@ ::csc/host] :opt-un [::network ::wait-for - ::csc/capture-logs?])) + ::log-to])) + (s/def ::init-options (s/keys :req-un [::csc/container] @@ -32,7 +37,7 @@ ::csc/command ::network ::wait-for - ::csc/capture-logs? + ::log-to ::csc/network-aliases])) (s/def ::create-options @@ -42,7 +47,7 @@ ::csc/command ::network ::wait-for - ::csc/capture-logs? + ::log-to ::csc/network-aliases])) (s/def ::create-network-options diff --git a/test/clj_test_containers/core_test.clj b/test/clj_test_containers/core_test.clj index b48f806..1c8cbaa 100644 --- a/test/clj_test_containers/core_test.clj +++ b/test/clj_test_containers/core_test.clj @@ -23,7 +23,7 @@ (let [container (sut/create {:image-name "postgres:12.2" :exposed-ports [5432] :env-vars {"POSTGRES_PASSWORD" "pw"} - :wait-for {:strategy :log :message "accept connections"}}) + :wait-for {:wait-strategy :log :message "accept connections"}}) initialized-container (sut/start! container) stopped-container (sut/stop! container)] (is (some? (:id initialized-container)))