Advanced Parallelism
Advanced parallel execution patterns and strategies
This page covers advanced parallelism patterns beyond the basics. See Configuration → Parallelism for basic configuration.
Resource Pooling with Parallel Arguments
When running arguments in parallel with limited resources, use a pool:
public class PooledResourceTest {
private static final Semaphore resourcePool = new Semaphore(3); // Max 3 concurrent
public static class TestContext {
private final Resource resource;
public TestContext(Resource resource) {
this.resource = resource;
}
public Resource getResource() {
return resource;
}
}
@Verifyica.ArgumentSupplier(parallelism = 10)
public static Object arguments() {
return IntStream.range(0, 10)
.mapToObj(i -> "arg-" + i)
.collect(Collectors.toList());
}
@Verifyica.BeforeAll
public void beforeAll(ArgumentContext argumentContext) throws InterruptedException {
// Acquire from pool (blocks if pool is full)
resourcePool.acquire();
Resource resource = ResourcePool.getInstance().acquire();
TestContext context = new TestContext(resource);
argumentContext.getMap().put("testContext", context);
}
@Verifyica.Test
public void test(ArgumentContext argumentContext) {
TestContext context = (TestContext) argumentContext.getMap().get("testContext");
context.getResource().use();
}
@Verifyica.AfterAll
public void afterAll(ArgumentContext argumentContext) {
TestContext context = (TestContext) argumentContext.getMap().get("testContext");
if (context != null && context.getResource() != null) {
ResourcePool.getInstance().release(context.getResource());
resourcePool.release(); // Return to pool
}
}
}
Dynamic Port Allocation
Avoid port conflicts when running parallel tests:
public class PortAllocationTest {
private static final AtomicInteger portCounter = new AtomicInteger(8000);
public static class TestContext {
private final int port;
private final Server server;
public TestContext(int port, Server server) {
this.port = port;
this.server = server;
}
public Server getServer() {
return server;
}
}
@Verifyica.ArgumentSupplier(parallelism = 5)
public static Object arguments() {
return List.of("service-1", "service-2", "service-3", "service-4", "service-5");
}
@Verifyica.BeforeAll
public void beforeAll(ArgumentContext argumentContext) {
// Allocate unique port for this argument
int port = portCounter.getAndIncrement();
Server server = new Server(port);
server.start();
TestContext context = new TestContext(port, server);
argumentContext.getMap().put("testContext", context);
}
@Verifyica.Test
public void testService(ArgumentContext argumentContext) {
TestContext context = (TestContext) argumentContext.getMap().get("testContext");
HttpClient client = new HttpClient("localhost", context.getServer().getPort());
Response response = client.get("/health");
assert response.getStatus() == 200;
}
@Verifyica.AfterAll
public void afterAll(ArgumentContext argumentContext) {
TestContext context = (TestContext) argumentContext.getMap().get("testContext");
if (context != null && context.getServer() != null) {
context.getServer().stop();
}
}
}
Partitioned Data Processing
Divide large datasets across parallel arguments:
public class DataPartitionTest {
@Verifyica.ArgumentSupplier(parallelism = 4)
public static Object arguments() {
List<Integer> allData = IntStream.range(0, 1000)
.boxed()
.collect(Collectors.toList());
// Partition into 4 chunks
int partitionSize = allData.size() / 4;
List<Argument<List<Integer>>> partitions = new ArrayList<>();
for (int i = 0; i < 4; i++) {
int start = i * partitionSize;
int end = (i == 3) ? allData.size() : (i + 1) * partitionSize;
List<Integer> partition = allData.subList(start, end);
partitions.add(Argument.of("partition-" + i, partition));
}
return partitions;
}
@Verifyica.Test
public void processPartition(List<Integer> partition) {
// Each argument processes its partition in parallel
partition.forEach(this::processItem);
}
private void processItem(int item) {
// Process individual item
}
}
Coordinating Parallel Tests
Use CountDownLatch to synchronize parallel arguments:
public class CoordinatedTest {
private static final CountDownLatch readyLatch = new CountDownLatch(3);
private static final CountDownLatch startLatch = new CountDownLatch(1);
@Verifyica.ArgumentSupplier(parallelism = 3)
public static Object arguments() {
return List.of("client-1", "client-2", "client-3");
}
@Verifyica.Test
public void coordinatedTest(String client) throws InterruptedException {
// Signal ready
System.out.println(client + " is ready");
readyLatch.countDown();
// Wait for all to be ready
readyLatch.await();
// All start together
System.out.println(client + " starting test");
performTest(client);
}
}
Mixing Sequential and Parallel Execution
Run some arguments sequentially, others in parallel:
public class MixedExecutionTest {
@Verifyica.ArgumentSupplier(parallelism = 1) // Sequential
public static Object criticalArguments() {
return List.of(
Argument.of("production-db", new DbConfig("prod"))
);
}
// In another test class with parallelism
@Verifyica.ArgumentSupplier(parallelism = 4) // Parallel
public static Object testArguments() {
return List.of(
Argument.of("test-db-1", new DbConfig("test1")),
Argument.of("test-db-2", new DbConfig("test2")),
Argument.of("test-db-3", new DbConfig("test3")),
Argument.of("test-db-4", new DbConfig("test4"))
);
}
}
Monitoring Parallel Execution
Track parallel test execution with metrics:
public class MonitoredParallelTest {
private static final AtomicInteger activeTests = new AtomicInteger(0);
private static final AtomicInteger completedTests = new AtomicInteger(0);
@Verifyica.ArgumentSupplier(parallelism = 8)
public static Object arguments() {
return IntStream.range(0, 20)
.mapToObj(i -> "arg-" + i)
.collect(Collectors.toList());
}
@Verifyica.BeforeAll
public void beforeAll(String argument) {
int active = activeTests.incrementAndGet();
System.out.println("Active tests: " + active + " (" + argument + ")");
}
@Verifyica.Test
public void test(String argument) {
// Test logic
}
@Verifyica.AfterAll
public void afterAll(String argument) {
activeTests.decrementAndGet();
int completed = completedTests.incrementAndGet();
System.out.println("Completed tests: " + completed + " (" + argument + ")");
}
}
Best Practices
Choose Appropriate Parallelism
// Good: Match parallelism to resources
@Verifyica.ArgumentSupplier(parallelism = 4) // 4 CPU cores
public static Object arguments() {
return getCpuBoundTests();
}
// Good: Higher parallelism for I/O bound
@Verifyica.ArgumentSupplier(parallelism = 20) // I/O bound
public static Object arguments() {
return getNetworkTests();
}
Avoid Excessive Parallelism
// Bad: Too much parallelism
@Verifyica.ArgumentSupplier(parallelism = 100)
public static Object arguments() {
return List.of("test"); // Only 1 argument!
}
Clean Up Resources
Always clean up in @AfterAll, even with failures:
@Verifyica.AfterAll
public void afterAll(ArgumentContext argumentContext) {
TestContext context = (TestContext) argumentContext.getMap().get("testContext");
if (context != null) {
try {
context.getResource().close();
} catch (Exception e) {
// Log but don't fail cleanup
logger.warn("Cleanup failed", e);
}
}
}
See Also
- Configuration → Parallelism - Basic parallelism configuration
- Execution Model - How parallel execution works
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.