1 /* 2 * Copyright (c) 2021, 2023, 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.util.NoSuchElementException; 32 import java.util.concurrent.Callable; 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 the run method is invoked. 52 */ 53 @ParameterizedTest 54 @MethodSource("factories") 55 void testRun(ThreadFactory factory) throws Exception { 56 test(factory, () -> { 57 class Box { static boolean executed; } 58 ScopedValue<String> name = ScopedValue.newInstance(); 59 ScopedValue.runWhere(name, "duke", () -> { Box.executed = true; }); 60 assertTrue(Box.executed); 61 }); 62 } 63 64 /** 65 * Test the run method throwing an exception. 66 */ 67 @ParameterizedTest 68 @MethodSource("factories") 69 void testRunThrows(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.runWhere(name, "duke", op)); 75 assertFalse(name.isBound()); 76 }); 77 } 78 79 /** 80 * Test that the call method is invoked. 81 */ 82 @ParameterizedTest 83 @MethodSource("factories") 84 void testCall(ThreadFactory factory) throws Exception { 85 test(factory, () -> { 86 ScopedValue<String> name = ScopedValue.newInstance(); 87 String result = ScopedValue.callWhere(name, "duke", name::get); 88 assertEquals("duke", result); 89 }); 90 } 91 92 /** 93 * Test that the get method is invoked. 94 */ 95 @ParameterizedTest 96 @MethodSource("factories") 97 void testGetWhere(ThreadFactory factory) throws Exception { 98 test(factory, () -> { 99 ScopedValue<String> name = ScopedValue.newInstance(); 100 String result = ScopedValue.getWhere(name, "duke", (Supplier<String>)(name::get)); 101 assertEquals("duke", result); 102 }); 103 } 104 105 /** 106 * Test the call method throwing an exception. 107 */ 108 @ParameterizedTest 109 @MethodSource("factories") 110 void testCallThrows(ThreadFactory factory) throws Exception { 111 test(factory, () -> { 112 class FooException extends RuntimeException { } 113 ScopedValue<String> name = ScopedValue.newInstance(); 114 Callable<Void> op = () -> { throw new FooException(); }; 115 assertThrows(FooException.class, () -> ScopedValue.callWhere(name, "duke", op)); 116 assertFalse(name.isBound()); 117 }); 118 } 119 120 /** 121 * Test the get(Supplier) method throwing an exception. 122 */ 123 @ParameterizedTest 124 @MethodSource("factories") 125 void testGetThrows(ThreadFactory factory) throws Exception { 126 test(factory, () -> { 127 class FooException extends RuntimeException { } 128 ScopedValue<String> name = ScopedValue.newInstance(); 129 Supplier<Void> op = () -> { throw new FooException(); }; 130 assertThrows(FooException.class, () -> ScopedValue.getWhere(name, "duke", op)); 131 assertFalse(name.isBound()); 132 }); 133 } 134 135 /** 136 * Test get method. 137 */ 138 @ParameterizedTest 139 @MethodSource("factories") 140 void testGet(ThreadFactory factory) throws Exception { 141 test(factory, () -> { 142 ScopedValue<String> name1 = ScopedValue.newInstance(); 143 ScopedValue<String> name2 = ScopedValue.newInstance(); 144 assertThrows(NoSuchElementException.class, name1::get); 145 assertThrows(NoSuchElementException.class, name2::get); 146 147 // run 148 ScopedValue.runWhere(name1, "duke", () -> { 149 assertEquals("duke", name1.get()); 150 assertThrows(NoSuchElementException.class, name2::get); 151 152 }); 153 assertThrows(NoSuchElementException.class, name1::get); 154 assertThrows(NoSuchElementException.class, name2::get); 155 156 // call 157 ScopedValue.callWhere(name1, "duke", () -> { 158 assertEquals("duke", name1.get()); 159 assertThrows(NoSuchElementException.class, name2::get); 160 return null; 161 }); 162 assertThrows(NoSuchElementException.class, name1::get); 163 assertThrows(NoSuchElementException.class, name2::get); 164 165 // get 166 ScopedValue.getWhere(name1, "duke", () -> { 167 assertEquals("duke", name1.get()); 168 assertThrows(NoSuchElementException.class, name2::get); 169 return null; 170 }); 171 assertThrows(NoSuchElementException.class, name1::get); 172 assertThrows(NoSuchElementException.class, name2::get); 173 }); 174 } 175 176 /** 177 * Test isBound method. 178 */ 179 @ParameterizedTest 180 @MethodSource("factories") 181 void testIsBound(ThreadFactory factory) throws Exception { 182 test(factory, () -> { 183 ScopedValue<String> name1 = ScopedValue.newInstance(); 184 ScopedValue<String> name2 = ScopedValue.newInstance(); 185 assertFalse(name1.isBound()); 186 assertFalse(name2.isBound()); 187 188 // run 189 ScopedValue.runWhere(name1, "duke", () -> { 190 assertTrue(name1.isBound()); 191 assertFalse(name2.isBound()); 192 }); 193 assertFalse(name1.isBound()); 194 assertFalse(name2.isBound()); 195 196 // call 197 ScopedValue.callWhere(name1, "duke", () -> { 198 assertTrue(name1.isBound()); 199 assertFalse(name2.isBound()); 200 return null; 201 }); 202 assertFalse(name1.isBound()); 203 assertFalse(name2.isBound()); 204 205 // call 206 ScopedValue.callWhere(name1, "duke", () -> { 207 assertTrue(name1.isBound()); 208 assertFalse(name2.isBound()); 209 return null; 210 }); 211 assertFalse(name1.isBound()); 212 assertFalse(name2.isBound()); 213 }); 214 } 215 216 /** 217 * Test orElse method. 218 */ 219 @ParameterizedTest 220 @MethodSource("factories") 221 void testOrElse(ThreadFactory factory) throws Exception { 222 test(factory, () -> { 223 ScopedValue<String> name = ScopedValue.newInstance(); 224 assertNull(name.orElse(null)); 225 assertEquals("default", name.orElse("default")); 226 227 // run 228 ScopedValue.runWhere(name, "duke", () -> { 229 assertEquals("duke", name.orElse(null)); 230 assertEquals("duke", name.orElse("default")); 231 }); 232 233 // call 234 ScopedValue.callWhere(name, "duke", () -> { 235 assertEquals("duke", name.orElse(null)); 236 assertEquals("duke", name.orElse("default")); 237 return null; 238 }); 239 }); 240 } 241 242 /** 243 * Test orElseThrow method. 244 */ 245 @ParameterizedTest 246 @MethodSource("factories") 247 void testOrElseThrow(ThreadFactory factory) throws Exception { 248 test(factory, () -> { 249 class FooException extends RuntimeException { } 250 ScopedValue<String> name = ScopedValue.newInstance(); 251 assertThrows(FooException.class, () -> name.orElseThrow(FooException::new)); 252 253 // run 254 ScopedValue.runWhere(name, "duke", () -> { 255 assertEquals("duke", name.orElseThrow(FooException::new)); 256 }); 257 258 // call 259 ScopedValue.callWhere(name, "duke", () -> { 260 assertEquals("duke", name.orElseThrow(FooException::new)); 261 return null; 262 }); 263 }); 264 } 265 266 /** 267 * Test two bindings. 268 */ 269 @ParameterizedTest 270 @MethodSource("factories") 271 void testTwoBindings(ThreadFactory factory) throws Exception { 272 test(factory, () -> { 273 ScopedValue<String> name = ScopedValue.newInstance(); 274 ScopedValue<Integer> age = ScopedValue.newInstance(); 275 276 // run 277 ScopedValue.where(name, "duke").where(age, 100).run(() -> { 278 assertTrue(name.isBound()); 279 assertTrue(age.isBound()); 280 assertEquals("duke", name.get()); 281 assertEquals(100, (int) age.get()); 282 }); 283 assertFalse(name.isBound()); 284 assertFalse(age.isBound()); 285 286 // call 287 ScopedValue.where(name, "duke").where(age, 100).call(() -> { 288 assertTrue(name.isBound()); 289 assertTrue(age.isBound()); 290 assertEquals("duke", name.get()); 291 assertEquals(100, (int) age.get()); 292 return null; 293 }); 294 assertFalse(name.isBound()); 295 assertFalse(age.isBound()); 296 297 // get 298 ScopedValue.where(name, "duke").where(age, 100).get(() -> { 299 assertTrue(name.isBound()); 300 assertTrue(age.isBound()); 301 assertEquals("duke", name.get()); 302 assertEquals(100, (int) age.get()); 303 return null; 304 }); 305 assertFalse(name.isBound()); 306 assertFalse(age.isBound()); 307 308 }); 309 } 310 311 /** 312 * Test rebinding. 313 */ 314 @ParameterizedTest 315 @MethodSource("factories") 316 void testRebinding(ThreadFactory factory) throws Exception { 317 test(factory, () -> { 318 ScopedValue<String> name = ScopedValue.newInstance(); 319 320 // run 321 ScopedValue.runWhere(name, "duke", () -> { 322 assertTrue(name.isBound()); 323 assertEquals("duke", name.get()); 324 325 ScopedValue.runWhere(name, "duchess", () -> { 326 assertTrue(name.isBound()); 327 assertEquals("duchess", name.get()); 328 }); 329 330 assertTrue(name.isBound()); 331 assertEquals("duke", name.get()); 332 }); 333 assertFalse(name.isBound()); 334 335 // call 336 ScopedValue.callWhere(name, "duke", () -> { 337 assertTrue(name.isBound()); 338 assertEquals("duke", name.get()); 339 340 ScopedValue.callWhere(name, "duchess", () -> { 341 assertTrue(name.isBound()); 342 assertEquals("duchess", name.get()); 343 return null; 344 }); 345 346 assertTrue(name.isBound()); 347 assertEquals("duke", name.get()); 348 return null; 349 }); 350 assertFalse(name.isBound()); 351 352 // get 353 ScopedValue.getWhere(name, "duke", () -> { 354 assertTrue(name.isBound()); 355 assertEquals("duke", name.get()); 356 357 ScopedValue.where(name, "duchess").get(() -> { 358 assertTrue(name.isBound()); 359 assertEquals("duchess", name.get()); 360 return null; 361 }); 362 363 assertTrue(name.isBound()); 364 assertEquals("duke", name.get()); 365 return null; 366 }); 367 assertFalse(name.isBound()); 368 }); 369 } 370 371 /** 372 * Test rebinding from null vaue to another value. 373 */ 374 @ParameterizedTest 375 @MethodSource("factories") 376 void testRebindingFromNull(ThreadFactory factory) throws Exception { 377 test(factory, () -> { 378 ScopedValue<String> name = ScopedValue.newInstance(); 379 380 // run 381 ScopedValue.runWhere(name, null, () -> { 382 assertTrue(name.isBound()); 383 assertNull(name.get()); 384 385 ScopedValue.runWhere(name, "duchess", () -> { 386 assertTrue(name.isBound()); 387 assertTrue("duchess".equals(name.get())); 388 }); 389 390 assertTrue(name.isBound()); 391 assertNull(name.get()); 392 }); 393 assertFalse(name.isBound()); 394 395 // call 396 ScopedValue.callWhere(name, null, () -> { 397 assertTrue(name.isBound()); 398 assertNull(name.get()); 399 400 ScopedValue.callWhere(name, "duchess", () -> { 401 assertTrue(name.isBound()); 402 assertTrue("duchess".equals(name.get())); 403 return null; 404 }); 405 406 assertTrue(name.isBound()); 407 assertNull(name.get()); 408 return null; 409 }); 410 assertFalse(name.isBound()); 411 412 // getWhere 413 ScopedValue.getWhere(name, null, () -> { 414 assertTrue(name.isBound()); 415 assertNull(name.get()); 416 417 ScopedValue.getWhere(name, "duchess", () -> { 418 assertTrue(name.isBound()); 419 assertTrue("duchess".equals(name.get())); 420 return null; 421 }); 422 423 assertTrue(name.isBound()); 424 assertNull(name.get()); 425 return null; 426 }); 427 assertFalse(name.isBound()); 428 }); 429 } 430 431 /** 432 * Test rebinding to null value. 433 */ 434 @ParameterizedTest 435 @MethodSource("factories") 436 void testRebindingToNull(ThreadFactory factory) throws Exception { 437 test(factory, () -> { 438 ScopedValue<String> name = ScopedValue.newInstance(); 439 440 // run 441 ScopedValue.runWhere(name, "duke", () -> { 442 assertTrue(name.isBound()); 443 assertEquals("duke", name.get()); 444 445 ScopedValue.runWhere(name, null, () -> { 446 assertTrue(name.isBound()); 447 assertNull(name.get()); 448 }); 449 450 assertTrue(name.isBound()); 451 assertEquals("duke", name.get()); 452 }); 453 assertFalse(name.isBound()); 454 455 // call 456 ScopedValue.callWhere(name, "duke", () -> { 457 assertTrue(name.isBound()); 458 assertEquals("duke", name.get()); 459 460 ScopedValue.callWhere(name, null, () -> { 461 assertTrue(name.isBound()); 462 assertNull(name.get()); 463 return null; 464 }); 465 466 assertTrue(name.isBound()); 467 assertEquals("duke", name.get()); 468 return null; 469 }); 470 assertFalse(name.isBound()); 471 472 // get 473 ScopedValue.where(name, "duke").get(() -> { 474 assertTrue(name.isBound()); 475 assertEquals("duke", name.get()); 476 477 ScopedValue.where(name, null).get(() -> { 478 assertTrue(name.isBound()); 479 assertNull(name.get()); 480 return null; 481 }); 482 483 assertTrue(name.isBound()); 484 assertEquals("duke", name.get()); 485 return null; 486 }); 487 assertFalse(name.isBound()); 488 }); 489 } 490 491 /** 492 * Test Carrier.get. 493 */ 494 @ParameterizedTest 495 @MethodSource("factories") 496 void testCarrierGet(ThreadFactory factory) throws Exception { 497 test(factory, () -> { 498 ScopedValue<String> name = ScopedValue.newInstance(); 499 ScopedValue<Integer> age = ScopedValue.newInstance(); 500 501 // one scoped value 502 var carrier1 = ScopedValue.where(name, "duke"); 503 assertEquals("duke", carrier1.get(name)); 504 assertThrows(NoSuchElementException.class, () -> carrier1.get(age)); 505 506 // two scoped values 507 var carrier2 = carrier1.where(age, 20); 508 assertEquals("duke", carrier2.get(name)); 509 assertEquals(20, (int) carrier2.get(age)); 510 }); 511 } 512 513 /** 514 * Test NullPointerException. 515 */ 516 @Test 517 void testNullPointerException() { 518 ScopedValue<String> name = ScopedValue.newInstance(); 519 520 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value")); 521 assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "value", () -> { })); 522 assertThrows(NullPointerException.class, () -> ScopedValue.getWhere(null, "value", () -> null)); 523 524 assertThrows(NullPointerException.class, () -> name.orElseThrow(null)); 525 526 var carrier = ScopedValue.where(name, "duke"); 527 assertThrows(NullPointerException.class, () -> carrier.where(null, "value")); 528 assertThrows(NullPointerException.class, () -> carrier.get((ScopedValue<?>)null)); 529 assertThrows(NullPointerException.class, () -> carrier.get((Supplier<?>)null)); 530 assertThrows(NullPointerException.class, () -> carrier.run(null)); 531 assertThrows(NullPointerException.class, () -> carrier.call(null)); 532 } 533 534 @FunctionalInterface 535 private interface ThrowingRunnable { 536 void run() throws Exception; 537 } 538 539 /** 540 * Run the given task in a thread created with the given thread factory. 541 * @throws Exception if the task throws an exception 542 */ 543 private static void test(ThreadFactory factory, ThrowingRunnable task) throws Exception { 544 try (var executor = Executors.newThreadPerTaskExecutor(factory)) { 545 var future = executor.submit(() -> { 546 task.run(); 547 return null; 548 }); 549 try { 550 future.get(); 551 } catch (ExecutionException ee) { 552 Throwable cause = ee.getCause(); 553 if (cause instanceof Exception e) 554 throw e; 555 if (cause instanceof Error e) 556 throw e; 557 throw new RuntimeException(cause); 558 } 559 } 560 } 561 }