0
# Engine Implementation
1
2
Core TestEngine interface and related classes that provide the foundation for implementing custom test engines in the JUnit Platform.
3
4
## Capabilities
5
6
### TestEngine Interface
7
8
The main interface that all custom test engines must implement to integrate with the JUnit Platform launcher.
9
10
```java { .api }
11
/**
12
* Main interface for test engines. Provides engine identification, test discovery, and execution capabilities.
13
*/
14
public interface TestEngine {
15
/**
16
* Get the unique identifier for this test engine.
17
* @return the engine ID (must be unique across all engines)
18
*/
19
String getId();
20
21
/**
22
* Discover tests from the provided discovery request.
23
* @param discoveryRequest the discovery request containing selectors and filters
24
* @param uniqueId the unique ID for the engine's root descriptor
25
* @return the root test descriptor containing discovered tests
26
*/
27
TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId);
28
29
/**
30
* Execute the tests described by the execution request.
31
* @param request the execution request containing test descriptors and configuration
32
*/
33
void execute(ExecutionRequest request);
34
35
/**
36
* Get the Maven group ID of this engine.
37
* @return optional group ID for engine metadata
38
*/
39
default Optional<String> getGroupId() {
40
return Optional.empty();
41
}
42
43
/**
44
* Get the Maven artifact ID of this engine.
45
* Default implementation tries to get module name first, then implementation title from package attributes.
46
* @return optional artifact ID for engine metadata
47
*/
48
default Optional<String> getArtifactId() {
49
Optional<String> moduleName = ModuleUtils.getModuleName(getClass());
50
if (moduleName.isPresent()) {
51
return moduleName;
52
}
53
return PackageUtils.getAttribute(getClass(), Package::getImplementationTitle);
54
}
55
56
/**
57
* Get the version of this engine.
58
* Default implementation tries "Engine-Version-" + getId() attribute first,
59
* then module or implementation version, defaulting to "DEVELOPMENT".
60
* @return optional version for engine metadata
61
*/
62
default Optional<String> getVersion() {
63
Optional<String> standalone = PackageUtils.getAttribute(getClass(), "Engine-Version-" + getId());
64
if (standalone.isPresent()) {
65
return standalone;
66
}
67
return Optional.of(PackageUtils.getModuleOrImplementationVersion(getClass()).orElse("DEVELOPMENT"));
68
}
69
}
70
```
71
72
**Usage Example:**
73
74
```java
75
import org.junit.platform.engine.*;
76
import org.junit.platform.engine.discovery.*;
77
import org.junit.platform.engine.support.descriptor.EngineDescriptor;
78
import org.junit.platform.commons.util.ModuleUtils;
79
import org.junit.platform.commons.util.PackageUtils;
80
81
public class CustomTestEngine implements TestEngine {
82
public static final String ENGINE_ID = "custom-engine";
83
84
@Override
85
public String getId() {
86
return ENGINE_ID;
87
}
88
89
@Override
90
public TestDescriptor discover(EngineDiscoveryRequest discoveryRequest, UniqueId uniqueId) {
91
EngineDescriptor engineDescriptor = new EngineDescriptor(uniqueId, "Custom Engine");
92
93
// Process class selectors
94
discoveryRequest.getSelectorsByType(ClassSelector.class)
95
.forEach(selector -> {
96
Class<?> testClass = selector.getJavaClass();
97
if (isTestClass(testClass)) {
98
TestDescriptor classDescriptor = createClassDescriptor(testClass, engineDescriptor);
99
engineDescriptor.addChild(classDescriptor);
100
101
// Discover test methods within the class
102
discoverTestMethods(testClass, classDescriptor);
103
}
104
});
105
106
return engineDescriptor;
107
}
108
109
@Override
110
public void execute(ExecutionRequest request) {
111
TestDescriptor rootDescriptor = request.getRootTestDescriptor();
112
EngineExecutionListener listener = request.getEngineExecutionListener();
113
ConfigurationParameters config = request.getConfigurationParameters();
114
115
executeDescriptor(rootDescriptor, listener, config);
116
}
117
118
private void executeDescriptor(TestDescriptor descriptor,
119
EngineExecutionListener listener,
120
ConfigurationParameters config) {
121
listener.executionStarted(descriptor);
122
123
try {
124
if (descriptor.getType() == TestDescriptor.Type.TEST) {
125
// Execute actual test logic
126
executeTest(descriptor, config);
127
listener.executionFinished(descriptor, TestExecutionResult.successful());
128
} else {
129
// Execute children for containers
130
descriptor.getChildren().forEach(child ->
131
executeDescriptor(child, listener, config));
132
listener.executionFinished(descriptor, TestExecutionResult.successful());
133
}
134
} catch (Exception e) {
135
listener.executionFinished(descriptor, TestExecutionResult.failed(e));
136
}
137
}
138
}
139
```
140
141
### TestExecutionResult
142
143
Encapsulates the result of executing a test or container, including success, failure, or abortion status.
144
145
```java { .api }
146
/**
147
* Result of executing a test or container.
148
*/
149
public final class TestExecutionResult {
150
/**
151
* Create a successful execution result.
152
* @return successful result
153
*/
154
public static TestExecutionResult successful();
155
156
/**
157
* Create a failed execution result.
158
* @param throwable the cause of failure
159
* @return failed result with throwable
160
*/
161
public static TestExecutionResult failed(Throwable throwable);
162
163
/**
164
* Create an aborted execution result.
165
* @param throwable the cause of abortion
166
* @return aborted result with throwable
167
*/
168
public static TestExecutionResult aborted(Throwable throwable);
169
170
/**
171
* Get the execution status.
172
* @return the status (SUCCESSFUL, FAILED, or ABORTED)
173
*/
174
public Status getStatus();
175
176
/**
177
* Get the throwable associated with this result.
178
* @return optional throwable for failed/aborted results
179
*/
180
public Optional<Throwable> getThrowable();
181
182
/**
183
* Execution status enumeration.
184
*/
185
public enum Status { SUCCESSFUL, ABORTED, FAILED }
186
}
187
```
188
189
### UniqueId
190
191
Encapsulates the creation, parsing, and display of unique identifiers for test descriptors within the engine hierarchy.
192
193
```java { .api }
194
/**
195
* Unique identifier for test descriptors, supporting hierarchical structure.
196
*/
197
public final class UniqueId implements Serializable, Cloneable {
198
/**
199
* Parse a unique ID from string representation.
200
* @param uniqueId string representation of unique ID
201
* @return parsed UniqueId
202
*/
203
public static UniqueId parse(String uniqueId);
204
205
/**
206
* Create a root unique ID for an engine.
207
* @param engineId the engine identifier
208
* @return root UniqueId for the engine
209
*/
210
public static UniqueId forEngine(String engineId);
211
212
/**
213
* Create a root unique ID with custom type and value.
214
* @param type the segment type
215
* @param value the segment value
216
* @return root UniqueId
217
*/
218
public static UniqueId root(String type, String value);
219
220
/**
221
* Append a new segment to this unique ID.
222
* @param type the segment type
223
* @param value the segment value
224
* @return new UniqueId with appended segment
225
*/
226
public UniqueId append(String type, String value);
227
228
/**
229
* Get all segments of this unique ID.
230
* @return immutable list of segments
231
*/
232
public List<Segment> getSegments();
233
234
/**
235
* Get the engine ID if this is an engine-level unique ID.
236
* @return optional engine ID
237
*/
238
public Optional<String> getEngineId();
239
240
/**
241
* Individual segment of a unique ID.
242
*/
243
public static final class Segment {
244
public String getType();
245
public String getValue();
246
}
247
}
248
```
249
250
**Usage Example:**
251
252
```java
253
// Create engine root ID
254
UniqueId engineId = UniqueId.forEngine("my-engine");
255
256
// Build hierarchical IDs
257
UniqueId classId = engineId.append("class", "com.example.TestClass");
258
UniqueId methodId = classId.append("method", "testMethod");
259
260
// Parse from string
261
UniqueId parsed = UniqueId.parse("[engine:my-engine]/[class:com.example.TestClass]/[method:testMethod]");
262
```
263
264
### TestTag
265
266
Immutable value object representing tags for categorizing and filtering tests.
267
268
```java { .api }
269
/**
270
* Immutable value object for test tags.
271
*/
272
public final class TestTag implements Serializable {
273
/**
274
* Validate tag name syntax.
275
* @param name the tag name to validate
276
* @return true if valid, false otherwise
277
*/
278
public static boolean isValid(String name);
279
280
/**
281
* Create a test tag.
282
* @param name the tag name (must be valid)
283
* @return TestTag instance
284
*/
285
public static TestTag create(String name);
286
287
/**
288
* Get the tag name.
289
* @return the tag name
290
*/
291
public String getName();
292
293
/**
294
* Set of characters not allowed in tag names.
295
*/
296
public static final Set<Character> RESERVED_CHARACTERS;
297
}
298
```
299
300
### TestSource Interface
301
302
Marker interface for representing the source location of tests (classes, methods, files, etc.).
303
304
```java { .api }
305
/**
306
* Marker interface for test source locations.
307
*/
308
public interface TestSource extends Serializable {
309
// Marker interface - implementations provide specific source types
310
}
311
```
312
313
Common TestSource implementations are provided in the `org.junit.platform.engine.support.descriptor` package for classes, methods, files, directories, URIs, and other source types.