Skip to content

Commit 6b95ba0

Browse files
enabled parallel tests.
starting up only one container for shared containers and setup in some parent class.
1 parent 4ff8d1d commit 6b95ba0

File tree

3 files changed

+130
-4
lines changed

3 files changed

+130
-4
lines changed

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

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package org.testcontainers.junit.jupiter;
22

3+
import java.util.concurrent.ConcurrentHashMap;
4+
import java.util.concurrent.atomic.AtomicInteger;
35
import lombok.Getter;
46
import org.junit.jupiter.api.extension.AfterAllCallback;
57
import org.junit.jupiter.api.extension.AfterEachCallback;
@@ -41,6 +43,7 @@ public class TestcontainersExtension
4143
private static final String SHARED_LIFECYCLE_AWARE_CONTAINERS = "sharedLifecycleAwareContainers";
4244

4345
private static final String LOCAL_LIFECYCLE_AWARE_CONTAINERS = "localLifecycleAwareContainers";
46+
private static final ConcurrentHashMap<String, StoreAdapterThread> STORE_ADAPTER_THREADS = new ConcurrentHashMap<>();
4447

4548
@Override
4649
public void beforeAll(ExtensionContext context) {
@@ -74,7 +77,7 @@ private void startContainers(List<StoreAdapter> storeAdapters, Store store, Exte
7477
Stream<Startable> startables = storeAdapters
7578
.stream()
7679
.map(storeAdapter -> {
77-
store.getOrComputeIfAbsent(storeAdapter.getKey(), k -> storeAdapter);
80+
store.getOrComputeIfAbsent(storeAdapter.getKey(), k -> storeAdapterStart(k, storeAdapter));
7881
return storeAdapter.container;
7982
});
8083
Startables.deepStart(startables).join();
@@ -130,6 +133,20 @@ public void afterEach(ExtensionContext context) {
130133
signalAfterTestToContainersFor(LOCAL_LIFECYCLE_AWARE_CONTAINERS, context);
131134
}
132135

136+
private static synchronized StoreAdapter storeAdapterStart(String key, StoreAdapter adapter) {
137+
boolean isInitialized = STORE_ADAPTER_THREADS.containsKey(key);
138+
139+
if (!isInitialized) {
140+
StoreAdapter storeAdapter = adapter.start();
141+
STORE_ADAPTER_THREADS.put(key, new StoreAdapterThread(storeAdapter));
142+
return storeAdapter;
143+
}
144+
145+
StoreAdapterThread storeAdapter = STORE_ADAPTER_THREADS.get(key);
146+
storeAdapter.threads.incrementAndGet();
147+
return storeAdapter.storeAdapter;
148+
}
149+
133150
private void signalBeforeTestToContainers(
134151
List<TestLifecycleAware> lifecycleAwareContainers,
135152
TestDescription testDescription
@@ -267,9 +284,9 @@ private static StoreAdapter getContainerInstance(final Object testInstance, fina
267284
private static class StoreAdapter implements CloseableResource {
268285

269286
@Getter
270-
private String key;
287+
private final String key;
271288

272-
private Startable container;
289+
private final Startable container;
273290

274291
private StoreAdapter(Class<?> declaringClass, String fieldName, Startable container) {
275292
this.key = declaringClass.getName() + "." + fieldName;
@@ -283,7 +300,25 @@ private StoreAdapter start() {
283300

284301
@Override
285302
public void close() {
286-
container.stop();
303+
int total = STORE_ADAPTER_THREADS.getOrDefault(key, StoreAdapterThread.NULL).threads.decrementAndGet();
304+
if (total < 1) {
305+
container.stop();
306+
STORE_ADAPTER_THREADS.remove(key);
307+
}
308+
}
309+
310+
}
311+
312+
private static class StoreAdapterThread {
313+
314+
public static final StoreAdapterThread NULL = new StoreAdapterThread(null);
315+
public final StoreAdapter storeAdapter;
316+
public final AtomicInteger threads = new AtomicInteger(1);
317+
318+
private StoreAdapterThread(StoreAdapter storeAdapter) {
319+
this.storeAdapter = storeAdapter;
287320
}
321+
288322
}
323+
289324
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
package org.testcontainers.junit.jupiter;
2+
3+
import static org.junit.Assert.assertEquals;
4+
import static org.testcontainers.junit.jupiter.JUnitJupiterTestImages.POSTGRES_IMAGE;
5+
6+
import com.zaxxer.hikari.HikariConfig;
7+
import com.zaxxer.hikari.HikariDataSource;
8+
import java.sql.ResultSet;
9+
import java.sql.SQLException;
10+
import java.sql.Statement;
11+
import org.junit.jupiter.api.Nested;
12+
import org.junit.jupiter.api.Test;
13+
import org.junit.jupiter.api.parallel.Execution;
14+
import org.junit.jupiter.api.parallel.ExecutionMode;
15+
import org.testcontainers.containers.PostgreSQLContainer;
16+
17+
@Testcontainers
18+
@Execution(ExecutionMode.CONCURRENT)
19+
public class ParallelContainerTests {
20+
21+
@Container
22+
protected static final PostgreSQLContainer<?> POSTGRE_SQL_CONTAINER = new PostgreSQLContainer<>(POSTGRES_IMAGE)
23+
.withDatabaseName("foo")
24+
.withUsername("foo")
25+
.withPassword("secret");
26+
27+
@Test
28+
void container_should_be_running_first_test() throws SQLException {
29+
assertContainerIsRunning(POSTGRE_SQL_CONTAINER);
30+
}
31+
32+
@Test
33+
void container_should_be_running_second_test() throws SQLException {
34+
assertContainerIsRunning(POSTGRE_SQL_CONTAINER);
35+
}
36+
37+
@Nested
38+
@Execution(ExecutionMode.CONCURRENT)
39+
class FirstParallelTest extends BaseContainerTests {
40+
41+
@Test
42+
void container_should_be_running() throws SQLException {
43+
assertContainerIsRunning(POSTGRE_SQL_BASE_CONTAINER);
44+
}
45+
46+
}
47+
48+
@Nested
49+
@Execution(ExecutionMode.CONCURRENT)
50+
class SecondParallelTest extends BaseContainerTests {
51+
52+
@Test
53+
void container_should_be_running() throws SQLException {
54+
assertContainerIsRunning(POSTGRE_SQL_BASE_CONTAINER);
55+
}
56+
57+
}
58+
59+
@Testcontainers
60+
private static class BaseContainerTests {
61+
62+
@Container
63+
protected static final PostgreSQLContainer<?> POSTGRE_SQL_BASE_CONTAINER = new PostgreSQLContainer<>(POSTGRES_IMAGE)
64+
.withDatabaseName("foo")
65+
.withUsername("foo")
66+
.withPassword("secret");
67+
68+
}
69+
70+
@SuppressWarnings({"SqlDialectInspection", "SqlNoDataSourceInspection"})
71+
private static void assertContainerIsRunning(PostgreSQLContainer<?> container) throws SQLException {
72+
HikariConfig hikariConfig = new HikariConfig();
73+
hikariConfig.setJdbcUrl(container.getJdbcUrl());
74+
hikariConfig.setUsername("foo");
75+
hikariConfig.setPassword("secret");
76+
77+
try (HikariDataSource ds = new HikariDataSource(hikariConfig)) {
78+
Statement statement = ds.getConnection().createStatement();
79+
statement.execute("SELECT 1");
80+
ResultSet resultSet = statement.getResultSet();
81+
resultSet.next();
82+
83+
int resultSetInt = resultSet.getInt(1);
84+
assertEquals(1, resultSetInt);
85+
}
86+
}
87+
88+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
junit.jupiter.execution.parallel.enabled=true
2+
junit.jupiter.execution.parallel.mode.default=same_thread
3+
junit.jupiter.execution.parallel.mode.classes.default=same_thread

0 commit comments

Comments
 (0)