Skip to content

Commit b333e29

Browse files
authored
Framework-agnostic container & test lifecycle (#702)
* Implement framework-agnostic container & test lifecycle * Simpliy TestDescription interface * {before,after}TestBlock -> {before,after}Test
1 parent 85b76d7 commit b333e29

File tree

9 files changed

+158
-44
lines changed

9 files changed

+158
-44
lines changed

core/src/main/java/org/testcontainers/containers/DockerComposeContainer.java

Lines changed: 32 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.github.dockerjava.api.DockerClient;
44
import com.github.dockerjava.api.model.Container;
5-
import com.google.common.annotations.VisibleForTesting;
65
import com.google.common.base.Joiner;
76
import com.google.common.base.Splitter;
87
import com.google.common.collect.Maps;
@@ -11,6 +10,7 @@
1110
import org.apache.commons.lang.StringUtils;
1211
import org.apache.commons.lang.SystemUtils;
1312
import org.junit.runner.Description;
13+
import org.junit.runners.model.Statement;
1414
import org.slf4j.Logger;
1515
import org.slf4j.LoggerFactory;
1616
import org.slf4j.profiler.Profiler;
@@ -21,6 +21,7 @@
2121
import org.testcontainers.containers.wait.strategy.Wait;
2222
import org.testcontainers.containers.wait.strategy.WaitAllStrategy;
2323
import org.testcontainers.containers.wait.strategy.WaitStrategy;
24+
import org.testcontainers.lifecycle.Startable;
2425
import org.testcontainers.utility.*;
2526
import org.zeroturnaround.exec.InvalidExitValueException;
2627
import org.zeroturnaround.exec.ProcessExecutor;
@@ -54,7 +55,7 @@
5455
/**
5556
* Container which launches Docker Compose, for the purposes of launching a defined set of containers.
5657
*/
57-
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> extends FailureDetectingExternalResource {
58+
public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>> extends FailureDetectingExternalResource implements Startable {
5859

5960
/**
6061
* 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<File> composeFiles) {
115116
}
116117

117118
@Override
118-
@VisibleForTesting
119+
@Deprecated
120+
public Statement apply(Statement base, Description description) {
121+
return super.apply(base, description);
122+
}
123+
124+
@Override
125+
@Deprecated
119126
public void starting(Description description) {
127+
start();
128+
}
129+
130+
@Override
131+
@Deprecated
132+
protected void succeeded(Description description) {
133+
}
134+
135+
@Override
136+
@Deprecated
137+
protected void failed(Throwable e, Description description) {
138+
}
139+
140+
@Override
141+
@Deprecated
142+
public void finished(Description description) {
143+
stop();
144+
}
145+
146+
@Override
147+
public void start() {
120148
final Profiler profiler = new Profiler("Docker Compose container rule");
121149
profiler.setLogger(logger());
122150
profiler.start("Docker Compose container startup");
@@ -227,10 +255,7 @@ private Logger logger() {
227255
}
228256

229257
@Override
230-
@VisibleForTesting
231-
public void finished(Description description) {
232-
233-
258+
public void stop() {
234259
synchronized (MUTEX) {
235260
try {
236261
// shut down the ambassador container

core/src/main/java/org/testcontainers/containers/GenericContainer.java

Lines changed: 67 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import org.jetbrains.annotations.NotNull;
2222
import org.jetbrains.annotations.Nullable;
2323
import org.junit.runner.Description;
24+
import org.junit.runners.model.Statement;
2425
import org.rnorth.ducttape.ratelimits.RateLimiter;
2526
import org.rnorth.ducttape.ratelimits.RateLimiterBuilder;
2627
import org.rnorth.ducttape.unreliables.Unreliables;
@@ -38,6 +39,9 @@
3839
import org.testcontainers.containers.wait.WaitStrategy;
3940
import org.testcontainers.containers.wait.strategy.WaitStrategyTarget;
4041
import org.testcontainers.images.RemoteDockerImage;
42+
import org.testcontainers.lifecycle.Startable;
43+
import org.testcontainers.lifecycle.TestLifecycleAware;
44+
import org.testcontainers.lifecycle.TestDescription;
4145
import org.testcontainers.utility.*;
4246

4347
import java.io.File;
@@ -75,7 +79,7 @@
7579
@EqualsAndHashCode(callSuper = false)
7680
public class GenericContainer<SELF extends GenericContainer<SELF>>
7781
extends FailureDetectingExternalResource
78-
implements Container<SELF>, AutoCloseable, WaitStrategyTarget {
82+
implements Container<SELF>, AutoCloseable, WaitStrategyTarget, Startable {
7983

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

@@ -195,7 +199,15 @@ public GenericContainer(@NonNull final Future<String> image) {
195199
/**
196200
* Starts the container using docker, pulling an image if necessary.
197201
*/
202+
@Override
198203
public void start() {
204+
if (containerId != null) {
205+
return;
206+
}
207+
doStart();
208+
}
209+
210+
protected void doStart() {
199211
Profiler profiler = new Profiler("Container startup");
200212
profiler.setLogger(logger());
201213

@@ -288,21 +300,27 @@ private void tryStart(Profiler profiler) {
288300
/**
289301
* Stops the container.
290302
*/
303+
@Override
291304
public void stop() {
292305

293306
if (containerId == null) {
294307
return;
295308
}
296309

297-
String imageName;
298-
299310
try {
300-
imageName = image.get();
301-
} catch (Exception e) {
302-
imageName = "<unknown>";
303-
}
311+
String imageName;
312+
313+
try {
314+
imageName = image.get();
315+
} catch (Exception e) {
316+
imageName = "<unknown>";
317+
}
304318

305-
ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName);
319+
ResourceReaper.instance().stopAndRemoveContainer(containerId, imageName);
320+
} finally {
321+
containerId = null;
322+
containerInfo = null;
323+
}
306324
}
307325

308326
/**
@@ -640,12 +658,53 @@ public void addExposedPorts(int... ports) {
640658
}
641659
}
642660

661+
private TestDescription toDescription(Description description) {
662+
return new TestDescription() {
663+
@Override
664+
public String getTestId() {
665+
return description.getDisplayName();
666+
}
667+
668+
@Override
669+
public String getFilesystemFriendlyName() {
670+
return description.getClassName() + "-" + description.getMethodName();
671+
}
672+
};
673+
}
674+
643675
@Override
676+
@Deprecated
677+
public Statement apply(Statement base, Description description) {
678+
return super.apply(base, description);
679+
}
680+
681+
@Override
682+
@Deprecated
644683
protected void starting(Description description) {
684+
if (this instanceof TestLifecycleAware) {
685+
((TestLifecycleAware) this).beforeTest(toDescription(description));
686+
}
645687
this.start();
646688
}
647689

648690
@Override
691+
@Deprecated
692+
protected void succeeded(Description description) {
693+
if (this instanceof TestLifecycleAware) {
694+
((TestLifecycleAware) this).afterTest(toDescription(description), Optional.empty());
695+
}
696+
}
697+
698+
@Override
699+
@Deprecated
700+
protected void failed(Throwable e, Description description) {
701+
if (this instanceof TestLifecycleAware) {
702+
((TestLifecycleAware) this).afterTest(toDescription(description), Optional.of(e));
703+
}
704+
}
705+
706+
@Override
707+
@Deprecated
649708
protected void finished(Description description) {
650709
this.stop();
651710
}
@@ -989,11 +1048,6 @@ public SELF withStartupAttempts(int attempts) {
9891048
return self();
9901049
}
9911050

992-
@Override
993-
public void close() {
994-
stop();
995-
}
996-
9971051
/**
9981052
* Allow low level modifications of {@link CreateContainerCmd} after it was pre-configured in {@link #tryStart(Profiler)}.
9991053
* Invocation happens eagerly on a moment when container is created.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package org.testcontainers.lifecycle;
2+
3+
public interface Startable extends AutoCloseable {
4+
5+
void start();
6+
7+
void stop();
8+
9+
@Override
10+
default void close() {
11+
stop();
12+
}
13+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
package org.testcontainers.lifecycle;
2+
3+
public interface TestDescription {
4+
5+
String getTestId();
6+
7+
String getFilesystemFriendlyName();
8+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package org.testcontainers.lifecycle;
2+
3+
import java.util.Optional;
4+
5+
public interface TestLifecycleAware {
6+
7+
default void beforeTest(TestDescription description) {
8+
9+
}
10+
11+
default void afterTest(TestDescription description, Optional<Throwable> throwable) {
12+
13+
}
14+
}

modules/kafka/src/main/java/org/testcontainers/containers/KafkaContainer.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ public String getBootstrapServers() {
5757
}
5858

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

79-
super.start();
79+
super.doStart();
8080
}
8181

8282
@Override

modules/selenium/src/main/java/org/testcontainers/containers/BrowserWebDriverContainer.java

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
import com.google.common.collect.ImmutableSet;
55
import org.jetbrains.annotations.NotNull;
66
import org.jetbrains.annotations.Nullable;
7-
import org.junit.runner.Description;
87
import org.openqa.selenium.remote.BrowserType;
98
import org.openqa.selenium.remote.DesiredCapabilities;
109
import org.openqa.selenium.remote.RemoteWebDriver;
@@ -18,11 +17,14 @@
1817
import org.testcontainers.containers.wait.LogMessageWaitStrategy;
1918
import org.testcontainers.containers.wait.WaitAllStrategy;
2019
import org.testcontainers.containers.wait.WaitStrategy;
20+
import org.testcontainers.lifecycle.TestDescription;
21+
import org.testcontainers.lifecycle.TestLifecycleAware;
2122

2223
import java.io.File;
2324
import java.net.MalformedURLException;
2425
import java.net.URL;
2526
import java.time.Duration;
27+
import java.util.Optional;
2628
import java.util.Set;
2729
import java.util.concurrent.TimeUnit;
2830

@@ -33,7 +35,7 @@
3335
* <p>
3436
* The container should expose Selenium remote control protocol and VNC.
3537
*/
36-
public class BrowserWebDriverContainer<SELF extends BrowserWebDriverContainer<SELF>> extends GenericContainer<SELF> implements VncService, LinkableContainer {
38+
public class BrowserWebDriverContainer<SELF extends BrowserWebDriverContainer<SELF>> extends GenericContainer<SELF> implements VncService, LinkableContainer, TestLifecycleAware {
3739

3840
private static final String CHROME_IMAGE = "selenium/standalone-chrome-debug:%s";
3941
private static final String FIREFOX_IMAGE = "selenium/standalone-firefox-debug:%s";
@@ -203,13 +205,8 @@ public RemoteWebDriver getWebDriver() {
203205
}
204206

205207
@Override
206-
protected void failed(Throwable e, Description description) {
207-
stopAndRetainRecordingForDescriptionAndSuccessState(description, false);
208-
}
209-
210-
@Override
211-
protected void succeeded(Description description) {
212-
stopAndRetainRecordingForDescriptionAndSuccessState(description, true);
208+
public void afterTest(TestDescription description, Optional<Throwable> throwable) {
209+
retainRecordingIfNeeded(description.getFilesystemFriendlyName(), throwable.isPresent());
213210
}
214211

215212
@Override
@@ -233,7 +230,7 @@ public void stop() {
233230
super.stop();
234231
}
235232

236-
private void stopAndRetainRecordingForDescriptionAndSuccessState(Description description, boolean succeeded) {
233+
private void retainRecordingIfNeeded(String prefix, boolean succeeded) {
237234
final boolean shouldRecord;
238235
switch (recordingMode) {
239236
case RECORD_ALL:
@@ -248,8 +245,8 @@ private void stopAndRetainRecordingForDescriptionAndSuccessState(Description des
248245
}
249246

250247
if (shouldRecord) {
251-
File recordingFile = recordingFileFactory.recordingFileForTest(vncRecordingDirectory, description, succeeded);
252-
LOGGER.info("Screen recordings for test {} will be stored at: {}", description.getDisplayName(), recordingFile);
248+
File recordingFile = recordingFileFactory.recordingFileForTest(vncRecordingDirectory, prefix, succeeded);
249+
LOGGER.info("Screen recordings for test {} will be stored at: {}", prefix, recordingFile);
253250

254251
vncRecordingContainer.saveRecordingToFile(recordingFile);
255252
}

modules/selenium/src/main/java/org/testcontainers/containers/DefaultRecordingFileFactory.java

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
package org.testcontainers.containers;
22

3-
import org.junit.runner.Description;
4-
53
import java.io.File;
64
import java.text.SimpleDateFormat;
75
import java.util.Date;
@@ -11,16 +9,15 @@ public class DefaultRecordingFileFactory implements RecordingFileFactory {
119
private static final SimpleDateFormat filenameDateFormat = new SimpleDateFormat("YYYYMMdd-HHmmss");
1210
private static final String PASSED = "PASSED";
1311
private static final String FAILED = "FAILED";
14-
private static final String FILENAME_FORMAT = "%s-%s-%s-%s.flv";
12+
private static final String FILENAME_FORMAT = "%s-%s-%s.flv";
1513

1614
@Override
17-
public File recordingFileForTest(File vncRecordingDirectory, Description description, boolean succeeded) {
18-
final String prefix = succeeded ? PASSED : FAILED;
15+
public File recordingFileForTest(File vncRecordingDirectory, String prefix, boolean succeeded) {
16+
final String resultMarker = succeeded ? PASSED : FAILED;
1917
final String fileName = String.format(FILENAME_FORMAT,
20-
prefix,
21-
description.getTestClass().getSimpleName(),
22-
description.getMethodName(),
23-
filenameDateFormat.format(new Date())
18+
resultMarker,
19+
prefix,
20+
filenameDateFormat.format(new Date())
2421
);
2522
return new File(vncRecordingDirectory, fileName);
2623
}

modules/selenium/src/main/java/org/testcontainers/containers/RecordingFileFactory.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,11 @@
55
import java.io.File;
66

77
public interface RecordingFileFactory {
8-
File recordingFileForTest(File vncRecordingDirectory, Description description, boolean succeeded);
8+
9+
@Deprecated
10+
default File recordingFileForTest(File vncRecordingDirectory, Description description, boolean succeeded) {
11+
return recordingFileForTest(vncRecordingDirectory, description.getTestClass().getSimpleName() + "-" + description.getMethodName(), succeeded);
12+
}
13+
14+
File recordingFileForTest(File vncRecordingDirectory, String prefix, boolean succeeded);
915
}

0 commit comments

Comments
 (0)