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:
| Unit | Example |
|---|
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:
[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