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 * @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 runWhere 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.runWhere(name, "duke", () -> { Box.executed = true; }); 60 assertTrue(Box.executed); 61 }); 62 } 63 64 /** 65 * Test runWhere 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.runWhere(name, "duke", 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.callWhere(name, "duke", 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.callWhere(name, "duke", 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 // runWhere 120 ScopedValue.runWhere(name1, "duke", () -> { 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.callWhere(name1, "duke", () -> { 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 // runWhere 152 ScopedValue.runWhere(name1, "duke", () -> { 153 assertTrue(name1.isBound()); 154 assertFalse(name2.isBound()); 155 }); 156 assertFalse(name1.isBound()); 157 assertFalse(name2.isBound()); 158 159 // callWhere 160 ScopedValue.callWhere(name1, "duke", () -> { 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 assertNull(name.orElse(null)); 179 assertEquals("default", name.orElse("default")); 180 181 // runWhere 182 ScopedValue.runWhere(name, "duke", () -> { 183 assertEquals("duke", name.orElse(null)); 184 assertEquals("duke", name.orElse("default")); 185 }); 186 187 // callWhere 188 ScopedValue.callWhere(name, "duke", () -> { 189 assertEquals("duke", name.orElse(null)); 190 assertEquals("duke", name.orElse("default")); 191 return null; 192 }); 193 }); 194 } 195 196 /** 197 * Test orElseThrow method. 198 */ 199 @ParameterizedTest 200 @MethodSource("factories") 201 void testOrElseThrow(ThreadFactory factory) throws Exception { 202 test(factory, () -> { 203 class FooException extends RuntimeException { } 204 ScopedValue<String> name = ScopedValue.newInstance(); 205 assertThrows(FooException.class, () -> name.orElseThrow(FooException::new)); 206 207 // runWhere 208 ScopedValue.runWhere(name, "duke", () -> { 209 assertEquals("duke", name.orElseThrow(FooException::new)); 210 }); 211 212 // callWhere 213 ScopedValue.callWhere(name, "duke", () -> { 214 assertEquals("duke", name.orElseThrow(FooException::new)); 215 return null; 216 }); 217 }); 218 } 219 220 /** 221 * Test two bindings. 222 */ 223 @ParameterizedTest 224 @MethodSource("factories") 225 void testTwoBindings(ThreadFactory factory) throws Exception { 226 test(factory, () -> { 227 ScopedValue<String> name = ScopedValue.newInstance(); 228 ScopedValue<Integer> age = ScopedValue.newInstance(); 229 230 // Carrier.run 231 ScopedValue.where(name, "duke").where(age, 100).run(() -> { 232 assertTrue(name.isBound()); 233 assertTrue(age.isBound()); 234 assertEquals("duke", name.get()); 235 assertEquals(100, (int) age.get()); 236 }); 237 assertFalse(name.isBound()); 238 assertFalse(age.isBound()); 239 240 // Carrier.call 241 ScopedValue.where(name, "duke").where(age, 100).call(() -> { 242 assertTrue(name.isBound()); 243 assertTrue(age.isBound()); 244 assertEquals("duke", name.get()); 245 assertEquals(100, (int) age.get()); 246 return null; 247 }); 248 assertFalse(name.isBound()); 249 assertFalse(age.isBound()); 250 }); 251 } 252 253 /** 254 * Test rebinding. 255 */ 256 @ParameterizedTest 257 @MethodSource("factories") 258 void testRebinding(ThreadFactory factory) throws Exception { 259 test(factory, () -> { 260 ScopedValue<String> name = ScopedValue.newInstance(); 261 262 // runWhere 263 ScopedValue.runWhere(name, "duke", () -> { 264 assertTrue(name.isBound()); 265 assertEquals("duke", name.get()); 266 267 ScopedValue.runWhere(name, "duchess", () -> { 268 assertTrue(name.isBound()); 269 assertEquals("duchess", name.get()); 270 }); 271 272 assertTrue(name.isBound()); 273 assertEquals("duke", name.get()); 274 }); 275 assertFalse(name.isBound()); 276 277 // callWhere 278 ScopedValue.callWhere(name, "duke", () -> { 279 assertTrue(name.isBound()); 280 assertEquals("duke", name.get()); 281 282 ScopedValue.callWhere(name, "duchess", () -> { 283 assertTrue(name.isBound()); 284 assertEquals("duchess", name.get()); 285 return null; 286 }); 287 288 assertTrue(name.isBound()); 289 assertEquals("duke", name.get()); 290 return null; 291 }); 292 assertFalse(name.isBound()); 293 }); 294 } 295 296 /** 297 * Test rebinding from null vaue to another value. 298 */ 299 @ParameterizedTest 300 @MethodSource("factories") 301 void testRebindingFromNull(ThreadFactory factory) throws Exception { 302 test(factory, () -> { 303 ScopedValue<String> name = ScopedValue.newInstance(); 304 305 // runWhere 306 ScopedValue.runWhere(name, null, () -> { 307 assertTrue(name.isBound()); 308 assertNull(name.get()); 309 310 ScopedValue.runWhere(name, "duchess", () -> { 311 assertTrue(name.isBound()); 312 assertTrue("duchess".equals(name.get())); 313 }); 314 315 assertTrue(name.isBound()); 316 assertNull(name.get()); 317 }); 318 assertFalse(name.isBound()); 319 320 // callWhere 321 ScopedValue.callWhere(name, null, () -> { 322 assertTrue(name.isBound()); 323 assertNull(name.get()); 324 325 ScopedValue.callWhere(name, "duchess", () -> { 326 assertTrue(name.isBound()); 327 assertTrue("duchess".equals(name.get())); 328 return null; 329 }); 330 331 assertTrue(name.isBound()); 332 assertNull(name.get()); 333 return null; 334 }); 335 assertFalse(name.isBound()); 336 }); 337 } 338 339 /** 340 * Test rebinding to null value. 341 */ 342 @ParameterizedTest 343 @MethodSource("factories") 344 void testRebindingToNull(ThreadFactory factory) throws Exception { 345 test(factory, () -> { 346 ScopedValue<String> name = ScopedValue.newInstance(); 347 348 // runWhere 349 ScopedValue.runWhere(name, "duke", () -> { 350 assertTrue(name.isBound()); 351 assertEquals("duke", name.get()); 352 353 ScopedValue.runWhere(name, null, () -> { 354 assertTrue(name.isBound()); 355 assertNull(name.get()); 356 }); 357 358 assertTrue(name.isBound()); 359 assertEquals("duke", name.get()); 360 }); 361 assertFalse(name.isBound()); 362 363 // callWhere 364 ScopedValue.callWhere(name, "duke", () -> { 365 assertTrue(name.isBound()); 366 assertEquals("duke", name.get()); 367 368 ScopedValue.callWhere(name, null, () -> { 369 assertTrue(name.isBound()); 370 assertNull(name.get()); 371 return null; 372 }); 373 374 assertTrue(name.isBound()); 375 assertEquals("duke", name.get()); 376 return null; 377 }); 378 assertFalse(name.isBound()); 379 }); 380 } 381 382 /** 383 * Test Carrier.get. 384 */ 385 @ParameterizedTest 386 @MethodSource("factories") 387 void testCarrierGet(ThreadFactory factory) throws Exception { 388 test(factory, () -> { 389 ScopedValue<String> name = ScopedValue.newInstance(); 390 ScopedValue<Integer> age = ScopedValue.newInstance(); 391 392 // one scoped value 393 var carrier1 = ScopedValue.where(name, "duke"); 394 assertEquals("duke", carrier1.get(name)); 395 assertThrows(NoSuchElementException.class, () -> carrier1.get(age)); 396 397 // two scoped values 398 var carrier2 = carrier1.where(age, 20); 399 assertEquals("duke", carrier2.get(name)); 400 assertEquals(20, (int) carrier2.get(age)); 401 }); 402 } 403 404 /** 405 * Test NullPointerException. 406 */ 407 @Test 408 void testNullPointerException() { 409 ScopedValue<String> name = ScopedValue.newInstance(); 410 411 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "duke")); 412 413 assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(null, "duke", () -> { })); 414 assertThrows(NullPointerException.class, () -> ScopedValue.runWhere(name, "duke", null)); 415 416 assertThrows(NullPointerException.class, () -> ScopedValue.callWhere(null, "duke", () -> "")); 417 assertThrows(NullPointerException.class, () -> ScopedValue.callWhere(name, "duke", null)); 418 419 assertThrows(NullPointerException.class, () -> name.orElseThrow(null)); 420 421 var carrier = ScopedValue.where(name, "duke"); 422 assertThrows(NullPointerException.class, () -> carrier.where(null, "duke")); 423 assertThrows(NullPointerException.class, () -> carrier.get((ScopedValue<?>)null)); 424 assertThrows(NullPointerException.class, () -> carrier.run(null)); 425 assertThrows(NullPointerException.class, () -> carrier.call(null)); 426 } 427 428 @FunctionalInterface 429 private interface ThrowingRunnable { 430 void run() throws Exception; 431 } 432 433 /** 434 * Run the given task in a thread created with the given thread factory. 435 * @throws Exception if the task throws an exception 436 */ 437 private static void test(ThreadFactory factory, ThrowingRunnable task) throws Exception { 438 try (var executor = Executors.newThreadPerTaskExecutor(factory)) { 439 var future = executor.submit(() -> { 440 task.run(); 441 return null; 442 }); 443 try { 444 future.get(); 445 } catch (ExecutionException ee) { 446 Throwable cause = ee.getCause(); 447 if (cause instanceof Exception e) 448 throw e; 449 if (cause instanceof Error e) 450 throw e; 451 throw new RuntimeException(cause); 452 } 453 } 454 } 455 }