Skip to content

Commit c2dcc88

Browse files
committed
Merge branch 'main' into composev2
2 parents 6a39b89 + be7cbe4 commit c2dcc88

File tree

12 files changed

+263
-15
lines changed

12 files changed

+263
-15
lines changed

.github/pull_request_template.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ New Modules:
66
Make sure to add it to `bug_report.yaml`, `enhancement.yaml` and `feature.yaml`.
77
Also, add it to `dependabot.yml` and `labeler.yml`.
88
9-
Before comitting, run `./gradlew checkstyleMain checkstyleTest spotlessApply` and fix any issues that occur.
9+
Before committing, please run `./gradlew checkstyleMain checkstyleTest spotlessApply` and fix any issues that occur.
1010
1111
Describing Your Changes:
1212

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

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,9 @@ public class ComposeContainer extends FailureDetectingExternalResource implement
5555
*/
5656
private Map<String, String> env = new HashMap<>();
5757

58-
private ComposeContainer.RemoveImages removeImages;
58+
private RemoveImages removeImages;
59+
60+
private boolean removeVolumes = true;
5961

6062
public static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker.exe" : "docker";
6163

@@ -156,7 +158,10 @@ public void stop() {
156158
this.composeDelegate.getAmbassadorContainer().stop();
157159

158160
// Kill the services using docker
159-
String cmd = "compose down -v";
161+
String cmd = "compose down";
162+
if (removeVolumes) {
163+
cmd += " -v";
164+
}
160165
if (removeImages != null) {
161166
cmd += " --rmi " + removeImages.dockerRemoveImagesType();
162167
}
@@ -326,6 +331,17 @@ public ComposeContainer withRemoveImages(ComposeContainer.RemoveImages removeIma
326331
return this;
327332
}
328333

334+
/**
335+
* Remove volumes after containers shut down.
336+
*
337+
* @param removeVolumes whether volumes are to be removed.
338+
* @return this instance, for chaining.
339+
*/
340+
public ComposeContainer withRemoveVolumes(boolean removeVolumes) {
341+
this.removeVolumes = removeVolumes;
342+
return this;
343+
}
344+
329345
/**
330346
* Set the maximum startup timeout all the waits set are bounded to.
331347
*

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

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ public class DockerComposeContainer<SELF extends DockerComposeContainer<SELF>>
5858

5959
private RemoveImages removeImages;
6060

61+
private boolean removeVolumes = true;
62+
6163
public static final String COMPOSE_EXECUTABLE = SystemUtils.IS_OS_WINDOWS ? "docker-compose.exe" : "docker-compose";
6264

6365
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("docker/compose:1.29.2");
@@ -162,7 +164,11 @@ public void stop() {
162164
this.composeDelegate.getAmbassadorContainer().stop();
163165

164166
// Kill the services using docker-compose
165-
String cmd = "down -v";
167+
String cmd = "down";
168+
169+
if (removeVolumes) {
170+
cmd += " -v";
171+
}
166172
if (removeImages != null) {
167173
cmd += " --rmi " + removeImages.dockerRemoveImagesType();
168174
}
@@ -328,6 +334,17 @@ public SELF withRemoveImages(RemoveImages removeImages) {
328334
return self();
329335
}
330336

337+
/**
338+
* Remove volumes after containers shut down.
339+
*
340+
* @param removeVolumes whether volumes are to be removed.
341+
* @return this instance, for chaining.
342+
*/
343+
public SELF withRemoveVolumes(boolean removeVolumes) {
344+
this.removeVolumes = removeVolumes;
345+
return self();
346+
}
347+
331348
/**
332349
* Set the maximum startup timeout all the waits set are bounded to.
333350
*
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.testcontainers.junit;
2+
3+
import org.junit.Test;
4+
import org.junit.runner.RunWith;
5+
import org.junit.runners.Parameterized;
6+
import org.testcontainers.DockerClientFactory;
7+
import org.testcontainers.containers.ComposeContainer;
8+
9+
import java.io.File;
10+
import java.util.LinkedHashSet;
11+
import java.util.Set;
12+
import java.util.concurrent.atomic.AtomicReference;
13+
import java.util.stream.Stream;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.awaitility.Awaitility.await;
17+
18+
@RunWith(Parameterized.class)
19+
public class ComposeContainerVolumeRemovalTest {
20+
21+
public ComposeContainerVolumeRemovalTest(
22+
final boolean removeVolumes,
23+
final boolean shouldVolumesBePresentAfterRunning
24+
) {
25+
this.removeVolumes = removeVolumes;
26+
this.shouldVolumesBePresentAfterRunning = shouldVolumesBePresentAfterRunning;
27+
}
28+
29+
public final boolean removeVolumes;
30+
31+
public final boolean shouldVolumesBePresentAfterRunning;
32+
33+
@Parameterized.Parameters(name = "removeVolumes = {0}")
34+
public static Object[][] params() {
35+
return new Object[][] { { true, false }, { false, true } };
36+
}
37+
38+
@Test
39+
public void performTest() {
40+
final File composeFile = new File("src/test/resources/compose-test.yml");
41+
42+
final AtomicReference<String> volumeName = new AtomicReference<>("");
43+
try (
44+
ComposeContainer environment = new ComposeContainer(composeFile)
45+
.withExposedService("redis", 6379)
46+
.withRemoveVolumes(this.removeVolumes)
47+
.withRemoveImages(ComposeContainer.RemoveImages.ALL)
48+
) {
49+
environment.start();
50+
51+
volumeName.set(volumeNameForRunningContainer("_redis_1"));
52+
final boolean isVolumePresentWhileRunning = isVolumePresent(volumeName.get());
53+
assertThat(isVolumePresentWhileRunning).as("the container volume is present while running").isEqualTo(true);
54+
}
55+
56+
await()
57+
.untilAsserted(() -> {
58+
final boolean isVolumePresentAfterRunning = isVolumePresent(volumeName.get());
59+
assertThat(isVolumePresentAfterRunning)
60+
.as("the container volume is present after running")
61+
.isEqualTo(this.shouldVolumesBePresentAfterRunning);
62+
});
63+
}
64+
65+
private String volumeNameForRunningContainer(final String containerNameSuffix) {
66+
return DockerClientFactory
67+
.instance()
68+
.client()
69+
.listContainersCmd()
70+
.exec()
71+
.stream()
72+
.filter(it -> Stream.of(it.getNames()).anyMatch(name -> name.endsWith(containerNameSuffix)))
73+
.findFirst()
74+
.map(container -> container.getMounts().get(0).getName())
75+
.orElseThrow(IllegalStateException::new);
76+
}
77+
78+
private boolean isVolumePresent(final String volumeName) {
79+
Set<String> nameFilter = new LinkedHashSet<>(1);
80+
nameFilter.add(volumeName);
81+
return DockerClientFactory
82+
.instance()
83+
.client()
84+
.listVolumesCmd()
85+
.withFilter("name", nameFilter)
86+
.exec()
87+
.getVolumes()
88+
.stream()
89+
.findFirst()
90+
.isPresent();
91+
}
92+
}
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
package org.testcontainers.junit;
2+
3+
import org.junit.Test;
4+
import org.junit.runner.RunWith;
5+
import org.junit.runners.Parameterized;
6+
import org.testcontainers.DockerClientFactory;
7+
import org.testcontainers.containers.DockerComposeContainer;
8+
9+
import java.io.File;
10+
import java.util.LinkedHashSet;
11+
import java.util.Set;
12+
import java.util.concurrent.atomic.AtomicReference;
13+
import java.util.stream.Stream;
14+
15+
import static org.assertj.core.api.Assertions.assertThat;
16+
import static org.awaitility.Awaitility.await;
17+
18+
@RunWith(Parameterized.class)
19+
public class DockerComposeContainerVolumeRemovalTest {
20+
21+
public DockerComposeContainerVolumeRemovalTest(
22+
final boolean removeVolumes,
23+
final boolean shouldVolumesBePresentAfterRunning
24+
) {
25+
this.removeVolumes = removeVolumes;
26+
this.shouldVolumesBePresentAfterRunning = shouldVolumesBePresentAfterRunning;
27+
}
28+
29+
public final boolean removeVolumes;
30+
31+
public final boolean shouldVolumesBePresentAfterRunning;
32+
33+
@Parameterized.Parameters(name = "removeVolumes = {0}")
34+
public static Object[][] params() {
35+
return new Object[][] { { true, false }, { false, true } };
36+
}
37+
38+
@Test
39+
public void performTest() {
40+
final File composeFile = new File("src/test/resources/compose-test.yml");
41+
42+
final AtomicReference<String> volumeName = new AtomicReference<>("");
43+
try (
44+
DockerComposeContainer environment = new DockerComposeContainer<>(composeFile)
45+
.withExposedService("redis", 6379)
46+
.withRemoveVolumes(this.removeVolumes)
47+
.withRemoveImages(DockerComposeContainer.RemoveImages.ALL)
48+
) {
49+
environment.start();
50+
51+
volumeName.set(volumeNameForRunningContainer("_redis_1"));
52+
final boolean isVolumePresentWhileRunning = isVolumePresent(volumeName.get());
53+
assertThat(isVolumePresentWhileRunning).as("the container volume is present while running").isEqualTo(true);
54+
}
55+
56+
await()
57+
.untilAsserted(() -> {
58+
final boolean isVolumePresentAfterRunning = isVolumePresent(volumeName.get());
59+
assertThat(isVolumePresentAfterRunning)
60+
.as("the container volume is present after running")
61+
.isEqualTo(this.shouldVolumesBePresentAfterRunning);
62+
});
63+
}
64+
65+
private String volumeNameForRunningContainer(final String containerNameSuffix) {
66+
return DockerClientFactory
67+
.instance()
68+
.client()
69+
.listContainersCmd()
70+
.exec()
71+
.stream()
72+
.filter(it -> Stream.of(it.getNames()).anyMatch(name -> name.endsWith(containerNameSuffix)))
73+
.findFirst()
74+
.map(container -> container.getMounts().get(0).getName())
75+
.orElseThrow(IllegalStateException::new);
76+
}
77+
78+
private boolean isVolumePresent(final String volumeName) {
79+
Set<String> nameFilter = new LinkedHashSet<>(1);
80+
nameFilter.add(volumeName);
81+
return DockerClientFactory
82+
.instance()
83+
.client()
84+
.listVolumesCmd()
85+
.withFilter("name", nameFilter)
86+
.exec()
87+
.getVolumes()
88+
.stream()
89+
.findFirst()
90+
.isPresent();
91+
}
92+
}

docs/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ A huge thank you to our sponsors:
222222
* [Bucket4j](https://github.com/bucket4j/bucket4j) - Java rate-limiting library based on the token-bucket algorithm.
223223
* [Spark ClickHouse Connector](https://github.com/housepower/spark-clickhouse-connector) - Integration tests for Apache Spark with both single node ClickHouse instance and multi-node ClickHouse cluster.
224224
* [Quarkus](https://github.com/quarkusio/quarkus) - Testcontainers is used extensively for Quarkus' [DevServices](https://quarkus.io/guides/dev-services) feature.
225+
* [Apache Kyuubi](https://kyuubi.apache.org) - Integration testing with Trino as data source engine, Kafka, etc.
225226

226227
## License
227228

gradle.properties

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
org.gradle.parallel=false
22
org.gradle.caching=true
33
org.gradle.configureondemand=true
4-
testcontainers.version=1.18.0
4+
testcontainers.version=1.18.1

mkdocs.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,4 +119,4 @@ nav:
119119
- bounty.md
120120
edit_uri: edit/main/docs/
121121
extra:
122-
latest_version: 1.18.0
122+
latest_version: 1.18.1

modules/cratedb/src/main/java/org/testcontainers/cratedb/CrateDBContainer.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class CrateDBContainer extends JdbcDatabaseContainer<CrateDBContainer> {
1313

1414
static final String IMAGE = "crate";
1515

16-
static final String DEFAULT_TAG = "5.2.5";
16+
static final String DEFAULT_TAG = "5.3.1";
1717

1818
private static final DockerImageName DEFAULT_IMAGE_NAME = DockerImageName.parse("crate");
1919

@@ -34,6 +34,7 @@ public CrateDBContainer(final String dockerImageName) {
3434
public CrateDBContainer(final DockerImageName dockerImageName) {
3535
super(dockerImageName);
3636
dockerImageName.assertCompatibleWith(DEFAULT_IMAGE_NAME);
37+
withCommand("crate -C discovery.type=single-node");
3738

3839
this.waitStrategy = Wait.forHttp("/").forPort(CRATEDB_HTTP_PORT).forStatusCode(200);
3940

modules/cratedb/src/test/java/org/testcontainers/junit/cratedb/SimpleCrateDBTest.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ public void testSimple() throws SQLException {
3434
public void testCommandOverride() throws SQLException {
3535
try (
3636
CrateDBContainer cratedb = new CrateDBContainer(CrateDBTestImages.CRATEDB_TEST_IMAGE)
37-
.withCommand("crate -C cluster.name=testcontainers")
37+
.withCommand("crate -C discovery.type=single-node -C cluster.name=testcontainers")
3838
) {
3939
cratedb.start();
4040

0 commit comments

Comments
 (0)