1 /* 2 * Licensed to the Apache Software Foundation (ASF) under one or more 3 * contributor license agreements. See the NOTICE file distributed with 4 * this work for additional information regarding copyright ownership. 5 * The ASF licenses this file to You under the Apache License, Version 2.0 6 * (the "License"); you may not use this file except in compliance with 7 * the License. You may obtain a copy of the License at 8 * 9 * http://www.apache.org/licenses/LICENSE-2.0 10 * 11 * Unless required by applicable law or agreed to in writing, software 12 * distributed under the License is distributed on an "AS IS" BASIS, 13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 * See the License for the specific language governing permissions and 15 * limitations under the License. 16 */ 17 18 package org.apache.harmony.tests.java.lang; 19 20 import java.util.Vector; 21 22 public class ThreadGroupTest extends junit.framework.TestCase { 23 24 private TestThreadDefaultUncaughtExceptionHandler testThreadDefaultUncaughtExceptionHandler; 25 private ThreadGroup rootThreadGroup; 26 private ThreadGroup initialThreadGroup; 27 private Thread.UncaughtExceptionHandler originalThreadDefaultUncaughtExceptionHandler; 28 29 @Override setUp()30 protected void setUp() { 31 initialThreadGroup = Thread.currentThread().getThreadGroup(); 32 rootThreadGroup = initialThreadGroup; 33 while (rootThreadGroup.getParent() != null) { 34 rootThreadGroup = rootThreadGroup.getParent(); 35 } 36 37 // When running as a CTS test Android will by default treat an uncaught exception as a 38 // fatal application error and kill the test. To avoid this the default 39 // UncaughtExceptionHandler is replaced for the duration of the test (if one exists). It 40 // also allows us to test that ultimately the default handler is called if a ThreadGroup's 41 // UncaughtExceptionHandler doesn't handle an exception. 42 originalThreadDefaultUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler(); 43 testThreadDefaultUncaughtExceptionHandler = new TestThreadDefaultUncaughtExceptionHandler(); 44 Thread.setDefaultUncaughtExceptionHandler(testThreadDefaultUncaughtExceptionHandler); 45 } 46 47 @Override tearDown()48 protected void tearDown() { 49 // Reset the uncaughtExceptionHandler to what it was when the test began. 50 Thread.setDefaultUncaughtExceptionHandler(originalThreadDefaultUncaughtExceptionHandler); 51 } 52 53 // Test for method java.lang.ThreadGroup(java.lang.String) test_ConstructorLjava_lang_String()54 public void test_ConstructorLjava_lang_String() { 55 // Unfortunately we have to use other APIs as well as we test the constructor 56 ThreadGroup initial = initialThreadGroup; 57 final String name = "Test name"; 58 ThreadGroup newGroup = new ThreadGroup(name); 59 assertTrue( 60 "Has to be possible to create a subgroup of current group using simple constructor", 61 newGroup.getParent() == initial); 62 assertTrue("Name has to be correct", newGroup.getName().equals(name)); 63 64 // cleanup 65 newGroup.destroy(); 66 } 67 68 // Test for method java.lang.ThreadGroup(java.lang.ThreadGroup, java.lang.String) test_ConstructorLjava_lang_ThreadGroupLjava_lang_String()69 public void test_ConstructorLjava_lang_ThreadGroupLjava_lang_String() { 70 // Unfortunately we have to use other APIs as well as we test the constructor 71 ThreadGroup newGroup = null; 72 try { 73 newGroup = new ThreadGroup(null, null); 74 } catch (NullPointerException e) { 75 } 76 assertNull("Can't create a ThreadGroup with a null parent", newGroup); 77 78 newGroup = new ThreadGroup(initialThreadGroup, null); 79 assertTrue("Has to be possible to create a subgroup of current group", 80 newGroup.getParent() == Thread.currentThread().getThreadGroup()); 81 82 // Lets start all over 83 newGroup.destroy(); 84 85 newGroup = new ThreadGroup(rootThreadGroup, "a name here"); 86 assertTrue("Has to be possible to create a subgroup of root group", 87 newGroup.getParent() == rootThreadGroup); 88 89 // Lets start all over 90 newGroup.destroy(); 91 92 try { 93 newGroup = new ThreadGroup(newGroup, "a name here"); 94 } catch (IllegalThreadStateException e) { 95 newGroup = null; 96 } 97 assertNull("Can't create a subgroup of a destroyed group", newGroup); 98 } 99 100 // Test for method int java.lang.ThreadGroup.activeCount() test_activeCount()101 public void test_activeCount() { 102 ThreadGroup tg = new ThreadGroup("activeCount"); 103 Thread t1 = new Thread(tg, new Runnable() { 104 public void run() { 105 try { 106 Thread.sleep(5000); 107 } catch (InterruptedException e) { 108 } 109 } 110 }); 111 int count = tg.activeCount(); 112 assertTrue("wrong active count: " + count, count == 0); 113 t1.start(); 114 count = tg.activeCount(); 115 assertTrue("wrong active count: " + count, count == 1); 116 t1.interrupt(); 117 try { 118 t1.join(); 119 } catch (InterruptedException e) { 120 } 121 // cleanup 122 tg.destroy(); 123 } 124 125 // Test for method void java.lang.ThreadGroup.destroy() test_destroy()126 public void test_destroy() { 127 final ThreadGroup originalCurrent = initialThreadGroup; 128 ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group"); 129 final int DEPTH = 4; 130 final Vector<ThreadGroup> subgroups = buildRandomTreeUnder(testRoot, DEPTH); 131 132 // destroy them all 133 testRoot.destroy(); 134 135 for (int i = 0; i < subgroups.size(); i++) { 136 ThreadGroup child = subgroups.elementAt(i); 137 assertEquals("Destroyed child can't have children", 0, child.activeCount()); 138 boolean passed = false; 139 try { 140 child.destroy(); 141 } catch (IllegalThreadStateException e) { 142 passed = true; 143 } 144 assertTrue("Destroyed child can't be destroyed again", passed); 145 } 146 147 testRoot = new ThreadGroup(originalCurrent, "Test group (daemon)"); 148 testRoot.setDaemon(true); 149 150 ThreadGroup child = new ThreadGroup(testRoot, "daemon child"); 151 152 // If we destroy the last daemon's child, the daemon should get destroyed 153 // as well 154 child.destroy(); 155 156 boolean passed = false; 157 try { 158 child.destroy(); 159 } catch (IllegalThreadStateException e) { 160 passed = true; 161 } 162 assertTrue("Daemon should have been destroyed already", passed); 163 164 passed = false; 165 try { 166 testRoot.destroy(); 167 } catch (IllegalThreadStateException e) { 168 passed = true; 169 } 170 assertTrue("Daemon parent should have been destroyed automatically", 171 passed); 172 173 assertTrue( 174 "Destroyed daemon's child should not be in daemon's list anymore", 175 !arrayIncludes(groups(testRoot), child)); 176 assertTrue("Destroyed daemon should not be in parent's list anymore", 177 !arrayIncludes(groups(originalCurrent), testRoot)); 178 179 testRoot = new ThreadGroup(originalCurrent, "Test group (daemon)"); 180 testRoot.setDaemon(true); 181 Thread noOp = new Thread(testRoot, null, "no-op thread") { 182 @Override 183 public void run() { 184 } 185 }; 186 noOp.start(); 187 188 // Wait for the no-op thread to run inside daemon ThreadGroup 189 waitForThreadToDieUninterrupted(noOp); 190 191 passed = false; 192 try { 193 child.destroy(); 194 } catch (IllegalThreadStateException e) { 195 passed = true; 196 } 197 assertTrue("Daemon group should have been destroyed already when last thread died", passed); 198 199 testRoot = new ThreadGroup(originalCurrent, "Test group (daemon)"); 200 noOp = new Thread(testRoot, null, "no-op thread") { 201 @Override 202 public void run() { 203 try { 204 Thread.sleep(500); 205 } catch (InterruptedException ie) { 206 fail("Should not be interrupted"); 207 } 208 } 209 }; 210 211 // Has to execute the next lines in an interval < the sleep interval of the no-op thread 212 noOp.start(); 213 passed = false; 214 try { 215 testRoot.destroy(); 216 } catch (IllegalThreadStateException its) { 217 passed = true; 218 } 219 assertTrue("Can't destroy a ThreadGroup that has threads", passed); 220 221 // But after the thread dies, we have to be able to destroy the thread group 222 waitForThreadToDieUninterrupted(noOp); 223 passed = true; 224 try { 225 testRoot.destroy(); 226 } catch (IllegalThreadStateException its) { 227 passed = false; 228 } 229 assertTrue("Should be able to destroy a ThreadGroup that has no threads", passed); 230 } 231 232 // Test for method java.lang.ThreadGroup.destroy() test_destroy_subtest0()233 public void test_destroy_subtest0() { 234 ThreadGroup group1 = new ThreadGroup("test_destroy_subtest0"); 235 group1.destroy(); 236 try { 237 new Thread(group1, "test_destroy_subtest0"); 238 fail("should throw IllegalThreadStateException"); 239 } catch (IllegalThreadStateException e) { 240 } 241 } 242 243 // Test for method int java.lang.ThreadGroup.getMaxPriority() test_getMaxPriority()244 public void test_getMaxPriority() { 245 final ThreadGroup originalCurrent = initialThreadGroup; 246 ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group"); 247 248 boolean passed = true; 249 try { 250 testRoot.setMaxPriority(Thread.MIN_PRIORITY); 251 } catch (IllegalArgumentException iae) { 252 passed = false; 253 } 254 assertTrue("Should be able to set priority", passed); 255 256 assertTrue("New value should be the same as we set", 257 testRoot.getMaxPriority() == Thread.MIN_PRIORITY); 258 259 testRoot.destroy(); 260 } 261 262 // Test for method java.lang.String java.lang.ThreadGroup.getName() test_getName()263 public void test_getName() { 264 final ThreadGroup originalCurrent = initialThreadGroup; 265 final String name = "Test group"; 266 final ThreadGroup testRoot = new ThreadGroup(originalCurrent, name); 267 268 assertTrue("Setting a name&getting does not work", testRoot.getName().equals(name)); 269 270 testRoot.destroy(); 271 } 272 273 // Test for method java.lang.ThreadGroup java.lang.ThreadGroup.getParent() test_getParent()274 public void test_getParent() { 275 final ThreadGroup originalCurrent = initialThreadGroup; 276 ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group"); 277 278 assertTrue("Parent is wrong", testRoot.getParent() == originalCurrent); 279 280 // Create some groups, nested some levels. 281 final int TOTAL_DEPTH = 5; 282 ThreadGroup current = testRoot; 283 Vector<ThreadGroup> groups = new Vector<ThreadGroup>(); 284 // To maintain the invariant that a thread in the Vector is parent 285 // of the next one in the collection (and child of the previous one) 286 groups.addElement(testRoot); 287 288 for (int i = 0; i < TOTAL_DEPTH; i++) { 289 current = new ThreadGroup(current, "level " + i); 290 groups.addElement(current); 291 } 292 293 // Now we walk the levels down, checking if parent is ok 294 for (int i = 1; i < groups.size(); i++) { 295 current = groups.elementAt(i); 296 ThreadGroup previous = groups.elementAt(i - 1); 297 assertTrue("Parent is wrong", current.getParent() == previous); 298 } 299 300 testRoot.destroy(); 301 } 302 303 // Test for method void java.lang.ThreadGroup.list() test_list()304 public void test_list() { 305 final ThreadGroup originalCurrent = initialThreadGroup; 306 final ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group"); 307 308 // First save the original System.out 309 java.io.PrintStream originalOut = System.out; 310 311 try { 312 java.io.ByteArrayOutputStream contentsStream = new java.io.ByteArrayOutputStream(100); 313 java.io.PrintStream newOut = new java.io.PrintStream(contentsStream); 314 315 // We have to "redirect" System.out to test the method 'list' 316 System.setOut(newOut); 317 318 originalCurrent.list(); 319 320 /* 321 * The output has to look like this: 322 * 323 * java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main] 324 * java.lang.ThreadGroup[name=Test group,maxpri=10] 325 */ 326 String contents = new String(contentsStream.toByteArray()); 327 boolean passed = (contents.indexOf("ThreadGroup[name=main") != -1) && 328 (contents.indexOf("Thread[") != -1) && 329 (contents.indexOf("ThreadGroup[name=Test group") != -1); 330 assertTrue("'list()' does not print expected contents. " 331 + "Result from list: " 332 + contents, passed); 333 // Do proper cleanup 334 testRoot.destroy(); 335 336 } finally { 337 // No matter what, we need to restore the original System.out 338 System.setOut(originalOut); 339 } 340 } 341 342 // Test for method boolean java.lang.ThreadGroup.parentOf(java.lang.ThreadGroup) test_parentOfLjava_lang_ThreadGroup()343 public void test_parentOfLjava_lang_ThreadGroup() { 344 final ThreadGroup originalCurrent = initialThreadGroup; 345 final ThreadGroup testRoot = new ThreadGroup(originalCurrent, 346 "Test group"); 347 final int DEPTH = 4; 348 buildRandomTreeUnder(testRoot, DEPTH); 349 350 final ThreadGroup[] allChildren = allGroups(testRoot); 351 for (ThreadGroup element : allChildren) { 352 assertTrue("Have to be parentOf all children", testRoot.parentOf(element)); 353 } 354 355 assertTrue("Have to be parentOf itself", testRoot.parentOf(testRoot)); 356 357 testRoot.destroy(); 358 assertTrue("Parent can't have test group as subgroup anymore", 359 !arrayIncludes(groups(testRoot.getParent()), testRoot)); 360 } 361 362 // Test for method boolean java.lang.ThreadGroup.isDaemon() and 363 // void java.lang.ThreadGroup.setDaemon(boolean) test_setDaemon_isDaemon()364 public void test_setDaemon_isDaemon() { 365 final ThreadGroup originalCurrent = initialThreadGroup; 366 final ThreadGroup testRoot = new ThreadGroup(originalCurrent, 367 "Test group"); 368 369 testRoot.setDaemon(true); 370 assertTrue("Setting daemon&getting does not work", testRoot.isDaemon()); 371 372 testRoot.setDaemon(false); 373 assertTrue("Setting daemon&getting does not work", !testRoot.isDaemon()); 374 375 testRoot.destroy(); 376 } 377 378 /* 379 * java.lang.ThreadGroupt#setDaemon(boolean) 380 */ test_setDaemon_Parent_Child()381 public void test_setDaemon_Parent_Child() { 382 ThreadGroup ptg = new ThreadGroup("Parent"); 383 ThreadGroup ctg = new ThreadGroup(ptg, "Child"); 384 385 ctg.setDaemon(true); 386 assertTrue(ctg.isDaemon()); 387 388 ctg.setDaemon(false); 389 assertFalse(ctg.isDaemon()); 390 391 ptg.setDaemon(true); 392 assertFalse(ctg.isDaemon()); 393 394 ptg.setDaemon(false); 395 assertFalse(ctg.isDaemon()); 396 } 397 398 // Test for method void java.lang.ThreadGroup.setMaxPriority(int) test_setMaxPriorityI()399 public void test_setMaxPriorityI() { 400 final ThreadGroup originalCurrent = initialThreadGroup; 401 ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group"); 402 403 boolean passed; 404 405 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 406 407 int currentMax = testRoot.getMaxPriority(); 408 testRoot.setMaxPriority(Thread.MAX_PRIORITY + 1); 409 passed = testRoot.getMaxPriority() == currentMax; 410 assertTrue( 411 "setMaxPriority: Any value higher than the current one is ignored. Before: " 412 + currentMax + " , after: " + testRoot.getMaxPriority(), 413 passed); 414 415 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 416 417 currentMax = testRoot.getMaxPriority(); 418 testRoot.setMaxPriority(Thread.MIN_PRIORITY - 1); 419 passed = testRoot.getMaxPriority() == Thread.MIN_PRIORITY; 420 assertTrue( 421 "setMaxPriority: Any value smaller than MIN_PRIORITY is adjusted to MIN_PRIORITY. Before: " 422 + currentMax + " , after: " + testRoot.getMaxPriority(), passed); 423 424 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 425 426 testRoot.destroy(); 427 testRoot = new ThreadGroup(originalCurrent, "Test group"); 428 429 // Create some groups, nested some levels. Each level will have maxPrio 430 // 1 unit smaller than the parent's. However, there can't be a group 431 // with priority < Thread.MIN_PRIORITY 432 final int TOTAL_DEPTH = testRoot.getMaxPriority() - Thread.MIN_PRIORITY 433 - 2; 434 ThreadGroup current = testRoot; 435 for (int i = 0; i < TOTAL_DEPTH; i++) { 436 current = new ThreadGroup(current, "level " + i); 437 } 438 439 // Now we walk the levels down, changing the maxPrio and later verifying 440 // that the value is indeed 1 unit smaller than the parent's maxPrio. 441 int maxPrio, parentMaxPrio; 442 current = testRoot; 443 444 // To maintain the invariant that when we are to modify a child, 445 // its maxPriority is always 1 unit smaller than its parent's. 446 // We have to set it for the root manually, and the loop does the rest 447 // for all the other sub-levels 448 current.setMaxPriority(current.getParent().getMaxPriority() - 1); 449 450 for (int i = 0; i < TOTAL_DEPTH; i++) { 451 maxPrio = current.getMaxPriority(); 452 parentMaxPrio = current.getParent().getMaxPriority(); 453 454 ThreadGroup[] children = groups(current); 455 assertEquals("Can only have 1 subgroup", 1, children.length); 456 current = children[0]; 457 assertTrue( 458 "Had to be 1 unit smaller than parent's priority in iteration=" 459 + i + " checking->" + current, 460 maxPrio == parentMaxPrio - 1); 461 current.setMaxPriority(maxPrio - 1); 462 463 // The next test is sort of redundant, since in next iteration it 464 // will be the parent tGroup, so the test will be done. 465 assertTrue("Had to be possible to change max priority", current 466 .getMaxPriority() == maxPrio - 1); 467 } 468 469 assertTrue( 470 "Priority of leaf child group has to be much smaller than original root group", 471 current.getMaxPriority() == testRoot.getMaxPriority() - TOTAL_DEPTH); 472 473 testRoot.destroy(); 474 475 // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 476 477 passed = true; 478 testRoot = new ThreadGroup(originalCurrent, "Test group"); 479 try { 480 testRoot.setMaxPriority(Thread.MAX_PRIORITY); 481 } catch (IllegalArgumentException iae) { 482 passed = false; 483 } 484 assertTrue( 485 "Max Priority = Thread.MAX_PRIORITY should be possible if the test is run with default system ThreadGroup as root", 486 passed); 487 testRoot.destroy(); 488 } 489 490 /* 491 * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread, 492 * java.lang.Throwable) 493 * Tests if a Thread tells its ThreadGroup about ThreadDeath. 494 */ test_uncaughtException_threadDeath()495 public void test_uncaughtException_threadDeath() { 496 final boolean[] passed = new boolean[1]; 497 498 ThreadGroup testRoot = new ThreadGroup(rootThreadGroup, 499 "Test Forcing a throw of ThreadDeath") { 500 @Override 501 public void uncaughtException(Thread t, Throwable e) { 502 if (e instanceof ThreadDeath) { 503 passed[0] = true; 504 } 505 // always forward, any exception 506 super.uncaughtException(t, e); 507 } 508 }; 509 510 final ThreadDeath threadDeath = new ThreadDeath(); 511 Thread thread = new Thread(testRoot, null, "suicidal thread") { 512 @Override 513 public void run() { 514 throw threadDeath; 515 } 516 }; 517 thread.start(); 518 waitForThreadToDieUninterrupted(thread); 519 testThreadDefaultUncaughtExceptionHandler.assertWasCalled(thread, threadDeath); 520 521 testRoot.destroy(); 522 assertTrue( 523 "Any thread should notify its ThreadGroup about its own death, even if suicide:" 524 + testRoot, passed[0]); 525 } 526 527 /* 528 * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread, 529 * java.lang.Throwable) 530 * Test if a Thread tells its ThreadGroup about a natural (non-exception) death. 531 */ test_uncaughtException_naturalDeath()532 public void test_uncaughtException_naturalDeath() { 533 final boolean[] failed = new boolean[1]; 534 535 ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test ThreadDeath") { 536 @Override 537 public void uncaughtException(Thread t, Throwable e) { 538 failed[0] = true; 539 540 // always forward any exception 541 super.uncaughtException(t, e); 542 } 543 }; 544 545 Thread thread = new Thread(testRoot, null, "no-op thread"); 546 thread.start(); 547 waitForThreadToDieUninterrupted(thread); 548 testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled(); 549 testRoot.destroy(); 550 assertFalse("A thread should not call uncaughtException when it dies:" 551 + testRoot, failed[0]); 552 } 553 554 /* 555 * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread, 556 * java.lang.Throwable) 557 * Test if a Thread tells its ThreadGroup about an Exception 558 */ test_uncaughtException_runtimeException()559 public void test_uncaughtException_runtimeException() { 560 // Our own exception class 561 class TestException extends RuntimeException { 562 private static final long serialVersionUID = 1L; 563 } 564 565 final boolean[] passed = new boolean[1]; 566 567 ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test other Exception") { 568 @Override 569 public void uncaughtException(Thread t, Throwable e) { 570 if (e instanceof TestException) { 571 passed[0] = true; 572 } 573 // always forward any exception 574 super.uncaughtException(t, e); 575 } 576 }; 577 578 final TestException testException = new TestException(); 579 Thread thread = new Thread(testRoot, null, "RuntimeException thread") { 580 @Override 581 public void run() { 582 throw testException; 583 } 584 }; 585 thread.start(); 586 waitForThreadToDieUninterrupted(thread); 587 testThreadDefaultUncaughtExceptionHandler.assertWasCalled(thread, testException); 588 testRoot.destroy(); 589 assertTrue( 590 "Any thread should notify its ThreadGroup about an uncaught exception:" 591 + testRoot, passed[0]); 592 } 593 594 /* 595 * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread, 596 * java.lang.Throwable) 597 * Test if a handler doesn't pass on the exception to super.uncaughtException that's ok. 598 */ test_uncaughtException_exceptionHandledByHandler()599 public void test_uncaughtException_exceptionHandledByHandler() { 600 // Our own exception class 601 class TestException extends RuntimeException { 602 private static final long serialVersionUID = 1L; 603 } 604 605 ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test other Exception") { 606 @Override 607 public void uncaughtException(Thread t, Throwable e) { 608 // Swallow TestException and always forward any other exception 609 if (!(e instanceof TestException)) { 610 super.uncaughtException(t, e); 611 } 612 } 613 }; 614 615 final TestException testException = new TestException(); 616 Thread thread = new Thread(testRoot, null, "RuntimeException thread") { 617 @Override 618 public void run() { 619 throw testException; 620 } 621 }; 622 thread.start(); 623 waitForThreadToDieUninterrupted(thread); 624 testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled(); 625 testRoot.destroy(); 626 } 627 628 /* 629 * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread, 630 * java.lang.Throwable) 631 * Tests an exception thrown by the handler itself. 632 */ test_uncaughtException_exceptionInUncaughtException()633 public void test_uncaughtException_exceptionInUncaughtException() { 634 // Our own uncaught exception classes 635 class UncaughtException extends RuntimeException { 636 private static final long serialVersionUID = 1L; 637 } 638 639 ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, 640 "Test Exception in uncaught exception") { 641 @Override 642 public void uncaughtException(Thread t, Throwable e) { 643 // This should be no-op according to the spec 644 throw new UncaughtException(); 645 } 646 }; 647 648 Thread thread = new Thread(testRoot, null, "no-op thread") { 649 @Override 650 public void run() { 651 throw new RuntimeException(); 652 } 653 }; 654 thread.start(); 655 waitForThreadToDieUninterrupted(thread); 656 testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled(); 657 testRoot.destroy(); 658 } 659 allGroups(ThreadGroup parent)660 private static ThreadGroup[] allGroups(ThreadGroup parent) { 661 int count = parent.activeGroupCount(); 662 ThreadGroup[] all = new ThreadGroup[count]; 663 parent.enumerate(all, true); 664 return all; 665 } 666 asyncBuildRandomTreeUnder(final ThreadGroup aGroup, final int depth, final Vector<ThreadGroup> allCreated)667 private static void asyncBuildRandomTreeUnder(final ThreadGroup aGroup, 668 final int depth, final Vector<ThreadGroup> allCreated) { 669 if (depth <= 0) { 670 return; 671 } 672 673 final int maxImmediateSubgroups = random(3); 674 for (int i = 0; i < maxImmediateSubgroups; i++) { 675 final int iClone = i; 676 final String name = " Depth = " + depth + ",N = " + iClone 677 + ",Vector size at creation: " + allCreated.size(); 678 // Use concurrency to maximize chance of exposing concurrency bugs 679 // in ThreadGroups 680 Thread t = new Thread(aGroup, name) { 681 @Override 682 public void run() { 683 ThreadGroup newGroup = new ThreadGroup(aGroup, name); 684 allCreated.addElement(newGroup); 685 asyncBuildRandomTreeUnder(newGroup, depth - 1, allCreated); 686 } 687 }; 688 t.start(); 689 } 690 691 } 692 asyncBuildRandomTreeUnder(final ThreadGroup aGroup, final int depth)693 private static Vector<ThreadGroup> asyncBuildRandomTreeUnder(final ThreadGroup aGroup, 694 final int depth) { 695 Vector<ThreadGroup> result = new Vector<ThreadGroup>(); 696 asyncBuildRandomTreeUnder(aGroup, depth, result); 697 return result; 698 699 } 700 groups(ThreadGroup parent)701 private static ThreadGroup[] groups(ThreadGroup parent) { 702 // No API to get the count of immediate children only ? 703 int count = parent.activeGroupCount(); 704 ThreadGroup[] all = new ThreadGroup[count]; 705 parent.enumerate(all, false); 706 // Now we may have nulls in the array, we must find the actual size 707 int actualSize = 0; 708 for (; actualSize < all.length; actualSize++) { 709 if (all[actualSize] == null) { 710 break; 711 } 712 } 713 ThreadGroup[] result; 714 if (actualSize == all.length) { 715 result = all; 716 } else { 717 result = new ThreadGroup[actualSize]; 718 System.arraycopy(all, 0, result, 0, actualSize); 719 } 720 721 return result; 722 723 } 724 random(int max)725 private static int random(int max) { 726 return 1 + ((new Object()).hashCode() % max); 727 } 728 buildRandomTreeUnder(ThreadGroup aGroup, int depth)729 private static Vector<ThreadGroup> buildRandomTreeUnder(ThreadGroup aGroup, int depth) { 730 Vector<ThreadGroup> result = asyncBuildRandomTreeUnder(aGroup, depth); 731 while (true) { 732 int sizeBefore = result.size(); 733 try { 734 Thread.sleep(1000); 735 int sizeAfter = result.size(); 736 // If no activity for a while, we assume async building may be 737 // done. 738 if (sizeBefore == sizeAfter) { 739 // It can only be done if no more threads. Unfortunately we 740 // are relying on this API to work as well. 741 // If it does not, we may loop forever. 742 if (aGroup.activeCount() == 0) { 743 break; 744 } 745 } 746 } catch (InterruptedException e) { 747 } 748 } 749 return result; 750 751 } 752 arrayIncludes(Object[] array, Object toTest)753 private static boolean arrayIncludes(Object[] array, Object toTest) { 754 for (Object element : array) { 755 if (element == toTest) { 756 return true; 757 } 758 } 759 return false; 760 } 761 waitForThreadToDieUninterrupted(Thread thread)762 private static void waitForThreadToDieUninterrupted(Thread thread) { 763 try { 764 thread.join(); 765 } catch (InterruptedException ie) { 766 fail("Should not have been interrupted"); 767 } 768 } 769 770 private static class TestThreadDefaultUncaughtExceptionHandler 771 implements Thread.UncaughtExceptionHandler { 772 773 private boolean called; 774 private Throwable ex; 775 private Thread thread; 776 777 @Override uncaughtException(Thread thread, Throwable ex)778 public void uncaughtException(Thread thread, Throwable ex) { 779 this.called = true; 780 this.thread = thread; 781 this.ex = ex; 782 } 783 assertWasCalled(Thread thread, Throwable ex)784 public void assertWasCalled(Thread thread, Throwable ex) { 785 assertTrue(called); 786 assertSame(this.thread, thread); 787 assertSame(this.ex, ex); 788 } 789 assertWasNotCalled()790 public void assertWasNotCalled() { 791 assertFalse(called); 792 } 793 } 794 795 } 796