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; - } - - } - -}