From 3809bbb28c4200cecbbabe712e545c5f5451ed3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Sun, 14 Apr 2024 12:52:23 +0200 Subject: [PATCH 1/4] Refactoring to use new namespaces This matches the default testcontainers naming patterns and gets rid of the hyphen --- README.md | 12 ++++-------- doc/tutorial.md | 2 +- .../core.clj | 4 ++-- .../spec/container.clj | 2 +- .../spec/core.clj | 6 +++--- .../spec/network.clj | 2 +- .../core_test.clj | 4 ++-- 7 files changed, 14 insertions(+), 18 deletions(-) rename src/{clj_test_containers => testcontainers_clj}/core.clj (99%) rename src/{clj_test_containers => testcontainers_clj}/spec/container.clj (95%) rename src/{clj_test_containers => testcontainers_clj}/spec/core.clj (91%) rename src/{clj_test_containers => testcontainers_clj}/spec/network.clj (90%) rename test/{clj_test_containers => testcontainers_clj}/core_test.clj (99%) diff --git a/README.md b/README.md index e884ff0..0ad5d06 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# clj-test-containers +# testcontainers-clj [![Clojars Project](http://clojars.org/clj-test-containers/latest-version.svg)](http://clojars.org/clj-test-containers) @@ -11,17 +11,13 @@ This library is a lightweight wrapper around the [Testcontainers Java library](h This library does not provide tools to include testcontainers in your testing lifecycle. As there are many different test tools with different approaches to testing in the clojure world, handling the lifecycle is up to you. -## Integration with test runners - -There is an [experimental kaocha plugin](https://github.com/lambdaschmiede/kaocha-testcontainers-plugin) you can try out - ## Usage The library provides a set of functions to interact with the testcontainers. A simple example, how to create a container with a Docker label, could look like this: ```clojure -(require '[clj-test-containers.core :as tc]) +(require '[testcontainers-clj.core :as tc]) (def container (-> (tc/create {:image-name "postgres:12.1" :exposed-ports [5432] @@ -41,7 +37,7 @@ If you'd rather create a container from a Dockerfile in your project, it could l ```clojure -(require '[clj-test-containers.core :as tc]) +(require '[testcontainers-clj.core :as tc]) (def container (-> (tc/create-from-docker-file {:env-vars {"FOO" "bar"} :exposed-ports [80] @@ -52,7 +48,7 @@ If you'd rather create a container from a Dockerfile in your project, it could l If you prefer to use prebuilt containers from the Testcontainers project, you can do it like this ```clojure -(require '[clj-test-containers.core :as tc]) +(require '[testcontainers-clj.core :as tc]) (:import [org.testcontainers.containers PostgreSQLContainer]) (def container (-> (tc/init {:container (PostgreSQLContainer. "postgres:12.2") diff --git a/doc/tutorial.md b/doc/tutorial.md index 2910ff6..d92d079 100644 --- a/doc/tutorial.md +++ b/doc/tutorial.md @@ -15,7 +15,7 @@ The functions accept and return a map structure, which enables us to operate the consistent way. The example shown with Java Interop above would look like this, when using the wrapped functions: ```clojure -(require '[clj-test-containers.core :as tc]) +(require '[testcontainers-clj.core :as tc]) (deftest db-integration-test (testing "A simple PostgreSQL integration test" diff --git a/src/clj_test_containers/core.clj b/src/testcontainers_clj/core.clj similarity index 99% rename from src/clj_test_containers/core.clj rename to src/testcontainers_clj/core.clj index b43cc12..cbb2343 100644 --- a/src/clj_test_containers/core.clj +++ b/src/testcontainers_clj/core.clj @@ -1,6 +1,6 @@ -(ns clj-test-containers.core +(ns testcontainers-clj.core (:require - [clj-test-containers.spec.core :as cs] + [testcontainers-clj.spec.core :as cs] [clojure.spec.alpha :as s] [clojure.string]) (:import diff --git a/src/clj_test_containers/spec/container.clj b/src/testcontainers_clj/spec/container.clj similarity index 95% rename from src/clj_test_containers/spec/container.clj rename to src/testcontainers_clj/spec/container.clj index 5712ba1..1fab7b8 100644 --- a/src/clj_test_containers/spec/container.clj +++ b/src/testcontainers_clj/spec/container.clj @@ -1,4 +1,4 @@ -(ns clj-test-containers.spec.container +(ns testcontainers-clj.spec.container (:require [clojure.spec.alpha :as s] [clojure.spec.gen.alpha :as gen]) diff --git a/src/clj_test_containers/spec/core.clj b/src/testcontainers_clj/spec/core.clj similarity index 91% rename from src/clj_test_containers/spec/core.clj rename to src/testcontainers_clj/spec/core.clj index c2d7c53..45b47fa 100644 --- a/src/clj_test_containers/spec/core.clj +++ b/src/testcontainers_clj/spec/core.clj @@ -1,7 +1,7 @@ -(ns clj-test-containers.spec.core +(ns testcontainers-clj.spec.core (:require - [clj-test-containers.spec.container :as csc] - [clj-test-containers.spec.network :as csn] + [testcontainers-clj.spec.container :as csc] + [testcontainers-clj.spec.network :as csn] [clojure.spec.alpha :as s])) (s/def ::wait-for diff --git a/src/clj_test_containers/spec/network.clj b/src/testcontainers_clj/spec/network.clj similarity index 90% rename from src/clj_test_containers/spec/network.clj rename to src/testcontainers_clj/spec/network.clj index 8cb4bb2..42715b1 100644 --- a/src/clj_test_containers/spec/network.clj +++ b/src/testcontainers_clj/spec/network.clj @@ -1,4 +1,4 @@ -(ns clj-test-containers.spec.network +(ns testcontainers-clj.spec.network (:require [clojure.spec.alpha :as s]) (:import diff --git a/test/clj_test_containers/core_test.clj b/test/testcontainers_clj/core_test.clj similarity index 99% rename from test/clj_test_containers/core_test.clj rename to test/testcontainers_clj/core_test.clj index ec80794..1aaa877 100644 --- a/test/clj_test_containers/core_test.clj +++ b/test/testcontainers_clj/core_test.clj @@ -1,6 +1,6 @@ -(ns clj-test-containers.core-test +(ns testcontainers-clj.core-test (:require - [clj-test-containers.core :as sut] + [testcontainers-clj.core :as sut] [clojure.string :refer [includes?]] [clojure.test :refer [deftest is testing]]) (:import From 0e2d483715d63429278c1c9287a11e1e366b2d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Sun, 14 Apr 2024 13:54:00 +0200 Subject: [PATCH 2/4] Added :reuse flag (#72) Included a new flag for the container reuse feature --- README.md | 47 ++++++++++++++++------- src/testcontainers_clj/core.clj | 9 ++++- src/testcontainers_clj/spec/container.clj | 3 ++ src/testcontainers_clj/spec/core.clj | 4 +- test/testcontainers_clj/core_test.clj | 14 ++++++- 5 files changed, 60 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 0ad5d06..87ae16f 100644 --- a/README.md +++ b/README.md @@ -68,19 +68,21 @@ Creates a testcontainers instance from a given Docker label and returns them | ------------- | :------------- |:----------------------------------------------------------------------------------------------------| | `:image-name` | String, mandatory | The name and label of an image, e.g. `postgres:12.2` | | `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port | +| `:reuse` | Boolean | Should the container be reused, if another Testcontainer with identical config is started? | | `:env-vars` | Map | A map with environment variables | | `:command` | Vector with strings | The start command of the container | | `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | | `:network-aliases` | Map | A list of alias names for the container on the network | -| `:wait-for` | Map | A map containing the [wait strategy](doc/wait-strategies.md) to use and the condition to check for | -| `:log-to` | Map | A map containing the [log strategy](doc/log-strategies.md) to use, e.g. {:log-strategy string} | +| `:wait-for` | Map | A map containing the [wait strategy](doc/wait-strategies.md) to use and the condition to check for | +| `:log-to` | Map | A map containing the [log strategy](doc/log-strategies.md) to use, e.g. {:log-strategy string} | #### Result: | Key | Type | Description | -| ------------- | :------------- | :----- | +|------------------|:------------------------------------------|:------------------------------------------------------------------------------------------| | `:container` | `org.testcontainers.containers.Container` | The Testcontainers instance, accessible for everything this library doesn't provide (yet) | | `:exposed-ports` | Vector with ints | Value of the same input parameter | +| `:reuse` | Boolean | Is this container reusable? | | `:env-vars` | Map | Value of the same input parameter | | `:host` | String | The host for the Docker Container | | `:network` | Map | The network configuration of the Container, if provided | @@ -99,6 +101,20 @@ Creates a testcontainers instance from a given Docker label and returns them "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) ``` +#### Example with reuse + +```clojure +(create {:image-name "alpine:3.2" + :exposed-ports [80] + :reuse true + :env-vars {"MAGIC_NUMBER" "42"} + :network (create-network) + :network-aliases ["api-server"] + :command ["/bin/sh" + "-c" + "while true; do echo \"$MAGIC_NUMBER\" | nc -l -p 80; done"]}) +``` + #### Example using wait-for and healthcheck: ```clojure @@ -119,17 +135,18 @@ Initializes a given Testcontainer, which was e.g. provided by a library #### Config parameters: -| Key | Type | Description | -| ------------- | :------------- |:----------------------------------------------------------------------------------------------------| -| `:container` | `org.testcontainers.containers.GenericContainer`, mandatory | The name and label of an image, e.g. `postgres:12.2` | -| `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port | -| `:env-vars` | Map | A map with environment variables | -| `:command` | Vector with strings | The start command of the container | -| `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | -| `:network-aliases` | Map | A list of alias names for the container on the network | +| Key | Type | Description | +|--------------------|:------------------------------------------------------------|:---------------------------------------------------------------------------------------------------| +| `:container` | `org.testcontainers.containers.GenericContainer`, mandatory | The name and label of an image, e.g. `postgres:12.2` | +| `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port | +| `:reuse` | Boolean | Should the container be reused, if another Testcontainer with identical config is started? | +| `:env-vars` | Map | A map with environment variables | +| `:command` | Vector with strings | The start command of the container | +| `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | +| `:network-aliases` | Map | A list of alias names for the container on the network | | `:wait-for` | Map | A map containing the [wait strategy](doc/wait-strategies.md) to use and the condition to check for | -| `:log-to` | Map | A map containing the [log strategy](doc/log-strategies.md) to use, e.g. {:log-strategy string} | -| | | | +| `:log-to` | Map | A map containing the [log strategy](doc/log-strategies.md) to use, e.g. {:log-strategy string} | +| | | | #### Result: @@ -137,6 +154,7 @@ Initializes a given Testcontainer, which was e.g. provided by a library | ------------- | :------------- |:------------------------------------------------------------------------------------------| | `:container` | `org.testcontainers.containers.Container` | The Testcontainers instance, accessible for everything this library doesn't provide (yet) | | `:exposed-ports` | Vector with ints | Value of the same input parameter | +| `:reuse` | Boolean | Is this container reusable? | | `:env-vars` | Map | Value of the same input parameter | | `:host` | String | The host for the Docker Container | | `:network` | Map | The network configuration of the Container, if provided | @@ -177,6 +195,7 @@ Creates a testcontainer from a Dockerfile | ------------- | :------------- | :----- | | `:docker-file` | String, mandatory | String containing a path to a Dockerfile | | `:exposed-ports` | Vector with ints, mandatory | All ports which should be exposed and mapped to a local port | +| `:reuse` | Boolean | Should the container be reused, if another Testcontainer with identical config is started? | | `:env-vars` | Map | A map with environment variables | | `:command` | Vector with strings | The start command of the container | | `:network` | Map | A map containing the configuration of a Docker Network (see: `create-network`) | @@ -191,6 +210,7 @@ Creates a testcontainer from a Dockerfile | ------------- | :------------- | :----- | | `:container` | `org.testcontainers.containers.Container` | The Testcontainers instance, accessible for everything this library doesn't provide (yet) | | `:exposed-ports` | Vector with ints | Value of the same input parameter | +| `:reuse` | Boolean | Is this container reusable? | | `:env-vars` | Map | Value of the same input parameter | | `:host` | String | The host for the Docker Container | | `:network` | Map | The network configuration of the Container, if provided | @@ -227,6 +247,7 @@ Starts the Testcontainer, which was defined by `create` | ------------- | :------------- | :----- | | `:container` | `org.testcontainers.containers.Container` | The Testcontainers instance, accessible for everything this library doesn't provide (yet) | | `:exposed-ports` | Vector with ints | Value of the same input parameter | +| `:reuse` | Boolean | Is this container reusable? | | `:env-vars` | Map | Value of the same input parameter | | `:host` | String | The host for the Docker Container | | `:id` | String | The ID of the started docker container | diff --git a/src/testcontainers_clj/core.clj b/src/testcontainers_clj/core.clj index cbb2343..72af6c2 100644 --- a/src/testcontainers_clj/core.clj +++ b/src/testcontainers_clj/core.clj @@ -183,6 +183,7 @@ "Sets the properties for a testcontainer instance" [{:keys [^GenericContainer container exposed-ports + reuse env-vars command network @@ -194,6 +195,9 @@ (doseq [[k v] env-vars] (.addEnv container k v)) + (when reuse + (.withReuse container true)) + (when command (.setCommand container ^"[Ljava.lang.String;" (into-array String command))) @@ -425,5 +429,6 @@ ;; REPL Helpers (comment - (start! (create {:image-name "postgres:12.1"})) - (perform-cleanup!)) + (start! (create {:image-name "postgres:12.1" :reuse true})) + (perform-cleanup!) + ) diff --git a/src/testcontainers_clj/spec/container.clj b/src/testcontainers_clj/spec/container.clj index 1fab7b8..b4cb95e 100644 --- a/src/testcontainers_clj/spec/container.clj +++ b/src/testcontainers_clj/spec/container.clj @@ -14,6 +14,9 @@ (s/def ::exposed-ports (s/coll-of (s/int-in 1 65535))) +(s/def ::reuse + boolean?) + (s/def ::env-vars (s/map-of string? string?)) diff --git a/src/testcontainers_clj/spec/core.clj b/src/testcontainers_clj/spec/core.clj index 45b47fa..cc1098a 100644 --- a/src/testcontainers_clj/spec/core.clj +++ b/src/testcontainers_clj/spec/core.clj @@ -25,13 +25,15 @@ ::csc/exposed-ports ::csc/env-vars ::csc/host] - :opt-un [::network + :opt-un [::csc/reuse + ::network ::wait-for ::log-to])) (s/def ::init-options (s/keys :req-un [::csc/container] :opt-un [::csc/exposed-ports + ::csc/reuse ::csc/env-vars ::csc/command ::network diff --git a/test/testcontainers_clj/core_test.clj b/test/testcontainers_clj/core_test.clj index 1aaa877..239e8f0 100644 --- a/test/testcontainers_clj/core_test.clj +++ b/test/testcontainers_clj/core_test.clj @@ -115,7 +115,19 @@ result (sut/execute-command! initialized-container ["whoami"]) _stopped-container (sut/stop! container)] (is (= 0 (:exit-code result))) - (is (= "root\n" (:stdout result)))))) + (is (= "root\n" (:stdout result))))) + + (testing "Reusing a container with the :reuse flag" + (let [container-1 (sut/init {:container (PostgreSQLContainer. "postgres:15.3") + :exposed-ports [5432] + :reuse true}) + container-2 (sut/init {:container (PostgreSQLContainer. "postgres:15.3") + :exposed-ports [5432] + :reuse true}) + initialized-container-1 (sut/start! container-1) + initialized-container-2 (sut/start! container-2)] + (is (= (.getContainerId (:container initialized-container-1)) + (.getContainerId (:container initialized-container-2))))))) (deftest execute-command-in-container From 7d2ff63148ffb37829be177cb1c56415c46ea9f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Sun, 14 Apr 2024 14:21:54 +0200 Subject: [PATCH 3/4] Added env variable for reusable containers (#72) The flag is needed for reusable containes to work --- .github/workflows/clojure.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/clojure.yml b/.github/workflows/clojure.yml index 0bf6ed7..e433531 100644 --- a/.github/workflows/clojure.yml +++ b/.github/workflows/clojure.yml @@ -6,6 +6,9 @@ on: pull_request: branches: [ main ] +env: + TESTCONTAINERS_REUSE_ENABLE: true + permissions: contents: read From b2610ee2915e7d358554ce517634c900c137c740 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Z=C3=B6ller?= Date: Sun, 14 Apr 2024 16:57:17 +0200 Subject: [PATCH 4/4] Set proper group ID for Maven This will make it possible to publish the artifact under the existing Testcontainers Maven group --- project.clj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/project.clj b/project.clj index 675df90..4c1f6f6 100644 --- a/project.clj +++ b/project.clj @@ -1,4 +1,4 @@ -(defproject testcontainers-clj "unspecified" +(defproject org.testcontainers/testcontainers-clj "unspecified" :description "A lightweight, official wrapper around the Testcontainers Java library" :url "https://github.com/testcontainers/testcontainers-clj"