diff --git a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java index f6b8a647bd8..a206652637e 100644 --- a/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java +++ b/core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java @@ -2,7 +2,6 @@ import com.github.dockerjava.api.DockerClient; import com.github.dockerjava.api.model.Container; -import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Joiner; import com.google.common.base.Splitter; import com.google.common.collect.Maps; @@ -11,6 +10,7 @@ import org.apache.commons.lang.StringUtils; import org.apache.commons.lang.SystemUtils; import org.junit.runner.Description; +import org.junit.runners.model.Statement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.slf4j.profiler.Profiler; @@ -21,6 +21,7 @@ import org.testcontainers.containers.wait.strategy.Wait; import org.testcontainers.containers.wait.strategy.WaitAllStrategy; import org.testcontainers.containers.wait.strategy.WaitStrategy; +import org.testcontainers.lifecycle.Startable; import org.testcontainers.utility.*; import org.zeroturnaround.exec.InvalidExitValueException; import org.zeroturnaround.exec.ProcessExecutor; @@ -54,7 +55,7 @@ /** * Container which launches Docker Compose, for the purposes of launching a defined set of containers. */ -public class DockerComposeContainer> extends FailureDetectingExternalResource { +public class DockerComposeContainer> extends FailureDetectingExternalResource implements Startable { /** * Random identifier which will become part of spawned containers names, so we can shut them down @@ -115,8 +116,35 @@ public DockerComposeContainer(String identifier, List composeFiles) { } @Override - @VisibleForTesting + @Deprecated + public Statement apply(Statement base, Description description) { + return super.apply(base, description); + } + + @Override + @Deprecated public void starting(Description description) { + start(); + } + + @Override + @Deprecated + protected void succeeded(Description description) { + } + + @Override + @Deprecated + protected void failed(Throwable e, Description description) { + } + + @Override + @Deprecated + public void finished(Description description) { + stop(); + } + + @Override + public void start() { final Profiler profiler = new Profiler("Docker Compose container rule"); profiler.setLogger(logger()); profiler.start("Docker Compose container startup"); @@ -227,10 +255,7 @@ private Logger logger() { } @Override - @VisibleForTesting - public void finished(Description description) { - - + public void stop() { synchronized (MUTEX) { try { // shut down the ambassador container diff --git a/core/src/main/java/org/testcontainers/containers/GenericContainer.java b/core/src/main/java/org/testcontainers/containers/GenericContainer.java index 9ec38a2f2bf..5df1a065d59 100644 --- a/core/src/main/java/org/testcontainers/containers/GenericContainer.java +++ b/core/src/main/java/org/testcontainers/containers/GenericContainer.java @@ -21,6 +21,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.junit.runner.Description; +import org.junit.runners.model.Statement; import org.rnorth.ducttape.ratelimits.RateLimiter; import org.rnorth.ducttape.ratelimits.RateLimiterBuilder; import org.rnorth.ducttape.unreliables.Unreliables; @@ -38,6 +39,9 @@ import org.testcontainers.containers.wait.WaitStrategy; import org.testcontainers.containers.wait.strategy.WaitStrategyTarget; import org.testcontainers.images.RemoteDockerImage; +import org.testcontainers.lifecycle.Startable; +import org.testcontainers.lifecycle.TestLifecycleAware; +import org.testcontainers.lifecycle.TestDescription; import org.testcontainers.utility.*; import java.io.File; @@ -75,7 +79,7 @@ @EqualsAndHashCode(callSuper = false) public class GenericContainer> extends FailureDetectingExternalResource - implements Container, AutoCloseable, WaitStrategyTarget { + implements Container, AutoCloseable, WaitStrategyTarget, Startable { private static final Charset UTF8 = Charset.forName("UTF-8"); @@ -195,7 +199,15 @@ public GenericContainer(@NonNull final Future image) { /** * Starts the container using docker, pulling an image if necessary. */ + @Override public void start() { + if (containerId != null) { + return; + } + doStart(); + } + + protected void doStart() { Profiler profiler = new Profiler("Container startup"); profiler.setLogger(logger()); @@ -288,21 +300,27 @@ private void tryStart(Profiler profiler) { /** * Stops the container. */ + @Override public void stop() { if (containerId == null) { return; } - String imageName; - try { - imageName = image.get(); - } catch (Exception e) { - imageName = ""; - } + String imageName; + + try { + imageName = image.get(); + } catch (Exception e) { + imageName = ""; + } - ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName); + ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName); + } finally { + containerId = null; + containerInfo = null; + } } /** @@ -640,12 +658,53 @@ public void addExposedPorts(int... ports) { } } + private TestDescription toDescription(Description description) { + return new TestDescription() { + @Override + public String getTestId() { + return description.getDisplayName(); + } + + @Override + public String getFilesystemFriendlyName() { + return description.getClassName() + "-" + description.getMethodName(); + } + }; + } + @Override + @Deprecated + public Statement apply(Statement base, Description description) { + return super.apply(base, description); + } + + @Override + @Deprecated protected void starting(Description description) { + if (this instanceof TestLifecycleAware) { + ((TestLifecycleAware) this).beforeTest(toDescription(description)); + } this.start(); } @Override + @Deprecated + protected void succeeded(Description description) { + if (this instanceof TestLifecycleAware) { + ((TestLifecycleAware) this).afterTest(toDescription(description), Optional.empty()); + } + } + + @Override + @Deprecated + protected void failed(Throwable e, Description description) { + if (this instanceof TestLifecycleAware) { + ((TestLifecycleAware) this).afterTest(toDescription(description), Optional.of(e)); + } + } + + @Override + @Deprecated protected void finished(Description description) { this.stop(); } @@ -989,11 +1048,6 @@ public SELF withStartupAttempts(int attempts) { return self(); } - @Override - public void close() { - stop(); - } - /** * Allow low level modifications of {@link CreateContainerCmd} after it was pre-configured in {@link #tryStart(Profiler)}. * Invocation happens eagerly on a moment when container is created. diff --git a/core/src/main/java/org/testcontainers/lifecycle/Startable.java b/core/src/main/java/org/testcontainers/lifecycle/Startable.java new file mode 100644 index 00000000000..fa2bfd9dfc5 --- /dev/null +++ b/core/src/main/java/org/testcontainers/lifecycle/Startable.java @@ -0,0 +1,13 @@ +package org.testcontainers.lifecycle; + +public interface Startable extends AutoCloseable { + + void start(); + + void stop(); + + @Override + default void close() { + stop(); + } +} diff --git a/core/src/main/java/org/testcontainers/lifecycle/TestDescription.java b/core/src/main/java/org/testcontainers/lifecycle/TestDescription.java new file mode 100644 index 00000000000..c27f59fb0fc --- /dev/null +++ b/core/src/main/java/org/testcontainers/lifecycle/TestDescription.java @@ -0,0 +1,8 @@ +package org.testcontainers.lifecycle; + +public interface TestDescription { + + String getTestId(); + + String getFilesystemFriendlyName(); +} diff --git a/core/src/main/java/org/testcontainers/lifecycle/TestLifecycleAware.java b/core/src/main/java/org/testcontainers/lifecycle/TestLifecycleAware.java new file mode 100644 index 00000000000..178e081c083 --- /dev/null +++ b/core/src/main/java/org/testcontainers/lifecycle/TestLifecycleAware.java @@ -0,0 +1,14 @@ +package org.testcontainers.lifecycle; + +import java.util.Optional; + +public interface TestLifecycleAware { + + default void beforeTest(TestDescription description) { + + } + + default void afterTest(TestDescription description, Optional throwable) { + + } +} diff --git a/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java index 9a222c7e974..e202c55cf9e 100644 --- a/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java +++ b/modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java @@ -57,7 +57,7 @@ public String getBootstrapServers() { } @Override - public void start() { + protected void doStart() { String networkAlias = getNetworkAliases().get(0); proxy = new SocatContainer() .withNetwork(getNetwork()) @@ -76,7 +76,7 @@ public void start() { withCommand("sh", "-c", "zookeeper-server-start /zookeeper.properties & /etc/confluent/docker/run"); } - super.start(); + super.doStart(); } @Override diff --git a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java index 9a527d998dd..fcc8b6f9ae2 100644 --- a/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java +++ b/modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java @@ -4,7 +4,6 @@ import com.google.common.collect.ImmutableSet; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.junit.runner.Description; import org.openqa.selenium.remote.BrowserType; import org.openqa.selenium.remote.DesiredCapabilities; import org.openqa.selenium.remote.RemoteWebDriver; @@ -18,11 +17,14 @@ import org.testcontainers.containers.wait.LogMessageWaitStrategy; import org.testcontainers.containers.wait.WaitAllStrategy; import org.testcontainers.containers.wait.WaitStrategy; +import org.testcontainers.lifecycle.TestDescription; +import org.testcontainers.lifecycle.TestLifecycleAware; import java.io.File; import java.net.MalformedURLException; import java.net.URL; import java.time.Duration; +import java.util.Optional; import java.util.Set; import java.util.concurrent.TimeUnit; @@ -33,7 +35,7 @@ *

* The container should expose Selenium remote control protocol and VNC. */ -public class BrowserWebDriverContainer> extends GenericContainer implements VncService, LinkableContainer { +public class BrowserWebDriverContainer> extends GenericContainer implements VncService, LinkableContainer, TestLifecycleAware { private static final String CHROME_IMAGE = "selenium/standalone-chrome-debug:%s"; private static final String FIREFOX_IMAGE = "selenium/standalone-firefox-debug:%s"; @@ -203,13 +205,8 @@ public RemoteWebDriver getWebDriver() { } @Override - protected void failed(Throwable e, Description description) { - stopAndRetainRecordingForDescriptionAndSuccessState(description, false); - } - - @Override - protected void succeeded(Description description) { - stopAndRetainRecordingForDescriptionAndSuccessState(description, true); + public void afterTest(TestDescription description, Optional throwable) { + retainRecordingIfNeeded(description.getFilesystemFriendlyName(), throwable.isPresent()); } @Override @@ -233,7 +230,7 @@ public void stop() { super.stop(); } - private void stopAndRetainRecordingForDescriptionAndSuccessState(Description description, boolean succeeded) { + private void retainRecordingIfNeeded(String prefix, boolean succeeded) { final boolean shouldRecord; switch (recordingMode) { case RECORD_ALL: @@ -248,8 +245,8 @@ private void stopAndRetainRecordingForDescriptionAndSuccessState(Description des } if (shouldRecord) { - File recordingFile = recordingFileFactory.recordingFileForTest(vncRecordingDirectory, description, succeeded); - LOGGER.info("Screen recordings for test {} will be stored at: {}", description.getDisplayName(), recordingFile); + File recordingFile = recordingFileFactory.recordingFileForTest(vncRecordingDirectory, prefix, succeeded); + LOGGER.info("Screen recordings for test {} will be stored at: {}", prefix, recordingFile); vncRecordingContainer.saveRecordingToFile(recordingFile); } diff --git a/modules/selenium/src/main/java/org/testcontainers/containers/DefaultRecordingFileFactory.java b/modules/selenium/src/main/java/org/testcontainers/containers/DefaultRecordingFileFactory.java index b68b03d46a9..64e8bbe7055 100644 --- a/modules/selenium/src/main/java/org/testcontainers/containers/DefaultRecordingFileFactory.java +++ b/modules/selenium/src/main/java/org/testcontainers/containers/DefaultRecordingFileFactory.java @@ -1,7 +1,5 @@ package org.testcontainers.containers; -import org.junit.runner.Description; - import java.io.File; import java.text.SimpleDateFormat; import java.util.Date; @@ -11,16 +9,15 @@ public class DefaultRecordingFileFactory implements RecordingFileFactory { private static final SimpleDateFormat filenameDateFormat = new SimpleDateFormat("YYYYMMdd-HHmmss"); private static final String PASSED = "PASSED"; private static final String FAILED = "FAILED"; - private static final String FILENAME_FORMAT = "%s-%s-%s-%s.flv"; + private static final String FILENAME_FORMAT = "%s-%s-%s.flv"; @Override - public File recordingFileForTest(File vncRecordingDirectory, Description description, boolean succeeded) { - final String prefix = succeeded ? PASSED : FAILED; + public File recordingFileForTest(File vncRecordingDirectory, String prefix, boolean succeeded) { + final String resultMarker = succeeded ? PASSED : FAILED; final String fileName = String.format(FILENAME_FORMAT, - prefix, - description.getTestClass().getSimpleName(), - description.getMethodName(), - filenameDateFormat.format(new Date()) + resultMarker, + prefix, + filenameDateFormat.format(new Date()) ); return new File(vncRecordingDirectory, fileName); } diff --git a/modules/selenium/src/main/java/org/testcontainers/containers/RecordingFileFactory.java b/modules/selenium/src/main/java/org/testcontainers/containers/RecordingFileFactory.java index cbd91562301..2cbd373493b 100644 --- a/modules/selenium/src/main/java/org/testcontainers/containers/RecordingFileFactory.java +++ b/modules/selenium/src/main/java/org/testcontainers/containers/RecordingFileFactory.java @@ -5,5 +5,11 @@ import java.io.File; public interface RecordingFileFactory { - File recordingFileForTest(File vncRecordingDirectory, Description description, boolean succeeded); + + @Deprecated + default File recordingFileForTest(File vncRecordingDirectory, Description description, boolean succeeded) { + return recordingFileForTest(vncRecordingDirectory, description.getTestClass().getSimpleName() + "-" + description.getMethodName(), succeeded); + } + + File recordingFileForTest(File vncRecordingDirectory, String prefix, boolean succeeded); }