Skip to content

Commit

Permalink
Framework-agnostic container & test lifecycle (#702)
Browse files Browse the repository at this point in the history
* Implement framework-agnostic container & test lifecycle

* Simpliy TestDescription interface

* {before,after}TestBlock -> {before,after}Test
  • Loading branch information
bsideup committed Jun 13, 2018
1 parent 85b76d7 commit b333e29
Show file tree
Hide file tree
Showing 9 changed files with 158 additions and 44 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -54,7 +55,7 @@
/**
* Container which launches Docker Compose, for the purposes of launching a defined set of containers.
*/
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> extends FailureDetectingExternalResource {
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> extends FailureDetectingExternalResource implements Startable {

/**
* Random identifier which will become part of spawned containers names, so we can shut them down
Expand Down Expand Up @@ -115,8 +116,35 @@ public DockerComposeContainer(String identifier, List<File> 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");
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -75,7 +79,7 @@
@EqualsAndHashCode(callSuper = false)
public class GenericContainer<SELF extends GenericContainer<SELF>>
extends FailureDetectingExternalResource
implements Container<SELF>, AutoCloseable, WaitStrategyTarget {
implements Container<SELF>, AutoCloseable, WaitStrategyTarget, Startable {

private static final Charset UTF8 = Charset.forName("UTF-8");

Expand Down Expand Up @@ -195,7 +199,15 @@ public GenericContainer(@NonNull final Future<String> 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());

Expand Down Expand Up @@ -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 = "<unknown>";
}
String imageName;

try {
imageName = image.get();
} catch (Exception e) {
imageName = "<unknown>";
}

ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName);
ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName);
} finally {
containerId = null;
containerInfo = null;
}
}

/**
Expand Down Expand Up @@ -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();
}
Expand Down Expand Up @@ -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.
Expand Down
13 changes: 13 additions & 0 deletions core/src/main/java/org/testcontainers/lifecycle/Startable.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package org.testcontainers.lifecycle;

public interface Startable extends AutoCloseable {

void start();

void stop();

@Override
default void close() {
stop();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package org.testcontainers.lifecycle;

public interface TestDescription {

String getTestId();

String getFilesystemFriendlyName();
}
Original file line number Diff line number Diff line change
@@ -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> throwable) {

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ public String getBootstrapServers() {
}

@Override
public void start() {
protected void doStart() {
String networkAlias = getNetworkAliases().get(0);
proxy = new SocatContainer()
.withNetwork(getNetwork())
Expand All @@ -76,7 +76,7 @@ public void start() {
withCommand("sh", "-c", "zookeeper-server-start /zookeeper.properties & /etc/confluent/docker/run");
}

super.start();
super.doStart();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand All @@ -33,7 +35,7 @@
* <p>
* The container should expose Selenium remote control protocol and VNC.
*/
public class BrowserWebDriverContainer<SELF extends BrowserWebDriverContainer<SELF>> extends GenericContainer<SELF> implements VncService, LinkableContainer {
public class BrowserWebDriverContainer<SELF extends BrowserWebDriverContainer<SELF>> extends GenericContainer<SELF> 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";
Expand Down Expand Up @@ -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> throwable) {
retainRecordingIfNeeded(description.getFilesystemFriendlyName(), throwable.isPresent());
}

@Override
Expand All @@ -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:
Expand 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);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}

0 comments on commit b333e29

Please sign in to comment.