Skip to content

Commit 21325fc

Browse files
enabled parallel tests.
starting up only one container for shared containers and setup in some parent class.
1 parent 63692d3 commit 21325fc

File tree

3 files changed

+130
-5
lines changed

3 files changed

+130
-5
lines changed

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

Lines changed: 39 additions & 5 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;
@@ -23,7 +25,6 @@
2325
import org.testcontainers.lifecycle.TestLifecycleAware;
2426

2527
import java.lang.reflect.Field;
26-
import java.lang.reflect.Method;
2728
import java.util.LinkedHashSet;
2829
import java.util.List;
2930
import java.util.Optional;
@@ -41,6 +42,7 @@ class TestcontainersExtension implements BeforeEachCallback, BeforeAllCallback,
4142
private static final String TEST_INSTANCE = "testInstance";
4243
private static final String SHARED_LIFECYCLE_AWARE_CONTAINERS = "sharedLifecycleAwareContainers";
4344
private static final String LOCAL_LIFECYCLE_AWARE_CONTAINERS = "localLifecycleAwareContainers";
45+
private static final ConcurrentHashMap<String, StoreAdapterThread> STORE_ADAPTER_THREADS = new ConcurrentHashMap<>();
4446

4547
@Override
4648
public void postProcessTestInstance(final Object testInstance, final ExtensionContext context) {
@@ -56,7 +58,7 @@ public void beforeAll(ExtensionContext context) {
5658
Store store = context.getStore(NAMESPACE);
5759
List<StoreAdapter> sharedContainersStoreAdapters = findSharedContainers(testClass);
5860

59-
sharedContainersStoreAdapters.forEach(adapter -> store.getOrComputeIfAbsent(adapter.getKey(), k -> adapter.start()));
61+
sharedContainersStoreAdapters.forEach(adapter -> store.getOrComputeIfAbsent(adapter.getKey(), k -> storeAdapterStart(k, adapter)));
6062

6163
List<TestLifecycleAware> lifecycleAwareContainers = sharedContainersStoreAdapters
6264
.stream()
@@ -93,6 +95,20 @@ public void afterEach(ExtensionContext context) {
9395
signalAfterTestToContainersFor(LOCAL_LIFECYCLE_AWARE_CONTAINERS, context);
9496
}
9597

98+
private static synchronized StoreAdapter storeAdapterStart(String key, StoreAdapter adapter) {
99+
boolean isInitialized = STORE_ADAPTER_THREADS.containsKey(key);
100+
101+
if (!isInitialized) {
102+
StoreAdapter storeAdapter = adapter.start();
103+
STORE_ADAPTER_THREADS.put(key, new StoreAdapterThread(storeAdapter));
104+
return storeAdapter;
105+
}
106+
107+
StoreAdapterThread storeAdapter = STORE_ADAPTER_THREADS.get(key);
108+
storeAdapter.threads.incrementAndGet();
109+
return storeAdapter.storeAdapter;
110+
}
111+
96112
private void signalBeforeTestToContainers(List<TestLifecycleAware> lifecycleAwareContainers, TestDescription testDescription) {
97113
lifecycleAwareContainers.forEach(container -> container.beforeTest(testDescription));
98114
}
@@ -229,9 +245,9 @@ private static StoreAdapter getContainerInstance(final Object testInstance, fina
229245
private static class StoreAdapter implements CloseableResource {
230246

231247
@Getter
232-
private String key;
248+
private final String key;
233249

234-
private Startable container;
250+
private final Startable container;
235251

236252
private StoreAdapter(Class<?> declaringClass, String fieldName, Startable container) {
237253
this.key = declaringClass.getName() + "." + fieldName;
@@ -245,7 +261,25 @@ private StoreAdapter start() {
245261

246262
@Override
247263
public void close() {
248-
container.stop();
264+
int total = STORE_ADAPTER_THREADS.getOrDefault(key, StoreAdapterThread.NULL).threads.decrementAndGet();
265+
if (total < 1) {
266+
container.stop();
267+
STORE_ADAPTER_THREADS.remove(key);
268+
}
269+
}
270+
271+
}
272+
273+
private static class StoreAdapterThread {
274+
275+
public static final StoreAdapterThread NULL = new StoreAdapterThread(null);
276+
public final StoreAdapter storeAdapter;
277+
public final AtomicInteger threads = new AtomicInteger(1);
278+
279+
private StoreAdapterThread(StoreAdapter storeAdapter) {
280+
this.storeAdapter = storeAdapter;
249281
}
282+
250283
}
284+
251285
}
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)