0
# Parallel Execution and I/O Support
1
2
Thread-safe test execution with resource management, temporary directory support, and execution control annotations. This module provides capabilities for running tests concurrently while ensuring proper resource isolation and file system operations.
3
4
## Capabilities
5
6
### Parallel Execution Control
7
8
Configure how tests execute in relation to each other and control thread usage.
9
10
```java { .api }
11
/**
12
* Configure parallel execution mode for test classes and methods
13
*/
14
@Execution(ExecutionMode value)
15
16
/**
17
* Execute test in isolation (forces sequential execution)
18
*/
19
@Isolated
20
21
/**
22
* Execution mode enumeration
23
*/
24
enum ExecutionMode {
25
/**
26
* Execute in same thread as parent
27
*/
28
SAME_THREAD,
29
30
/**
31
* Execute concurrently when parallel execution is enabled
32
*/
33
CONCURRENT
34
}
35
```
36
37
**Usage Examples:**
38
39
```java
40
import org.junit.jupiter.api.parallel.*;
41
import org.junit.jupiter.api.execution.ExecutionMode;
42
43
// Class-level parallel execution
44
@Execution(ExecutionMode.CONCURRENT)
45
class ParallelTest {
46
47
@Test
48
@Execution(ExecutionMode.CONCURRENT)
49
void concurrentTest1() {
50
// Runs concurrently with other concurrent tests
51
performIndependentCalculation();
52
}
53
54
@Test
55
@Execution(ExecutionMode.CONCURRENT)
56
void concurrentTest2() {
57
// Can run in parallel with concurrentTest1
58
performAnotherIndependentCalculation();
59
}
60
61
@Test
62
@Execution(ExecutionMode.SAME_THREAD)
63
void sequentialTest() {
64
// Always runs in same thread as parent
65
performThreadSensitiveOperation();
66
}
67
}
68
69
// Force sequential execution for entire class
70
@Execution(ExecutionMode.SAME_THREAD)
71
class SequentialTest {
72
73
@Test
74
void test1() {
75
// Always sequential due to class-level annotation
76
}
77
78
@Test
79
void test2() {
80
// Runs after test1 completes
81
}
82
}
83
84
// Isolation for tests that cannot run concurrently with anything
85
@Isolated
86
class IsolatedTest {
87
88
@Test
89
void isolatedTest() {
90
// No other tests run concurrently with this
91
modifyGlobalState();
92
}
93
}
94
```
95
96
### Resource Management
97
98
Declare dependencies on shared resources to prevent concurrent access conflicts.
99
100
```java { .api }
101
/**
102
* Declare a resource dependency with access mode
103
*/
104
@ResourceLock(String value, ResourceAccessMode mode = ResourceAccessMode.READ_WRITE)
105
106
/**
107
* Multiple resource locks
108
*/
109
@ResourceLocks({
110
@ResourceLock(value = "resource1", mode = ResourceAccessMode.READ),
111
@ResourceLock(value = "resource2", mode = ResourceAccessMode.READ_WRITE)
112
})
113
114
/**
115
* Resource access modes
116
*/
117
enum ResourceAccessMode {
118
/**
119
* Read-only access - multiple readers allowed
120
*/
121
READ,
122
123
/**
124
* Read-write access - exclusive access required
125
*/
126
READ_WRITE
127
}
128
129
/**
130
* Programmatic resource lock provider
131
*/
132
interface ResourceLocksProvider {
133
java.util.Set<Lock> provideForNestedClass(Class<?> testClass);
134
java.util.Set<Lock> provideForMethod(Class<?> testClass, java.lang.reflect.Method testMethod);
135
}
136
```
137
138
**Usage Examples:**
139
140
```java
141
import org.junit.jupiter.api.parallel.*;
142
143
class ResourceLockTest {
144
145
@Test
146
@ResourceLock(value = "database", mode = ResourceAccessMode.READ_WRITE)
147
void exclusiveDatabaseTest() {
148
// Exclusive access to database resource
149
database.createTable("test_table");
150
database.insertData("test_data");
151
// No other database tests run concurrently
152
}
153
154
@Test
155
@ResourceLock(value = "database", mode = ResourceAccessMode.READ)
156
void readOnlyDatabaseTest1() {
157
// Can run concurrently with other READ mode tests
158
List<String> tables = database.listTables();
159
assertFalse(tables.isEmpty());
160
}
161
162
@Test
163
@ResourceLock(value = "database", mode = ResourceAccessMode.READ)
164
void readOnlyDatabaseTest2() {
165
// Can run concurrently with readOnlyDatabaseTest1
166
int count = database.getTableCount();
167
assertTrue(count >= 0);
168
}
169
170
@Test
171
@ResourceLocks({
172
@ResourceLock(value = "file.system", mode = ResourceAccessMode.READ_WRITE),
173
@ResourceLock(value = "network", mode = ResourceAccessMode.READ)
174
})
175
void multipleResourceTest() {
176
// Exclusive file system access, shared network access
177
File tempFile = new File("temp.txt");
178
tempFile.createNewFile();
179
180
NetworkService.readConfiguration(); // Read-only network access
181
}
182
}
183
184
// Built-in resource constants
185
class SystemResourceTest {
186
187
@Test
188
@ResourceLock(Resources.SYSTEM_PROPERTIES)
189
void systemPropertiesTest() {
190
// Exclusive access to system properties
191
String oldValue = System.getProperty("test.property");
192
System.setProperty("test.property", "test-value");
193
try {
194
// Test logic
195
} finally {
196
if (oldValue != null) {
197
System.setProperty("test.property", oldValue);
198
} else {
199
System.clearProperty("test.property");
200
}
201
}
202
}
203
204
@Test
205
@ResourceLock(Resources.SYSTEM_OUT)
206
void systemOutTest() {
207
// Exclusive access to System.out
208
PrintStream originalOut = System.out;
209
ByteArrayOutputStream capturedOutput = new ByteArrayOutputStream();
210
System.setOut(new PrintStream(capturedOutput));
211
212
try {
213
System.out.println("Test output");
214
assertEquals("Test output\n", capturedOutput.toString());
215
} finally {
216
System.setOut(originalOut);
217
}
218
}
219
}
220
```
221
222
### Built-in Resource Constants
223
224
Predefined resource identifiers for common shared resources.
225
226
```java { .api }
227
/**
228
* Predefined resource constants for common system resources
229
*/
230
class Resources {
231
/**
232
* System properties resource
233
*/
234
static final String SYSTEM_PROPERTIES = "java.lang.System.properties";
235
236
/**
237
* System.out resource
238
*/
239
static final String SYSTEM_OUT = "java.lang.System.out";
240
241
/**
242
* System.err resource
243
*/
244
static final String SYSTEM_ERR = "java.lang.System.err";
245
246
/**
247
* Default locale resource
248
*/
249
static final String LOCALE = "java.util.Locale.default";
250
}
251
```
252
253
### Temporary Directory Support
254
255
Automatic creation and cleanup of temporary directories for tests.
256
257
```java { .api }
258
/**
259
* Inject temporary directory into test parameter or field
260
*/
261
@TempDir(
262
CleanupMode cleanup = CleanupMode.DEFAULT,
263
TempDirFactory factory = TempDirFactory.Standard.class
264
)
265
266
/**
267
* Cleanup modes for temporary directories
268
*/
269
enum CleanupMode {
270
/**
271
* Use default cleanup behavior (clean up after test completion)
272
*/
273
DEFAULT,
274
275
/**
276
* Always clean up temporary directory
277
*/
278
ALWAYS,
279
280
/**
281
* Clean up only if test succeeds
282
*/
283
ON_SUCCESS,
284
285
/**
286
* Never clean up temporary directory
287
*/
288
NEVER
289
}
290
291
/**
292
* Factory for creating temporary directories
293
*/
294
interface TempDirFactory extends java.io.Closeable {
295
/**
296
* Create a new temporary directory
297
* @param elementContext the context of the field or parameter where @TempDir is declared
298
* @param extensionContext the current extension context
299
* @return the path to the newly created temporary directory
300
* @throws Exception in case of failures
301
*/
302
java.nio.file.Path createTempDirectory(java.lang.reflect.AnnotatedElement elementContext, ExtensionContext extensionContext) throws Exception;
303
304
/**
305
* Close resources - default implementation does nothing
306
*/
307
@Override
308
default void close() throws java.io.IOException;
309
310
/**
311
* Standard implementation using Files.createTempDirectory() with "junit-" prefix
312
*/
313
class Standard implements TempDirFactory {
314
public static final TempDirFactory INSTANCE = new Standard();
315
316
@Override
317
public java.nio.file.Path createTempDirectory(java.lang.reflect.AnnotatedElement elementContext, ExtensionContext extensionContext) throws java.io.IOException;
318
}
319
}
320
```
321
322
**Usage Examples:**
323
324
```java
325
import org.junit.jupiter.api.io.*;
326
import java.nio.file.*;
327
328
class TempDirectoryTest {
329
330
// Field injection
331
@TempDir
332
Path tempDirectory;
333
334
// Alternative field injection with File type
335
@TempDir
336
File tempDir;
337
338
@Test
339
void testWithFieldInjection() throws Exception {
340
// Use tempDirectory field
341
Path testFile = tempDirectory.resolve("test.txt");
342
Files.write(testFile, "Hello World".getBytes());
343
344
assertTrue(Files.exists(testFile));
345
assertEquals("Hello World", Files.readString(testFile));
346
347
// Also works with File type
348
File anotherFile = new File(tempDir, "another.txt");
349
assertTrue(anotherFile.createNewFile());
350
}
351
352
@Test
353
void testWithParameterInjection(@TempDir Path tempDir) throws Exception {
354
// Parameter injection
355
Path configFile = tempDir.resolve("config.properties");
356
Properties props = new Properties();
357
props.setProperty("key", "value");
358
359
try (OutputStream out = Files.newOutputStream(configFile)) {
360
props.store(out, "Test configuration");
361
}
362
363
assertTrue(Files.exists(configFile));
364
}
365
366
@Test
367
void testWithCustomCleanup(@TempDir(cleanup = CleanupMode.NEVER) Path persistentDir) throws Exception {
368
// Directory won't be cleaned up after test
369
Path importantFile = persistentDir.resolve("important.data");
370
Files.write(importantFile, "Important data that should persist".getBytes());
371
372
System.out.println("Data saved to: " + importantFile.toAbsolutePath());
373
}
374
375
@Test
376
void testCleanupOnSuccess(@TempDir(cleanup = CleanupMode.ON_SUCCESS) Path conditionalDir) throws Exception {
377
// Directory cleaned up only if test passes
378
Path debugFile = conditionalDir.resolve("debug.log");
379
Files.write(debugFile, "Debug information".getBytes());
380
381
// If test fails, debug.log will remain for investigation
382
assertTrue(Files.exists(debugFile));
383
}
384
385
// Multiple temp directories
386
@Test
387
void testMultipleTempDirs(@TempDir Path inputDir, @TempDir Path outputDir) throws Exception {
388
// Create input file
389
Path input = inputDir.resolve("input.txt");
390
Files.write(input, "Input data".getBytes());
391
392
// Process and create output file
393
Path output = outputDir.resolve("output.txt");
394
String processed = Files.readString(input).toUpperCase();
395
Files.write(output, processed.getBytes());
396
397
assertEquals("INPUT DATA", Files.readString(output));
398
}
399
}
400
401
// Custom temp directory factory
402
class CustomTempDirectoryTest {
403
404
public static class CustomTempDirFactory implements TempDirFactory {
405
@Override
406
public java.nio.file.Path createTempDirectory(java.lang.reflect.AnnotatedElement annotatedElement, ExtensionContext extensionContext) throws Exception {
407
// Create temp directory in specific location with custom naming
408
java.nio.file.Path baseDir = java.nio.file.Paths.get(System.getProperty("user.home"), "test-temp");
409
java.nio.file.Files.createDirectories(baseDir);
410
411
String dirName = "test-" + extensionContext.getDisplayName().replaceAll("[^a-zA-Z0-9]", "-") + "-" + System.currentTimeMillis();
412
return java.nio.file.Files.createDirectory(baseDir.resolve(dirName));
413
}
414
}
415
416
@Test
417
void testWithCustomFactory(@TempDir(factory = CustomTempDirFactory.class) Path customTempDir) throws Exception {
418
// Uses custom factory for directory creation
419
assertTrue(customTempDir.toAbsolutePath().toString().contains("test-temp"));
420
421
Path testFile = customTempDir.resolve("custom-test.txt");
422
Files.write(testFile, "Custom temp directory test".getBytes());
423
assertTrue(Files.exists(testFile));
424
}
425
}
426
```
427
428
### Timeout Support
429
430
Control test execution timeouts at method and class levels.
431
432
```java { .api }
433
import java.util.concurrent.TimeUnit;
434
435
/**
436
* Set timeout for test execution
437
*/
438
@Timeout(
439
long value,
440
TimeUnit unit = TimeUnit.SECONDS,
441
ThreadMode threadMode = ThreadMode.INFERRED
442
)
443
444
/**
445
* Thread mode for timeout handling
446
*/
447
enum ThreadMode {
448
/**
449
* Infer thread mode based on execution mode
450
*/
451
INFERRED,
452
453
/**
454
* Interrupt test execution in same thread
455
*/
456
SAME_THREAD,
457
458
/**
459
* Execute test in separate thread for timeout
460
*/
461
SEPARATE_THREAD
462
}
463
```
464
465
**Usage Examples:**
466
467
```java
468
import org.junit.jupiter.api.Timeout;
469
import java.util.concurrent.TimeUnit;
470
471
class TimeoutTest {
472
473
@Test
474
@Timeout(5) // 5 seconds default
475
void fastTest() {
476
// Must complete within 5 seconds
477
performQuickOperation();
478
}
479
480
@Test
481
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
482
void veryFastTest() {
483
// Must complete within 500 milliseconds
484
int result = 2 + 2;
485
assertEquals(4, result);
486
}
487
488
@Test
489
@Timeout(value = 30, unit = TimeUnit.SECONDS, threadMode = ThreadMode.SEPARATE_THREAD)
490
void longRunningTest() {
491
// Runs in separate thread, interrupted after 30 seconds
492
performLongRunningOperation();
493
}
494
495
@Test
496
@Timeout(value = 1, unit = TimeUnit.MINUTES)
497
void integrationTest() {
498
// Integration test with 1 minute timeout
499
performIntegrationTest();
500
}
501
}
502
503
// Class-level timeout applies to all test methods
504
@Timeout(10)
505
class TimedTestClass {
506
507
@Test
508
void test1() {
509
// Inherits 10 second timeout from class
510
}
511
512
@Test
513
@Timeout(30) // Overrides class-level timeout
514
void slowTest() {
515
// Gets 30 second timeout instead of class default
516
}
517
}
518
```
519
520
### Thread Interruption Handling
521
522
Extensions can register callbacks for thread interruption events.
523
524
```java { .api }
525
/**
526
* Called before a thread is interrupted due to timeout
527
*/
528
interface PreInterruptCallback extends Extension {
529
void preInterrupt(ExtensionContext context, Thread thread) throws Exception;
530
}
531
```
532
533
**Usage Examples:**
534
535
```java
536
// Extension to handle cleanup before thread interruption
537
public class CleanupBeforeInterruptExtension implements PreInterruptCallback {
538
539
@Override
540
public void preInterrupt(ExtensionContext context, Thread thread) throws Exception {
541
System.out.println("Test about to be interrupted: " + context.getDisplayName());
542
543
// Perform cleanup before interruption
544
cleanup();
545
546
// Set flag to allow graceful shutdown
547
TestState.setInterrupting(true);
548
}
549
550
private void cleanup() {
551
// Close resources, save state, etc.
552
ResourceManager.closeAll();
553
}
554
}
555
556
@ExtendWith(CleanupBeforeInterruptExtension.class)
557
class InterruptibleTest {
558
559
@Test
560
@Timeout(5)
561
void longRunningTest() {
562
while (!TestState.isInterrupting()) {
563
// Check interruption flag periodically
564
performWork();
565
566
if (Thread.currentThread().isInterrupted()) {
567
break;
568
}
569
}
570
}
571
}
572
```
573
574
## Configuration
575
576
Parallel execution is configured through JUnit Platform configuration properties.
577
578
**Usage Examples:**
579
580
```properties
581
# Enable parallel execution
582
junit.jupiter.execution.parallel.enabled=true
583
584
# Execution mode strategy
585
junit.jupiter.execution.parallel.mode.default=concurrent
586
junit.jupiter.execution.parallel.mode.classes.default=concurrent
587
588
# Thread pool configuration
589
junit.jupiter.execution.parallel.config.strategy=dynamic
590
junit.jupiter.execution.parallel.config.dynamic.factor=1.0
591
592
# Custom thread pool
593
junit.jupiter.execution.parallel.config.strategy=custom
594
junit.jupiter.execution.parallel.config.custom.class=com.example.CustomParallelExecutionConfigurationStrategy
595
```
596
597
```java
598
// Programmatic configuration
599
@ExtendWith(ParallelConfigExtension.class)
600
class ConfiguredParallelTest {
601
// Tests with custom parallel configuration
602
}
603
604
public class ParallelConfigExtension implements BeforeAllCallback {
605
@Override
606
public void beforeAll(ExtensionContext context) throws Exception {
607
// Configure parallel execution programmatically
608
System.setProperty("junit.jupiter.execution.parallel.enabled", "true");
609
System.setProperty("junit.jupiter.execution.parallel.mode.default", "concurrent");
610
}
611
}
612
```
613
614
## Types
615
616
### Resource Lock
617
618
```java { .api }
619
/**
620
* Represents a resource lock for synchronization
621
*/
622
class Lock {
623
Lock(String key, ResourceAccessMode mode);
624
625
String getKey();
626
ResourceAccessMode getMode();
627
}
628
```
629
630
### Execution Configuration
631
632
```java { .api }
633
/**
634
* Strategy interface for parallel execution configuration
635
*/
636
interface ParallelExecutionConfigurationStrategy {
637
ParallelExecutionConfiguration createConfiguration(ConfigurationParameters configurationParameters);
638
}
639
640
/**
641
* Configuration for parallel execution
642
*/
643
interface ParallelExecutionConfiguration {
644
int getParallelism();
645
int getMinimumRunnable();
646
int getMaxPoolSize();
647
int getCorePoolSize();
648
long getKeepAliveTime();
649
java.util.concurrent.TimeUnit getKeepAliveTimeUnit();
650
}
651
```
652
653
### Test Resource Management
654
655
```java { .api }
656
/**
657
* Interface for closeable test resources that are automatically cleaned up
658
*/
659
@FunctionalInterface
660
interface CloseableResource {
661
void close() throws Exception;
662
}
663
664
/**
665
* Resource management through extension context
666
*/
667
interface Store {
668
/**
669
* Store a closeable resource that will be automatically closed
670
*/
671
void put(Object key, CloseableResource resource);
672
}
673
```
674
675
**Usage Examples:**
676
677
```java
678
public class DatabaseTestExtension implements BeforeEachCallback, AfterEachCallback {
679
680
private static final Namespace NAMESPACE = Namespace.create("database");
681
682
@Override
683
public void beforeEach(ExtensionContext context) throws Exception {
684
// Store closeable resource
685
DatabaseConnection connection = new DatabaseConnection();
686
context.getStore(NAMESPACE).put("connection", connection::close);
687
context.getStore(NAMESPACE).put("connectionObject", connection);
688
}
689
690
// AfterEachCallback not needed - resources auto-closed by JUnit
691
}
692
```