From cf637ce49bbc33f4c6670b259169554b218375fc Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sun, 1 Apr 2018 15:46:24 +0200 Subject: [PATCH 01/15] Support multiple HTTP status codes for HttpWaitStrategy In the context of elasticsearch test containers module, I'd like to add an ElasticsearchWaitStrategy class which extends the HttpWaitStrategy with some default settings so it will be even easier for a end user to start an ElasticsearchTestContainer. Anyway, in this context, I found helpful that the HttpWaitStrategy expects more than on status code. For example, you can imagine running elasticsearch in secure mode or without any security. In which cases the elasticsearch service might answer 200 or 401. This commit proposes this change. --- .../containers/wait/HttpWaitStrategy.java | 14 +++++++++++ .../testcontainers/containers/wait/Wait.java | 2 +- .../wait/strategy/HttpWaitStrategy.java | 24 +++++++++++++++---- .../containers/wait/strategy/Wait.java | 2 +- docs/usage/docker_compose.md | 6 ++--- docs/usage/options.md | 4 ++-- .../vault/VaultContainerTest.java | 4 ++-- 7 files changed, 43 insertions(+), 13 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java index d27133c499d..904ed3644ce 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java @@ -2,6 +2,7 @@ import org.testcontainers.containers.GenericContainer; +import java.util.Arrays; import java.util.function.Predicate; /** @@ -21,12 +22,25 @@ public class HttpWaitStrategy extends GenericContainer.AbstractWaitStrategy { * * @param statusCode the expected status code * @return this + * @deprecated Use {@link #forStatusCodes(Integer...)} instead */ + @Deprecated public HttpWaitStrategy forStatusCode(int statusCode) { delegateStrategy.forStatusCode(statusCode); return this; } + /** + * Waits for one of the given status codes. + * + * @param statusCodes the expected status codes + * @return this + */ + public HttpWaitStrategy forStatusCodes(Integer... statusCodes) { + delegateStrategy.forStatusCodes(statusCodes); + return this; + } + /** * Waits for the given path. * diff --git a/core/src/main/java/org/testcontainers/containers/wait/Wait.java b/core/src/main/java/org/testcontainers/containers/wait/Wait.java index ac96d0b7de5..a1ae2db99ee 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/Wait.java +++ b/core/src/main/java/org/testcontainers/containers/wait/Wait.java @@ -40,7 +40,7 @@ public static HostPortWaitStrategy forListeningPort() { public static HttpWaitStrategy forHttp(String path) { return new HttpWaitStrategy() .forPath(path) - .forStatusCode(HttpURLConnection.HTTP_OK); + .forStatusCodes(HttpURLConnection.HTTP_OK); } /** diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index bf63e9708d5..13dcaa30e68 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -12,6 +12,9 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -32,7 +35,7 @@ public class HttpWaitStrategy extends AbstractWaitStrategy { private static final String AUTH_BASIC = "Basic "; private String path = "/"; - private int statusCode = HttpURLConnection.HTTP_OK; + private List statusCodes = Collections.singletonList(HttpURLConnection.HTTP_OK); private boolean tlsEnabled; private String username; private String password; @@ -43,9 +46,22 @@ public class HttpWaitStrategy extends AbstractWaitStrategy { * * @param statusCode the expected status code * @return this + * @deprecated Use {@link #forStatusCodes(Integer...)} instead */ + @Deprecated public HttpWaitStrategy forStatusCode(int statusCode) { - this.statusCode = statusCode; + forStatusCodes(statusCode); + return this; + } + + /** + * Waits for one of the given status codes. + * + * @param statusCodes the expected status codes + * @return this + */ + public HttpWaitStrategy forStatusCodes(Integer... statusCodes) { + this.statusCodes = Arrays.asList(statusCodes); return this; } @@ -122,7 +138,7 @@ protected void waitUntilReady() { connection.setRequestMethod("GET"); connection.connect(); - if (statusCode != connection.getResponseCode()) { + if (!statusCodes.contains(connection.getResponseCode())) { throw new RuntimeException(String.format("HTTP response code was: %s", connection.getResponseCode())); } @@ -144,7 +160,7 @@ protected void waitUntilReady() { } catch (TimeoutException e) { throw new ContainerLaunchException(String.format( - "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCode)); + "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCodes)); } } diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java index 789927060b3..62afb63692f 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java @@ -36,7 +36,7 @@ public static HostPortWaitStrategy forListeningPort() { public static HttpWaitStrategy forHttp(String path) { return new HttpWaitStrategy() .forPath(path) - .forStatusCode(HttpURLConnection.HTTP_OK); + .forStatusCodes(HttpURLConnection.HTTP_OK); } /** diff --git a/docs/usage/docker_compose.md b/docs/usage/docker_compose.md index b4a6e822106..4c5e779d4e1 100644 --- a/docs/usage/docker_compose.md +++ b/docs/usage/docker_compose.md @@ -72,14 +72,14 @@ public static DockerComposeContainer environment = Wait.forListeningPort().withStartupTimeout(Duration.ofSeconds(30))); ``` -Wait for arbitrary status code on an HTTPS endpoint: +Wait for arbitrary status codes on an HTTPS endpoint: ```java @ClassRule public static DockerComposeContainer environment = new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, Wait.forHttp("/all") - .forStatusCode(301) + .forStatusCodes(200, 401) .usingTls()); ``` @@ -91,7 +91,7 @@ public static DockerComposeContainer environment = .withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort()) .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, Wait.forHttp("/all") - .forStatusCode(301) + .forStatusCodes(200, 401) .usingTls()); ``` diff --git a/docs/usage/options.md b/docs/usage/options.md index 83211cc8f64..36825af24a0 100644 --- a/docs/usage/options.md +++ b/docs/usage/options.md @@ -97,7 +97,7 @@ public static GenericContainer elasticsearch = .waitingFor(Wait.forHttp("/all")); ``` -Wait for arbitrary status code on an HTTPS endpoint: +Wait for 200 or 401 status codes on an HTTPS endpoint: ```java @ClassRule public static GenericContainer elasticsearch = @@ -105,7 +105,7 @@ public static GenericContainer elasticsearch = .withExposedPorts(9200) .waitingFor( Wait.forHttp("/all") - .forStatusCode(301) + .forStatusCodes(200, 401) .usingTls()); ``` diff --git a/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java b/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java index 5e1c55b33ac..022e0f164ab 100644 --- a/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java +++ b/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java @@ -3,7 +3,7 @@ import org.junit.ClassRule; import org.junit.Test; import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.Wait; +import org.testcontainers.containers.wait.strategy.Wait; import java.io.IOException; @@ -30,7 +30,7 @@ public class VaultContainerTest { .withSecretInVault("secret/testing2", "secret_one=password1", "secret_two=password2", "secret_three=password3", "secret_three=password3", "secret_four=password4") - .waitingFor(Wait.forHttp("/v1/secret/testing1").forStatusCode(400)); + .waitingFor(Wait.forHttp("/v1/secret/testing1").forStatusCodes(400)); @Test public void readFirstSecretPathWithCli() throws IOException, InterruptedException { From da44bd9d3a3c79b1b5c7e1602c570c7377cc4b1f Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 6 Apr 2018 16:27:27 +0200 Subject: [PATCH 02/15] Revert changes in Deprecated class --- .../containers/wait/HttpWaitStrategy.java | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java index 904ed3644ce..d27133c499d 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java @@ -2,7 +2,6 @@ import org.testcontainers.containers.GenericContainer; -import java.util.Arrays; import java.util.function.Predicate; /** @@ -22,25 +21,12 @@ public class HttpWaitStrategy extends GenericContainer.AbstractWaitStrategy { * * @param statusCode the expected status code * @return this - * @deprecated Use {@link #forStatusCodes(Integer...)} instead */ - @Deprecated public HttpWaitStrategy forStatusCode(int statusCode) { delegateStrategy.forStatusCode(statusCode); return this; } - /** - * Waits for one of the given status codes. - * - * @param statusCodes the expected status codes - * @return this - */ - public HttpWaitStrategy forStatusCodes(Integer... statusCodes) { - delegateStrategy.forStatusCodes(statusCodes); - return this; - } - /** * Waits for the given path. * From e5ddd1656edb54a3cd550ea804bfd7ca90350f6d Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 6 Apr 2018 16:38:05 +0200 Subject: [PATCH 03/15] Revert changes in Deprecated class --- core/src/main/java/org/testcontainers/containers/wait/Wait.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/Wait.java b/core/src/main/java/org/testcontainers/containers/wait/Wait.java index a1ae2db99ee..ac96d0b7de5 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/Wait.java +++ b/core/src/main/java/org/testcontainers/containers/wait/Wait.java @@ -40,7 +40,7 @@ public static HostPortWaitStrategy forListeningPort() { public static HttpWaitStrategy forHttp(String path) { return new HttpWaitStrategy() .forPath(path) - .forStatusCodes(HttpURLConnection.HTTP_OK); + .forStatusCode(HttpURLConnection.HTTP_OK); } /** From 1b7cbaf979fcbc20be4d144f23e305e2ea185684 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sat, 7 Apr 2018 12:36:22 +0200 Subject: [PATCH 04/15] Make forStatusCode chainable and add forStatusCodeMatching method `forStatusCodeMatching()` comes with a default Predicate which checks status codes that have been provided with `forStatusCode()`. Also copied the default tests which were using the deprecated package to the new one to make sure we test the new methods. --- .../testcontainers/containers/wait/Wait.java | 3 +- .../wait/strategy/HttpWaitStrategy.java | 25 ++--- .../containers/wait/strategy/Wait.java | 3 +- .../strategy/AbstractWaitStrategyTest.java | 93 +++++++++++++++++++ .../wait/strategy/HttpWaitStrategyTest.java | 74 +++++++++++++++ docs/usage/docker_compose.md | 6 +- docs/usage/options.md | 17 +++- 7 files changed, 201 insertions(+), 20 deletions(-) create mode 100644 core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java create mode 100644 core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java diff --git a/core/src/main/java/org/testcontainers/containers/wait/Wait.java b/core/src/main/java/org/testcontainers/containers/wait/Wait.java index ac96d0b7de5..66d055395c5 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/Wait.java +++ b/core/src/main/java/org/testcontainers/containers/wait/Wait.java @@ -39,8 +39,7 @@ public static HostPortWaitStrategy forListeningPort() { */ public static HttpWaitStrategy forHttp(String path) { return new HttpWaitStrategy() - .forPath(path) - .forStatusCode(HttpURLConnection.HTTP_OK); + .forPath(path); } /** diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index 13dcaa30e68..c87fa5040ef 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -12,8 +12,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; -import java.util.Arrays; -import java.util.Collections; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -35,33 +34,35 @@ public class HttpWaitStrategy extends AbstractWaitStrategy { private static final String AUTH_BASIC = "Basic "; private String path = "/"; - private List statusCodes = Collections.singletonList(HttpURLConnection.HTTP_OK); + private List statusCodes = new ArrayList<>(); private boolean tlsEnabled; private String username; private String password; private Predicate responsePredicate; + private Predicate statusCodePredicate = responseCode -> { + // If we did not provide any status code, we assume by default HttpURLConnection.HTTP_OK + return (!statusCodes.isEmpty() || HttpURLConnection.HTTP_OK == responseCode) && + statusCodes.contains(responseCode); + }; /** * Waits for the given status code. * * @param statusCode the expected status code * @return this - * @deprecated Use {@link #forStatusCodes(Integer...)} instead */ - @Deprecated public HttpWaitStrategy forStatusCode(int statusCode) { - forStatusCodes(statusCode); + statusCodes.add(statusCode); return this; } /** - * Waits for one of the given status codes. - * - * @param statusCodes the expected status codes + * Waits for the response to pass the given predicate + * @param statusCodePredicate The predicate to test the response against * @return this */ - public HttpWaitStrategy forStatusCodes(Integer... statusCodes) { - this.statusCodes = Arrays.asList(statusCodes); + public HttpWaitStrategy forStatusCodeMatching(Predicate statusCodePredicate) { + this.statusCodePredicate = statusCodePredicate; return this; } @@ -138,7 +139,7 @@ protected void waitUntilReady() { connection.setRequestMethod("GET"); connection.connect(); - if (!statusCodes.contains(connection.getResponseCode())) { + if (!statusCodePredicate.test(connection.getResponseCode())) { throw new RuntimeException(String.format("HTTP response code was: %s", connection.getResponseCode())); } diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java index 037b3f35bf6..034a83a2949 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/Wait.java @@ -35,8 +35,7 @@ public static HostPortWaitStrategy forListeningPort() { */ public static HttpWaitStrategy forHttp(String path) { return new HttpWaitStrategy() - .forPath(path) - .forStatusCodes(HttpURLConnection.HTTP_OK); + .forPath(path); } /** diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java new file mode 100644 index 00000000000..f0ccb9df25d --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java @@ -0,0 +1,93 @@ +package org.testcontainers.junit.wait.strategy; + +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.rnorth.ducttape.RetryCountExceededException; +import org.rnorth.visibleassertions.VisibleAssertions; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.WaitStrategy; + +import java.time.Duration; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.junit.Assert.assertTrue; + +/** + * Common test methods for {@link WaitStrategy} implementations. + * + * @author Pete Cornish {@literal } + */ +public abstract class AbstractWaitStrategyTest { + private static final long WAIT_TIMEOUT_MILLIS = 3000; + private static final String IMAGE_NAME = "alpine:latest"; + + /** + * Indicates that the WaitStrategy has completed waiting successfully. + */ + private AtomicBoolean ready; + + /** + * Subclasses should return an instance of {@link W} that sets {@code ready} to {@code true}, + * if the wait was successful. + * + * @param ready the AtomicBoolean on which to indicate success + * @return WaitStrategy implementation + */ + @NotNull + protected abstract W buildWaitStrategy(final AtomicBoolean ready); + + @Before + public void setUp() throws Exception { + ready = new AtomicBoolean(false); + } + + /** + * Starts a GenericContainer with the {@link #IMAGE_NAME} image, passing the given {@code shellCommand} as + * a parameter to {@literal sh -c} (the container CMD). + * + * @param shellCommand the shell command to execute + * @return the (unstarted) container + */ + private GenericContainer startContainerWithCommand(String shellCommand) { + final WaitStrategy waitStrategy = buildWaitStrategy(ready) + .withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS)); + + // apply WaitStrategy to container + return new GenericContainer(IMAGE_NAME) + .withExposedPorts(8080) + .withCommand("sh", "-c", shellCommand) + .waitingFor(waitStrategy); + } + + /** + * Expects that the WaitStrategy returns successfully after connection to a container with a listening port. + * + * @param shellCommand the shell command to execute + */ + protected void waitUntilReadyAndSucceed(String shellCommand) { + final GenericContainer container = startContainerWithCommand(shellCommand); + + // start() blocks until successful or timeout + container.start(); + + assertTrue(String.format("Expected container to be ready after timeout of %sms", + WAIT_TIMEOUT_MILLIS), ready.get()); + } + + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection + * to a container with a listening port. + * + * @param shellCommand the shell command to execute + */ + protected void waitUntilReadyAndTimeout(String shellCommand) { + final GenericContainer container = startContainerWithCommand(shellCommand); + + // start() blocks until successful or timeout + VisibleAssertions.assertThrows("an exception is thrown when timeout occurs (" + WAIT_TIMEOUT_MILLIS + "ms)", + ContainerLaunchException.class, + container::start); + + } +} diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java new file mode 100644 index 00000000000..0f32cf68430 --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java @@ -0,0 +1,74 @@ +package org.testcontainers.junit.wait.strategy; + +import org.jetbrains.annotations.NotNull; +import org.junit.Test; +import org.rnorth.ducttape.RetryCountExceededException; +import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; + +import java.util.concurrent.atomic.AtomicBoolean; + +/** + * Tests for {@link HttpWaitStrategy}. + * + * @author Pete Cornish {@literal } + */ +public class HttpWaitStrategyTest extends AbstractWaitStrategyTest { + /** + * newline sequence indicating end of the HTTP header. + */ + private static final String NEWLINE = "\r\n"; + + private static final String GOOD_RESPONSE_BODY = "Good Response Body"; + + /** + * Expects that the WaitStrategy returns successfully after receiving an HTTP 200 response from the container. + */ + @Test + public void testWaitUntilReady_Success() { + waitUntilReadyAndSucceed(createShellCommand("200 OK", GOOD_RESPONSE_BODY)); + } + + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving an HTTP 200 + * response from the container within the timeout period. + */ + @Test + public void testWaitUntilReady_Timeout() { + waitUntilReadyAndTimeout(createShellCommand("400 Bad Request", GOOD_RESPONSE_BODY)); + } + + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not the expected response body + * from the container within the timeout period. + */ + @Test + public void testWaitUntilReady_Timeout_BadResponseBody() { + waitUntilReadyAndTimeout(createShellCommand("200 OK", "Bad Response")); + } + + /** + * @param ready the AtomicBoolean on which to indicate success + * @return the WaitStrategy under test + */ + @NotNull + protected HttpWaitStrategy buildWaitStrategy(final AtomicBoolean ready) { + return new HttpWaitStrategy() { + @Override + protected void waitUntilReady() { + // blocks until ready or timeout occurs + super.waitUntilReady(); + ready.set(true); + } + } + .forResponsePredicate(s -> s.equals(GOOD_RESPONSE_BODY)) + .forStatusCodeMatching(it -> it >= 200 && it < 300 || it == 401); + } + + private String createShellCommand(String header, String responseBody) { + int length = responseBody.getBytes().length; + return "while true; do { echo -e \"HTTP/1.1 "+header+NEWLINE+ + "Content-Type: text/html"+NEWLINE+ + "Content-Length: "+length +NEWLINE+ "\";" + +" echo \""+responseBody+"\";} | nc -lp 8080; done"; + } +} diff --git a/docs/usage/docker_compose.md b/docs/usage/docker_compose.md index 4c5e779d4e1..ee693fa45a4 100644 --- a/docs/usage/docker_compose.md +++ b/docs/usage/docker_compose.md @@ -79,7 +79,8 @@ public static DockerComposeContainer environment = new DockerComposeContainer(new File("src/test/resources/compose-test.yml")) .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, Wait.forHttp("/all") - .forStatusCodes(200, 401) + .forStatusCode(200) + .forStatusCode(401) .usingTls()); ``` @@ -91,7 +92,8 @@ public static DockerComposeContainer environment = .withExposedService("redis_1", REDIS_PORT, Wait.forListeningPort()) .withExposedService("elasticsearch_1", ELASTICSEARCH_PORT, Wait.forHttp("/all") - .forStatusCodes(200, 401) + .forStatusCode(200) + .forStatusCode(401) .usingTls()); ``` diff --git a/docs/usage/options.md b/docs/usage/options.md index 253ec1b8029..d26d5a319d5 100644 --- a/docs/usage/options.md +++ b/docs/usage/options.md @@ -105,9 +105,22 @@ public static GenericContainer elasticsearch = .withExposedPorts(9200) .waitingFor( Wait.forHttp("/all") - .forStatusCodes(200, 401) + .forStatusCode(200) + .forStatusCode(401) .usingTls()); - ``` +``` + +Wait for 200...299 or 401 status codes on an HTTPS endpoint: +```java +@ClassRule +public static GenericContainer elasticsearch = + new GenericContainer("elasticsearch:2.3") + .withExposedPorts(9200) + .waitingFor( + Wait.forHttp("/all") + .forStatusCodeMatching(it -> it >= 200 && it < 300 || it == 401) + .usingTls()); +``` If the used image supports Docker's [Healthcheck](https://docs.docker.com/engine/reference/builder/#healthcheck) feature, you can directly leverage the `healthy` state of the container as your wait condition: ```java From f2d1592050dd86d5cc69a1037dcd8bbace4221ed Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sat, 7 Apr 2018 12:52:54 +0200 Subject: [PATCH 05/15] Fix quality --- .../junit/wait/strategy/HttpWaitStrategyTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java index 0f32cf68430..6bf15354df2 100644 --- a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java @@ -24,7 +24,7 @@ public class HttpWaitStrategyTest extends AbstractWaitStrategyTest Date: Sat, 7 Apr 2018 18:59:25 +0200 Subject: [PATCH 06/15] Fix compile issue --- .../test/java/org/testcontainers/vault/VaultContainerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java b/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java index 022e0f164ab..c6d06f4b21f 100644 --- a/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java +++ b/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java @@ -30,7 +30,7 @@ public class VaultContainerTest { .withSecretInVault("secret/testing2", "secret_one=password1", "secret_two=password2", "secret_three=password3", "secret_three=password3", "secret_four=password4") - .waitingFor(Wait.forHttp("/v1/secret/testing1").forStatusCodes(400)); + .waitingFor(Wait.forHttp("/v1/secret/testing1").forStatusCode(400)); @Test public void readFirstSecretPathWithCli() throws IOException, InterruptedException { From 3b34909af81f4df3ebe76789eeda5087f36fef0b Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sat, 7 Apr 2018 19:06:43 +0200 Subject: [PATCH 07/15] Support both Predicates and Status codes Also use a Set instead of a List to avoid duplicates. --- .../containers/wait/strategy/HttpWaitStrategy.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index c87fa5040ef..e650d03c5f8 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -12,8 +12,7 @@ import java.net.HttpURLConnection; import java.net.URI; import java.net.URL; -import java.util.ArrayList; -import java.util.List; +import java.util.HashSet; import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Predicate; @@ -34,7 +33,7 @@ public class HttpWaitStrategy extends AbstractWaitStrategy { private static final String AUTH_BASIC = "Basic "; private String path = "/"; - private List statusCodes = new ArrayList<>(); + private Set statusCodes = new HashSet<>(); private boolean tlsEnabled; private String username; private String password; @@ -57,12 +56,12 @@ public HttpWaitStrategy forStatusCode(int statusCode) { } /** - * Waits for the response to pass the given predicate + * Waits for the status code to pass the given predicate * @param statusCodePredicate The predicate to test the response against * @return this */ public HttpWaitStrategy forStatusCodeMatching(Predicate statusCodePredicate) { - this.statusCodePredicate = statusCodePredicate; + this.statusCodePredicate.and(statusCodePredicate); return this; } From afe06dcca65f561d59a8bfd9259dcc80ca5308ba Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sat, 7 Apr 2018 19:22:26 +0200 Subject: [PATCH 08/15] Fix setter --- .../containers/wait/strategy/HttpWaitStrategy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index e650d03c5f8..1ca5c817a8a 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -61,7 +61,7 @@ public HttpWaitStrategy forStatusCode(int statusCode) { * @return this */ public HttpWaitStrategy forStatusCodeMatching(Predicate statusCodePredicate) { - this.statusCodePredicate.and(statusCodePredicate); + this.statusCodePredicate = this.statusCodePredicate.and(statusCodePredicate); return this; } From 9f1b28a55e72068641e79afcde08cb9ec17be078 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sun, 8 Apr 2018 06:30:24 +0200 Subject: [PATCH 09/15] Revert unrelated change --- .../test/java/org/testcontainers/vault/VaultContainerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java b/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java index c6d06f4b21f..5e1c55b33ac 100644 --- a/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java +++ b/modules/vault/src/test/java/org/testcontainers/vault/VaultContainerTest.java @@ -3,7 +3,7 @@ import org.junit.ClassRule; import org.junit.Test; import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.containers.wait.Wait; import java.io.IOException; From bb393730a59f06d3365abcb3f4899b5ece6e599e Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sun, 8 Apr 2018 06:35:13 +0200 Subject: [PATCH 10/15] Fix error message (note that with predicates it might be incorrect though) --- .../containers/wait/strategy/HttpWaitStrategy.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index 1ca5c817a8a..ffb667424d4 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -160,7 +160,8 @@ protected void waitUntilReady() { } catch (TimeoutException e) { throw new ContainerLaunchException(String.format( - "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCodes)); + "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCodes == null ? + HttpURLConnection.HTTP_OK : statusCodes)); } } From 10ca9cdc6f38ef66b027d9471144ae97a8285fa3 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Sun, 8 Apr 2018 06:43:20 +0200 Subject: [PATCH 11/15] Fix default predicate --- .../containers/wait/strategy/HttpWaitStrategy.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index ffb667424d4..08695902ad1 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -40,8 +40,8 @@ public class HttpWaitStrategy extends AbstractWaitStrategy { private Predicate responsePredicate; private Predicate statusCodePredicate = responseCode -> { // If we did not provide any status code, we assume by default HttpURLConnection.HTTP_OK - return (!statusCodes.isEmpty() || HttpURLConnection.HTTP_OK == responseCode) && - statusCodes.contains(responseCode); + if (statusCodes.isEmpty() && HttpURLConnection.HTTP_OK == responseCode) return true; + return statusCodes.contains(responseCode); }; /** @@ -160,7 +160,7 @@ protected void waitUntilReady() { } catch (TimeoutException e) { throw new ContainerLaunchException(String.format( - "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCodes == null ? + "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCodes.isEmpty() ? HttpURLConnection.HTTP_OK : statusCodes)); } } From 370c35a322dd09562b7dcd4977c06cc8d05466ff Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 13 Apr 2018 11:04:28 +0200 Subject: [PATCH 12/15] Move WaitStrategy tests to the right package And stop testing deprecated methods --- .../junit/wait/AbstractWaitStrategyTest.java | 93 ------------------- .../junit/wait/HostPortWaitStrategyTest.java | 48 ---------- .../junit/wait/HttpWaitStrategyTest.java | 72 -------------- .../LogMessageWaitStrategyTest.java | 4 +- 4 files changed, 2 insertions(+), 215 deletions(-) delete mode 100644 core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java delete mode 100644 core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java delete mode 100644 core/src/test/java/org/testcontainers/junit/wait/HttpWaitStrategyTest.java rename core/src/test/java/org/testcontainers/junit/wait/{ => strategy}/LogMessageWaitStrategyTest.java (90%) diff --git a/core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java deleted file mode 100644 index 6578a430b69..00000000000 --- a/core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java +++ /dev/null @@ -1,93 +0,0 @@ -package org.testcontainers.junit.wait; - -import org.jetbrains.annotations.NotNull; -import org.junit.Before; -import org.rnorth.ducttape.RetryCountExceededException; -import org.rnorth.visibleassertions.VisibleAssertions; -import org.testcontainers.containers.ContainerLaunchException; -import org.testcontainers.containers.GenericContainer; -import org.testcontainers.containers.wait.WaitStrategy; - -import java.time.Duration; -import java.util.concurrent.atomic.AtomicBoolean; - -import static org.junit.Assert.*; - -/** - * Common test methods for {@link WaitStrategy} implementations. - * - * @author Pete Cornish {@literal } - */ -public abstract class AbstractWaitStrategyTest { - private static final long WAIT_TIMEOUT_MILLIS = 3000; - private static final String IMAGE_NAME = "alpine:latest"; - - /** - * Indicates that the WaitStrategy has completed waiting successfully. - */ - private AtomicBoolean ready; - - /** - * Subclasses should return an instance of {@link W} that sets {@code ready} to {@code true}, - * if the wait was successful. - * - * @param ready the AtomicBoolean on which to indicate success - * @return WaitStrategy implementation - */ - @NotNull - protected abstract W buildWaitStrategy(final AtomicBoolean ready); - - @Before - public void setUp() throws Exception { - ready = new AtomicBoolean(false); - } - - /** - * Starts a GenericContainer with the {@link #IMAGE_NAME} image, passing the given {@code shellCommand} as - * a parameter to {@literal sh -c} (the container CMD). - * - * @param shellCommand the shell command to execute - * @return the (unstarted) container - */ - private GenericContainer startContainerWithCommand(String shellCommand) { - final WaitStrategy waitStrategy = buildWaitStrategy(ready) - .withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS)); - - // apply WaitStrategy to container - return new GenericContainer(IMAGE_NAME) - .withExposedPorts(8080) - .withCommand("sh", "-c", shellCommand) - .waitingFor(waitStrategy); - } - - /** - * Expects that the WaitStrategy returns successfully after connection to a container with a listening port. - * - * @param shellCommand the shell command to execute - */ - protected void waitUntilReadyAndSucceed(String shellCommand) { - final GenericContainer container = startContainerWithCommand(shellCommand); - - // start() blocks until successful or timeout - container.start(); - - assertTrue(String.format("Expected container to be ready after timeout of %sms", - WAIT_TIMEOUT_MILLIS), ready.get()); - } - - /** - * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection - * to a container with a listening port. - * - * @param shellCommand the shell command to execute - */ - protected void waitUntilReadyAndTimeout(String shellCommand) { - final GenericContainer container = startContainerWithCommand(shellCommand); - - // start() blocks until successful or timeout - VisibleAssertions.assertThrows("an exception is thrown when timeout occurs (" + WAIT_TIMEOUT_MILLIS + "ms)", - ContainerLaunchException.class, - container::start); - - } -} diff --git a/core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java deleted file mode 100644 index 114750c6d0f..00000000000 --- a/core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.testcontainers.junit.wait; - -import org.jetbrains.annotations.NotNull; -import org.junit.Test; -import org.rnorth.ducttape.RetryCountExceededException; -import org.testcontainers.containers.wait.HostPortWaitStrategy; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Tests for {@link HostPortWaitStrategy}. - * - * @author Pete Cornish {@literal } - */ -public class HostPortWaitStrategyTest extends AbstractWaitStrategyTest { - /** - * Expects that the WaitStrategy returns successfully after connection to a container with a listening port. - */ - @Test - public void testWaitUntilReady_Success() { - waitUntilReadyAndSucceed("while true; do nc -lp 8080; done"); - } - - /** - * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection - * to a container with a listening port within the timeout period. - */ - @Test - public void testWaitUntilReady_Timeout() { - waitUntilReadyAndTimeout("exit 1"); - } - - /** - * @param ready the AtomicBoolean on which to indicate success - * @return the WaitStrategy under test - */ - @NotNull - protected HostPortWaitStrategy buildWaitStrategy(final AtomicBoolean ready) { - return new HostPortWaitStrategy() { - @Override - protected void waitUntilReady() { - // blocks until ready or timeout occurs - super.waitUntilReady(); - ready.set(true); - } - }; - } -} diff --git a/core/src/test/java/org/testcontainers/junit/wait/HttpWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/HttpWaitStrategyTest.java deleted file mode 100644 index e434ff90466..00000000000 --- a/core/src/test/java/org/testcontainers/junit/wait/HttpWaitStrategyTest.java +++ /dev/null @@ -1,72 +0,0 @@ -package org.testcontainers.junit.wait; - -import org.jetbrains.annotations.NotNull; -import org.junit.Test; -import org.rnorth.ducttape.RetryCountExceededException; -import org.testcontainers.containers.wait.HttpWaitStrategy; - -import java.util.concurrent.atomic.AtomicBoolean; - -/** - * Tests for {@link HttpWaitStrategy}. - * - * @author Pete Cornish {@literal } - */ -public class HttpWaitStrategyTest extends AbstractWaitStrategyTest { - /** - * newline sequence indicating end of the HTTP header. - */ - private static final String NEWLINE = "\r\n"; - - private static final String GOOD_RESPONSE_BODY = "Good Response Body"; - - /** - * Expects that the WaitStrategy returns successfully after receiving an HTTP 200 response from the container. - */ - @Test - public void testWaitUntilReady_Success() { - waitUntilReadyAndSucceed(createShellCommand("200 OK", GOOD_RESPONSE_BODY)); - } - - /** - * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving an HTTP 200 - * response from the container within the timeout period. - */ - @Test - public void testWaitUntilReady_Timeout() { - waitUntilReadyAndTimeout(createShellCommand("400 Bad Request", GOOD_RESPONSE_BODY)); - } - - /** - * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not the expected response body - * from the container within the timeout period. - */ - @Test - public void testWaitUntilReady_Timeout_BadResponseBody() { - waitUntilReadyAndTimeout(createShellCommand("200 OK", "Bad Response")); - } - - /** - * @param ready the AtomicBoolean on which to indicate success - * @return the WaitStrategy under test - */ - @NotNull - protected HttpWaitStrategy buildWaitStrategy(final AtomicBoolean ready) { - return new HttpWaitStrategy() { - @Override - protected void waitUntilReady() { - // blocks until ready or timeout occurs - super.waitUntilReady(); - ready.set(true); - } - }.forResponsePredicate(s -> s.equals(GOOD_RESPONSE_BODY)); - } - - private String createShellCommand(String header, String responseBody) { - int length = responseBody.getBytes().length; - return "while true; do { echo -e \"HTTP/1.1 "+header+NEWLINE+ - "Content-Type: text/html"+NEWLINE+ - "Content-Length: "+length +NEWLINE+ "\";" - +" echo \""+responseBody+"\";} | nc -lp 8080; done"; - } -} diff --git a/core/src/test/java/org/testcontainers/junit/wait/LogMessageWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/LogMessageWaitStrategyTest.java similarity index 90% rename from core/src/test/java/org/testcontainers/junit/wait/LogMessageWaitStrategyTest.java rename to core/src/test/java/org/testcontainers/junit/wait/strategy/LogMessageWaitStrategyTest.java index 4596f546d15..7154bab06fa 100644 --- a/core/src/test/java/org/testcontainers/junit/wait/LogMessageWaitStrategyTest.java +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/LogMessageWaitStrategyTest.java @@ -1,8 +1,8 @@ -package org.testcontainers.junit.wait; +package org.testcontainers.junit.wait.strategy; import org.jetbrains.annotations.NotNull; import org.junit.Test; -import org.testcontainers.containers.wait.LogMessageWaitStrategy; +import org.testcontainers.containers.wait.strategy.LogMessageWaitStrategy; import java.util.concurrent.atomic.AtomicBoolean; From 808dcf82584815aa0ab4fc41bdf1554babcf4bad Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 13 Apr 2018 11:46:35 +0200 Subject: [PATCH 13/15] Add a test (and fix the bug!) --- .../containers/wait/strategy/HttpWaitStrategy.java | 2 +- .../junit/wait/strategy/HttpWaitStrategyTest.java | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java index 08695902ad1..f74b51c602b 100644 --- a/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java +++ b/core/src/main/java/org/testcontainers/containers/wait/strategy/HttpWaitStrategy.java @@ -61,7 +61,7 @@ public HttpWaitStrategy forStatusCode(int statusCode) { * @return this */ public HttpWaitStrategy forStatusCodeMatching(Predicate statusCodePredicate) { - this.statusCodePredicate = this.statusCodePredicate.and(statusCodePredicate); + this.statusCodePredicate = this.statusCodePredicate.or(statusCodePredicate); return this; } diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java index 6bf15354df2..133147e26a6 100644 --- a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java @@ -6,6 +6,7 @@ import org.testcontainers.containers.wait.strategy.HttpWaitStrategy; import java.util.concurrent.atomic.AtomicBoolean; +import java.util.function.Predicate; /** * Tests for {@link HttpWaitStrategy}. @@ -28,6 +29,15 @@ public void testWaitUntilReadyWithSuccess() { waitUntilReadyAndSucceed(createShellCommand("200 OK", GOOD_RESPONSE_BODY)); } + /** + * Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container. + * This 401 response is checked with a lambda using {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)} + */ + @Test + public void testWaitUntilReadyWithUnauthorized() { + waitUntilReadyAndSucceed(createShellCommand("401 UNAUTHORIZED", GOOD_RESPONSE_BODY)); + } + /** * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving an HTTP 200 * response from the container within the timeout period. From d6837f7b959e02646c024008cc9bb0468fc79511 Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 13 Apr 2018 12:35:54 +0200 Subject: [PATCH 14/15] Add more tests --- .../strategy/AbstractWaitStrategyTest.java | 52 +++++++++++---- .../wait/strategy/HttpWaitStrategyTest.java | 64 +++++++++++++++++-- 2 files changed, 97 insertions(+), 19 deletions(-) diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java index f0ccb9df25d..c6216f0b184 100644 --- a/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/AbstractWaitStrategyTest.java @@ -19,13 +19,13 @@ * @author Pete Cornish {@literal } */ public abstract class AbstractWaitStrategyTest { - private static final long WAIT_TIMEOUT_MILLIS = 3000; - private static final String IMAGE_NAME = "alpine:latest"; + static final long WAIT_TIMEOUT_MILLIS = 3000; + static final String IMAGE_NAME = "alpine:latest"; /** * Indicates that the WaitStrategy has completed waiting successfully. */ - private AtomicBoolean ready; + AtomicBoolean ready; /** * Subclasses should return an instance of {@link W} that sets {@code ready} to {@code true}, @@ -50,14 +50,23 @@ public void setUp() throws Exception { * @return the (unstarted) container */ private GenericContainer startContainerWithCommand(String shellCommand) { - final WaitStrategy waitStrategy = buildWaitStrategy(ready) - .withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS)); + return startContainerWithCommand(shellCommand, buildWaitStrategy(ready)); + } + /** + * Starts a GenericContainer with the {@link #IMAGE_NAME} image, passing the given {@code shellCommand} as + * a parameter to {@literal sh -c} (the container CMD) and apply a give wait strategy. + * Note that the timeout will be overwritten if any with {@link #WAIT_TIMEOUT_MILLIS}. + * @param shellCommand the shell command to execute + * @param waitStrategy The wait strategy to apply + * @return the (unstarted) container + */ + protected GenericContainer startContainerWithCommand(String shellCommand, WaitStrategy waitStrategy) { // apply WaitStrategy to container return new GenericContainer(IMAGE_NAME) .withExposedPorts(8080) .withCommand("sh", "-c", shellCommand) - .waitingFor(waitStrategy); + .waitingFor(waitStrategy.withStartupTimeout(Duration.ofMillis(WAIT_TIMEOUT_MILLIS))); } /** @@ -66,13 +75,7 @@ private GenericContainer startContainerWithCommand(String shellCommand) { * @param shellCommand the shell command to execute */ protected void waitUntilReadyAndSucceed(String shellCommand) { - final GenericContainer container = startContainerWithCommand(shellCommand); - - // start() blocks until successful or timeout - container.start(); - - assertTrue(String.format("Expected container to be ready after timeout of %sms", - WAIT_TIMEOUT_MILLIS), ready.get()); + waitUntilReadyAndSucceed(startContainerWithCommand(shellCommand)); } /** @@ -82,12 +85,33 @@ protected void waitUntilReadyAndSucceed(String shellCommand) { * @param shellCommand the shell command to execute */ protected void waitUntilReadyAndTimeout(String shellCommand) { - final GenericContainer container = startContainerWithCommand(shellCommand); + waitUntilReadyAndTimeout(startContainerWithCommand(shellCommand)); + } + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection + * to a container with a listening port. + * + * @param container the container to start + */ + protected void waitUntilReadyAndTimeout(GenericContainer container) { // start() blocks until successful or timeout VisibleAssertions.assertThrows("an exception is thrown when timeout occurs (" + WAIT_TIMEOUT_MILLIS + "ms)", ContainerLaunchException.class, container::start); } + + /** + * Expects that the WaitStrategy returns successfully after connection to a container with a listening port. + * + * @param container the container to start + */ + protected void waitUntilReadyAndSucceed(GenericContainer container) { + // start() blocks until successful or timeout + container.start(); + + assertTrue(String.format("Expected container to be ready after timeout of %sms", + WAIT_TIMEOUT_MILLIS), ready.get()); + } } diff --git a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java index 133147e26a6..56c58fa0b2d 100644 --- a/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java +++ b/core/src/test/java/org/testcontainers/junit/wait/strategy/HttpWaitStrategyTest.java @@ -34,8 +34,54 @@ public void testWaitUntilReadyWithSuccess() { * This 401 response is checked with a lambda using {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)} */ @Test - public void testWaitUntilReadyWithUnauthorized() { - waitUntilReadyAndSucceed(createShellCommand("401 UNAUTHORIZED", GOOD_RESPONSE_BODY)); + public void testWaitUntilReadyWithUnauthorizedWithLambda() { + waitUntilReadyAndSucceed(startContainerWithCommand(createShellCommand("401 UNAUTHORIZED", GOOD_RESPONSE_BODY), + createHttpWaitStrategy(ready) + .forStatusCodeMatching(it -> it >= 200 && it < 300 || it == 401) + )); + } + + /** + * Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container. + * This 401 response is checked with many status codes using {@link HttpWaitStrategy#forStatusCode(int)} + */ + @Test + public void testWaitUntilReadyWithManyStatusCodes() { + waitUntilReadyAndSucceed(startContainerWithCommand(createShellCommand("401 UNAUTHORIZED", GOOD_RESPONSE_BODY), + createHttpWaitStrategy(ready) + .forStatusCode(300) + .forStatusCode(401) + .forStatusCode(500) + )); + } + + /** + * Expects that the WaitStrategy returns successfully after receiving an HTTP 401 response from the container. + * This 401 response is checked with with many status codes using {@link HttpWaitStrategy#forStatusCode(int)} + * and a lambda using {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)} + */ + @Test + public void testWaitUntilReadyWithManyStatusCodesAndLambda() { + waitUntilReadyAndSucceed(startContainerWithCommand(createShellCommand("401 UNAUTHORIZED", GOOD_RESPONSE_BODY), + createHttpWaitStrategy(ready) + .forStatusCode(300) + .forStatusCode(500) + .forStatusCodeMatching(it -> it == 401) + )); + } + + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving any of the + * error code defined with {@link HttpWaitStrategy#forStatusCode(int)} + * and {@link HttpWaitStrategy#forStatusCodeMatching(Predicate)} + */ + @Test + public void testWaitUntilReadyWithTimeoutAndWithManyStatusCodesAndLambda() { + waitUntilReadyAndTimeout(startContainerWithCommand(createShellCommand("401 UNAUTHORIZED", GOOD_RESPONSE_BODY), + createHttpWaitStrategy(ready) + .forStatusCode(300) + .forStatusCodeMatching(it -> it == 500) + )); } /** @@ -62,6 +108,16 @@ public void testWaitUntilReadyWithTimeoutAndBadResponseBody() { */ @NotNull protected HttpWaitStrategy buildWaitStrategy(final AtomicBoolean ready) { + return createHttpWaitStrategy(ready) + .forResponsePredicate(s -> s.equals(GOOD_RESPONSE_BODY)); + } + + /** + * Create a HttpWaitStrategy instance with a waitUntilReady implementation + * @param ready Indicates that the WaitStrategy has completed waiting successfully. + * @return the HttpWaitStrategy instance + */ + private HttpWaitStrategy createHttpWaitStrategy(final AtomicBoolean ready) { return new HttpWaitStrategy() { @Override protected void waitUntilReady() { @@ -69,9 +125,7 @@ protected void waitUntilReady() { super.waitUntilReady(); ready.set(true); } - } - .forResponsePredicate(s -> s.equals(GOOD_RESPONSE_BODY)) - .forStatusCodeMatching(it -> it >= 200 && it < 300 || it == 401); + }; } private String createShellCommand(String header, String responseBody) { From 6b24c2a7dce3f35c4594100fb4e72c78f5103bfb Mon Sep 17 00:00:00 2001 From: David Pilato Date: Fri, 13 Apr 2018 14:03:45 +0200 Subject: [PATCH 15/15] Add entry in changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fa67b433a9d..33fc40968ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ All notable changes to this project will be documented in this file. ### Changed +- Support multiple HTTP status codes for HttpWaitStrategy ([\#630](https://github.com/testcontainers/testcontainers-java/issues/630)) + ## [1.7.0] - 2018-04-07 ### Fixed