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