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 * @modules jdk.incubator.concurrent 28 * @run junit ScopeValueAPI 29 */ 30 31 import jdk.incubator.concurrent.ScopedValue; 32 import java.util.NoSuchElementException; 33 import java.util.concurrent.Callable; 34 import java.util.concurrent.ExecutionException; 35 import java.util.concurrent.Executors; 36 import java.util.concurrent.ThreadFactory; 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 ScopeValueAPI { 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.where(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.where(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.where(name, "duke", name::get); 88 assertEquals("duke", result); 89 }); 90 } 91 92 /** 93 * Test the call method throwing an exception. 94 */ 95 @ParameterizedTest 96 @MethodSource("factories") 97 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 @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 // run 120 ScopedValue.where(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 // call 129 ScopedValue.where(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 // run 152 ScopedValue.where(name1, "duke", () -> { 153 assertTrue(name1.isBound()); 154 assertFalse(name2.isBound()); 155 }); 156 assertFalse(name1.isBound()); 157 assertFalse(name2.isBound()); 158 159 // call 160 ScopedValue.where(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 // run 182 ScopedValue.where(name, "duke", () -> { 183 assertEquals("duke", name.orElse(null)); 184 assertEquals("duke", name.orElse("default")); 185 }); 186 187 // call 188 ScopedValue.where(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 // run 208 ScopedValue.where(name, "duke", () -> { 209 assertEquals("duke", name.orElseThrow(FooException::new)); 210 }); 211 212 // call 213 ScopedValue.where(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 // 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 // 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 /** 255 * Test rebinding. 256 */ 257 @ParameterizedTest 258 @MethodSource("factories") 259 void testRebinding(ThreadFactory factory) throws Exception { 260 test(factory, () -> { 261 ScopedValue<String> name = ScopedValue.newInstance(); 262 263 // run 264 ScopedValue.where(name, "duke", () -> { 265 assertTrue(name.isBound()); 266 assertEquals("duke", name.get()); 267 268 ScopedValue.where(name, "duchess", () -> { 269 assertTrue(name.isBound()); 270 assertEquals("duchess", name.get()); 271 }); 272 273 assertTrue(name.isBound()); 274 assertEquals("duke", name.get()); 275 }); 276 assertFalse(name.isBound()); 277 278 // call 279 ScopedValue.where(name, "duke", () -> { 280 assertTrue(name.isBound()); 281 assertEquals("duke", name.get()); 282 283 ScopedValue.where(name, "duchess", () -> { 284 assertTrue(name.isBound()); 285 assertEquals("duchess", name.get()); 286 return null; 287 }); 288 289 assertTrue(name.isBound()); 290 assertEquals("duke", name.get()); 291 return null; 292 }); 293 assertFalse(name.isBound()); 294 }); 295 } 296 297 /** 298 * Test rebinding from null vaue to another value. 299 */ 300 @ParameterizedTest 301 @MethodSource("factories") 302 void testRebindingFromNull(ThreadFactory factory) throws Exception { 303 test(factory, () -> { 304 ScopedValue<String> name = ScopedValue.newInstance(); 305 306 // run 307 ScopedValue.where(name, null, () -> { 308 assertTrue(name.isBound()); 309 assertNull(name.get()); 310 311 ScopedValue.where(name, "duchess", () -> { 312 assertTrue(name.isBound()); 313 assertTrue("duchess".equals(name.get())); 314 }); 315 316 assertTrue(name.isBound()); 317 assertNull(name.get()); 318 }); 319 assertFalse(name.isBound()); 320 321 // call 322 ScopedValue.where(name, null, () -> { 323 assertTrue(name.isBound()); 324 assertNull(name.get()); 325 326 ScopedValue.where(name, "duchess", () -> { 327 assertTrue(name.isBound()); 328 assertTrue("duchess".equals(name.get())); 329 return null; 330 }); 331 332 assertTrue(name.isBound()); 333 assertNull(name.get()); 334 return null; 335 }); 336 assertFalse(name.isBound()); 337 }); 338 } 339 340 /** 341 * Test rebinding to null value. 342 */ 343 @ParameterizedTest 344 @MethodSource("factories") 345 void testRebindingToNull(ThreadFactory factory) throws Exception { 346 test(factory, () -> { 347 ScopedValue<String> name = ScopedValue.newInstance(); 348 349 // run 350 ScopedValue.where(name, "duke", () -> { 351 assertTrue(name.isBound()); 352 assertEquals("duke", name.get()); 353 354 ScopedValue.where(name, null, () -> { 355 assertTrue(name.isBound()); 356 assertNull(name.get()); 357 }); 358 359 assertTrue(name.isBound()); 360 assertEquals("duke", name.get()); 361 }); 362 assertFalse(name.isBound()); 363 364 // call 365 ScopedValue.where(name, "duke", () -> { 366 assertTrue(name.isBound()); 367 assertEquals("duke", name.get()); 368 369 ScopedValue.where(name, null, () -> { 370 assertTrue(name.isBound()); 371 assertNull(name.get()); 372 return null; 373 }); 374 375 assertTrue(name.isBound()); 376 assertEquals("duke", name.get()); 377 return null; 378 }); 379 assertFalse(name.isBound()); 380 }); 381 } 382 383 /** 384 * Test Carrier.get. 385 */ 386 @ParameterizedTest 387 @MethodSource("factories") 388 void testCarrierGet(ThreadFactory factory) throws Exception { 389 test(factory, () -> { 390 ScopedValue<String> name = ScopedValue.newInstance(); 391 ScopedValue<Integer> age = ScopedValue.newInstance(); 392 393 // one scoped value 394 var carrier1 = ScopedValue.where(name, "duke"); 395 assertEquals("duke", carrier1.get(name)); 396 assertThrows(NoSuchElementException.class, () -> carrier1.get(age)); 397 398 // two scoped values 399 var carrier2 = carrier1.where(age, 20); 400 assertEquals("duke", carrier2.get(name)); 401 assertEquals(20, (int) carrier2.get(age)); 402 }); 403 } 404 405 /** 406 * Test NullPointerException. 407 */ 408 @Test 409 void testNullPointerException() { 410 ScopedValue<String> name = ScopedValue.newInstance(); 411 412 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value")); 413 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> { })); 414 assertThrows(NullPointerException.class, () -> ScopedValue.where(null, "value", () -> null)); 415 416 assertThrows(NullPointerException.class, () -> name.orElseThrow(null)); 417 418 var carrier = ScopedValue.where(name, "duke"); 419 assertThrows(NullPointerException.class, () -> carrier.where(null, "value")); 420 assertThrows(NullPointerException.class, () -> carrier.get(null)); 421 assertThrows(NullPointerException.class, () -> carrier.run(null)); 422 assertThrows(NullPointerException.class, () -> carrier.call(null)); 423 } 424 425 @FunctionalInterface 426 private interface ThrowingRunnable { 427 void run() throws Exception; 428 } 429 430 /** 431 * Run the given task in a thread created with the given thread factory. 432 * @throws Exception if the task throws an exception 433 */ 434 private static void test(ThreadFactory factory, ThrowingRunnable task) throws Exception { 435 try (var executor = Executors.newThreadPerTaskExecutor(factory)) { 436 var future = executor.submit(() -> { 437 task.run(); 438 return null; 439 }); 440 try { 441 future.get(); 442 } catch (ExecutionException ee) { 443 Throwable cause = ee.getCause(); 444 if (cause instanceof Exception e) 445 throw e; 446 if (cause instanceof Error e) 447 throw e; 448 throw new RuntimeException(cause); 449 } 450 } 451 } 452 }