-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Support multiple HTTP status codes for HttpWaitStrategy #630
Changes from 9 commits
cf637ce
da44bd9
0af2eab
e5ddd16
1b7cbaf
f2d1592
bf12100
3b34909
afe06dc
9f1b28a
cb4875d
bb39373
10ca9cd
370c35a
808dcf8
d6837f7
6b24c2a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,93 @@ | ||
package org.testcontainers.junit.wait.strategy; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hm... I'm not sure these changes are related to the PR There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's somewhat needed to test the new methods I added. See There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should just start using new implementation (under There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @dadoonet ping :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hum. I'm missing something. I probably misunderstood what you are expecting. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I added a call to my method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I added a call to my method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should just fix the imports in the old test and start using new wait strategies, so that you can also add your new method to that test There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Works for me. I'll do that. In which case, I'll also move the tests to the new package, right? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, thanks :) |
||
|
||
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 <[email protected]>} | ||
*/ | ||
public abstract class AbstractWaitStrategyTest<W extends WaitStrategy> { | ||
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); | ||
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <[email protected]>} | ||
*/ | ||
public class HttpWaitStrategyTest extends AbstractWaitStrategyTest<HttpWaitStrategy> { | ||
/** | ||
* 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 testWaitUntilReadyWithSuccess() { | ||
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 testWaitUntilReadyWithTimeout() { | ||
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 testWaitUntilReadyWithTimeoutAndBadResponseBody() { | ||
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)) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please add a test where both There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Good call. My code is actually failing! :p There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But we're still missing a test where we call
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So should I clone the class or just change the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. just add a test method which is not based on There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I did it. A bit differently though so I can reuse most of the existing code and that could help in the future. |
||
.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"; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. unrelated to this PR |
||
|
||
import java.io.IOException; | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm thinking about the use case where somebody will call
Wait.forHttp().forStatusCode(200).forStatusCodeMatching(it -> it > 400)
. Maybe we should throw an error, or handle both statuses?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I do really like the Predicate approach here - it opens the door to having some useful ready-to-use predicates in the future (e.g.
anySuccessfulStatusCode()
oranyServerErrorStatusCode()
etc...There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
With the latest change below I'm now hopefully supporting both.