0
# Configuration and Storage
1
2
Configuration system and hierarchical storage for test engines to manage parameters and test-scoped data. This system provides engines with access to configuration parameters and hierarchical data storage with automatic lifecycle management.
3
4
## Capabilities
5
6
### ConfigurationParameters Interface
7
8
Interface for accessing configuration parameters provided to test engines during discovery and execution.
9
10
```java { .api }
11
/**
12
* Configuration parameters for test engines, providing type-safe parameter access.
13
*/
14
public interface ConfigurationParameters {
15
/**
16
* Get a configuration parameter value.
17
* @param key the parameter key
18
* @return optional parameter value
19
*/
20
Optional<String> get(String key);
21
22
/**
23
* Get a boolean configuration parameter.
24
* @param key the parameter key
25
* @return optional boolean value (true/false)
26
*/
27
Optional<Boolean> getBoolean(String key);
28
29
/**
30
* Get a configuration parameter with custom transformation.
31
* @param key the parameter key
32
* @param transformer function to transform string value to desired type
33
* @return optional transformed value
34
*/
35
<T> Optional<T> get(String key, Function<String, T> transformer);
36
37
/**
38
* Get all configuration parameter keys.
39
* @return unmodifiable set of parameter keys
40
*/
41
Set<String> keySet();
42
43
/**
44
* Default configuration file name.
45
*/
46
String CONFIG_FILE_NAME = "junit-platform.properties";
47
}
48
```
49
50
**Usage Example:**
51
52
```java
53
@Override
54
public void execute(ExecutionRequest request) {
55
ConfigurationParameters config = request.getConfigurationParameters();
56
57
// Get string parameters
58
String environment = config.get("test.environment").orElse("development");
59
Optional<String> dbUrl = config.get("database.url");
60
61
// Get boolean parameters
62
boolean parallelExecution = config.getBoolean("junit.jupiter.execution.parallel.enabled")
63
.orElse(false);
64
boolean verboseLogging = config.getBoolean("logging.verbose").orElse(false);
65
66
// Get numeric parameters with transformation
67
int timeout = config.get("test.timeout.seconds", Integer::parseInt).orElse(30);
68
double retryDelay = config.get("retry.delay", Double::parseDouble).orElse(1.0);
69
70
// Get enum parameters
71
LogLevel logLevel = config.get("log.level", LogLevel::valueOf)
72
.orElse(LogLevel.INFO);
73
74
// List all available configuration keys
75
Set<String> allKeys = config.keySet();
76
System.out.println("Available configuration keys: " + allKeys);
77
}
78
```
79
80
### PrefixedConfigurationParameters
81
82
Configuration parameters wrapper that automatically applies a prefix to parameter keys, useful for engine-specific configuration.
83
84
```java { .api }
85
/**
86
* Configuration parameters with automatic prefix handling for engine-specific configuration.
87
*/
88
public class PrefixedConfigurationParameters implements ConfigurationParameters {
89
/**
90
* Create prefixed configuration parameters.
91
* @param delegate the underlying configuration parameters
92
* @param prefix the prefix to apply to keys
93
*/
94
public PrefixedConfigurationParameters(ConfigurationParameters delegate, String prefix);
95
96
// All ConfigurationParameters methods automatically apply prefix
97
}
98
```
99
100
**Usage Example:**
101
102
```java
103
public class MyTestEngine implements TestEngine {
104
private static final String ENGINE_PREFIX = "myengine.";
105
106
@Override
107
public void execute(ExecutionRequest request) {
108
// Create engine-specific configuration
109
ConfigurationParameters engineConfig = new PrefixedConfigurationParameters(
110
request.getConfigurationParameters(), ENGINE_PREFIX
111
);
112
113
// Access engine-specific parameters without prefix
114
boolean enableFeature = engineConfig.getBoolean("feature.enabled").orElse(false);
115
// Actual key looked up: "myengine.feature.enabled"
116
117
int threadCount = engineConfig.get("thread.count", Integer::parseInt).orElse(1);
118
// Actual key looked up: "myengine.thread.count"
119
}
120
}
121
```
122
123
### NamespacedHierarchicalStore
124
125
Hierarchical, namespaced key-value store with automatic resource management and parent-child relationships for test-scoped data storage.
126
127
```java { .api }
128
/**
129
* Hierarchical, namespaced key-value store with automatic resource management.
130
* @param N the namespace type
131
*/
132
public final class NamespacedHierarchicalStore<N> implements AutoCloseable {
133
134
/**
135
* Get a value from the store.
136
* @param namespace the namespace
137
* @param key the key
138
* @return the stored value, or null if not found
139
*/
140
public Object get(N namespace, Object key);
141
142
/**
143
* Get a typed value from the store.
144
* @param namespace the namespace
145
* @param key the key
146
* @param requiredType the required value type
147
* @return the stored value cast to the required type
148
* @throws ClassCastException if value cannot be cast to required type
149
*/
150
public <T> T get(N namespace, Object key, Class<T> requiredType);
151
152
/**
153
* Store a value in the store.
154
* @param namespace the namespace
155
* @param key the key
156
* @param value the value to store
157
* @return the previously stored value, or null if none
158
*/
159
public Object put(N namespace, Object key, Object value);
160
161
/**
162
* Remove a value from the store.
163
* @param namespace the namespace
164
* @param key the key
165
* @return the removed value, or null if not found
166
*/
167
public Object remove(N namespace, Object key);
168
169
/**
170
* Get a value or compute it if absent.
171
* @param namespace the namespace
172
* @param key the key
173
* @param defaultCreator function to create default value
174
* @return existing or newly computed value
175
*/
176
public <K, V> Object getOrComputeIfAbsent(N namespace, K key, Function<K, V> defaultCreator);
177
178
/**
179
* Create a child store with this store as parent.
180
* @return new child store
181
*/
182
public NamespacedHierarchicalStore<N> newChild();
183
184
/**
185
* Get the parent store.
186
* @return optional parent store
187
*/
188
public Optional<NamespacedHierarchicalStore<N>> getParent();
189
190
/**
191
* Check if this store has been closed.
192
* @return true if closed
193
*/
194
public boolean isClosed();
195
196
/**
197
* Close this store and execute all close actions.
198
* Automatically called when execution context ends.
199
*/
200
@Override
201
public void close() throws Exception;
202
203
/**
204
* Action to execute when store is closed.
205
* @param N the namespace type
206
*/
207
@FunctionalInterface
208
public interface CloseAction<N> {
209
void close(N namespace, Object key, Object value) throws Exception;
210
}
211
}
212
```
213
214
**Usage Example:**
215
216
```java
217
public class MyExecutionContext implements EngineExecutionContext {
218
private final NamespacedHierarchicalStore<Namespace> store;
219
220
public void setupTestInstance(TestDescriptor testDescriptor) {
221
// Store test instance in test-specific namespace
222
Namespace testNamespace = Namespace.create("test", testDescriptor.getUniqueId());
223
Object testInstance = createTestInstance();
224
store.put(testNamespace, "instance", testInstance);
225
226
// Store database connection with cleanup
227
DataSource dataSource = store.getOrComputeIfAbsent(
228
Namespace.create("database"),
229
"connection",
230
key -> createDatabaseConnection()
231
);
232
233
// Store temporary files with automatic cleanup
234
Path tempDir = store.getOrComputeIfAbsent(
235
testNamespace,
236
"temp.dir",
237
key -> {
238
try {
239
Path dir = Files.createTempDirectory("test-");
240
// Register cleanup action
241
store.put(testNamespace, "temp.dir.cleanup",
242
(CloseAction<Namespace>) (ns, k, v) -> deleteDirectory((Path) v));
243
return dir;
244
} catch (IOException e) {
245
throw new RuntimeException(e);
246
}
247
}
248
);
249
}
250
251
public <T> T getTestScopedValue(TestDescriptor testDescriptor, String key, Class<T> type) {
252
Namespace testNamespace = Namespace.create("test", testDescriptor.getUniqueId());
253
return store.get(testNamespace, key, type);
254
}
255
256
public void cleanupTest(TestDescriptor testDescriptor) {
257
// Create child store for this test - will be automatically closed
258
try (NamespacedHierarchicalStore<Namespace> testStore = store.newChild()) {
259
Namespace testNamespace = Namespace.create("test", testDescriptor.getUniqueId());
260
261
// Test-specific operations...
262
// Store will be automatically closed, executing cleanup actions
263
}
264
}
265
}
266
```
267
268
### Namespace
269
270
Represents namespaces for organizing data within the hierarchical store.
271
272
```java { .api }
273
/**
274
* Represents a namespace for organizing data in hierarchical stores.
275
*/
276
public final class Namespace {
277
/**
278
* Create a namespace from parts.
279
* @param parts the namespace parts
280
* @return namespace instance
281
*/
282
public static Namespace create(Object... parts);
283
284
/**
285
* Get namespace parts.
286
* @return list of namespace parts
287
*/
288
public List<Object> getParts();
289
290
// Common namespaces
291
public static final Namespace GLOBAL = create("global");
292
}
293
```
294
295
### Store Exception Handling
296
297
Exception handling for hierarchical store operations.
298
299
```java { .api }
300
/**
301
* Exception thrown by hierarchical store operations.
302
*/
303
public class NamespacedHierarchicalStoreException extends RuntimeException {
304
/**
305
* Create store exception with message.
306
* @param message the exception message
307
*/
308
public NamespacedHierarchicalStoreException(String message);
309
310
/**
311
* Create store exception with message and cause.
312
* @param message the exception message
313
* @param cause the underlying cause
314
*/
315
public NamespacedHierarchicalStoreException(String message, Throwable cause);
316
}
317
```
318
319
### Advanced Store Usage Patterns
320
321
**Resource Management with Cleanup:**
322
323
```java
324
public class DatabaseTestSupport {
325
private final NamespacedHierarchicalStore<Namespace> store;
326
327
public DataSource getDatabaseConnection(TestDescriptor testDescriptor) {
328
Namespace dbNamespace = Namespace.create("database", testDescriptor.getUniqueId());
329
330
return store.getOrComputeIfAbsent(dbNamespace, "connection", key -> {
331
try {
332
DataSource dataSource = createDataSource();
333
334
// Register cleanup action
335
store.put(dbNamespace, "connection.cleanup",
336
(NamespacedHierarchicalStore.CloseAction<Namespace>)
337
(namespace, k, value) -> {
338
if (value instanceof DataSource) {
339
closeDataSource((DataSource) value);
340
}
341
});
342
343
return dataSource;
344
} catch (SQLException e) {
345
throw new RuntimeException("Failed to create database connection", e);
346
}
347
});
348
}
349
}
350
```
351
352
**Hierarchical Configuration:**
353
354
```java
355
public class ConfigurableTestEngine implements TestEngine {
356
357
@Override
358
public void execute(ExecutionRequest request) {
359
ConfigurationParameters config = request.getConfigurationParameters();
360
NamespacedHierarchicalStore<Namespace> store = request.getStore();
361
362
// Store global configuration
363
Namespace globalConfig = Namespace.create("config", "global");
364
store.put(globalConfig, "parallel.enabled",
365
config.getBoolean("parallel.enabled").orElse(false));
366
store.put(globalConfig, "timeout.seconds",
367
config.get("timeout.seconds", Integer::parseInt).orElse(30));
368
369
// Execute test hierarchy with inherited configuration
370
executeTestHierarchy(request.getRootTestDescriptor(), store);
371
}
372
373
private void executeTestHierarchy(TestDescriptor descriptor,
374
NamespacedHierarchicalStore<Namespace> store) {
375
// Create child store for this test level
376
try (NamespacedHierarchicalStore<Namespace> childStore = store.newChild()) {
377
Namespace testConfig = Namespace.create("config", descriptor.getUniqueId());
378
379
// Test-specific configuration inherits from parent through hierarchy
380
Boolean parallelEnabled = childStore.get(
381
Namespace.create("config", "global"),
382
"parallel.enabled",
383
Boolean.class
384
);
385
386
// Override for specific tests if needed
387
if (hasCustomParallelSetting(descriptor)) {
388
childStore.put(testConfig, "parallel.enabled", false);
389
}
390
391
// Execute with child store context
392
executeWithStore(descriptor, childStore);
393
}
394
}
395
}
396
```