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     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()
    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()
    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()
    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()
    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)
    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)
    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      */
    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)
    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      */
    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      */
    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      */
    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      */
    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      */
    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 
    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 
    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 
    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 
    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 
    725     private static int random(int max) {
    726         return 1 + ((new Object()).hashCode() % max);
    727     }
    728 
    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 
    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 
    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
    778         public void uncaughtException(Thread thread, Throwable ex) {
    779             this.called = true;
    780             this.thread = thread;
    781             this.ex = ex;
    782         }
    783 
    784         public void assertWasCalled(Thread thread, Throwable ex) {
    785             assertTrue(called);
    786             assertSame(this.thread, thread);
    787             assertSame(this.ex, ex);
    788         }
    789 
    790         public void assertWasNotCalled() {
    791             assertFalse(called);
    792         }
    793     }
    794 
    795 }
    796