From c0c08b2e269c774489239d9cd80fc32bca458995 Mon Sep 17 00:00:00 2001 From: Rob Hanlon Date: Sun, 9 Aug 2020 23:15:00 -0700 Subject: [PATCH] Configure clojure.spec, add a few specs (#23) Creates specs for init, create, and init-network --- .gitignore | 1 + dev-src/user.clj | 14 ++ project.clj | 14 +- src/clj_test_containers/core.clj | 51 ++++--- src/clj_test_containers/spec/container.clj | 27 ++++ src/clj_test_containers/spec/core.clj | 39 ++++++ src/clj_test_containers/spec/network.clj | 22 +++ test/clj_test_containers/core_test.clj | 147 ++++++++++----------- tests.edn | 1 + 9 files changed, 216 insertions(+), 100 deletions(-) create mode 100644 dev-src/user.clj create mode 100644 src/clj_test_containers/spec/container.clj create mode 100644 src/clj_test_containers/spec/core.clj create mode 100644 src/clj_test_containers/spec/network.clj diff --git a/.gitignore b/.gitignore index 6f34463..b2809f3 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ pom.xml.asc .hgignore .hg/ .DS_Store +.lsp diff --git a/dev-src/user.clj b/dev-src/user.clj new file mode 100644 index 0000000..4f8b357 --- /dev/null +++ b/dev-src/user.clj @@ -0,0 +1,14 @@ +(ns user + (:require + [clojure.spec.alpha :as s] + [expound.alpha :as ea] + [kaocha.repl])) + +(try + ;; Attempt to set *explain-out*, assuming that we're inside of + ;; a binding context... + (set! s/*explain-out* ea/printer) + + (catch IllegalStateException _ + ;; ...if not, just alter the root binding. + (alter-var-root #'s/*explain-out* (constantly ea/printer)))) diff --git a/project.clj b/project.clj index 1a93bcf..cd4cda5 100644 --- a/project.clj +++ b/project.clj @@ -14,14 +14,18 @@ :plugins [[jainsahab/lein-githooks "1.0.0"]] - :profiles {:dev {:dependencies [[org.testcontainers/postgresql "1.14.3"] - [lambdaisland/kaocha-cloverage "1.0-45"] + :profiles {:dev {:dependencies [[expound "0.8.5"] [lambdaisland/kaocha "1.0.641"] + [lambdaisland/kaocha-cloverage "1.0-45"] [lambdaisland/kaocha-junit-xml "0.0.76"] - [mvxcvi/cljstyle "0.13.0" :exclusions [org.clojure/clojure]]] + [lambdaisland/kaocha-junit-xml "0.0.76"] + [mvxcvi/cljstyle "0.13.0" :exclusions [org.clojure/clojure]] + [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"]}}} + :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 6547c0d..b23d153 100644 --- a/src/clj_test_containers/core.clj +++ b/src/clj_test_containers/core.clj @@ -1,5 +1,7 @@ (ns clj-test-containers.core (:require + [clj-test-containers.spec.container :as csc] + [clj-test-containers.spec.core :as cs] [clojure.spec.alpha :as s]) (:import (java.nio.file @@ -19,6 +21,10 @@ BindMode/READ_WRITE BindMode/READ_ONLY)) +(s/fdef init + :args (s/cat :init-options ::cs/init-options) + :ret ::cs/container) + (defn init "Sets the properties for a testcontainer instance" [{:keys [container exposed-ports env-vars command network network-aliases]}] @@ -42,6 +48,10 @@ :host (.getHost container) :network network}) +(s/fdef create + :args (s/cat :create-options ::cs/create-options) + :ret ::cs/container) + (defn create "Creates a generic testcontainer and sets its properties" [{:keys [image-name] :as options}] @@ -122,26 +132,27 @@ (dissoc :id) (dissoc :mapped-ports))) -(defn- build-network - [{:keys [ipv6 driver]}] - (let [builder (Network/builder)] +(s/fdef init-network + :args (s/alt :nullary (s/cat) + :unary (s/cat :init-network-options + ::cs/init-network-options)) + :ret ::cs/network) - (when ipv6 - (.enableIpv6 builder true)) - - (when driver - (.driver builder driver)) - - (let [network (.build builder)] - {:network network - :id (.getId network) - :name (.getName network) - :ipv6 (.getEnableIpv6 network) - :driver (.getDriver network)}))) - -(defn init-network +(defn ^:no-gen init-network "Creates a network. The optional map accepts config values for enabling ipv6 and setting the driver" ([] - (build-network {})) - ([options] - (build-network options))) + (init-network {})) + ([{:keys [ipv6 driver]}] + (let [builder (Network/builder)] + (when ipv6 + (.enableIpv6 builder true)) + + (when driver + (.driver builder driver)) + + (let [network (.build builder)] + {:network network + :id (.getId network) + :name (.getName network) + :ipv6 (.getEnableIpv6 network) + :driver (.getDriver network)})))) diff --git a/src/clj_test_containers/spec/container.clj b/src/clj_test_containers/spec/container.clj new file mode 100644 index 0000000..6714253 --- /dev/null +++ b/src/clj_test_containers/spec/container.clj @@ -0,0 +1,27 @@ +(ns clj-test-containers.spec.container + (:require + [clojure.spec.alpha :as s] + [clojure.spec.gen.alpha :as gen]) + (:import + (org.testcontainers.containers + GenericContainer))) + +(s/def ::container + (s/with-gen #(instance? GenericContainer %) + #(gen/fmap (fn [image-name] (GenericContainer. image-name)) + (gen/string-alphanumeric)))) + +(s/def ::exposed-ports + (s/coll-of (s/int-in 1 65535))) + +(s/def ::env-vars + (s/map-of string? string?)) + +(s/def ::command + (s/coll-of string?)) + +(s/def ::network-aliases + (s/coll-of string?)) + +(s/def ::image-name + string?) diff --git a/src/clj_test_containers/spec/core.clj b/src/clj_test_containers/spec/core.clj new file mode 100644 index 0000000..8e9aded --- /dev/null +++ b/src/clj_test_containers/spec/core.clj @@ -0,0 +1,39 @@ +(ns clj-test-containers.spec.core + (:require + [clj-test-containers.spec.container :as csc] + [clj-test-containers.spec.network :as csn] + [clojure.spec.alpha :as s])) + +(s/def ::network + (s/nilable (s/keys :req-un [::csn/network + ::csn/id + ::csn/name + ::csn/ipv6 + ::csn/driver]))) + +(s/def ::container + (s/keys :req-un [::csc/container + ::csc/exposed-ports + ::csc/env-vars + ::csc/host] + :opt-un [::network])) + +(s/def ::init-options + (s/keys :req-un [::csc/container] + :opt-un [::csc/exposed-ports + ::csc/env-vars + ::csc/command + ::network + ::csc/network-aliases])) + +(s/def ::create-options + (s/keys :req-un [::csc/image-name] + :opt-un [::csc/exposed-ports + ::csc/env-vars + ::csc/command + ::network + ::csc/network-aliases])) + +(s/def ::init-network-options + (s/keys :opt-un [::csn/ipv6 + ::csn/driver])) diff --git a/src/clj_test_containers/spec/network.clj b/src/clj_test_containers/spec/network.clj new file mode 100644 index 0000000..8cb4bb2 --- /dev/null +++ b/src/clj_test_containers/spec/network.clj @@ -0,0 +1,22 @@ +(ns clj-test-containers.spec.network + (:require + [clojure.spec.alpha :as s]) + (:import + (org.testcontainers.containers + Network))) + +(s/def ::id + string?) + +(s/def ::ipv6 + (s/nilable boolean?)) + +(s/def ::driver + (s/nilable string?)) + +(s/def ::name + string?) + +(s/def ::network + (s/with-gen #(instance? Network %) + #(s/gen #{(Network/newNetwork)}))) diff --git a/test/clj_test_containers/core_test.clj b/test/clj_test_containers/core_test.clj index 5053cc9..ae4d7ed 100644 --- a/test/clj_test_containers/core_test.clj +++ b/test/clj_test_containers/core_test.clj @@ -1,19 +1,18 @@ (ns clj-test-containers.core-test (:require - [clj-test-containers.core :refer :all] - [clojure.test :refer :all]) + [clj-test-containers.core :as sut] + [clojure.test :refer [deftest is testing]]) (:import (org.testcontainers.containers PostgreSQLContainer))) (deftest create-test (testing "Testing basic testcontainer generic image initialisation" - - (let [container (create {:image-name "postgres:12.2" - :exposed-ports [5432] - :env-vars {"POSTGRES_PASSWORD" "pw"}}) - initialized-container (start! container) - stopped-container (stop! container)] + (let [container (sut/create {:image-name "postgres:12.2" + :exposed-ports [5432] + :env-vars {"POSTGRES_PASSWORD" "pw"}}) + initialized-container (sut/start! container) + stopped-container (sut/stop! container)] (is (some? (:id initialized-container))) (is (some? (:mapped-ports initialized-container))) (is (some? (get (:mapped-ports initialized-container) 5432))) @@ -21,10 +20,10 @@ (is (nil? (:mapped-ports stopped-container))))) (testing "Testing basic testcontainer image creation from docker file" - (let [container (create-from-docker-file {:exposed-ports [80] - :docker-file "test/resources/Dockerfile"}) - initialized-container (start! container) - stopped-container (stop! container)] + (let [container (sut/create-from-docker-file {:exposed-ports [80] + :docker-file "test/resources/Dockerfile"}) + initialized-container (sut/start! container) + stopped-container (sut/stop! container)] (is (some? (:id initialized-container))) (is (some? (:mapped-ports initialized-container))) (is (some? (get (:mapped-ports initialized-container) 80))) @@ -33,37 +32,37 @@ (testing "Executing a command in the running Docker container with a custom container" - (let [container (init {:container (PostgreSQLContainer. "postgres:12.2")}) - initialized-container (start! container) - result (execute-command! initialized-container ["whoami"]) - stopped-container (stop! container)] + (let [container (sut/init {:container (PostgreSQLContainer. "postgres:12.2")}) + initialized-container (sut/start! container) + result (sut/execute-command! initialized-container ["whoami"]) + _stopped-container (sut/stop! container)] (is (= 0 (:exit-code result))) (is (= "root\n" (:stdout result)))))) (deftest execute-command-in-container (testing "Executing a command in the running Docker container" - (let [container (create {:image-name "postgres:12.2" - :exposed-ports [5432] - :env-vars {"POSTGRES_PASSWORD" "pw"}}) - initialized-container (start! container) - result (execute-command! initialized-container ["whoami"]) - stopped-container (stop! container)] + (let [container (sut/create {:image-name "postgres:12.2" + :exposed-ports [5432] + :env-vars {"POSTGRES_PASSWORD" "pw"}}) + initialized-container (sut/start! container) + result (sut/execute-command! initialized-container ["whoami"]) + _stopped-container (sut/stop! container)] (is (= 0 (:exit-code result))) (is (= "root\n" (:stdout result)))))) (deftest init-volume-test (testing "Testing mapping of a classpath resource" - (let [container (-> (create {:image-name "postgres:12.2" - :exposed-ports [5432] - :env-vars {"POSTGRES_PASSWORD" "pw"}}) - (map-classpath-resource! {:resource-path "test.sql" - :container-path "/opt/test.sql" - :mode :read-only})) - initialized-container (start! container) - file-check (execute-command! initialized-container ["tail" "/opt/test.sql"]) - stopped-container (stop! container)] + (let [container (-> (sut/create {:image-name "postgres:12.2" + :exposed-ports [5432] + :env-vars {"POSTGRES_PASSWORD" "pw"}}) + (sut/map-classpath-resource! {:resource-path "test.sql" + :container-path "/opt/test.sql" + :mode :read-only})) + initialized-container (sut/start! container) + file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"]) + stopped-container (sut/stop! container)] (is (some? (:id initialized-container))) (is (some? (:mapped-ports initialized-container))) (is (some? (get (:mapped-ports initialized-container) 5432))) @@ -72,15 +71,15 @@ (is (nil? (:mapped-ports stopped-container))))) (testing "Testing mapping of a filesystem-binding" - (let [container (-> (create {:image-name "postgres:12.2" - :exposed-ports [5432] - :env-vars {"POSTGRES_PASSWORD" "pw"}}) - (bind-filesystem! {:host-path "." - :container-path "/opt" - :mode :read-only})) - initialized-container (start! container) - file-check (execute-command! initialized-container ["tail" "/opt/README.md"]) - stopped-container (stop! container)] + (let [container (-> (sut/create {:image-name "postgres:12.2" + :exposed-ports [5432] + :env-vars {"POSTGRES_PASSWORD" "pw"}}) + (sut/bind-filesystem! {:host-path "." + :container-path "/opt" + :mode :read-only})) + initialized-container (sut/start! container) + file-check (sut/execute-command! initialized-container ["tail" "/opt/README.md"]) + stopped-container (sut/stop! container)] (is (some? (:id initialized-container))) (is (some? (:mapped-ports initialized-container))) (is (some? (get (:mapped-ports initialized-container) 5432))) @@ -89,15 +88,15 @@ (is (nil? (:mapped-ports stopped-container))))) (testing "Copying a file from the host into the container" - (let [container (-> (create {:image-name "postgres:12.2" - :exposed-ports [5432] - :env-vars {"POSTGRES_PASSWORD" "pw"}}) - (copy-file-to-container! {:path "test.sql" - :container-path "/opt/test.sql" - :type :host-path})) - initialized-container (start! container) - file-check (execute-command! initialized-container ["tail" "/opt/test.sql"]) - stopped-container (stop! container)] + (let [container (-> (sut/create {:image-name "postgres:12.2" + :exposed-ports [5432] + :env-vars {"POSTGRES_PASSWORD" "pw"}}) + (sut/copy-file-to-container! {:path "test.sql" + :container-path "/opt/test.sql" + :type :host-path})) + initialized-container (sut/start! container) + file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"]) + stopped-container (sut/stop! container)] (is (some? (:id initialized-container))) (is (some? (:mapped-ports initialized-container))) (is (some? (get (:mapped-ports initialized-container) 5432))) @@ -106,15 +105,15 @@ (is (nil? (:mapped-ports stopped-container))))) (testing "Copying a file from the classpath into the container" - (let [container (-> (create {:image-name "postgres:12.2" - :exposed-ports [5432] - :env-vars {"POSTGRES_PASSWORD" "pw"}}) - (copy-file-to-container! {:path "test.sql" - :container-path "/opt/test.sql" - :type :classpath-resource})) - initialized-container (start! container) - file-check (execute-command! initialized-container ["tail" "/opt/test.sql"]) - stopped-container (stop! container)] + (let [container (-> (sut/create {:image-name "postgres:12.2" + :exposed-ports [5432] + :env-vars {"POSTGRES_PASSWORD" "pw"}}) + (sut/copy-file-to-container! {:path "test.sql" + :container-path "/opt/test.sql" + :type :classpath-resource})) + initialized-container (sut/start! container) + file-check (sut/execute-command! initialized-container ["tail" "/opt/test.sql"]) + stopped-container (sut/stop! container)] (is (some? (:id initialized-container))) (is (some? (:mapped-ports initialized-container))) (is (some? (get (:mapped-ports initialized-container) 5432))) @@ -123,23 +122,21 @@ (is (nil? (:mapped-ports stopped-container)))))) (deftest networking-test - (testing "Putting two containers into the same network and check their communication" - (let [network (init-network) - server-container (create {:image-name "alpine:3.5" - :network network - :network-aliases ["foo"] - :command ["/bin/sh" - "-c" - "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"]}) - client-container (create {:image-name "alpine:3.5" - :network network - :command ["top"]}) - started-server (start! server-container) - started-client (start! client-container) - response (execute-command! started-client ["wget", "-O", "-", "http://foo:8080"]) - stopped-server (stop! started-server) - stopped-client (stop! started-client)] - + (let [network (sut/init-network) + server-container (sut/create {:image-name "alpine:3.5" + :network network + :network-aliases ["foo"] + :command ["/bin/sh" + "-c" + "while true ; do printf 'HTTP/1.1 200 OK\\n\\nyay' | nc -l -p 8080; done"]}) + client-container (sut/create {:image-name "alpine:3.5" + :network network + :command ["top"]}) + started-server (sut/start! server-container) + started-client (sut/start! client-container) + response (sut/execute-command! started-client ["wget", "-O", "-", "http://foo:8080"]) + _stopped-server (sut/stop! started-server) + _stopped-client (sut/stop! started-client)] (is (= 0 (:exit-code response))) (is (= "yay" (:stdout response)))))) diff --git a/tests.edn b/tests.edn index e2e964b..383aeba 100644 --- a/tests.edn +++ b/tests.edn @@ -2,4 +2,5 @@ {:plugins [:kaocha.plugin/junit-xml :kaocha.plugin/cloverage :kaocha.plugin.alpha/spec-test-check] + :reporter [kaocha.report/documentation] :kaocha.plugin.junit-xml/target-file "target/junit.xml"}