Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Only create ThreadGroups if FailOnTimeout.lookForStuckThread is true. #1691

Merged
merged 4 commits into from
Jan 10, 2021
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -120,14 +120,7 @@ public FailOnTimeout build(Statement statement) {
public void evaluate() throws Throwable {
CallableStatement callable = new CallableStatement();
FutureTask<Throwable> task = new FutureTask<Throwable>(callable);
ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup");
if (!threadGroup.isDaemon()) {
try {
threadGroup.setDaemon(true);
} catch (SecurityException e) {
// Swallow the exception to keep the same behavior as in JUnit 4.12.
}
}
ThreadGroup threadGroup = threadGroupForNewThread();
Thread thread = new Thread(threadGroup, task, "Time-limited test");
thread.setDaemon(true);
thread.start();
Expand All @@ -138,6 +131,31 @@ public void evaluate() throws Throwable {
}
}

private ThreadGroup threadGroupForNewThread() {
if (!lookForStuckThread) {
// Use the default ThreadGroup (usually the one from the current
// thread).
return null;
}

// Create the thread in a new ThreadGroup, so if the time-limited thread
// becomes stuck, getStuckThread() can find the thread likely to be the
// culprit.
ThreadGroup threadGroup = new ThreadGroup("FailOnTimeoutGroup");
if (!threadGroup.isDaemon()) {
// Mark the new ThreadGroup as a daemon thread group, so it will be
// destroyed after the time-limited thread completes. By ensuring the
// ThreadGroup is destroyed, any data associated with the ThreadGroup
// (ex: via java.beans.ThreadGroupContext) is destroyed.
try {
threadGroup.setDaemon(true);
} catch (SecurityException e) {
// Swallow the exception to keep the same behavior as in JUnit 4.12.
}
}
return threadGroup;
}

/**
* Wait for the test task, returning the exception thrown by the test if the
* test failed, an exception indicating a timeout if the test timed out, or
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,32 +8,53 @@
import static java.util.concurrent.TimeUnit.MILLISECONDS;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.internal.runners.statements.FailOnTimeout.builder;
import static org.junit.Assume.assumeFalse;
import static org.junit.Assume.assumeTrue;

import java.util.Arrays;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.junit.Test;
import org.junit.function.ThrowingRunnable;
import org.junit.internal.runners.statements.Fail;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import org.junit.runners.model.Statement;
import org.junit.runners.model.TestTimedOutException;


/**
* @author Asaf Ary, Stefan Birkner
*/
@RunWith(Parameterized.class)
public class FailOnTimeoutTest {
private static final long TIMEOUT = 100;
private static final long DURATION_THAT_EXCEEDS_TIMEOUT = 60 * 60 * 1000; //1 hour

private final TestStatement statement = new TestStatement();

private final FailOnTimeout failOnTimeout = builder().withTimeout(TIMEOUT, MILLISECONDS).build(statement);
private final boolean lookingForStuckThread;
private final FailOnTimeout failOnTimeout;

@Parameterized.Parameters(name = "lookingForStuckThread = {0}")
public static Iterable<Boolean> getParameters() {
return Arrays.asList(Boolean.TRUE, Boolean.FALSE);
}

public FailOnTimeoutTest(Boolean lookingForStuckThread) {
this.lookingForStuckThread = lookingForStuckThread;
this.failOnTimeout = builder().withTimeout(TIMEOUT, MILLISECONDS).build(statement);
}

private FailOnTimeout.Builder builder() {
return FailOnTimeout.builder().withLookingForStuckThread(lookingForStuckThread);
}

@Test
public void throwsTestTimedOutException() {
Expand Down Expand Up @@ -212,14 +233,17 @@ private void notTheRealCauseOfTheTimeout() {
}

@Test
public void threadGroupNotLeaked() throws Throwable {
public void lookingForStuckThread_threadGroupNotLeaked() throws Throwable {
assumeTrue(lookingForStuckThread);
final AtomicReference<ThreadGroup> innerThreadGroup = new AtomicReference<ThreadGroup>();
final AtomicReference<Thread> innerThread = new AtomicReference<Thread>();
final ThreadGroup outerThreadGroup = currentThread().getThreadGroup();
ThrowingRunnable runnable = evaluateWithDelegate(new Statement() {
@Override
public void evaluate() {
innerThread.set(currentThread());
ThreadGroup group = currentThread().getThreadGroup();
assertNotSame("inner thread should use a different thread group", outerThreadGroup, group);
innerThreadGroup.set(group);
assertTrue("the 'FailOnTimeoutGroup' thread group should be a daemon thread group", group.isDaemon());
}
Expand All @@ -231,4 +255,19 @@ public void evaluate() {
innerThread.get().join();
assertTrue("the 'FailOnTimeoutGroup' thread group should be destroyed after running the test", innerThreadGroup.get().isDestroyed());
}

@Test
public void notLookingForStuckThread_usesSameThreadGroup() throws Throwable {
assumeFalse(lookingForStuckThread);
final ThreadGroup outerThreadGroup = currentThread().getThreadGroup();
ThrowingRunnable runnable = evaluateWithDelegate(new Statement() {
@Override
public void evaluate() {
ThreadGroup group = currentThread().getThreadGroup();
assertSame("inner thread should use the same thread group", outerThreadGroup, group);
}
});

runnable.run();
}
}