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