1 /*
  2  * Copyright (c) 2021, 2022, 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  * @enablePreview
 28  * @modules jdk.incubator.concurrent
 29  * @run testng ScopeValueAPI
 30  */
 31 
 32 import jdk.incubator.concurrent.ScopedValue;
 33 import java.util.NoSuchElementException;
 34 import java.util.concurrent.Callable;
 35 import java.util.concurrent.ExecutionException;
 36 import java.util.concurrent.Executors;
 37 import java.util.concurrent.ThreadFactory;
 38 
 39 import org.testng.annotations.DataProvider;
 40 import org.testng.annotations.Test;
 41 import static org.testng.Assert.*;
 42 
 43 @Test
 44 public class ScopeValueAPI {
 45 
 46     @DataProvider
 47     public Object[][] factories() {
 48         return new Object[][] {
 49                 { Thread.ofPlatform().factory() },
 50                 { Thread.ofVirtual().factory() },
 51         };
 52     }
 53 
 54     /**
 55      * Test that the run method is invoked.
 56      */
 57     @Test(dataProvider = "factories")
 58     public void testRun(ThreadFactory factory) throws Exception {
 59         test(factory, () -> {
 60             class Box { static boolean executed; }
 61             ScopedValue<String> name = ScopedValue.newInstance();
 62             ScopedValue.where(name, "duke", () -> { Box.executed = true; });
 63             assertTrue(Box.executed);
 64         });
 65     }
 66 
 67     /**
 68      * Test the run method throwing an exception.
 69      */
 70     @Test(dataProvider = "factories")
 71     public void testRunThrows(ThreadFactory factory) throws Exception {
 72         test(factory, () -> {
 73             class FooException extends RuntimeException {  }
 74             ScopedValue<String> name = ScopedValue.newInstance();
 75             Runnable op = () -> { throw new FooException(); };
 76             assertThrows(FooException.class, () -> ScopedValue.where(name, "duke", op));
 77             assertFalse(name.isBound());
 78         });
 79     }
 80 
 81     /**
 82      * Test that the call method is invoked.
 83      */
 84     @Test(dataProvider = "factories")
 85     public void testCall(ThreadFactory factory) throws Exception {
 86         test(factory, () -> {
 87             ScopedValue<String> name = ScopedValue.newInstance();
 88             String result = ScopedValue.where(name, "duke", name::get);
 89             assertEquals(result, "duke");
 90         });
 91     }
 92 
 93     /**
 94      * Test the call method throwing an exception.
 95      */
 96     @Test(dataProvider = "factories")
 97     public void testCallThrows(ThreadFactory factory) throws Exception {
 98         test(factory, () -> {
 99             class FooException extends RuntimeException {  }
100             ScopedValue<String> name = ScopedValue.newInstance();
101             Callable<Void> op = () -> { throw new FooException(); };
102             assertThrows(FooException.class, () -> ScopedValue.where(name, "duke", op));
103             assertFalse(name.isBound());
104         });
105     }
106 
107     /**
108      * Test get method.
109      */
110     @Test(dataProvider = "factories")
111     public 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             // run
119             ScopedValue.where(name1, "duke", () -> {
120                 assertEquals(name1.get(), "duke");
121                 assertThrows(NoSuchElementException.class, name2::get);
122 
123             });
124             assertThrows(NoSuchElementException.class, name1::get);
125             assertThrows(NoSuchElementException.class, name2::get);
126 
127             // call
128             ScopedValue.where(name1, "duke", () -> {
129                 assertEquals(name1.get(), "duke");
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     @Test(dataProvider = "factories")
142     public void testIsBound(ThreadFactory factory) throws Exception {
143         test(factory, () -> {
144             ScopedValue<String> name1 = ScopedValue.newInstance();
145             ScopedValue<String> name2 = ScopedValue.newInstance();
146             assertFalse(name1.isBound());
147             assertFalse(name2.isBound());
148 
149             // run
150             ScopedValue.where(name1, "duke", () -> {
151                 assertTrue(name1.isBound());
152                 assertFalse(name2.isBound());
153             });
154             assertFalse(name1.isBound());
155             assertFalse(name2.isBound());
156 
157             // call
158             ScopedValue.where(name1, "duke", () -> {
159                 assertTrue(name1.isBound());
160                 assertFalse(name2.isBound());
161                 return null;
162             });
163             assertFalse(name1.isBound());
164             assertFalse(name2.isBound());
165         });
166     }
167 
168     /**
169      * Test orElse method.
170      */
171     @Test(dataProvider = "factories")
172     public void testOrElse(ThreadFactory factory) throws Exception {
173         test(factory, () -> {
174             ScopedValue<String> name = ScopedValue.newInstance();
175             assertTrue(name.orElse(null) == null);
176             assertEquals(name.orElse("default"), "default");
177 
178             // run
179             ScopedValue.where(name, "duke", () -> {
180                 assertEquals(name.orElse(null), "duke");
181                 assertEquals(name.orElse("default"), "duke");
182             });
183 
184             // call
185             ScopedValue.where(name, "duke", () -> {
186                 assertEquals(name.orElse(null), "duke");
187                 assertEquals(name.orElse("default"), "duke");
188                 return null;
189             });
190         });
191     }
192 
193     /**
194      * Test orElseThrow method.
195      */
196     @Test(dataProvider = "factories")
197     public 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             // run
204             ScopedValue.where(name, "duke", () -> {
205                 assertEquals(name.orElseThrow(FooException::new), "duke");
206             });
207 
208             // call
209             ScopedValue.where(name, "duke", () -> {
210                 assertEquals(name.orElseThrow(FooException::new), "duke");
211                 return null;
212             });
213         });
214     }
215 
216     /**
217      * Test two bindings.
218      */
219     @Test(dataProvider = "factories")
220     public void testTwoBindings(ThreadFactory factory) throws Exception {
221         test(factory, () -> {
222             ScopedValue<String> name = ScopedValue.newInstance();
223             ScopedValue<Integer> age = ScopedValue.newInstance();
224 
225             // run
226             ScopedValue.where(name, "duke").where(age, 100).run(() -> {
227                 assertTrue(name.isBound());
228                 assertTrue(age.isBound());
229                 assertEquals(name.get(), "duke");
230                 assertEquals((int) age.get(), 100);
231             });
232             assertFalse(name.isBound());
233             assertFalse(age.isBound());
234 
235             // call
236             ScopedValue.where(name, "duke").where(age, 100).call(() -> {
237                 assertTrue(name.isBound());
238                 assertTrue(age.isBound());
239                 assertEquals(name.get(), "duke");
240                 assertEquals((int) age.get(), 100);
241                 return null;
242             });
243             assertFalse(name.isBound());
244             assertFalse(age.isBound());
245 
246         });
247     }
248 
249     /**
250      * Test rebinding.
251      */
252     @Test(dataProvider = "factories")
253     public void testRebinding(ThreadFactory factory) throws Exception {
254         test(factory, () -> {
255             ScopedValue<String> name = ScopedValue.newInstance();
256 
257             // run
258             ScopedValue.where(name, "duke", () -> {
259                 assertTrue(name.isBound());
260                 assertEquals(name.get(), "duke");
261 
262                 ScopedValue.where(name, "duchess", () -> {
263                     assertTrue(name.isBound());
264                     assertTrue("duchess".equals(name.get()));
265                 });
266 
267                 assertTrue(name.isBound());
268                 assertEquals(name.get(), "duke");
269             });
270             assertFalse(name.isBound());
271 
272             // call
273             ScopedValue.where(name, "duke", () -> {
274                 assertTrue(name.isBound());
275                 assertEquals(name.get(), "duke");
276 
277                 ScopedValue.where(name, "duchess", () -> {
278                     assertTrue(name.isBound());
279                     assertTrue("duchess".equals(name.get()));
280                     return null;
281                 });
282 
283                 assertTrue(name.isBound());
284                 assertEquals(name.get(), "duke");
285                 return null;
286             });
287             assertFalse(name.isBound());
288         });
289     }
290 
291     /**
292      * Test rebinding from null vaue to another value.
293      */
294     @Test(dataProvider = "factories")
295     public void testRebindingFromNull(ThreadFactory factory) throws Exception {
296         test(factory, () -> {
297             ScopedValue<String> name = ScopedValue.newInstance();
298 
299             // run
300             ScopedValue.where(name, null, () -> {
301                 assertTrue(name.isBound());
302                 assertEquals(name.get(), null);
303 
304                 ScopedValue.where(name, "duchess", () -> {
305                     assertTrue(name.isBound());
306                     assertTrue("duchess".equals(name.get()));
307                 });
308 
309                 assertTrue(name.isBound());
310                 assertTrue(name.get() == null);
311             });
312             assertFalse(name.isBound());
313 
314             // call
315             ScopedValue.where(name, null, () -> {
316                 assertTrue(name.isBound());
317                 assertEquals(name.get(), null);
318 
319                 ScopedValue.where(name, "duchess", () -> {
320                     assertTrue(name.isBound());
321                     assertTrue("duchess".equals(name.get()));
322                     return null;
323                 });
324 
325                 assertTrue(name.isBound());
326                 assertTrue(name.get() == null);
327                 return null;
328             });
329             assertFalse(name.isBound());
330         });
331     }
332 
333     /**
334      * Test rebinding to null value.
335      */
336     @Test(dataProvider = "factories")
337     public void testRebindingToNull(ThreadFactory factory) throws Exception {
338         test(factory, () -> {
339             ScopedValue<String> name = ScopedValue.newInstance();
340 
341             // run
342             ScopedValue.where(name, "duke", () -> {
343                 assertTrue(name.isBound());
344                 assertEquals(name.get(), "duke");
345 
346                 ScopedValue.where(name, null, () -> {
347                     assertTrue(name.isBound());
348                     assertTrue(name.get() == null);
349                 });
350 
351                 assertTrue(name.isBound());
352                 assertEquals(name.get(), "duke");
353             });
354             assertFalse(name.isBound());
355 
356             // call
357             ScopedValue.where(name, "duke", () -> {
358                 assertTrue(name.isBound());
359                 assertEquals(name.get(), "duke");
360 
361                 ScopedValue.where(name, null, () -> {
362                     assertTrue(name.isBound());
363                     assertTrue(name.get() == null);
364                     return null;
365                 });
366 
367                 assertTrue(name.isBound());
368                 assertEquals(name.get(), "duke");
369                 return null;
370             });
371             assertFalse(name.isBound());
372         });
373     }
374 
375     /**
376      * Test Carrier.get.
377      */
378     @Test(dataProvider = "factories")
379     public void testCarrierGet(ThreadFactory factory) throws Exception {
380         test(factory, () -> {
381             ScopedValue<String> name = ScopedValue.newInstance();
382             ScopedValue<Integer> age = ScopedValue.newInstance();
383 
384             // one scoped value
385             var carrier1 = ScopedValue.where(name, "duke");
386             assertEquals(carrier1.get(name), "duke");
387             assertThrows(NoSuchElementException.class, () -> carrier1.get(age));
388 
389             // two scoped values
390             var carrier2 = carrier1.where(age, 20);
391             assertEquals(carrier2.get(name), "duke");
392             assertEquals((int) carrier2.get(age), 20);
393         });
394     }
395 
396     /**
397      * Test NullPointerException.
398      */
399     public void testNullPointerException() {
400         ScopedValue<String> name = ScopedValue.newInstance();
401 
402         assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value"));
403         assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> { }));
404         assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> null));
405 
406         assertThrows(NullPointerException.class, () -> name.orElseThrow(null));
407 
408         var carrier = ScopedValue.where(name, "duke");
409         assertThrows(NullPointerException.class, () -> carrier.where(null, "value"));
410         assertThrows(NullPointerException.class, () -> carrier.get(null));
411         assertThrows(NullPointerException.class, () -> carrier.run(null));
412         assertThrows(NullPointerException.class, () -> carrier.call(null));
413     }
414 
415     @FunctionalInterface
416     private interface ThrowingRunnable {
417         void run() throws Exception;
418     }
419 
420     /**
421      * Run the given task in a thread created with the given thread factory.
422      * @throws Exception if the task throws an exception
423      */
424     private static void test(ThreadFactory factory, ThrowingRunnable task) throws Exception {
425         try (var executor = Executors.newThreadPerTaskExecutor(factory)) {
426             var future = executor.submit(() -> {
427                 task.run();
428                 return null;
429             });
430             try {
431                 future.get();
432             } catch (ExecutionException ee) {
433                 Throwable cause = ee.getCause();
434                 if (cause instanceof Exception e)
435                     throw e;
436                 if (cause instanceof Error e)
437                     throw e;
438                 throw new RuntimeException(cause);
439             }
440         }
441     }
442 }