diff --git a/ci/images/docker-lib.sh b/ci/images/docker-lib.sh
new file mode 100644
index 0000000000..497a422ceb
--- /dev/null
+++ b/ci/images/docker-lib.sh
@@ -0,0 +1,87 @@
+sanitize_cgroups() {
+ mkdir -p /sys/fs/cgroup
+ mountpoint -q /sys/fs/cgroup || \
+ mount -t tmpfs -o uid=0,gid=0,mode=0755 cgroup /sys/fs/cgroup
+
+ mount -o remount,rw /sys/fs/cgroup
+
+ sed -e 1d /proc/cgroups | while read sys hierarchy num enabled; do
+ if [ "$enabled" != "1" ]; then
+ # subsystem disabled; skip
+ continue
+ fi
+
+ grouping="$(cat /proc/self/cgroup | cut -d: -f2 | grep "\\<$sys\\>")"
+ if [ -z "$grouping" ]; then
+ # subsystem not mounted anywhere; mount it on its own
+ grouping="$sys"
+ fi
+
+ mountpoint="/sys/fs/cgroup/$grouping"
+
+ mkdir -p "$mountpoint"
+
+ # clear out existing mount to make sure new one is read-write
+ if mountpoint -q "$mountpoint"; then
+ umount "$mountpoint"
+ fi
+
+ mount -n -t cgroup -o "$grouping" cgroup "$mountpoint"
+
+ if [ "$grouping" != "$sys" ]; then
+ if [ -L "/sys/fs/cgroup/$sys" ]; then
+ rm "/sys/fs/cgroup/$sys"
+ fi
+
+ ln -s "$mountpoint" "/sys/fs/cgroup/$sys"
+ fi
+ done
+}
+
+start_docker() {
+ mkdir -p /var/log
+ mkdir -p /var/run
+
+ sanitize_cgroups
+
+ # check for /proc/sys being mounted readonly, as systemd does
+ if grep '/proc/sys\s\+\w\+\s\+ro,' /proc/mounts >/dev/null; then
+ mount -o remount,rw /proc/sys
+ fi
+
+ local server_args=""
+
+ for registry in $1; do
+ server_args="${server_args} --insecure-registry ${registry}"
+ done
+
+ if [ -n "$2" ]; then
+ server_args="${server_args} --registry-mirror=$2"
+ fi
+
+ if [ -n "$3" ]; then
+ server_args="${server_args} -g=$3"
+ fi
+
+ docker daemon --data-root /scratch/docker ${server_args} >/tmp/docker.log 2>&1 &
+ echo $! > /tmp/docker.pid
+
+ trap stop_docker EXIT
+
+ sleep 1
+
+ until docker info >/dev/null 2>&1; do
+ echo waiting for docker to come up...
+ sleep 1
+ done
+}
+
+stop_docker() {
+ local pid=$(cat /tmp/docker.pid)
+ if [ -z "$pid" ]; then
+ return 0
+ fi
+
+ kill -TERM $pid
+ wait $pid
+}
\ No newline at end of file
diff --git a/ci/images/spring-boot-ci-image/Dockerfile b/ci/images/spring-boot-ci-image/Dockerfile
index da239ca060..654b1110c8 100644
--- a/ci/images/spring-boot-ci-image/Dockerfile
+++ b/ci/images/spring-boot-ci-image/Dockerfile
@@ -5,4 +5,29 @@ RUN apt-get update && \
apt-get install -y libxml2-utils && \
apt-get install -y jq
-ADD https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.1/concourse-java.sh /opt/
\ No newline at end of file
+ADD https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.1/concourse-java.sh /opt/
+
+ENV DOCKER_VERSION=17.05.0-ce \
+ ENTRYKIT_VERSION=0.4.0
+
+RUN apt-get update && \
+ apt-get install -y curl && \
+ apt-get install -y libudev1 && \
+ apt-get install -y iptables && \
+ curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz | tar zx && \
+ mv /docker/* /bin/ && chmod +x /bin/docker*
+
+# Install entrykit
+RUN curl -L https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz | tar zx && \
+ chmod +x entrykit && \
+ mv entrykit /bin/entrykit && \
+ entrykit --symlink
+
+COPY ../docker-lib.sh /docker-lib.sh
+
+ENTRYPOINT [ \
+ "switch", \
+ "shell=/bin/sh", "--", \
+ "codep", \
+ "/bin/docker daemon" \
+]
\ No newline at end of file
diff --git a/ci/images/spring-boot-jdk9-ci-image/Dockerfile b/ci/images/spring-boot-jdk9-ci-image/Dockerfile
index 33bd2215f2..9403fe4676 100644
--- a/ci/images/spring-boot-jdk9-ci-image/Dockerfile
+++ b/ci/images/spring-boot-jdk9-ci-image/Dockerfile
@@ -5,4 +5,29 @@ RUN apt-get update && \
apt-get install -y libxml2-utils && \
apt-get install -y jq
-ADD https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.1/concourse-java.sh /opt/
\ No newline at end of file
+ADD https://raw.githubusercontent.com/spring-io/concourse-java-scripts/v0.0.1/concourse-java.sh /opt/
+
+ENV DOCKER_VERSION=17.05.0-ce \
+ ENTRYKIT_VERSION=0.4.0
+
+RUN apt-get update && \
+ apt-get install -y curl && \
+ apt-get install -y libudev1 && \
+ apt-get install -y iptables && \
+ curl https://get.docker.com/builds/Linux/x86_64/docker-${DOCKER_VERSION}.tgz | tar zx && \
+ mv /docker/* /bin/ && chmod +x /bin/docker*
+
+# Install entrykit
+RUN curl -L https://github.com/progrium/entrykit/releases/download/v${ENTRYKIT_VERSION}/entrykit_${ENTRYKIT_VERSION}_Linux_x86_64.tgz | tar zx && \
+ chmod +x entrykit && \
+ mv entrykit /bin/entrykit && \
+ entrykit --symlink
+
+COPY ../docker-lib.sh /docker-lib.sh
+
+ENTRYPOINT [ \
+ "switch", \
+ "shell=/bin/sh", "--", \
+ "codep", \
+ "/bin/docker daemon" \
+]
\ No newline at end of file
diff --git a/ci/pipeline.yml b/ci/pipeline.yml
index b16c99eb8e..5dff9a3b42 100644
--- a/ci/pipeline.yml
+++ b/ci/pipeline.yml
@@ -85,6 +85,7 @@ jobs:
trigger: true
- do:
- task: build-project
+ privileged: true
timeout: 1h30m
image: spring-boot-ci-image
file: git-repo/ci/tasks/build-project.yml
@@ -170,6 +171,7 @@ jobs:
trigger: true
- do:
- task: build-project
+ privileged: true
timeout: 1h30m
image: spring-boot-jdk9-ci-image
file: git-repo/ci/tasks/build-project.yml
diff --git a/ci/tasks/build-project.yml b/ci/tasks/build-project.yml
index b4576a4849..120695b239 100644
--- a/ci/tasks/build-project.yml
+++ b/ci/tasks/build-project.yml
@@ -8,4 +8,11 @@ caches:
- path: maven
- path: gradle
run:
- path: git-repo/ci/scripts/build-project.sh
+ path: bash
+ args:
+ - -exc
+ - |
+ source /docker-lib.sh
+ start_docker
+ ${PWD}/git-repo/ci/scripts/build-project.sh
+
diff --git a/spring-boot-project/spring-boot-autoconfigure/pom.xml b/spring-boot-project/spring-boot-autoconfigure/pom.xml
index 7005ece6a3..6c79cf09a7 100755
--- a/spring-boot-project/spring-boot-autoconfigure/pom.xml
+++ b/spring-boot-project/spring-boot-autoconfigure/pom.xml
@@ -760,5 +760,10 @@
tomcat-embed-jasper
test
+
+ org.testcontainers
+ testcontainers
+ test
+
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/DockerTestContainer.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/DockerTestContainer.java
new file mode 100644
index 0000000000..35159889aa
--- /dev/null
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/DockerTestContainer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.autoconfigure;
+
+import java.util.function.Supplier;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.containers.GenericContainer;
+
+/**
+ * {@link TestRule} for working with an optional Docker environment. Spins up a {@link GenericContainer}
+ * if a valid docker environment is found.
+ *
+ * @author Madhura Bhave
+ */
+public class DockerTestContainer implements TestRule {
+
+ private Supplier containerSupplier;
+
+ public DockerTestContainer(Supplier containerSupplier) {
+ this.containerSupplier = containerSupplier;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ try {
+ DockerClientFactory.instance().client();
+ return this.containerSupplier.get().apply(base, description);
+ }
+ catch (Throwable t) {
+ return new SkipStatement();
+ }
+ }
+
+ private static class SkipStatement extends Statement {
+
+ @Override
+ public void evaluate() {
+ throw new AssumptionViolatedException(
+ "Could not find a valid Docker environment.");
+ }
+
+ }
+}
+
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java
index f3c0983965..fd8309c507 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraDataAutoConfigurationIntegrationTests.java
@@ -16,12 +16,24 @@
package org.springframework.boot.autoconfigure.data.cassandra;
+import java.util.concurrent.Callable;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Supplier;
+
+import com.datastax.driver.core.Cluster;
import com.datastax.driver.core.Session;
+import com.datastax.driver.core.exceptions.NoHostAvailableException;
import org.junit.After;
-import org.junit.Rule;
+import org.junit.ClassRule;
import org.junit.Test;
+import org.rnorth.ducttape.TimeoutException;
+import org.rnorth.ducttape.unreliables.Unreliables;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.HostPortWaitStrategy;
import org.springframework.boot.autoconfigure.AutoConfigurationPackages;
+import org.springframework.boot.autoconfigure.DockerTestContainer;
import org.springframework.boot.autoconfigure.cassandra.CassandraAutoConfiguration;
import org.springframework.boot.autoconfigure.data.cassandra.city.City;
import org.springframework.boot.test.util.TestPropertyValues;
@@ -39,8 +51,11 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class CassandraDataAutoConfigurationIntegrationTests {
- @Rule
- public final CassandraTestServer cassandra = new CassandraTestServer();
+ @ClassRule
+ public static DockerTestContainer genericContainer = new DockerTestContainer<>((Supplier) () -> new FixedHostPortGenericContainer("cassandra:latest")
+ .withFixedExposedPort(9042, 9042)
+ .waitingFor(new ConnectionVerifyingWaitStrategy()));
+
private AnnotationConfigApplicationContext context;
@@ -84,10 +99,39 @@ public class CassandraDataAutoConfigurationIntegrationTests {
}
private void createTestKeyspaceIfNotExists() {
- try (Session session = this.cassandra.getCluster().connect()) {
+ Cluster cluster = Cluster.builder().addContactPoint("localhost").build();
+ try (Session session = cluster.connect()) {
session.execute("CREATE KEYSPACE IF NOT EXISTS boot_test"
+ " WITH REPLICATION = { 'class' : 'SimpleStrategy', 'replication_factor' : 1 };");
}
}
+ static class ConnectionVerifyingWaitStrategy extends HostPortWaitStrategy {
+
+ @Override
+ protected void waitUntilReady() {
+ super.waitUntilReady();
+ Cluster cluster = Cluster.builder().addContactPoint("localhost").build();
+ try {
+ Unreliables.retryUntilTrue((int) startupTimeout.getSeconds(), TimeUnit.SECONDS,
+ checkConnection(cluster));
+ }
+ catch (TimeoutException e) {
+ throw new IllegalStateException();
+ }
+ }
+
+ private Callable checkConnection(Cluster cluster) {
+ return () -> {
+ try {
+ cluster.connect();
+ return true;
+ }
+ catch (NoHostAvailableException ex) {
+ return false;
+ }
+ };
+ }
+ }
+
}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java
deleted file mode 100644
index 433938f762..0000000000
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/cassandra/CassandraTestServer.java
+++ /dev/null
@@ -1,100 +0,0 @@
-/*
- * Copyright 2012-2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.autoconfigure.data.cassandra;
-
-import com.datastax.driver.core.Cluster;
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.Assume;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-/**
- * {@link TestRule} for working with an optional Cassandra server.
- *
- * @author Stephane Nicoll
- */
-public class CassandraTestServer implements TestRule {
-
- private static final Log logger = LogFactory.getLog(CassandraTestServer.class);
-
- private Cluster cluster;
-
- @Override
- public Statement apply(Statement base, Description description) {
- try {
- this.cluster = newCluster();
- return new CassandraStatement(base, this.cluster);
- }
- catch (Exception ex) {
- logger.error("No Cassandra server available", ex);
- return new SkipStatement();
- }
- }
-
- private Cluster newCluster() {
- Cluster cluster = Cluster.builder().addContactPoint("localhost").build();
- testCluster(cluster);
- return cluster;
- }
-
- private void testCluster(Cluster cluster) {
- cluster.connect().close();
- }
-
- /**
- * @return the cluster if any
- */
- public Cluster getCluster() {
- return this.cluster;
- }
-
- private static class CassandraStatement extends Statement {
-
- private final Statement base;
-
- private final Cluster cluster;
-
- CassandraStatement(Statement base, Cluster cluster) {
- this.base = base;
- this.cluster = cluster;
- }
-
- @Override
- public void evaluate() throws Throwable {
- try {
- this.base.evaluate();
- }
- finally {
- this.cluster.closeAsync();
- }
- }
-
- }
-
- private static class SkipStatement extends Statement {
-
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeTrue("Skipping test due to Cassandra not being available",
- false);
- }
-
- }
-
-}
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java
index dc1f8f5d25..27acba2cd4 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/data/redis/RedisRepositoriesAutoConfigurationTests.java
@@ -17,16 +17,17 @@
package org.springframework.boot.autoconfigure.data.redis;
import org.junit.After;
-import org.junit.Rule;
+import org.junit.ClassRule;
import org.junit.Test;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
+import org.springframework.boot.autoconfigure.DockerTestContainer;
import org.springframework.boot.autoconfigure.TestAutoConfigurationPackage;
import org.springframework.boot.autoconfigure.context.PropertyPlaceholderAutoConfiguration;
import org.springframework.boot.autoconfigure.data.alt.redis.CityRedisRepository;
import org.springframework.boot.autoconfigure.data.empty.EmptyDataPackage;
import org.springframework.boot.autoconfigure.data.redis.city.City;
import org.springframework.boot.autoconfigure.data.redis.city.CityRepository;
-import org.springframework.boot.testsupport.rule.RedisTestServer;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;
@@ -40,8 +41,10 @@ import static org.assertj.core.api.Assertions.assertThat;
*/
public class RedisRepositoriesAutoConfigurationTests {
- @Rule
- public RedisTestServer redis = new RedisTestServer();
+ @ClassRule
+ public static DockerTestContainer genericContainer = new DockerTestContainer<>(() ->
+ new FixedHostPortGenericContainer("redis:latest")
+ .withFixedExposedPort(6379, 6379));
private AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext();
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java
index ba0ff4dfb3..982e3efb17 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/ReactiveSessionAutoConfigurationRedisTests.java
@@ -16,18 +16,19 @@
package org.springframework.boot.autoconfigure.session;
-import org.junit.Rule;
+import org.junit.ClassRule;
import org.junit.Test;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.DockerTestContainer;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.data.redis.RedisReactiveAutoConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableReactiveWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.ReactiveWebApplicationContextRunner;
-import org.springframework.boot.testsupport.rule.RedisTestServer;
import org.springframework.session.data.mongo.ReactiveMongoOperationsSessionRepository;
import org.springframework.session.data.redis.ReactiveRedisOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
@@ -44,8 +45,10 @@ import static org.assertj.core.api.Assertions.assertThat;
public class ReactiveSessionAutoConfigurationRedisTests
extends AbstractSessionAutoConfigurationTests {
- @Rule
- public final RedisTestServer redis = new RedisTestServer();
+ @ClassRule
+ public static DockerTestContainer redis = new DockerTestContainer<>(() ->
+ new FixedHostPortGenericContainer("redis:latest")
+ .withFixedExposedPort(6379, 6379));
protected final ReactiveWebApplicationContextRunner contextRunner = new ReactiveWebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
diff --git a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java
index 88ce73a1b6..06481c929f 100644
--- a/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java
+++ b/spring-boot-project/spring-boot-autoconfigure/src/test/java/org/springframework/boot/autoconfigure/session/SessionAutoConfigurationRedisTests.java
@@ -16,18 +16,19 @@
package org.springframework.boot.autoconfigure.session;
-import org.junit.Rule;
+import org.junit.ClassRule;
import org.junit.Test;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
import org.springframework.beans.DirectFieldAccessor;
import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.autoconfigure.DockerTestContainer;
import org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration;
import org.springframework.boot.autoconfigure.session.RedisSessionConfiguration.SpringBootRedisHttpSessionConfiguration;
import org.springframework.boot.test.context.FilteredClassLoader;
import org.springframework.boot.test.context.assertj.AssertableWebApplicationContext;
import org.springframework.boot.test.context.runner.ContextConsumer;
import org.springframework.boot.test.context.runner.WebApplicationContextRunner;
-import org.springframework.boot.testsupport.rule.RedisTestServer;
import org.springframework.session.data.mongo.MongoOperationsSessionRepository;
import org.springframework.session.data.redis.RedisFlushMode;
import org.springframework.session.data.redis.RedisOperationsSessionRepository;
@@ -45,8 +46,10 @@ import static org.assertj.core.api.Assertions.assertThat;
public class SessionAutoConfigurationRedisTests
extends AbstractSessionAutoConfigurationTests {
- @Rule
- public final RedisTestServer redis = new RedisTestServer();
+ @ClassRule
+ public static DockerTestContainer redis = new DockerTestContainer<>(() ->
+ new FixedHostPortGenericContainer("redis:latest")
+ .withFixedExposedPort(6379, 6379));
protected final WebApplicationContextRunner contextRunner = new WebApplicationContextRunner()
.withConfiguration(AutoConfigurations.of(SessionAutoConfiguration.class));
diff --git a/spring-boot-project/spring-boot-parent/pom.xml b/spring-boot-project/spring-boot-parent/pom.xml
index ea1e7a16fe..f2174bf79f 100644
--- a/spring-boot-project/spring-boot-parent/pom.xml
+++ b/spring-boot-project/spring-boot-parent/pom.xml
@@ -87,6 +87,11 @@
mockwebserver
3.9.0
+
+ org.testcontainers
+ testcontainers
+ 1.5.1
+
com.vaadin.external.google
android-json
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/pom.xml b/spring-boot-project/spring-boot-test-autoconfigure/pom.xml
index 00c9a685ae..d472e15264 100644
--- a/spring-boot-project/spring-boot-test-autoconfigure/pom.xml
+++ b/spring-boot-project/spring-boot-test-autoconfigure/pom.xml
@@ -273,5 +273,10 @@
de.flapdoodle.embed.mongo
test
+
+ org.testcontainers
+ testcontainers
+ test
+
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/DockerTestContainer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/DockerTestContainer.java
new file mode 100644
index 0000000000..25e8d3d781
--- /dev/null
+++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/DockerTestContainer.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2012-2018 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.boot.test.autoconfigure;
+
+import java.util.function.Supplier;
+
+import org.junit.AssumptionViolatedException;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+import org.testcontainers.DockerClientFactory;
+import org.testcontainers.containers.GenericContainer;
+
+/**
+ * {@link TestRule} for working with an optional Docker environment. Spins up a {@link GenericContainer}
+ * if a valid docker environment is found.
+ *
+ * @author Madhura Bhave
+ */
+public class DockerTestContainer implements TestRule {
+
+ private Supplier containerSupplier;
+
+ public DockerTestContainer(Supplier containerSupplier) {
+ this.containerSupplier = containerSupplier;
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ try {
+ DockerClientFactory.instance().client();
+ return this.containerSupplier.get().apply(base, description);
+ }
+ catch (Throwable thrown) {
+ return new SkipStatement();
+ }
+ }
+
+ private static class SkipStatement extends Statement {
+
+ @Override
+ public void evaluate() {
+ throw new AssumptionViolatedException(
+ "Could not find a valid Docker environment.");
+ }
+
+ }
+}
+
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java
index 5883fcb8b3..71e34b6748 100644
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java
+++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestIntegrationTests.java
@@ -16,14 +16,21 @@
package org.springframework.boot.test.autoconfigure.data.neo4j;
+import java.util.function.Supplier;
+
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.neo4j.ogm.session.Session;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
+import org.testcontainers.containers.GenericContainer;
+import org.testcontainers.containers.wait.HostPortWaitStrategy;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.DockerTestContainer;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.junit4.SpringRunner;
@@ -39,9 +46,11 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataNeo4jTest
public class DataNeo4jTestIntegrationTests {
- @Rule
- public Neo4jTestServer server = new Neo4jTestServer(
- new String[] { "org.springframework.boot.test.autoconfigure.data.neo4j" });
+ @ClassRule
+ public static DockerTestContainer genericContainer = new DockerTestContainer<>((Supplier) () -> new FixedHostPortGenericContainer("neo4j:latest")
+ .withFixedExposedPort(7687, 7687)
+ .waitingFor(new AdditionalSleepWaitStrategy()).withEnv("NEO4J_AUTH", "none"));
+
@Rule
public ExpectedException thrown = ExpectedException.none();
@@ -71,4 +80,18 @@ public class DataNeo4jTestIntegrationTests {
this.applicationContext.getBean(ExampleService.class);
}
+ static class AdditionalSleepWaitStrategy extends HostPortWaitStrategy {
+
+ @Override
+ protected void waitUntilReady() {
+ super.waitUntilReady();
+ try {
+ Thread.sleep(5000);
+ }
+ catch (InterruptedException e) {
+ Thread.currentThread().interrupt();
+ }
+ }
+ }
+
}
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java
index 5c1230a3d0..b591f66d8b 100644
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java
+++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/DataNeo4jTestWithIncludeFilterIntegrationTests.java
@@ -16,11 +16,16 @@
package org.springframework.boot.test.autoconfigure.data.neo4j;
-import org.junit.Rule;
+import java.util.function.Supplier;
+
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
+import org.testcontainers.containers.GenericContainer;
import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.autoconfigure.DockerTestContainer;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringRunner;
@@ -36,9 +41,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataNeo4jTest(includeFilters = @Filter(Service.class))
public class DataNeo4jTestWithIncludeFilterIntegrationTests {
- @Rule
- public Neo4jTestServer server = new Neo4jTestServer(
- new String[] { "org.springframework.boot.test.autoconfigure.data.neo4j" });
+ @ClassRule
+ public static DockerTestContainer genericContainer = new DockerTestContainer<>((Supplier) () -> new FixedHostPortGenericContainer("neo4j:latest")
+ .withFixedExposedPort(7687, 7687)
+ .waitingFor(new DataNeo4jTestIntegrationTests.AdditionalSleepWaitStrategy()).withEnv("NEO4J_AUTH", "none"));
@Autowired
private ExampleService service;
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/Neo4jTestServer.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/Neo4jTestServer.java
deleted file mode 100644
index b7e2c4f74f..0000000000
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/neo4j/Neo4jTestServer.java
+++ /dev/null
@@ -1,114 +0,0 @@
-/*
- * Copyright 2012-2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.test.autoconfigure.data.neo4j;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.Assume;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-import org.neo4j.ogm.config.Configuration;
-import org.neo4j.ogm.session.SessionFactory;
-
-/**
- * {@link TestRule} for working with an optional Neo4j server running on localhost. Make
- * sure to disable authentication if you haven't done so already.
- *
- * @author EddĂș MelĂ©ndez
- * @author Stephane Nicoll
- */
-public class Neo4jTestServer implements TestRule {
-
- private static final Log logger = LogFactory.getLog(Neo4jTestServer.class);
-
- private SessionFactory sessionFactory;
-
- private String[] packages;
-
- public Neo4jTestServer(String[] packages) {
- this.packages = packages;
- }
-
- @Override
- public Statement apply(Statement base, Description description) {
- try {
- this.sessionFactory = createSessionFactory();
- return new Neo4jStatement(base, this.sessionFactory);
- }
- catch (Exception ex) {
- logger.error("No Neo4j server available", ex);
- return new SkipStatement();
- }
- }
-
- public SessionFactory getSessionFactory() {
- return this.sessionFactory;
- }
-
- private SessionFactory createSessionFactory() {
- Configuration configuration = new Configuration.Builder()
- .uri("bolt://localhost:7687").build();
- SessionFactory sessionFactory = new SessionFactory(configuration, this.packages);
- testConnection(sessionFactory);
- return sessionFactory;
- }
-
- private void testConnection(SessionFactory sessionFactory) {
- sessionFactory.openSession().beginTransaction().close();
- }
-
- private static class Neo4jStatement extends Statement {
-
- private final Statement base;
-
- private final SessionFactory sessionFactory;
-
- Neo4jStatement(Statement base, SessionFactory sessionFactory) {
- this.base = base;
- this.sessionFactory = sessionFactory;
- }
-
- @Override
- public void evaluate() throws Throwable {
- try {
- this.base.evaluate();
- }
- finally {
- try {
- this.sessionFactory.close();
- }
- catch (Exception ex) {
- logger.warn("Exception while trying to cleanup neo4j resource", ex);
- }
- }
- }
-
- }
-
- private static class SkipStatement extends Statement {
-
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeTrue(
- "Skipping test due to Neo4j SessionFactory" + " not being available",
- false);
- }
-
- }
-
-}
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java
index da15a32b2d..567019cabc 100644
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java
+++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestIntegrationTests.java
@@ -19,14 +19,16 @@ package org.springframework.boot.test.autoconfigure.data.redis;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
+import org.junit.ClassRule;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.testsupport.rule.RedisTestServer;
+import org.springframework.boot.test.autoconfigure.DockerTestContainer;
import org.springframework.context.ApplicationContext;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.core.RedisOperations;
@@ -43,8 +45,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataRedisTest
public class DataRedisTestIntegrationTests {
- @Rule
- public RedisTestServer redis = new RedisTestServer();
+ @ClassRule
+ public static DockerTestContainer redis = new DockerTestContainer<>(() ->
+ new FixedHostPortGenericContainer("redis:latest")
+ .withFixedExposedPort(6379, 6379));
@Rule
public ExpectedException thrown = ExpectedException.none();
diff --git a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java
index 13146cb7d7..a787577fc9 100644
--- a/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java
+++ b/spring-boot-project/spring-boot-test-autoconfigure/src/test/java/org/springframework/boot/test/autoconfigure/data/redis/DataRedisTestWithIncludeFilterIntegrationTests.java
@@ -16,12 +16,13 @@
package org.springframework.boot.test.autoconfigure.data.redis;
-import org.junit.Rule;
+import org.junit.ClassRule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.testcontainers.containers.FixedHostPortGenericContainer;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.boot.testsupport.rule.RedisTestServer;
+import org.springframework.boot.test.autoconfigure.DockerTestContainer;
import org.springframework.context.annotation.ComponentScan.Filter;
import org.springframework.stereotype.Service;
import org.springframework.test.context.junit4.SpringRunner;
@@ -37,8 +38,10 @@ import static org.assertj.core.api.Assertions.assertThat;
@DataRedisTest(includeFilters = @Filter(Service.class))
public class DataRedisTestWithIncludeFilterIntegrationTests {
- @Rule
- public RedisTestServer redis = new RedisTestServer();
+ @ClassRule
+ public static DockerTestContainer redis = new DockerTestContainer<>(() ->
+ new FixedHostPortGenericContainer("redis:latest")
+ .withFixedExposedPort(6379, 6379));
@Autowired
private ExampleRepository exampleRepository;
diff --git a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/rule/RedisTestServer.java b/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/rule/RedisTestServer.java
deleted file mode 100644
index 9b7215c79c..0000000000
--- a/spring-boot-project/spring-boot-tools/spring-boot-test-support/src/main/java/org/springframework/boot/testsupport/rule/RedisTestServer.java
+++ /dev/null
@@ -1,152 +0,0 @@
-/*
- * Copyright 2012-2017 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.boot.testsupport.rule;
-
-import java.time.Duration;
-
-import org.apache.commons.logging.Log;
-import org.apache.commons.logging.LogFactory;
-import org.junit.Assume;
-import org.junit.rules.TestRule;
-import org.junit.runner.Description;
-import org.junit.runners.model.Statement;
-
-import org.springframework.beans.factory.DisposableBean;
-import org.springframework.data.redis.connection.RedisConnectionFactory;
-import org.springframework.data.redis.connection.RedisStandaloneConfiguration;
-import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
-import org.springframework.data.redis.connection.lettuce.LettuceClientConfiguration;
-import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
-import org.springframework.util.ClassUtils;
-
-/**
- * {@link TestRule} for working with an optional Redis server.
- *
- * @author Eric Bottard
- * @author Gary Russell
- * @author Dave Syer
- * @author Phillip Webb
- */
-public class RedisTestServer implements TestRule {
-
- private static final Log logger = LogFactory.getLog(RedisTestServer.class);
-
- private RedisConnectionFactory connectionFactory;
-
- @Override
- public Statement apply(Statement base, Description description) {
- try {
- this.connectionFactory = createConnectionFactory();
- return new RedisStatement(base, this.connectionFactory);
- }
- catch (Exception ex) {
- logger.error("No Redis server available", ex);
- return new SkipStatement();
- }
- }
-
- private RedisConnectionFactory createConnectionFactory() {
- ClassLoader classLoader = RedisTestServer.class.getClassLoader();
- RedisConnectionFactory cf;
- if (ClassUtils.isPresent("redis.clients.jedis.Jedis", classLoader)) {
- cf = new JedisConnectionFactoryConfiguration().createConnectionFactory();
- }
- else {
- cf = new LettuceConnectionFactoryConfiguration().createConnectionFactory();
- }
-
- testConnection(cf);
- return cf;
- }
-
- private void testConnection(RedisConnectionFactory connectionFactory) {
- connectionFactory.getConnection().close();
- }
-
- /**
- * Return the Redis connection factory or {@code null} if the factory is not
- * available.
- * @return the connection factory or {@code null}
- */
- public RedisConnectionFactory getConnectionFactory() {
- return this.connectionFactory;
- }
-
- private static class RedisStatement extends Statement {
-
- private final Statement base;
-
- private final RedisConnectionFactory connectionFactory;
-
- RedisStatement(Statement base, RedisConnectionFactory connectionFactory) {
- this.base = base;
- this.connectionFactory = connectionFactory;
- }
-
- @Override
- public void evaluate() throws Throwable {
- try {
- this.base.evaluate();
- }
- finally {
- try {
- if (this.connectionFactory instanceof DisposableBean) {
- ((DisposableBean) this.connectionFactory).destroy();
- }
- }
- catch (Exception ex) {
- logger.warn("Exception while trying to cleanup redis resource", ex);
- }
- }
- }
-
- }
-
- private static class SkipStatement extends Statement {
-
- @Override
- public void evaluate() throws Throwable {
- Assume.assumeTrue("Skipping test due to " + "Redis ConnectionFactory"
- + " not being available", false);
- }
-
- }
-
- private static class JedisConnectionFactoryConfiguration {
-
- RedisConnectionFactory createConnectionFactory() {
- JedisConnectionFactory connectionFactory = new JedisConnectionFactory();
- connectionFactory.afterPropertiesSet();
- return connectionFactory;
- }
-
- }
-
- private static class LettuceConnectionFactoryConfiguration {
-
- RedisConnectionFactory createConnectionFactory() {
- LettuceClientConfiguration config = LettuceClientConfiguration.builder()
- .shutdownTimeout(Duration.ofMillis(0)).build();
- LettuceConnectionFactory connectionFactory = new LettuceConnectionFactory(
- new RedisStandaloneConfiguration(), config);
- connectionFactory.afterPropertiesSet();
- return connectionFactory;
- }
-
- }
-
-}