1 /*
2 * Copyright (c) 2021, 2025, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24 /*
25 * @test
26 * @summary Test ScopedValue API
27 * @run junit ScopedValueAPI
28 */
29
30 import java.lang.ScopedValue.CallableOp;
31 import java.util.NoSuchElementException;
32 import java.util.concurrent.ExecutionException;
33 import java.util.concurrent.Executors;
34 import java.util.concurrent.ThreadFactory;
35 import java.util.function.Supplier;
36 import java.util.stream.Stream;
37
38 import org.junit.jupiter.api.Test;
39 import org.junit.jupiter.params.ParameterizedTest;
40 import org.junit.jupiter.params.provider.MethodSource;
41 import static org.junit.jupiter.api.Assertions.*;
42
43 class ScopedValueAPI {
44
45 private static Stream<ThreadFactory> factories() {
46 return Stream.of(Thread.ofPlatform().factory(), Thread.ofVirtual().factory());
47 }
48
49 /**
50 * Test that where invokes the Runnable's run method.
51 */
52 @ParameterizedTest
53 @MethodSource("factories")
54 void testRunWhere(ThreadFactory factory) throws Exception {
55 test(factory, () -> {
56 class Box { static boolean executed; }
57 ScopedValue<String> name = ScopedValue.newInstance();
58 ScopedValue.where(name, "duke").run(() -> { Box.executed = true; });
59 assertTrue(Box.executed);
60 });
61 }
62
63 /**
64 * Test where when the run method throws an exception.
65 */
66 @ParameterizedTest
67 @MethodSource("factories")
68 void testRunWhereThrows(ThreadFactory factory) throws Exception {
69 test(factory, () -> {
70 class FooException extends RuntimeException { }
71 ScopedValue<String> name = ScopedValue.newInstance();
72 Runnable op = () -> { throw new FooException(); };
73 assertThrows(FooException.class, () -> ScopedValue.where(name, "duke").run(op));
74 assertFalse(name.isBound());
75 });
76 }
77
78 /**
79 * Test that callWhere invokes the CallableOp's call method.
80 */
81 @ParameterizedTest
82 @MethodSource("factories")
83 void testCallWhere(ThreadFactory factory) throws Exception {
84 test(factory, () -> {
85 ScopedValue<String> name = ScopedValue.newInstance();
86 String result = ScopedValue.where(name, "duke").call(name::get);
87 assertEquals("duke", result);
88 });
89 }
90
91 /**
92 * Test callWhere when the call method throws an exception.
93 */
94 @ParameterizedTest
95 @MethodSource("factories")
96 void testCallWhereThrows(ThreadFactory factory) throws Exception {
97 test(factory, () -> {
98 class FooException extends RuntimeException { }
99 ScopedValue<String> name = ScopedValue.newInstance();
100 CallableOp<Void, RuntimeException> op = () -> { throw new FooException(); };
101 assertThrows(FooException.class, () -> ScopedValue.where(name, "duke").call(op));
102 assertFalse(name.isBound());
103 });
104 }
105
106 /**
107 * Test get method.
108 */
109 @ParameterizedTest
110 @MethodSource("factories")
111 void testGet(ThreadFactory factory) throws Exception {
112 test(factory, () -> {
113 ScopedValue<String> name1 = ScopedValue.newInstance();
114 ScopedValue<String> name2 = ScopedValue.newInstance();
115 assertThrows(NoSuchElementException.class, name1::get);
116 assertThrows(NoSuchElementException.class, name2::get);
117
118 // where
119 ScopedValue.where(name1, "duke").run(() -> {
120 assertEquals("duke", name1.get());
121 assertThrows(NoSuchElementException.class, name2::get);
122
123 });
124 assertThrows(NoSuchElementException.class, name1::get);
125 assertThrows(NoSuchElementException.class, name2::get);
126
127 // callWhere
128 ScopedValue.where(name1, "duke").call(() -> {
129 assertEquals("duke", name1.get());
130 assertThrows(NoSuchElementException.class, name2::get);
131 return null;
132 });
133 assertThrows(NoSuchElementException.class, name1::get);
134 assertThrows(NoSuchElementException.class, name2::get);
135 });
136 }
137
138 /**
139 * Test isBound method.
140 */
141 @ParameterizedTest
142 @MethodSource("factories")
143 void testIsBound(ThreadFactory factory) throws Exception {
144 test(factory, () -> {
145 ScopedValue<String> name1 = ScopedValue.newInstance();
146 ScopedValue<String> name2 = ScopedValue.newInstance();
147 assertFalse(name1.isBound());
148 assertFalse(name2.isBound());
149
150 // where
151 ScopedValue.where(name1, "duke").run(() -> {
152 assertTrue(name1.isBound());
153 assertFalse(name2.isBound());
154 });
155 assertFalse(name1.isBound());
156 assertFalse(name2.isBound());
157
158 // callWhere
159 ScopedValue.where(name1, "duke").call(() -> {
160 assertTrue(name1.isBound());
161 assertFalse(name2.isBound());
162 return null;
163 });
164 assertFalse(name1.isBound());
165 assertFalse(name2.isBound());
166 });
167 }
168
169 /**
170 * Test orElse method.
171 */
172 @ParameterizedTest
173 @MethodSource("factories")
174 void testOrElse(ThreadFactory factory) throws Exception {
175 test(factory, () -> {
176 ScopedValue<String> name = ScopedValue.newInstance();
177 assertEquals("default", name.orElse("default"));
178
179 // where
180 ScopedValue.where(name, "duke").run(() -> {
181 assertEquals("duke", name.orElse("default"));
182 });
183
184 // callWhere
185 ScopedValue.where(name, "duke").call(() -> {
186 assertEquals("duke", name.orElse("default"));
187 return null;
188 });
189 });
190 }
191
192 /**
193 * Test orElseThrow method.
194 */
195 @ParameterizedTest
196 @MethodSource("factories")
197 void testOrElseThrow(ThreadFactory factory) throws Exception {
198 test(factory, () -> {
199 class FooException extends RuntimeException { }
200 ScopedValue<String> name = ScopedValue.newInstance();
201 assertThrows(FooException.class, () -> name.orElseThrow(FooException::new));
202
203 // where
204 ScopedValue.where(name, "duke").run(() -> {
205 assertEquals("duke", name.orElseThrow(FooException::new));
206 });
207
208 // callWhere
209 ScopedValue.where(name, "duke").call(() -> {
210 assertEquals("duke", name.orElseThrow(FooException::new));
211 return null;
212 });
213 });
214 }
215
216 /**
217 * Test two bindings.
218 */
219 @ParameterizedTest
220 @MethodSource("factories")
221 void testTwoBindings(ThreadFactory factory) throws Exception {
222 test(factory, () -> {
223 ScopedValue<String> name = ScopedValue.newInstance();
224 ScopedValue<Integer> age = ScopedValue.newInstance();
225
226 // Carrier.run
227 ScopedValue.where(name, "duke").where(age, 100).run(() -> {
228 assertTrue(name.isBound());
229 assertTrue(age.isBound());
230 assertEquals("duke", name.get());
231 assertEquals(100, (int) age.get());
232 });
233 assertFalse(name.isBound());
234 assertFalse(age.isBound());
235
236 // Carrier.call
237 ScopedValue.where(name, "duke").where(age, 100).call(() -> {
238 assertTrue(name.isBound());
239 assertTrue(age.isBound());
240 assertEquals("duke", name.get());
241 assertEquals(100, (int) age.get());
242 return null;
243 });
244 assertFalse(name.isBound());
245 assertFalse(age.isBound());
246 });
247 }
248
249 /**
250 * Test rebinding.
251 */
252 @ParameterizedTest
253 @MethodSource("factories")
254 void testRebinding(ThreadFactory factory) throws Exception {
255 test(factory, () -> {
256 ScopedValue<String> name = ScopedValue.newInstance();
257
258 // where
259 ScopedValue.where(name, "duke").run(() -> {
260 assertTrue(name.isBound());
261 assertEquals("duke", name.get());
262
263 ScopedValue.where(name, "duchess").run(() -> {
264 assertTrue(name.isBound());
265 assertEquals("duchess", name.get());
266 });
267
268 assertTrue(name.isBound());
269 assertEquals("duke", name.get());
270 });
271 assertFalse(name.isBound());
272
273 // callWhere
274 ScopedValue.where(name, "duke").call(() -> {
275 assertTrue(name.isBound());
276 assertEquals("duke", name.get());
277
278 ScopedValue.where(name, "duchess").call(() -> {
279 assertTrue(name.isBound());
280 assertEquals("duchess", name.get());
281 return null;
282 });
283
284 assertTrue(name.isBound());
285 assertEquals("duke", name.get());
286 return null;
287 });
288 assertFalse(name.isBound());
289 });
290 }
291
292 /**
293 * Test rebinding from null vaue to another value.
294 */
295 @ParameterizedTest
296 @MethodSource("factories")
297 void testRebindingFromNull(ThreadFactory factory) throws Exception {
298 test(factory, () -> {
299 ScopedValue<String> name = ScopedValue.newInstance();
300
301 // where
302 ScopedValue.where(name, null).run(() -> {
303 assertTrue(name.isBound());
304 assertNull(name.get());
305
306 ScopedValue.where(name, "duchess").run(() -> {
307 assertTrue(name.isBound());
308 assertTrue("duchess".equals(name.get()));
309 });
310
311 assertTrue(name.isBound());
312 assertNull(name.get());
313 });
314 assertFalse(name.isBound());
315
316 // callWhere
317 ScopedValue.where(name, null).call(() -> {
318 assertTrue(name.isBound());
319 assertNull(name.get());
320
321 ScopedValue.where(name, "duchess").call(() -> {
322 assertTrue(name.isBound());
323 assertTrue("duchess".equals(name.get()));
324 return null;
325 });
326
327 assertTrue(name.isBound());
328 assertNull(name.get());
329 return null;
330 });
331 assertFalse(name.isBound());
332 });
333 }
334
335 /**
336 * Test rebinding to null value.
337 */
338 @ParameterizedTest
339 @MethodSource("factories")
340 void testRebindingToNull(ThreadFactory factory) throws Exception {
341 test(factory, () -> {
342 ScopedValue<String> name = ScopedValue.newInstance();
343
344 // where
345 ScopedValue.where(name, "duke").run(() -> {
346 assertTrue(name.isBound());
347 assertEquals("duke", name.get());
348
349 ScopedValue.where(name, null).run(() -> {
350 assertTrue(name.isBound());
351 assertNull(name.get());
352 });
353
354 assertTrue(name.isBound());
355 assertEquals("duke", name.get());
356 });
357 assertFalse(name.isBound());
358
359 // callWhere
360 ScopedValue.where(name, "duke").call(() -> {
361 assertTrue(name.isBound());
362 assertEquals("duke", name.get());
363
364 ScopedValue.where(name, null).call(() -> {
365 assertTrue(name.isBound());
366 assertNull(name.get());
367 return null;
368 });
369
370 assertTrue(name.isBound());
371 assertEquals("duke", name.get());
372 return null;
373 });
374 assertFalse(name.isBound());
375 });
376 }
377
378 /**
379 * Test Carrier.get.
380 */
381 @ParameterizedTest
382 @MethodSource("factories")
383 void testCarrierGet(ThreadFactory factory) throws Exception {
384 test(factory, () -> {
385 ScopedValue<String> name = ScopedValue.newInstance();
386 ScopedValue<Integer> age = ScopedValue.newInstance();
387
388 // one scoped value
389 var carrier1 = ScopedValue.where(name, "duke");
390 assertEquals("duke", carrier1.get(name));
391 assertThrows(NoSuchElementException.class, () -> carrier1.get(age));
392
393 // two scoped values
394 var carrier2 = carrier1.where(age, 20);
395 assertEquals("duke", carrier2.get(name));
396 assertEquals(20, (int) carrier2.get(age));
397 });
398 }
399
400 /**
401 * Test NullPointerException.
402 */
403 @Test
404 void testNullPointerException() {
405 ScopedValue<String> name = ScopedValue.newInstance();
406
407 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "duke"));
408
409 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "duke").run(() -> { }));
410 assertThrows(NullPointerException.class, () -> ScopedValue.where(name, "duke").run(null));
411
412 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "duke").call(() -> ""));
413 assertThrows(NullPointerException.class, () -> ScopedValue.where(name, "duke").call(null));
414
415 assertThrows(NullPointerException.class, () -> name.orElse(null));
416 assertThrows(NullPointerException.class, () -> name.orElseThrow(null));
417
418 var carrier = ScopedValue.where(name, "duke");
419 assertThrows(NullPointerException.class, () -> carrier.where(null, "duke"));
420 assertThrows(NullPointerException.class, () -> carrier.get((ScopedValue<?>)null));
421 assertThrows(NullPointerException.class, () -> carrier.run(null));
422 assertThrows(NullPointerException.class, () -> carrier.call(null));
423 assertThrows(NullPointerException.class, () -> carrier.run(() -> name.orElse(null)));
424 }
425
426 @FunctionalInterface
427 private interface ThrowingRunnable {
428 void run() throws Exception;
429 }
430
431 /**
432 * Run the given task in a thread created with the given thread factory.
433 * @throws Exception if the task throws an exception
434 */
435 private static void test(ThreadFactory factory, ThrowingRunnable task) throws Exception {
436 try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
437 var future = executor.submit(() -> {
438 task.run();
439 return null;
440 });
441 try {
442 future.get();
443 } catch (ExecutionException ee) {
444 Throwable cause = ee.getCause();
445 if (cause instanceof Exception e)
446 throw e;
447 if (cause instanceof Error e)
448 throw e;
449 throw new RuntimeException(cause);
450 }
451 }
452 }
453 }
--- EOF ---