1

In a badly written legacy codebase, the unit tests invoke code that fires up threads which are never stopped. In most cases it does not have an effect, but in some cases it slows down the build considerably, in others it causes completely unclear side-effects between tests in the same project during the build: e.g. test A starts thread, then B runs in the same JVM and breaks in some undefined manner (the fix is to have A stop that thread).

Is there some tool that can be used in conjunction with junit, so that at the end of every test (or set of tests in one class) it fails the test if there are any threads left running?

This would allow us to quickly identify and fix all existing cases, but also prevent new tests from being written this way.

Alexandros
  • 2,097
  • 20
  • 27
  • If you are asking for an existing tool, that is borderline off-topic. But it is trivial to implement a check that can be run as a post test suite check. – Stephen C Sep 28 '17 at 11:11
  • 1
    Don't think there is a tool but you can easily do it yourself with `java.lang.Thread.activeCount()` and check it before and after each test invocation with `AspectJ`. Also this might be off-topic but definitely not a duplicate @StephenC You're abusing your powers. – Oleg Sep 28 '17 at 11:11
  • 1
    Duplicate of https://stackoverflow.com/questions/82949/before-and-after-suite-execution-hook-in-junit-4-x and https://stackoverflow.com/questions/1922290/how-to-get-the-number-of-threads-in-a-java-process – Stephen C Sep 28 '17 at 11:16
  • Someone else can do it then :-) – Stephen C Sep 28 '17 at 11:16
  • @StephenC Please correct me if I'm wrong(preferably with a link) but a question that is answered by a combination of 2 different questions is not a duplicate. – Oleg Sep 28 '17 at 11:20
  • The references are helpful and I can see how I can roll my own. Is there a way to configure JUnit to run hooks _without_ using a base class? – Alexandros Sep 28 '17 at 11:22
  • I think you are wrong. Especially is the work involved to combine them is simple ... as Alexandros acknowledges. – Stephen C Sep 28 '17 at 11:34
  • @Alexandros - AFAIK, No. – Stephen C Sep 28 '17 at 11:34
  • Even if it is a duplicate, you've both been helpful so thank you. Having to integrate this via AspecJ is a third step and if someone does so I'd accept it as the answer, but I can't be bothered to introduce yet another dependency. The project I need this for is bad enough as it is... – Alexandros Sep 28 '17 at 11:48
  • @StephenC Looks like I was wrong https://meta.stackoverflow.com/questions/345295/what-can-we-do-if-a-question-is-a-duplicate-of-two-questions-combined I still think the question you choose initially wasn't enough. – Oleg Sep 28 '17 at 12:00
  • There were actually two ... except that >>someone<< interrupted me while I was adding the second one! – Stephen C Sep 28 '17 at 12:04
  • 1
    @Alexandros - I wouldn't use AspectJ. I would do the check in a Junit4 `@After` or `@AfterClass` hook or something like that. – Stephen C Sep 28 '17 at 12:07
  • Combining with this gives me a way to realistically do it with Maven/Surefire: https://stackoverflow.com/questions/15203756/maven-displaying-unit-test-currently-running – Alexandros Sep 28 '17 at 12:15

2 Answers2

0
public class FailOnLingeringThreadsTestBase
{
    private static Set<Thread> threadsBefore;

    @BeforeClass
    public static void takePhoto()
    {
        threadsBefore = Collections.unmodifiableSet(Thread.getAllStackTraces().keySet());
    }

    @AfterClass
    public static void spotTheDiffs()
    {
        Set<Thread> threadsAfter = Thread.getAllStackTraces().keySet();
        if (threadsAfter.size() != threadsBefore.size())
        {
            threadsAfter.removeAll(threadsBefore);
            throw new IllegalStateException("Lingering threads in test: " + threadsAfter);
        }
    }
}
Alexandros
  • 2,097
  • 20
  • 27
0

Apparently Maven/Surefire allows you to hook a listener using configuration! This is a realistic way to vertically integrate the check by implementing it as part of org.junit.runner.notification.RunListener.

Here is what I am using now:

public class FailOnLingeringThreadsRunListener extends org.junit.runner.notification.RunListener
{
    private Set<Thread> threadsBefore;

    @Override
    public synchronized void testRunStarted(Description description) throws Exception
    {
        threadsBefore = takePhoto();
        super.testRunStarted(description);
    }

    @Override
    public synchronized void testRunFinished(Result result) throws Exception
    {
        super.testRunFinished(result);

        Set<Thread> threadsAfter = spotTheDiffs(threadsBefore);

        // only complain on success, as failures may have caused cleanup code not to run...
        if (result.wasSuccessful())
        {
            if (!threadsAfter.isEmpty())
                throw new IllegalStateException("Lingering threads in test: " + threadsAfter);
        }
    }

    public static Set<Thread> takePhoto()
    {

        return Collections.unmodifiableSet(Thread.getAllStackTraces().keySet());
    }

    @AfterClass
    public static Set<Thread> spotTheDiffs(Set<Thread> threadsBefore)
    {
        Set<Thread> threadsAfter = Thread.getAllStackTraces().keySet();
        if (threadsAfter.size() != threadsBefore.size())
        {
            threadsAfter.removeAll(threadsBefore);
            return Collections.unmodifiableSet(threadsAfter);
        }

        return Collections.emptySet();
    }
}

Here is how I enable it in the build:

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <configuration>
                    <properties>
                        <property>
                            <name>listener</name>
                            <value>testutil.FailOnLingeringThreadsRunListener</value>
                        </property>
                    </properties>
                </configuration>
            </plugin>
...
Alexandros
  • 2,097
  • 20
  • 27