diff --git a/.github/workflows/clojure.yml b/.github/workflows/clojure.yml index cbfc1f1..92ef870 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 diff --git a/README.md b/README.md index e884ff0..87ae16f 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") @@ -72,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 | @@ -103,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 @@ -123,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: @@ -141,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 | @@ -181,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`) | @@ -195,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 | @@ -231,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/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/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" diff --git a/src/clj_test_containers/core.clj b/src/testcontainers_clj/core.clj similarity index 98% rename from src/clj_test_containers/core.clj rename to src/testcontainers_clj/core.clj index b43cc12..72af6c2 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 @@ -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/clj_test_containers/spec/container.clj b/src/testcontainers_clj/spec/container.clj similarity index 92% rename from src/clj_test_containers/spec/container.clj rename to src/testcontainers_clj/spec/container.clj index 5712ba1..b4cb95e 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]) @@ -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/clj_test_containers/spec/core.clj b/src/testcontainers_clj/spec/core.clj similarity index 86% rename from src/clj_test_containers/spec/core.clj rename to src/testcontainers_clj/spec/core.clj index c2d7c53..cc1098a 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 @@ -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/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 94% rename from test/clj_test_containers/core_test.clj rename to test/testcontainers_clj/core_test.clj index ec80794..239e8f0 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 @@ -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