From 1613efe99703f0afdad4ee08063c05a054d64604 Mon Sep 17 00:00:00 2001 From: Pete Cornish Date: Thu, 28 Apr 2016 17:52:44 +0100 Subject: [PATCH] Adds pluggable wait strategy, to enable containers to be checked for readiness using arbitrary logic. Adds HTTP(S) wait strategy to wait for a particular endpoint to be available. --- AUTHORS | 3 +- .../containers/ContainerLaunchException.java | 2 +- .../containers/GenericContainer.java | 132 +++++++++++---- .../containers/wait/HostPortWaitStrategy.java | 44 +++++ .../containers/wait/HttpWaitStrategy.java | 158 ++++++++++++++++++ .../testcontainers/containers/wait/Wait.java | 54 ++++++ .../containers/wait/WaitStrategy.java | 25 +++ .../junit/wait/AbstractWaitStrategyTest.java | 96 +++++++++++ .../junit/wait/HostPortWaitStrategyTest.java | 52 ++++++ .../junit/wait/HttpWaitStrategyTest.java | 57 +++++++ pom.xml | 5 + 11 files changed, 589 insertions(+), 39 deletions(-) create mode 100644 core/src/main/java/org/testcontainers/containers/wait/HostPortWaitStrategy.java create mode 100644 core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java create mode 100644 core/src/main/java/org/testcontainers/containers/wait/Wait.java create mode 100644 core/src/main/java/org/testcontainers/containers/wait/WaitStrategy.java create mode 100644 core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java create mode 100644 core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java create mode 100644 core/src/test/java/org/testcontainers/junit/wait/HttpWaitStrategyTest.java diff --git a/AUTHORS b/AUTHORS index 414001d39e1..9740f417747 100644 --- a/AUTHORS +++ b/AUTHORS @@ -6,4 +6,5 @@ Matthieu Baechler Krystian Nowak Viktor Schulz Asaf Mesika -Sergei Egorov \ No newline at end of file +Sergei Egorov +Pete Cornish \ No newline at end of file diff --git a/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java b/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java index 0e49bea467b..9943d9f612b 100644 --- a/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java +++ b/core/src/main/java/org/testcontainers/containers/ContainerLaunchException.java @@ -3,7 +3,7 @@ /** * AN exception that may be raised during launch of a container. */ -class ContainerLaunchException extends RuntimeException { +public class ContainerLaunchException extends RuntimeException { public ContainerLaunchException(String message) { super(message); diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index cfc76f3ed5f..f566bdab623 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -16,7 +16,6 @@ import org.jetbrains.annotations.Nullable; import org.junit.runner.Description; -import org.rnorth.ducttape.TimeoutException; import org.rnorth.ducttape.ratelimits.RateLimiter; import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; import org.rnorth.ducttape.unreliables.Unreliables; @@ -28,12 +27,13 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.containers.output.ToStringConsumer; import org.testcontainers.containers.traits.LinkableContainer; +import org.testcontainers.containers.wait.Wait; +import org.testcontainers.containers.wait.WaitStrategy; import org.testcontainers.images.RemoteDockerImage; import org.testcontainers.utility.*; import java.io.File; import java.io.IOException; -import java.net.Socket; import java.net.URL; import java.nio.charset.Charset; import java.nio.file.Path; @@ -63,7 +63,6 @@ public class GenericContainer extends FailureDetectingExternalResource implement public static final int CONTAINER_RUNNING_TIMEOUT_SEC = 30; - /* * Default settings */ @@ -91,9 +90,6 @@ public class GenericContainer extends FailureDetectingExternalResource implement @NonNull private Map linkedContainers = new HashMap<>(); - @NonNull - private Duration startupTimeout = Duration.ofSeconds(60); - @NonNull private Duration minimumRunningDuration = null; @@ -113,6 +109,12 @@ public class GenericContainer extends FailureDetectingExternalResource implement protected String containerId; protected String containerName; + /** + * The approach to determine if the container is ready. + */ + @NonNull + protected WaitStrategy waitStrategy = Wait.defaultWaitStrategy(); + @Nullable private InspectContainerResponse containerInfo; @@ -297,6 +299,9 @@ protected void containerIsStarting(InspectContainerResponse containerInfo) { protected void containerIsStarted(InspectContainerResponse containerInfo) { } + /** + * @return the port on which to check if the container is ready + */ protected Integer getLivenessCheckPort() { if (exposedPorts.size() > 0) { return getMappedPort(exposedPorts.get(0)); @@ -349,42 +354,37 @@ private void applyConfiguration(CreateContainerCmd createCommand) { } /** - * Wait until the container has started. The default implementation simply - * waits for a port to start listening; subclasses may override if more - * sophisticated behaviour is required. + * Specify the {@link WaitStrategy} to use to determine if the container is ready. + * + * @see Wait#defaultWaitStrategy() + * @param waitStrategy the WaitStrategy to use + * @return this */ - protected void waitUntilContainerStarted() { - waitForListeningPort(DockerClientFactory.instance().dockerHostIpAddress(), getLivenessCheckPort()); + public GenericContainer waitingFor(@NonNull WaitStrategy waitStrategy) { + this.waitStrategy = waitStrategy; + return this; } /** - * Waits for a port to start listening for incoming connections. + * The {@link WaitStrategy} to use to determine if the container is ready. + * Defaults to {@link Wait#defaultWaitStrategy()}. * - * @param ipAddress the IP address to attempt to connect to - * @param port the port which will start accepting connections + * @return the {@link WaitStrategy} to use */ - protected void waitForListeningPort(String ipAddress, Integer port) { - - if (port == null) { - return; - } - - try { - Unreliables.retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> { - DOCKER_CLIENT_RATE_LIMITER.doWhenReady(() -> { - try { - new Socket(ipAddress, port).close(); - } catch (IOException e) { - throw new RuntimeException(e); - } - }); - return true; - }); - } catch (TimeoutException e) { - throw new ContainerLaunchException("Timed out waiting for container port to open (" + ipAddress + ":" + port + " should be listening)"); - } + protected WaitStrategy getWaitStrategy() { + return waitStrategy; } + /** + * Wait until the container has started. The default implementation simply + * waits for a port to start listening; other implementations are available + * as implementations of {@link WaitStrategy} + * + * @see #waitingFor(WaitStrategy) + */ + protected void waitUntilContainerStarted() { + getWaitStrategy().waitUntilReady(this); + } /** * Set the command that should be run in the container @@ -539,13 +539,13 @@ public GenericContainer withClasspathResourceMapping(String resourcePath, String /** * Set the duration of waiting time until container treated as started. - * @see GenericContainer#waitForListeningPort(String, Integer) + * @see WaitStrategy#waitUntilReady(GenericContainer) * * @param startupTimeout timeout * @return this */ public GenericContainer withStartupTimeout(Duration startupTimeout) { - this.setStartupTimeout(startupTimeout); + getWaitStrategy().withStartupTimeout(startupTimeout); return this; } @@ -562,7 +562,6 @@ public String getContainerIpAddress() { * Only consider a container to have successfully started if it has been running for this duration. The default * value is null; if that's the value, ignore this check. */ - public GenericContainer withMinimumRunningDuration(Duration minimumRunningDuration) { this.setMinimumRunningDuration(minimumRunningDuration); return this; @@ -794,4 +793,63 @@ public String getStderr() { return stderr; } } + + /** + * Convenience class with access to non-public members of GenericContainer. + */ + public static abstract class AbstractWaitStrategy implements WaitStrategy { + protected GenericContainer container; + + @NonNull + protected Duration startupTimeout = Duration.ofSeconds(60); + + /** + * Wait until the container has started. + * + * @param container the container for which to wait + */ + @Override + public void waitUntilReady(GenericContainer container) { + this.container = container; + waitUntilReady(); + } + + /** + * Wait until {@link #container} has started. + */ + protected abstract void waitUntilReady(); + + /** + * Set the duration of waiting time until container treated as started. + * + * @param startupTimeout timeout + * @return this + * @see WaitStrategy#waitUntilReady(GenericContainer) + */ + public WaitStrategy withStartupTimeout(Duration startupTimeout) { + this.startupTimeout = startupTimeout; + return this; + } + + /** + * @return the container's logger + */ + protected Logger logger() { + return container.logger(); + } + + /** + * @return the port on which to check if the container is ready + */ + protected Integer getLivenessCheckPort() { + return container.getLivenessCheckPort(); + } + + /** + * @return the rate limiter to use + */ + protected RateLimiter getRateLimiter() { + return DOCKER_CLIENT_RATE_LIMITER; + } + } } diff --git a/core/src/main/java/org/testcontainers/containers/wait/HostPortWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/HostPortWaitStrategy.java new file mode 100644 index 00000000000..d334009f191 --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/wait/HostPortWaitStrategy.java @@ -0,0 +1,44 @@ +package org.testcontainers.containers.wait; + +import org.rnorth.ducttape.TimeoutException; +import org.rnorth.ducttape.unreliables.Unreliables; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.GenericContainer; + +import java.io.IOException; +import java.net.Socket; +import java.util.concurrent.TimeUnit; + +/** + * Waits until a socket connection can be established on a port exposed or mapped by the container. + * + * @author richardnorth + */ +public class HostPortWaitStrategy extends GenericContainer.AbstractWaitStrategy { + @Override + protected void waitUntilReady() { + final Integer port = getLivenessCheckPort(); + if (null == port) { + return; + } + + final String ipAddress = DockerClientFactory.instance().dockerHostIpAddress(); + try { + Unreliables.retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> { + getRateLimiter().doWhenReady(() -> { + try { + new Socket(ipAddress, port).close(); + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return true; + }); + + } catch (TimeoutException e) { + throw new ContainerLaunchException("Timed out waiting for container port to open (" + + ipAddress + ":" + port + " should be listening)"); + } + } +} diff --git a/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java new file mode 100644 index 00000000000..4edd695cf16 --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/wait/HttpWaitStrategy.java @@ -0,0 +1,158 @@ +package org.testcontainers.containers.wait; + +import com.google.common.base.Strings; +import com.google.common.io.BaseEncoding; +import org.rnorth.ducttape.TimeoutException; +import org.testcontainers.DockerClientFactory; +import org.testcontainers.containers.ContainerLaunchException; +import org.testcontainers.containers.GenericContainer; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.net.URI; +import java.net.URL; +import java.util.concurrent.TimeUnit; + +import static org.rnorth.ducttape.unreliables.Unreliables.retryUntilSuccess; + +/** + * Waits until an HTTP(S) endpoint returns a given status code. + * + * @author Pete Cornish {@literal } + */ +public class HttpWaitStrategy extends GenericContainer.AbstractWaitStrategy { + /** + * Authorization HTTP header. + */ + private static final String HEADER_AUTHORIZATION = "Authorization"; + + /** + * Basic Authorization scheme prefix. + */ + private static final String AUTH_BASIC = "Basic "; + + private String path = "/"; + private int statusCode = HttpURLConnection.HTTP_OK; + private boolean tlsEnabled; + private String username; + private String password; + + /** + * Waits for the given status code. + * + * @param statusCode the expected status code + * @return this + */ + public HttpWaitStrategy forStatusCode(int statusCode) { + this.statusCode = statusCode; + return this; + } + + /** + * Waits for the given path. + * + * @param path the path to check + * @return this + */ + public HttpWaitStrategy forPath(String path) { + this.path = path; + return this; + } + + /** + * Indicates that the status check should use HTTPS. + * + * @return this + */ + public HttpWaitStrategy usingTls() { + this.tlsEnabled = true; + return this; + } + + /** + * Authenticate with HTTP Basic Authorization credentials. + * + * @param username the username + * @param password the password + * @return this + */ + public HttpWaitStrategy withBasicCredentials(String username, String password) { + this.username = username; + this.password = password; + return this; + } + + @Override + protected void waitUntilReady() { + final Integer livenessCheckPort = getLivenessCheckPort(); + if (null == livenessCheckPort) { + logger().warn("No exposed ports or mapped ports - cannot wait for status"); + return; + } + + final String uri = buildLivenessUri(livenessCheckPort).toString(); + logger().info("Waiting for {} seconds for URL: {}", startupTimeout.getSeconds(), uri); + + // try to connect to the URL + try { + retryUntilSuccess((int) startupTimeout.getSeconds(), TimeUnit.SECONDS, () -> { + getRateLimiter().doWhenReady(() -> { + try { + final HttpURLConnection connection = (HttpURLConnection) new URL(uri).openConnection(); + + // authenticate + if (!Strings.isNullOrEmpty(username)) { + connection.setRequestProperty(HEADER_AUTHORIZATION, buildAuthString(username, password)); + connection.setUseCaches(false); + } + + connection.setRequestMethod("GET"); + connection.connect(); + + if (statusCode != connection.getResponseCode()) { + throw new RuntimeException(String.format("HTTP response code was: %s", + connection.getResponseCode())); + } + + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + return true; + }); + + } catch (TimeoutException e) { + throw new ContainerLaunchException(String.format( + "Timed out waiting for URL to be accessible (%s should return HTTP %s)", uri, statusCode)); + } + } + + /** + * Build the URI on which to check if the container is ready. + * + * @param livenessCheckPort the liveness port + * @return the liveness URI + */ + private URI buildLivenessUri(int livenessCheckPort) { + final String scheme = (tlsEnabled ? "https" : "http") + "://"; + final String host = DockerClientFactory.instance().dockerHostIpAddress(); + + final String portSuffix; + if ((tlsEnabled && 443 == livenessCheckPort) || (!tlsEnabled && 80 == livenessCheckPort)) { + portSuffix = ""; + } else { + portSuffix = ":" + String.valueOf(livenessCheckPort); + } + + return URI.create(scheme + host + portSuffix + path); + } + + /** + * @param username the username + * @param password the password + * @return a basic authentication string for the given credentials + */ + private String buildAuthString(String username, String password) { + return AUTH_BASIC + BaseEncoding.base64().encode((username + ":" + password).getBytes()); + } +} diff --git a/core/src/main/java/org/testcontainers/containers/wait/Wait.java b/core/src/main/java/org/testcontainers/containers/wait/Wait.java new file mode 100644 index 00000000000..4a6717df36b --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/wait/Wait.java @@ -0,0 +1,54 @@ +package org.testcontainers.containers.wait; + +import java.net.HttpURLConnection; + +/** + * Convenience class with logic for building common {@link WaitStrategy} instances. + * + * @author Pete Cornish {@literal } + */ +public class Wait { + /** + * Convenience method to return the default WaitStrategy. + * + * @return a WaitStrategy + */ + public static WaitStrategy defaultWaitStrategy() { + return forListeningPort(); + } + + /** + * Convenience method to return a WaitStrategy for an exposed or mapped port. + * + * @return the WaitStrategy + * @see HostPortWaitStrategy + */ + public static HostPortWaitStrategy forListeningPort() { + return new HostPortWaitStrategy(); + } + + /** + * Convenience method to return a WaitStrategy for an HTTP endpoint. + * + * @param path the path to check + * @return the WaitStrategy + * @see HttpWaitStrategy + */ + public static HttpWaitStrategy forHttp(String path) { + return new HttpWaitStrategy() + .forPath(path) + .forStatusCode(HttpURLConnection.HTTP_OK); + } + + /** + * Convenience method to return a WaitStrategy for an HTTPS endpoint. + * + * @param path the path to check + * @return the WaitStrategy + * @see HttpWaitStrategy + */ + public static HttpWaitStrategy forHttps(String path) { + return forHttp(path) + .usingTls(); + } +} diff --git a/core/src/main/java/org/testcontainers/containers/wait/WaitStrategy.java b/core/src/main/java/org/testcontainers/containers/wait/WaitStrategy.java new file mode 100644 index 00000000000..871c314a88d --- /dev/null +++ b/core/src/main/java/org/testcontainers/containers/wait/WaitStrategy.java @@ -0,0 +1,25 @@ +package org.testcontainers.containers.wait; + +import org.testcontainers.containers.GenericContainer; + +import java.time.Duration; + +/** + * Approach to determine whether a container is ready. + * + * @author Pete Cornish {@literal } + */ +public interface WaitStrategy { + /** + * Wait until the container has started. + * + * @param container the container for which to wait + */ + void waitUntilReady(GenericContainer container); + + /** + * @param startupTimeout the duration for which to wait + * @return this + */ + WaitStrategy withStartupTimeout(Duration startupTimeout); +} diff --git a/core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java new file mode 100644 index 00000000000..7b783a335eb --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/wait/AbstractWaitStrategyTest.java @@ -0,0 +1,96 @@ +package org.testcontainers.junit.wait; + +import org.jetbrains.annotations.NotNull; +import org.junit.Before; +import org.rnorth.ducttape.RetryCountExceededException; +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 + * @throws Exception + */ + protected void waitUntilReadyAndSucceed(String shellCommand) throws Exception { + 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 + * @throws Exception + */ + protected void waitUntilReadyAndTimeout(String shellCommand) throws Exception { + final GenericContainer container = startContainerWithCommand(shellCommand); + try { + // start() blocks until successful or timeout + container.start(); + fail(RetryCountExceededException.class + " expected"); + + } catch (RetryCountExceededException e) { + assertFalse(String.format("Wait should have timed-out after %sms", + WAIT_TIMEOUT_MILLIS), ready.get()); + } + } +} diff --git a/core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java b/core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java new file mode 100644 index 00000000000..eec4b164e40 --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/wait/HostPortWaitStrategyTest.java @@ -0,0 +1,52 @@ +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. + * + * @throws Exception + */ + @Test + public void testWaitUntilReady_Success() throws Exception { + waitUntilReadyAndSucceed("nc -lp 8080"); + } + + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after unsuccessful connection + * to a container with a listening port within the timeout period. + * + * @throws Exception + */ + @Test + public void testWaitUntilReady_Timeout() throws Exception { + waitUntilReadyAndTimeout(""); + } + + /** + * @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 new file mode 100644 index 00000000000..4acfcd9bb3a --- /dev/null +++ b/core/src/test/java/org/testcontainers/junit/wait/HttpWaitStrategyTest.java @@ -0,0 +1,57 @@ +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 { + /** + * Doubly-escaped newline sequence indicating end of the HTTP header. + */ + private static final String DOUBLE_NEWLINE = "\\\r\\\n\\\r\\\n"; + + /** + * Expects that the WaitStrategy returns successfully after receiving an HTTP 200 response from the container. + * + * @throws Exception + */ + @Test + public void testWaitUntilReady_Success() throws Exception { + waitUntilReadyAndSucceed("echo -e \"HTTP/1.1 200 OK" + DOUBLE_NEWLINE + "\" | nc -lp 8080"); + } + + /** + * Expects that the WaitStrategy throws a {@link RetryCountExceededException} after not receiving an HTTP 200 + * response from the container within the timeout period. + * + * @throws Exception + */ + @Test + public void testWaitUntilReady_Timeout() throws Exception { + waitUntilReadyAndTimeout("echo -e \"HTTP/1.1 400 Bad Request" + DOUBLE_NEWLINE + "\" | nc -lp 8080"); + } + + /** + * @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); + } + }; + } +} diff --git a/pom.xml b/pom.xml index 38f1594314f..94ab359b935 100644 --- a/pom.xml +++ b/pom.xml @@ -55,6 +55,11 @@ Viktor Schulz vschulz@mail.uni-mannheim.de + + pcornish + Pete Cornish + outofcoffee@gmail.com +