Skip to content

Commit 74a523a

Browse files
authored
[junit-jupiter] Add parallel attribute to @Testcontainers (#6658)
Fixes #5037
1 parent 65b6aeb commit 74a523a

File tree

4 files changed

+73
-7
lines changed

4 files changed

+73
-7
lines changed

modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/Testcontainers.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,4 +63,9 @@
6363
* Whether tests should be disabled (rather than failing) when Docker is not available.
6464
*/
6565
boolean disabledWithoutDocker() default false;
66+
67+
/**
68+
* Whether containers should start in parallel
69+
*/
70+
boolean parallel() default false;
6671
}

modules/junit-jupiter/src/main/java/org/testcontainers/junit/jupiter/TestcontainersExtension.java

Lines changed: 39 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
import org.junit.platform.commons.support.ReflectionSupport;
1919
import org.testcontainers.DockerClientFactory;
2020
import org.testcontainers.lifecycle.Startable;
21+
import org.testcontainers.lifecycle.Startables;
2122
import org.testcontainers.lifecycle.TestDescription;
2223
import org.testcontainers.lifecycle.TestLifecycleAware;
2324

@@ -52,9 +53,7 @@ public void beforeAll(ExtensionContext context) {
5253
Store store = context.getStore(NAMESPACE);
5354
List<StoreAdapter> sharedContainersStoreAdapters = findSharedContainers(testClass);
5455

55-
sharedContainersStoreAdapters.forEach(adapter -> {
56-
store.getOrComputeIfAbsent(adapter.getKey(), k -> adapter.start());
57-
});
56+
startContainers(sharedContainersStoreAdapters, store, context);
5857

5958
List<TestLifecycleAware> lifecycleAwareContainers = sharedContainersStoreAdapters
6059
.stream()
@@ -66,6 +65,18 @@ public void beforeAll(ExtensionContext context) {
6665
signalBeforeTestToContainers(lifecycleAwareContainers, testDescriptionFrom(context));
6766
}
6867

68+
private void startContainers(List<StoreAdapter> storeAdapters, Store store, ExtensionContext context) {
69+
if (storeAdapters.isEmpty()) {
70+
return;
71+
}
72+
73+
if (isParallelExecutionEnabled(context)) {
74+
Startables.deepStart(storeAdapters.stream().map(storeAdapter -> storeAdapter.container)).join();
75+
} else {
76+
storeAdapters.forEach(adapter -> store.getOrComputeIfAbsent(adapter.getKey(), k -> adapter.start()));
77+
}
78+
}
79+
6980
@Override
7081
public void afterAll(ExtensionContext context) {
7182
signalAfterTestToContainersFor(SHARED_LIFECYCLE_AWARE_CONTAINERS, context);
@@ -75,18 +86,39 @@ public void afterAll(ExtensionContext context) {
7586
public void beforeEach(final ExtensionContext context) {
7687
Store store = context.getStore(NAMESPACE);
7788

78-
List<TestLifecycleAware> lifecycleAwareContainers = collectParentTestInstances(context)
89+
List<StoreAdapter> restartContainers = collectParentTestInstances(context)
7990
.parallelStream()
8091
.flatMap(this::findRestartContainers)
81-
.peek(adapter -> store.getOrComputeIfAbsent(adapter.getKey(), k -> adapter.start()))
82-
.filter(this::isTestLifecycleAware)
83-
.map(lifecycleAwareAdapter -> (TestLifecycleAware) lifecycleAwareAdapter.container)
8492
.collect(Collectors.toList());
8593

94+
List<TestLifecycleAware> lifecycleAwareContainers = findTestLifecycleAwareContainers(
95+
restartContainers,
96+
store,
97+
context
98+
);
99+
86100
store.put(LOCAL_LIFECYCLE_AWARE_CONTAINERS, lifecycleAwareContainers);
87101
signalBeforeTestToContainers(lifecycleAwareContainers, testDescriptionFrom(context));
88102
}
89103

104+
private List<TestLifecycleAware> findTestLifecycleAwareContainers(
105+
List<StoreAdapter> restartContainers,
106+
Store store,
107+
ExtensionContext context
108+
) {
109+
startContainers(restartContainers, store, context);
110+
111+
return restartContainers
112+
.stream()
113+
.filter(this::isTestLifecycleAware)
114+
.map(lifecycleAwareAdapter -> (TestLifecycleAware) lifecycleAwareAdapter.container)
115+
.collect(Collectors.toList());
116+
}
117+
118+
private boolean isParallelExecutionEnabled(ExtensionContext context) {
119+
return findTestcontainers(context).map(Testcontainers::parallel).orElse(false);
120+
}
121+
90122
@Override
91123
public void afterEach(ExtensionContext context) {
92124
signalAfterTestToContainersFor(LOCAL_LIFECYCLE_AWARE_CONTAINERS, context);

modules/junit-jupiter/src/test/java/org/testcontainers/junit/jupiter/JUnitJupiterTestImages.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,4 +5,5 @@
55
public interface JUnitJupiterTestImages {
66
DockerImageName POSTGRES_IMAGE = DockerImageName.parse("postgres:9.6.12");
77
DockerImageName HTTPD_IMAGE = DockerImageName.parse("httpd:2.4-alpine");
8+
DockerImageName MYSQL_IMAGE = DockerImageName.parse("mysql:8.0.32");
89
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
package org.testcontainers.junit.jupiter;
2+
3+
import org.junit.jupiter.api.Test;
4+
import org.testcontainers.containers.MySQLContainer;
5+
import org.testcontainers.containers.PostgreSQLContainer;
6+
7+
import static org.assertj.core.api.Assertions.assertThat;
8+
9+
@Testcontainers(parallel = true)
10+
public class ParallelExecutionTests {
11+
12+
@Container
13+
private static final PostgreSQLContainer<?> POSTGRESQL_CONTAINER = new PostgreSQLContainer<>(
14+
JUnitJupiterTestImages.POSTGRES_IMAGE
15+
)
16+
.withDatabaseName("foo")
17+
.withUsername("foo")
18+
.withPassword("secret");
19+
20+
@Container
21+
private MySQLContainer<?> mySQLContainer = new MySQLContainer<>(JUnitJupiterTestImages.MYSQL_IMAGE);
22+
23+
@Test
24+
void test() {
25+
assertThat(POSTGRESQL_CONTAINER.isRunning()).isTrue();
26+
assertThat(mySQLContainer.isRunning()).isTrue();
27+
}
28+
}

0 commit comments

Comments
 (0)