|
5 | 5 | import com.github.dockerjava.api.DockerClient;
|
6 | 6 | import com.github.dockerjava.api.command.InspectContainerResponse;
|
7 | 7 | import com.github.dockerjava.api.command.InspectContainerResponse.ContainerState;
|
| 8 | +import com.github.dockerjava.api.model.Container; |
8 | 9 | import com.github.dockerjava.api.model.ExposedPort;
|
9 | 10 | import com.github.dockerjava.api.model.Info;
|
10 | 11 | import com.github.dockerjava.api.model.Ports;
|
|
28 | 29 | import org.testcontainers.utility.DockerImageName;
|
29 | 30 | import org.testcontainers.utility.MountableFile;
|
30 | 31 |
|
| 32 | +import java.time.Duration; |
31 | 33 | import java.util.Arrays;
|
32 | 34 | import java.util.List;
|
33 | 35 | import java.util.Map;
|
| 36 | +import java.util.Optional; |
34 | 37 | import java.util.concurrent.TimeUnit;
|
35 | 38 | import java.util.function.Predicate;
|
36 | 39 | import java.util.stream.Collectors;
|
37 | 40 |
|
| 41 | +import static com.google.common.base.MoreObjects.toStringHelper; |
| 42 | +import static com.google.common.collect.ImmutableList.toImmutableList; |
| 43 | +import static java.lang.String.format; |
| 44 | +import static java.util.Arrays.asList; |
| 45 | +import static java.util.Collections.singletonMap; |
| 46 | +import static java.util.stream.Collectors.joining; |
38 | 47 | import static org.assertj.core.api.Assertions.assertThat;
|
39 | 48 | import static org.assertj.core.api.Assertions.assertThatThrownBy;
|
40 | 49 | import static org.assertj.core.api.Assertions.catchThrowable;
|
41 | 50 | import static org.assertj.core.api.Assumptions.assumeThat;
|
| 51 | +import static org.testcontainers.containers.wait.strategy.Wait.forLogMessage; |
42 | 52 |
|
43 | 53 | public class GenericContainerTest {
|
44 | 54 |
|
| 55 | + @Test |
| 56 | + public void testStartupTimeoutWithAttemptsNotLeakingContainers() |
| 57 | + { |
| 58 | + try ( |
| 59 | + GenericContainer<?> container = new GenericContainer<>(TestImages.TINY_IMAGE) |
| 60 | + .withStartupAttempts(3) |
| 61 | + .waitingFor(forLogMessage("this text does not exist in logs", 1) |
| 62 | + .withStartupTimeout(Duration.ofMillis(1))) |
| 63 | + .withCommand("tail", "-f", "/dev/null"); |
| 64 | + ) { |
| 65 | + assertThatThrownBy(container::start) |
| 66 | + .hasStackTraceContaining("Retry limit hit with exception"); |
| 67 | + } |
| 68 | + assertThat(reportLeakedContainers()).isEmpty(); |
| 69 | + } |
| 70 | + |
45 | 71 | @Test
|
46 | 72 | public void shouldReportOOMAfterWait() {
|
47 | 73 | Info info = DockerClientFactory.instance().client().infoCmd().exec();
|
@@ -273,6 +299,32 @@ public void shouldRespectWaitStrategy() {
|
273 | 299 | }
|
274 | 300 | }
|
275 | 301 |
|
| 302 | + private static Optional<String> reportLeakedContainers() |
| 303 | + { |
| 304 | + @SuppressWarnings("resource") // Throws when close is attempted, as this is a global instance. |
| 305 | + DockerClient dockerClient = DockerClientFactory.lazyClient(); |
| 306 | + |
| 307 | + List<Container> containers = dockerClient.listContainersCmd() |
| 308 | + .withLabelFilter(singletonMap(DockerClientFactory.TESTCONTAINERS_SESSION_ID_LABEL, DockerClientFactory.SESSION_ID)) |
| 309 | + // ignore status "exited" - for example, failed containers after using `withStartupAttempts()` |
| 310 | + .withStatusFilter(asList("created", "restarting", "running", "paused")) |
| 311 | + .exec() |
| 312 | + .stream() |
| 313 | + .collect(toImmutableList()); |
| 314 | + |
| 315 | + if (containers.isEmpty()) { |
| 316 | + return Optional.empty(); |
| 317 | + } |
| 318 | + |
| 319 | + return Optional.of(format("Leaked containers: %s", containers.stream() |
| 320 | + .map(container -> toStringHelper("container") |
| 321 | + .add("id", container.getId()) |
| 322 | + .add("image", container.getImage()) |
| 323 | + .add("imageId", container.getImageId()) |
| 324 | + .toString()) |
| 325 | + .collect(joining(", ", "[", "]")))); |
| 326 | + } |
| 327 | + |
276 | 328 | static class NoopStartupCheckStrategy extends StartupCheckStrategy {
|
277 | 329 |
|
278 | 330 | @Override
|
|
0 commit comments