Skip to content

JUnit 4 to 5 migration of @Test(timeout=N) drops separate-thread semantics, allowing hanging tests to block the runner indefinitely #1073

Description

@mattdepaula

Description

JUnit 4's @Test(timeout = N) runs the test body in a daemon thread via FailOnTimeout — it wraps the test in a FutureTask and calls FutureTask.get(timeout, timeUnit). When the timeout elapses the runner thread unblocks immediately and reports failure. A hanging test never blocks the suite.

UpdateTestAnnotation migrates this to:

@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)

@Timeout without an explicit threadMode inherits ThreadMode.INFERRED, which resolves to SAME_THREAD by default. In SAME_THREAD mode the test runs synchronously in the runner thread. A ScheduledExecutorService sends an interrupt after N ms, but if the test body does not respond to interruption (infinite loop, lock contention without a blocking call) delegate.proceed() never returns and the runner thread blocks indefinitely.

End-to-end verification

Using the real JUnit Platform Launcher with two identical infinite-loop test methods — the only difference is threadMode:

-- CURRENT recipe output (@Timeout with SAME_THREAD — the bug) --
  RESULT : HUNG — runner still blocked after 3000ms
  A non-interruptible loop ignores SAME_THREAD's interrupt signal.
  In CI this causes the build to hang indefinitely.

-- CORRECT recipe output (@Timeout with SEPARATE_THREAD — the fix) --
  RESULT : completed in 319ms
  Tests run=1  failed=1
  Failure : hang() timed out after 300 milliseconds

Why SEPARATE_THREAD is safe for migrated tests

JUnit 4's FailOnTimeout was already running these tests in a separate daemon thread. Any test that passed under @Test(timeout=N) was already thread-isolated. Emitting SEPARATE_THREAD restores that exact model and cannot introduce threading issues that did not already exist in JUnit 4.

Expected output

@Timeout(value = 500, unit = TimeUnit.MILLISECONDS, threadMode = Timeout.ThreadMode.SEPARATE_THREAD)

Affected recipe

UpdateTestAnnotation

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    Status
    Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions