Skip to main content
Some tests need explicit time limits (to catch hangs or slow operations), while others need to run multiple times to verify consistency. HRTK provides @Timeout and @RepeatedTest for these scenarios.

@Timeout

Every test has a default timeout of 30 seconds. If your test completes within that window, you never need to think about timeouts. But if you need a tighter or looser limit, use @Timeout.
import com.frotty27.hrtk.api.annotation.Timeout;
import java.util.concurrent.TimeUnit;

@HytaleTest
@Timeout(5) // 5 seconds (default unit is seconds)
void testQuickOperation() {
    // Must complete within 5 seconds
    HytaleAssert.assertTrue(performQuickCheck());
}

@HytaleTest
@Timeout(value = 500, unit = TimeUnit.MILLISECONDS)
void testVeryQuickOperation() {
    // Must complete within 500ms
    HytaleAssert.assertNotNull(lookup("cached_value"));
}

@HytaleTest
@Timeout(value = 2, unit = TimeUnit.MINUTES)
void testSlowWorldGeneration() {
    // Allow up to 2 minutes for a heavy world operation
    generateAndValidateChunk();
}

What happens on timeout

When a test exceeds its timeout, the runner reports it as TIMED_OUT:
    [TIME] testSlowWorldGeneration (120003ms)
           Timed out after 120000ms
Timeouts on world-bound tests (@WorldTest, @EcsTest) are especially important. Because these tests run on the world thread, a stuck test would stall the entire world’s tick loop. The timeout ensures the runner gives up and moves on, preserving server stability.

Supported time units

The unit parameter accepts any java.util.concurrent.TimeUnit value:
UnitExample
TimeUnit.MILLISECONDS@Timeout(value = 200, unit = TimeUnit.MILLISECONDS)
TimeUnit.SECONDS@Timeout(5) (default unit)
TimeUnit.MINUTES@Timeout(value = 2, unit = TimeUnit.MINUTES)

@RepeatedTest

Run the same test method multiple times. Each repetition is reported as a separate result. This is useful for detecting flaky behavior, race conditions, or probabilistic logic.
import com.frotty27.hrtk.api.annotation.RepeatedTest;

@RepeatedTest(5)
void testRandomLootDrop() {
    // Runs 5 times -- each execution is independent
    float roll = Math.random();
    HytaleAssert.assertTrue("Roll should be in [0, 1)", roll >= 0.0 && roll < 1.0);
}

Output

Each repetition appears as its own result:
    [PASS] testRandomLootDrop (1ms)
    [PASS] testRandomLootDrop (0ms)
    [PASS] testRandomLootDrop (0ms)
    [PASS] testRandomLootDrop (1ms)
    [PASS] testRandomLootDrop (0ms)
@RepeatedTest implies @HytaleTest — you do not need both annotations. The value parameter specifies the number of repetitions.

Combining with @BeforeEach / @AfterEach

Lifecycle hooks run for every repetition, so each iteration gets a clean setup:
@HytaleSuite("Flaky Detection Tests")
public class FlakyTests {

    private int attempt;

    @BeforeEach
    void resetAttempt() {
        attempt = 0;
    }

    @RepeatedTest(10)
    void testAlwaysStartsAtZero() {
        HytaleAssert.assertEquals(0, attempt);
        attempt++; // This increment is reset before next repetition
    }
}

Combining with —fail-fast

When --fail-fast is enabled and a repeated test fails on any iteration, the remaining repetitions are skipped:
/hrtk run --fail-fast
    [PASS] testFlaky (1ms)
    [PASS] testFlaky (0ms)
    [FAIL] testFlaky (1ms)
           Expected true but was false
    // Remaining 7 repetitions skipped

Combining @Timeout and @RepeatedTest

You can use both annotations together. The timeout applies to each individual repetition.
@RepeatedTest(3)
@Timeout(value = 100, unit = TimeUnit.MILLISECONDS)
void testFastOperation() {
    // Each of the 3 repetitions must complete within 100ms
    HytaleAssert.assertNotNull(quickLookup());
}
Use @RepeatedTest with a tight @Timeout to stress-test performance-sensitive code paths. If any of the repetitions times out, you know your code has worst-case latency issues.

Next Steps