How can I unit test a method containing scheduled executon?

Viewed 141

I have a simple restarting Runnable:

static void launchThreads(){
    ScheduledExecutorService exec = Executors.newSingleThreadScheduledExecutor();
    try {
        exec.scheduleWithFixedDelay(new Runnable() {
            @Override
            public void run() {
                try {
                    System.out.println("line"); <--breakpoint
                }
                catch (Exception e) {
                    e.printStackTrace(); <--breakpoint
                }
            }
        }, 1, 1000, TimeUnit.MILLISECONDS);
    }catch (Exception e) {
        e.printStackTrace(); <--breakpoint
    }
}

If I launch that method from the main() method of the class, it works as expected - writes a line that looks like "line", once a second, forever.

line
line
line
line
...

But if I launch that from a TestNG test method:

@Test
public class PostpackagesIntegratorTest {
    @Test
    public void testLaunchThreads10SmallestWithoutFees() {
        PostpackagesIntegrator.launchThreads();
    }
}

,it outputs only one "line" and the test is passed. "Successfully".

If I make a JUnit4 test to launch the same method,

public class PostpackagesIntegratorJUnit4Test {
    @Test
    public void launchThreadsTest() {
        PostpackagesIntegrator.launchThreads();
    }
}

, the test is also passed, again with only one "line" in output.

If I am not running, but debugging the tests, my IntelliJ stops at printing the "line", but does not notice any catch content.

I do not understand, what prevents the ScheduledExecutorService from repetitions. According to docs, such non-repeating should happen at an exception, but no exception happens.

Is it possible to make ScheduledExecutorService in TestNG tests or must I use other classes? Due to the whole project, I am limited by Java 6 version and TestNG.

Edit: @Eugene advised to declare exec as private static final ScheduledExecutorService exec, for blocking erroneous GC, but it did not help and even didn't change anything - the problem is elsewhere.

1 Answers

I would start by dumping a lot of thread details.

Thread.currentThread().dumpStack() (or just (new Throwable()).printStackTrace()) would show any peculiar classes frames above your runnable. These could be quite different if junit/ng are fiddling with thread factories or such.

Then you can also inspect the thread.currentThread() for isDaemon() and the threadgroup's isDeamon(). Your new executorsvc may be part (and making worker threads in) a threadgroup that is interrupted. You might be able to reveal that by writing your own thread factory and issuing threads whose interrupt() is proxied for the sake of trapping it (before forwarding it). A main() is normally a non-daemon thread, so it would spawn non daemon threads too for the execsvc. I wouldn't be surprised is junit/ng are wrapping the test in a pseudo thread sandbox to 'try' to detect and perhaps stop leaked/forgotten threads from a test.

If your are in a debugger, you should be able to browse the top frame local variables and the thread instance already without much code, to reveal all of the above (except the unanticipated interrupt call, if any).

Related