diff --git a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java index 5c7aa65fff73..94c12e6e9770 100644 --- a/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java +++ b/src/main/java/org/junit/internal/runners/statements/FailOnTimeout.java @@ -122,12 +122,26 @@ public void evaluate() throws Throwable { FutureTask task = new FutureTask(callable); ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup"); Thread thread = new Thread(threadGroup, task, "Time-limited test"); - thread.setDaemon(true); - thread.start(); - callable.awaitStarted(); - Throwable throwable = getResult(task, thread); - if (throwable != null) { - throw throwable; + try { + thread.setDaemon(true); + thread.start(); + callable.awaitStarted(); + Throwable throwable = getResult(task, thread); + if (throwable != null) { + throw throwable; + } + } finally { + try { + thread.join(1); + } catch (InterruptedException e) { + Thread.currentThread().interrupt(); + } + try { + threadGroup.destroy(); + } catch (IllegalThreadStateException e) { + // If a thread from the group is still alive, the ThreadGroup cannot be destroyed. + // Swallow the exception to keep the same behavior prior to this change. + } } } diff --git a/src/test/java/org/junit/internal/runners/statements/FailOnTimeoutTest.java b/src/test/java/org/junit/internal/runners/statements/FailOnTimeoutTest.java index ad70b162cbdc..f4f4717c4b38 100644 --- a/src/test/java/org/junit/internal/runners/statements/FailOnTimeoutTest.java +++ b/src/test/java/org/junit/internal/runners/statements/FailOnTimeoutTest.java @@ -3,6 +3,7 @@ import static java.lang.Long.MAX_VALUE; import static java.lang.Math.atan; import static java.lang.System.currentTimeMillis; +import static java.lang.Thread.currentThread; import static java.lang.Thread.sleep; import static java.util.concurrent.TimeUnit.MILLISECONDS; import static org.hamcrest.core.Is.is; @@ -12,6 +13,10 @@ import static org.junit.Assert.fail; import static org.junit.internal.runners.statements.FailOnTimeout.builder; +import java.util.Arrays; +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; import java.util.concurrent.TimeUnit; import org.junit.Rule; @@ -192,4 +197,23 @@ private void notTheRealCauseOfTheTimeout() { } } } + + @Test + public void threadGroupNotLeaked() throws Throwable { + Collection groupsBeforeSet = subGroupsOfCurrentThread(); + + evaluateWithWaitDuration(0); + + for (ThreadGroup group: subGroupsOfCurrentThread()) { + if (!groupsBeforeSet.contains(group) && "FailOnTimeoutGroup".equals(group.getName())) { + fail("A 'FailOnTimeoutGroup' thread group remains referenced after the test execution."); + } + } + } + + private Collection subGroupsOfCurrentThread() { + ThreadGroup[] subGroups = new ThreadGroup[256]; + int numGroups = currentThread().getThreadGroup().enumerate(subGroups); + return Arrays.asList(subGroups).subList(0, numGroups); + } }