1 /*
  2  * Copyright (c) 2021, 2024, 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 ---