HRTK is a runtime testing framework purpose-built for Hytale server plugins. Unlike traditional test frameworks that run before deployment, HRTK executes tests inside a live server where the ECS, worlds, and plugin systems are fully operational. This page explains the key architectural decisions that make that possible.Documentation Index
Fetch the complete documentation index at: https://docs.hrtk.frotty27.com/llms.txt
Use this file to discover all available pages before exploring further.
Two-Artifact Design
HRTK ships as two separate JARs with distinct roles:hrtk-api
Compile-only dependency. Contains annotations (
@HytaleTest, @HytaleSuite, etc.), assertion classes (HytaleAssert, EcsAssert, WorldAssert), context interfaces (TestContext, EcsTestContext), and mock types. Your mod compiles against this JAR, but it is never loaded at runtime as a standalone plugin.hrtk-server
Server plugin. Loaded by the Hytale server as a regular
JavaPlugin. Contains the discovery engine, test runner, context implementations, isolation managers, the /hrtk command tree, and the result exporter. This is the only JAR that needs to be in your server’s plugins/ folder.Discovery via JAR Scanning
When the server starts - or when you run/hrtk scan - the discovery engine looks through every loaded plugin’s JAR file to find classes with HRTK annotations like @HytaleTest.
Inner classes (entries containing
$) are skipped during scanning. If you need helper classes for your tests, use top-level or static nested classes annotated with @HytaleSuite.TestClassInfo captures lifecycle hooks (@BeforeAll, @AfterAll, @BeforeEach, @AfterEach), isolation strategy, class-level tags, and the ordered list of test methods.
Command-Triggered Execution
Tests never run automatically. They are triggered through the/hrtk command tree:
| Command | Purpose |
|---|---|
/hrtk run | Run all tests (or filter by plugin/suite/tag) |
/hrtk bench | Run benchmarks only |
/hrtk scan | Re-discover tests from loaded plugins |
/hrtk list | List all discovered tests |
/hrtk results | Show results from the last run |
/hrtk export | Export results to JSON |
/hrtk watch | Auto-rerun tests when a plugin reloads |
Threading Model
HRTK uses two execution paths depending on the test type:Standard tests (no world required)
Standard tests (no world required)
Tests that don’t need a world (pure logic, codec checks, basic assertions) run on a background thread. They don’t block the server’s main threads.
World-bound tests (@EcsTest, @WorldTest, @FlowTest)
World-bound tests (@EcsTest, @WorldTest, @FlowTest)
Tests that interact with entities or world state run on the world thread - the same thread that processes the game loop. This ensures all entity operations happen safely. The test runner waits for completion with the configured timeout.
Crash Protection
A core design goal of HRTK is that a test must never crash the server. The test executor catches every possible error, not just regular exceptions. This catches:- Assertion failures - your test’s assertions failed, reported as
FAILED - Runtime errors (NullPointerException, etc.) - unexpected bugs, reported as
ERRORED - JVM-level errors (StackOverflowError, OutOfMemoryError) - severe issues, reported as
ERRORED
What Happens at a Glance
Discovery
On server start,
TestDiscoveryEngine scans all plugin JARs for HRTK annotations and builds the PluginTestRegistry.Execution
The
TestRunner iterates matching suites. For each suite, SuiteExecutor instantiates the test class, applies isolation, runs lifecycle hooks, and delegates to TestExecutor for each method.