1
+ :testDir: ../../../../../src/test/java
2
+ :testResourcesDir: ../../../../../src/test/resources
3
+
1
4
[[launcher-api]]
2
5
=== JUnit Platform Launcher API
3
6
@@ -132,10 +135,22 @@ package example.session;
132
135
133
136
include::{testDir}/example/session/GlobalSetupTeardownListener.java[tags=user_guide]
134
137
----
135
- <1> Start the HTTP server
136
- <2> Export its host address as a system property for consumption by tests
137
- <3> Export its port as a system property for consumption by tests
138
- <4> Stop the HTTP server
138
+ <1> Get the store from the launcher session
139
+ <2> Lazily create the HTTP server and put it into the store
140
+ <3> Start the HTTP server
141
+
142
+ It uses a wrapper class to ensure the server is stopped when the launcher session is
143
+ closed:
144
+
145
+ [source,java]
146
+ .src/test/java/example/session/CloseableHttpServer.java
147
+ ----
148
+ package example.session;
149
+
150
+ include::{testDir}/example/session/CloseableHttpServer.java[tags=user_guide]
151
+ ----
152
+ <1> The `close()` method is called when the launcher session is closed
153
+ <2> Stop the HTTP server
139
154
140
155
This sample uses the HTTP server implementation from the jdk.httpserver module that comes
141
156
with the JDK but would work similarly with any other server or resource. In order for the
@@ -158,10 +173,11 @@ package example.session;
158
173
159
174
include::{testDir}/example/session/HttpTests.java[tags=user_guide]
160
175
----
161
- <1> Read the host address of the server from the system property set by the listener
162
- <2> Read the port of the server from the system property set by the listener
163
- <3> Send a request to the server
164
- <4> Check the status code of the response
176
+ <1> Retrieve the HTTP server instance from the store
177
+ <2> Get the host string directly from the injected HTTP server instance
178
+ <3> Get the port number directly from the injected HTTP server instance
179
+ <4> Send a request to the server
180
+ <5> Check the status code of the response
165
181
166
182
[[launcher-api-launcher-interceptors-custom]]
167
183
==== Registering a LauncherInterceptor
@@ -285,3 +301,55 @@ execute any tests but will notify registered `{TestExecutionListener}` instances
285
301
tests had been skipped and their containers had been successful. This can be useful to
286
302
test changes in the configuration of a build or to verify a listener is called as expected
287
303
without having to wait for all tests to be executed.
304
+
305
+ [[launcher-api-managing-state-across-test-engines]]
306
+ ==== Managing State Across Test Engines
307
+
308
+ When running tests on the JUnit Platform, multiple test engines may need to access shared
309
+ resources. Rather than initializing these resources multiple times, JUnit Platform
310
+ provides mechanisms to share state across test engines efficiently. Test engines can use
311
+ the Platform's `{NamespacedHierarchicalStore}` API to lazily initialize and share
312
+ resources, ensuring they are created only once regardless of execution order. Any resource
313
+ that is put into the store and implements `AutoCloseable` will be closed automatically when
314
+ the execution is finished.
315
+
316
+ TIP: The Jupiter engine allows read and write access to such resources via its
317
+ `{ExtensionContext_Store}` API.
318
+
319
+ The following example demonstrates two custom test engines sharing a `ServerSocket`
320
+ resource. `FirstCustomEngine` attempts to retrieve an existing `ServerSocket` from the
321
+ global store or creates a new one if it doesn't exist:
322
+
323
+ [source,java]
324
+ ----
325
+ include::{testDir}/example/FirstCustomEngine.java[tags=user_guide]
326
+ ----
327
+
328
+ `SecondCustomEngine` follows the same pattern, ensuring that regardless whether it runs
329
+ before or after `FirstCustomEngine`, it will use the same socket instance:
330
+
331
+ [source,java]
332
+ ----
333
+ include::{testDir}/example/SecondCustomEngine.java[tags=user_guide]
334
+ ----
335
+
336
+ TIP: In this case, the `ServerSocket` can be stored directly in the global store while
337
+ ensuring since it gets closed because it implements `AutoCloseable`. If you need to use a
338
+ type that does not do so, you can wrap it in a custom class that implements
339
+ `AutoCloseable` and delegates to the original type. This is important to ensure that the
340
+ resource is closed properly when the test run is finished.
341
+
342
+ For illustration, the following test verifies that both engines are sharing the same
343
+ `ServerSocket` instance and that it's closed after `Launcher.execute()` returns:
344
+
345
+ [source,java,indent=0]
346
+ ----
347
+ include::{testDir}/example/sharedresources/SharedResourceDemo.java[tags=user_guide]
348
+ ----
349
+
350
+ By using the Platform's `{NamespacedHierarchicalStore}` API with shared namespaces in this
351
+ way, test engines can coordinate resource creation and sharing without direct dependencies
352
+ between them.
353
+
354
+ Alternatively, it's possible to inject resources into test engines by
355
+ <<launcher-api-launcher-session-listeners-custom, registering a `LauncherSessionListener`>>.
0 commit comments