Home | History | Annotate | Download | only in lang
      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
     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
     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)
     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)
     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()
    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()
    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()
    233     @SuppressWarnings("DeadThread")
    234     public void test_destroy_subtest0() {
    235         ThreadGroup group1 = new ThreadGroup("test_destroy_subtest0");
    236         group1.destroy();
    237         try {
    238             new Thread(group1, "test_destroy_subtest0");
    239             fail("should throw IllegalThreadStateException");
    240         } catch (IllegalThreadStateException e) {
    241         }
    242     }
    243 
    244     // Test for method int java.lang.ThreadGroup.getMaxPriority()
    245     public void test_getMaxPriority() {
    246         final ThreadGroup originalCurrent = initialThreadGroup;
    247         ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
    248 
    249         boolean passed = true;
    250         try {
    251             testRoot.setMaxPriority(Thread.MIN_PRIORITY);
    252         } catch (IllegalArgumentException iae) {
    253             passed = false;
    254         }
    255         assertTrue("Should be able to set priority", passed);
    256 
    257         assertTrue("New value should be the same as we set",
    258                 testRoot.getMaxPriority() == Thread.MIN_PRIORITY);
    259 
    260         testRoot.destroy();
    261     }
    262 
    263     // Test for method java.lang.String java.lang.ThreadGroup.getName()
    264     public void test_getName() {
    265         final ThreadGroup originalCurrent = initialThreadGroup;
    266         final String name = "Test group";
    267         final ThreadGroup testRoot = new ThreadGroup(originalCurrent, name);
    268 
    269         assertTrue("Setting a name&getting does not work", testRoot.getName().equals(name));
    270 
    271         testRoot.destroy();
    272     }
    273 
    274     // Test for method java.lang.ThreadGroup java.lang.ThreadGroup.getParent()
    275     public void test_getParent() {
    276         final ThreadGroup originalCurrent = initialThreadGroup;
    277         ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
    278 
    279         assertTrue("Parent is wrong", testRoot.getParent() == originalCurrent);
    280 
    281         // Create some groups, nested some levels.
    282         final int TOTAL_DEPTH = 5;
    283         ThreadGroup current = testRoot;
    284         Vector<ThreadGroup> groups = new Vector<ThreadGroup>();
    285         // To maintain the invariant that a thread in the Vector is parent
    286         // of the next one in the collection (and child of the previous one)
    287         groups.addElement(testRoot);
    288 
    289         for (int i = 0; i < TOTAL_DEPTH; i++) {
    290             current = new ThreadGroup(current, "level " + i);
    291             groups.addElement(current);
    292         }
    293 
    294         // Now we walk the levels down, checking if parent is ok
    295         for (int i = 1; i < groups.size(); i++) {
    296             current = groups.elementAt(i);
    297             ThreadGroup previous = groups.elementAt(i - 1);
    298             assertTrue("Parent is wrong", current.getParent() == previous);
    299         }
    300 
    301         testRoot.destroy();
    302     }
    303 
    304     // Test for method void java.lang.ThreadGroup.list()
    305     public void test_list() {
    306         final ThreadGroup originalCurrent = initialThreadGroup;
    307         final ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
    308 
    309         // First save the original System.out
    310         java.io.PrintStream originalOut = System.out;
    311 
    312         try {
    313             java.io.ByteArrayOutputStream contentsStream = new java.io.ByteArrayOutputStream(100);
    314             java.io.PrintStream newOut = new java.io.PrintStream(contentsStream);
    315 
    316             // We have to "redirect" System.out to test the method 'list'
    317             System.setOut(newOut);
    318 
    319             originalCurrent.list();
    320 
    321             /*
    322              * The output has to look like this:
    323              *
    324              * java.lang.ThreadGroup[name=main,maxpri=10] Thread[main,5,main]
    325              * java.lang.ThreadGroup[name=Test group,maxpri=10]
    326              */
    327             String contents = new String(contentsStream.toByteArray());
    328             boolean passed = (contents.indexOf("ThreadGroup[name=main") != -1) &&
    329                     (contents.indexOf("Thread[") != -1) &&
    330                     (contents.indexOf("ThreadGroup[name=Test group") != -1);
    331             assertTrue("'list()' does not print expected contents. "
    332                     + "Result from list: "
    333                     + contents, passed);
    334             // Do proper cleanup
    335             testRoot.destroy();
    336 
    337         } finally {
    338             // No matter what, we need to restore the original System.out
    339             System.setOut(originalOut);
    340         }
    341     }
    342 
    343     // Test for method boolean java.lang.ThreadGroup.parentOf(java.lang.ThreadGroup)
    344     public void test_parentOfLjava_lang_ThreadGroup() {
    345         final ThreadGroup originalCurrent = initialThreadGroup;
    346         final ThreadGroup testRoot = new ThreadGroup(originalCurrent,
    347                 "Test group");
    348         final int DEPTH = 4;
    349         buildRandomTreeUnder(testRoot, DEPTH);
    350 
    351         final ThreadGroup[] allChildren = allGroups(testRoot);
    352         for (ThreadGroup element : allChildren) {
    353             assertTrue("Have to be parentOf all children", testRoot.parentOf(element));
    354         }
    355 
    356         assertTrue("Have to be parentOf itself", testRoot.parentOf(testRoot));
    357 
    358         testRoot.destroy();
    359         assertTrue("Parent can't have test group as subgroup anymore",
    360                 !arrayIncludes(groups(testRoot.getParent()), testRoot));
    361     }
    362 
    363     // Test for method boolean java.lang.ThreadGroup.isDaemon() and
    364     // void java.lang.ThreadGroup.setDaemon(boolean)
    365     public void test_setDaemon_isDaemon() {
    366         final ThreadGroup originalCurrent = initialThreadGroup;
    367         final ThreadGroup testRoot = new ThreadGroup(originalCurrent,
    368                 "Test group");
    369 
    370         testRoot.setDaemon(true);
    371         assertTrue("Setting daemon&getting does not work", testRoot.isDaemon());
    372 
    373         testRoot.setDaemon(false);
    374         assertTrue("Setting daemon&getting does not work", !testRoot.isDaemon());
    375 
    376         testRoot.destroy();
    377     }
    378 
    379     /*
    380      * java.lang.ThreadGroupt#setDaemon(boolean)
    381      */
    382     public void test_setDaemon_Parent_Child() {
    383         ThreadGroup ptg = new ThreadGroup("Parent");
    384         ThreadGroup ctg = new ThreadGroup(ptg, "Child");
    385 
    386         ctg.setDaemon(true);
    387         assertTrue(ctg.isDaemon());
    388 
    389         ctg.setDaemon(false);
    390         assertFalse(ctg.isDaemon());
    391 
    392         ptg.setDaemon(true);
    393         assertFalse(ctg.isDaemon());
    394 
    395         ptg.setDaemon(false);
    396         assertFalse(ctg.isDaemon());
    397     }
    398 
    399     // Test for method void java.lang.ThreadGroup.setMaxPriority(int)
    400     public void test_setMaxPriorityI() {
    401         final ThreadGroup originalCurrent = initialThreadGroup;
    402         ThreadGroup testRoot = new ThreadGroup(originalCurrent, "Test group");
    403 
    404         boolean passed;
    405 
    406         // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    407 
    408         int currentMax = testRoot.getMaxPriority();
    409         testRoot.setMaxPriority(Thread.MAX_PRIORITY + 1);
    410         passed = testRoot.getMaxPriority() == currentMax;
    411         assertTrue(
    412                 "setMaxPriority: Any value higher than the current one is ignored. Before: "
    413                         + currentMax + " , after: " + testRoot.getMaxPriority(),
    414                 passed);
    415 
    416         // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    417 
    418         currentMax = testRoot.getMaxPriority();
    419         testRoot.setMaxPriority(Thread.MIN_PRIORITY - 1);
    420         passed = testRoot.getMaxPriority() == Thread.MIN_PRIORITY;
    421         assertTrue(
    422                 "setMaxPriority: Any value smaller than MIN_PRIORITY is adjusted to MIN_PRIORITY. Before: "
    423                         + currentMax + " , after: " + testRoot.getMaxPriority(), passed);
    424 
    425         // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    426 
    427         testRoot.destroy();
    428         testRoot = new ThreadGroup(originalCurrent, "Test group");
    429 
    430         // Create some groups, nested some levels. Each level will have maxPrio
    431         // 1 unit smaller than the parent's. However, there can't be a group
    432         // with priority < Thread.MIN_PRIORITY
    433         final int TOTAL_DEPTH = testRoot.getMaxPriority() - Thread.MIN_PRIORITY
    434                 - 2;
    435         ThreadGroup current = testRoot;
    436         for (int i = 0; i < TOTAL_DEPTH; i++) {
    437             current = new ThreadGroup(current, "level " + i);
    438         }
    439 
    440         // Now we walk the levels down, changing the maxPrio and later verifying
    441         // that the value is indeed 1 unit smaller than the parent's maxPrio.
    442         int maxPrio, parentMaxPrio;
    443         current = testRoot;
    444 
    445         // To maintain the invariant that when we are to modify a child,
    446         // its maxPriority is always 1 unit smaller than its parent's.
    447         // We have to set it for the root manually, and the loop does the rest
    448         // for all the other sub-levels
    449         current.setMaxPriority(current.getParent().getMaxPriority() - 1);
    450 
    451         for (int i = 0; i < TOTAL_DEPTH; i++) {
    452             maxPrio = current.getMaxPriority();
    453             parentMaxPrio = current.getParent().getMaxPriority();
    454 
    455             ThreadGroup[] children = groups(current);
    456             assertEquals("Can only have 1 subgroup", 1, children.length);
    457             current = children[0];
    458             assertTrue(
    459                     "Had to be 1 unit smaller than parent's priority in iteration="
    460                             + i + " checking->" + current,
    461                     maxPrio == parentMaxPrio - 1);
    462             current.setMaxPriority(maxPrio - 1);
    463 
    464             // The next test is sort of redundant, since in next iteration it
    465             // will be the parent tGroup, so the test will be done.
    466             assertTrue("Had to be possible to change max priority", current
    467                     .getMaxPriority() == maxPrio - 1);
    468         }
    469 
    470         assertTrue(
    471                 "Priority of leaf child group has to be much smaller than original root group",
    472                 current.getMaxPriority() == testRoot.getMaxPriority() - TOTAL_DEPTH);
    473 
    474         testRoot.destroy();
    475 
    476         // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
    477 
    478         passed = true;
    479         testRoot = new ThreadGroup(originalCurrent, "Test group");
    480         try {
    481             testRoot.setMaxPriority(Thread.MAX_PRIORITY);
    482         } catch (IllegalArgumentException iae) {
    483             passed = false;
    484         }
    485         assertTrue(
    486                 "Max Priority = Thread.MAX_PRIORITY should be possible if the test is run with default system ThreadGroup as root",
    487                 passed);
    488         testRoot.destroy();
    489     }
    490 
    491     /*
    492      * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
    493      * java.lang.Throwable)
    494      * Tests if a Thread tells its ThreadGroup about ThreadDeath.
    495      */
    496     public void test_uncaughtException_threadDeath() {
    497         final boolean[] passed = new boolean[1];
    498 
    499         ThreadGroup testRoot = new ThreadGroup(rootThreadGroup,
    500                 "Test Forcing a throw of ThreadDeath") {
    501             @Override
    502             public void uncaughtException(Thread t, Throwable e) {
    503                 if (e instanceof ThreadDeath) {
    504                     passed[0] = true;
    505                 }
    506                 // always forward, any exception
    507                 super.uncaughtException(t, e);
    508             }
    509         };
    510 
    511         final ThreadDeath threadDeath = new ThreadDeath();
    512         Thread thread = new Thread(testRoot, null, "suicidal thread") {
    513             @Override
    514             public void run() {
    515                 throw threadDeath;
    516             }
    517         };
    518         thread.start();
    519         waitForThreadToDieUninterrupted(thread);
    520         testThreadDefaultUncaughtExceptionHandler.assertWasCalled(thread, threadDeath);
    521 
    522         testRoot.destroy();
    523         assertTrue(
    524                 "Any thread should notify its ThreadGroup about its own death, even if suicide:"
    525                         + testRoot, passed[0]);
    526     }
    527 
    528     /*
    529      * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
    530      * java.lang.Throwable)
    531      * Test if a Thread tells its ThreadGroup about a natural (non-exception) death.
    532      */
    533     public void test_uncaughtException_naturalDeath() {
    534         final boolean[] failed = new boolean[1];
    535 
    536         ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test ThreadDeath") {
    537             @Override
    538             public void uncaughtException(Thread t, Throwable e) {
    539                 failed[0] = true;
    540 
    541                 // always forward any exception
    542                 super.uncaughtException(t, e);
    543             }
    544         };
    545 
    546         Thread thread = new Thread(testRoot, null, "no-op thread");
    547         thread.start();
    548         waitForThreadToDieUninterrupted(thread);
    549         testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled();
    550         testRoot.destroy();
    551         assertFalse("A thread should not call uncaughtException when it dies:"
    552                 + testRoot, failed[0]);
    553     }
    554 
    555     /*
    556      * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
    557      * java.lang.Throwable)
    558      * Test if a Thread tells its ThreadGroup about an Exception
    559      */
    560     public void test_uncaughtException_runtimeException() {
    561         // Our own exception class
    562         class TestException extends RuntimeException {
    563             private static final long serialVersionUID = 1L;
    564         }
    565 
    566         final boolean[] passed = new boolean[1];
    567 
    568         ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test other Exception") {
    569             @Override
    570             public void uncaughtException(Thread t, Throwable e) {
    571                 if (e instanceof TestException) {
    572                     passed[0] = true;
    573                 }
    574                 // always forward any exception
    575                 super.uncaughtException(t, e);
    576             }
    577         };
    578 
    579         final TestException testException = new TestException();
    580         Thread thread = new Thread(testRoot, null, "RuntimeException thread") {
    581             @Override
    582             public void run() {
    583                 throw testException;
    584             }
    585         };
    586         thread.start();
    587         waitForThreadToDieUninterrupted(thread);
    588         testThreadDefaultUncaughtExceptionHandler.assertWasCalled(thread, testException);
    589         testRoot.destroy();
    590         assertTrue(
    591                 "Any thread should notify its ThreadGroup about an uncaught exception:"
    592                         + testRoot, passed[0]);
    593     }
    594 
    595     /*
    596      * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
    597      * java.lang.Throwable)
    598      * Test if a handler doesn't pass on the exception to super.uncaughtException that's ok.
    599      */
    600     public void test_uncaughtException_exceptionHandledByHandler() {
    601         // Our own exception class
    602         class TestException extends RuntimeException {
    603             private static final long serialVersionUID = 1L;
    604         }
    605 
    606         ThreadGroup testRoot = new ThreadGroup(initialThreadGroup, "Test other Exception") {
    607             @Override
    608             public void uncaughtException(Thread t, Throwable e) {
    609                 // Swallow TestException and always forward any other exception
    610                 if (!(e instanceof TestException)) {
    611                     super.uncaughtException(t, e);
    612                 }
    613             }
    614         };
    615 
    616         final TestException testException = new TestException();
    617         Thread thread = new Thread(testRoot, null, "RuntimeException thread") {
    618             @Override
    619             public void run() {
    620                 throw testException;
    621             }
    622         };
    623         thread.start();
    624         waitForThreadToDieUninterrupted(thread);
    625         testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled();
    626         testRoot.destroy();
    627     }
    628 
    629     /*
    630      * Test for method void java.lang.ThreadGroup.uncaughtException(java.lang.Thread,
    631      * java.lang.Throwable)
    632      * Tests an exception thrown by the handler itself.
    633      */
    634     public void test_uncaughtException_exceptionInUncaughtException() {
    635         // Our own uncaught exception classes
    636         class UncaughtException extends RuntimeException {
    637             private static final long serialVersionUID = 1L;
    638         }
    639 
    640         ThreadGroup testRoot = new ThreadGroup(initialThreadGroup,
    641                 "Test Exception in uncaught exception") {
    642             @Override
    643             public void uncaughtException(Thread t, Throwable e) {
    644                 // This should be no-op according to the spec
    645                 throw new UncaughtException();
    646             }
    647         };
    648 
    649         Thread thread = new Thread(testRoot, null, "no-op thread") {
    650             @Override
    651             public void run() {
    652                 throw new RuntimeException();
    653             }
    654         };
    655         thread.start();
    656         waitForThreadToDieUninterrupted(thread);
    657         testThreadDefaultUncaughtExceptionHandler.assertWasNotCalled();
    658         testRoot.destroy();
    659     }
    660 
    661     private static ThreadGroup[] allGroups(ThreadGroup parent) {
    662         int count = parent.activeGroupCount();
    663         ThreadGroup[] all = new ThreadGroup[count];
    664         parent.enumerate(all, true);
    665         return all;
    666     }
    667 
    668     private static void asyncBuildRandomTreeUnder(final ThreadGroup aGroup,
    669             final int depth, final Vector<ThreadGroup> allCreated) {
    670         if (depth <= 0) {
    671             return;
    672         }
    673 
    674         final int maxImmediateSubgroups = random(3);
    675         for (int i = 0; i < maxImmediateSubgroups; i++) {
    676             final int iClone = i;
    677             final String name = " Depth = " + depth + ",N = " + iClone
    678                     + ",Vector size at creation: " + allCreated.size();
    679             // Use concurrency to maximize chance of exposing concurrency bugs
    680             // in ThreadGroups
    681             Thread t = new Thread(aGroup, name) {
    682                 @Override
    683                 public void run() {
    684                     ThreadGroup newGroup = new ThreadGroup(aGroup, name);
    685                     allCreated.addElement(newGroup);
    686                     asyncBuildRandomTreeUnder(newGroup, depth - 1, allCreated);
    687                 }
    688             };
    689             t.start();
    690         }
    691 
    692     }
    693 
    694     private static Vector<ThreadGroup> asyncBuildRandomTreeUnder(final ThreadGroup aGroup,
    695             final int depth) {
    696         Vector<ThreadGroup> result = new Vector<ThreadGroup>();
    697         asyncBuildRandomTreeUnder(aGroup, depth, result);
    698         return result;
    699 
    700     }
    701 
    702     private static ThreadGroup[] groups(ThreadGroup parent) {
    703         // No API to get the count of immediate children only ?
    704         int count = parent.activeGroupCount();
    705         ThreadGroup[] all = new ThreadGroup[count];
    706         parent.enumerate(all, false);
    707         // Now we may have nulls in the array, we must find the actual size
    708         int actualSize = 0;
    709         for (; actualSize < all.length; actualSize++) {
    710             if (all[actualSize] == null) {
    711                 break;
    712             }
    713         }
    714         ThreadGroup[] result;
    715         if (actualSize == all.length) {
    716             result = all;
    717         } else {
    718             result = new ThreadGroup[actualSize];
    719             System.arraycopy(all, 0, result, 0, actualSize);
    720         }
    721 
    722         return result;
    723 
    724     }
    725 
    726     private static int random(int max) {
    727         return 1 + ((new Object()).hashCode() % max);
    728     }
    729 
    730     private static Vector<ThreadGroup> buildRandomTreeUnder(ThreadGroup aGroup, int depth) {
    731         Vector<ThreadGroup> result = asyncBuildRandomTreeUnder(aGroup, depth);
    732         while (true) {
    733             int sizeBefore = result.size();
    734             try {
    735                 Thread.sleep(1000);
    736                 int sizeAfter = result.size();
    737                 // If no activity for a while, we assume async building may be
    738                 // done.
    739                 if (sizeBefore == sizeAfter) {
    740                     // It can only be done if no more threads. Unfortunately we
    741                     // are relying on this API to work as well.
    742                     // If it does not, we may loop forever.
    743                     if (aGroup.activeCount() == 0) {
    744                         break;
    745                     }
    746                 }
    747             } catch (InterruptedException e) {
    748             }
    749         }
    750         return result;
    751 
    752     }
    753 
    754     private static boolean arrayIncludes(Object[] array, Object toTest) {
    755         for (Object element : array) {
    756             if (element == toTest) {
    757                 return true;
    758             }
    759         }
    760         return false;
    761     }
    762 
    763     private static void waitForThreadToDieUninterrupted(Thread thread) {
    764         try {
    765             thread.join();
    766         } catch (InterruptedException ie) {
    767             fail("Should not have been interrupted");
    768         }
    769     }
    770 
    771     private static class TestThreadDefaultUncaughtExceptionHandler
    772             implements Thread.UncaughtExceptionHandler {
    773 
    774         private boolean called;
    775         private Throwable ex;
    776         private Thread thread;
    777 
    778         @Override
    779         public void uncaughtException(Thread thread, Throwable ex) {
    780             this.called = true;
    781             this.thread = thread;
    782             this.ex = ex;
    783         }
    784 
    785         public void assertWasCalled(Thread thread, Throwable ex) {
    786             assertTrue(called);
    787             assertSame(this.thread, thread);
    788             assertSame(this.ex, ex);
    789         }
    790 
    791         public void assertWasNotCalled() {
    792             assertFalse(called);
    793         }
    794     }
    795 
    796 }
    797