Anvil
Testing Anvil

Live tests

Verify native workers and real server/proxy behavior across the supported compatibility definitions.

Real platform tests live in anvil-testkit/tests/server/src/test. They start pinned distributions and run players or agent operations against them. They form three suites, one package each, with a task of its own, and run only with -Panvil.testMode=full:

SuitePackageTaskWhat it checks
ReleasereleasereleaseTest-<minecraft>, releaseTest for allEvery built-in player capability, the server's agent and a restart, against the direct Paper server of one release
PlatformplatformplatformTestLogin, commands, routes and restarts on Spigot, behind Velocity and BungeeCord, and on NeoForge
BehaviourbehaviorbehaviorTestLifecycles, sessions, kills, partial starts, routing, tooling and external extensions
./gradlew :anvil-testkit:tests:server:releaseTest-1.21.11 -Panvil.testMode=full
./gradlew :anvil-testkit:tests:server:platformTest -Panvil.testMode=full -PanvilPlatforms=velocity
./gradlew :anvil-testkit:tests:server:behaviorTest -Panvil.testMode=full
./gradlew :anvil-testkit:tests:server:test -Panvil.testMode=full

test runs every suite: each release one after another, then the platform and behaviour suites. The first run may provision distribution JARs, protocol runtimes, and the Java installations required by the selected versions. These checks accept the EULA through their test configuration. Do not add real online-account authentication to this suite.

Test a release

Each class of the release suite covers one capability, such as MessagesReleaseTest or InventoryReleaseTest, and is annotated with @Release. Its tests run against the release that releaseTest-<minecraft> names, or the newest release when a class runs on its own in the IDE. The release's server is kept for the task's JVM, so the whole suite starts it once, while each test gets a scenario and players of its own: FixtureServer.join() connects a player under a name no earlier test used. A test may leave the server with state the next tests do not depend on, such as a player it made an operator; a failed test stops the server, and the next test starts a fresh one.

A capability a release predates, such as dialogs before 1.21.6, is checked as absent there, and its other tests are skipped through JUnit assumptions on TestedRelease.isAtLeast(...):

@Test
void hasNoCookiesBeforeTheyExisted(ScenarioContext anvil) {
	assumeFalse(TestedRelease.isAtLeast(COOKIES), "Cookies exist since " + COOKIES);

	assertFalse(new FixtureServer(anvil).join().hasCapability(Cookies.class));
}

Select one class with --tests:

./gradlew :anvil-testkit:tests:server:releaseTest-1.16.5 -Panvil.testMode=full --tests '*InventoryReleaseTest'

Test platforms and behaviour

-PanvilPlatforms selects the platform suite's scenarios by the platform of their entrypoint, as a comma-separated list of platform IDs: spigot, velocity, bungeecord or neoforge. Without it the suite runs every platform. Behaviour tests start the scenarios they need through typed lookups, such as CompatibilityScenarioFactory.proxyScenario(Platforms.VELOCITY, Platforms.PAPER, version), never by a scenario's name.

Test classContract
PlatformCompatibilitySystemTestLogin, commands, agent extensions and backend switching on every platform scenario
PlatformRestartSystemTestRestarting the entrypoint and a proxy's backend of every platform scenario
NeoForgeServerSystemTestLogin, console commands and kicks on every NeoForge release
PlayerIdentityReconnectSystemTestKicks, reconnects, and observed identity replacement on Paper 1.21.11 and 26.1.2
GrantedProfileObservationSystemTestPlayers whose usernames differ only in letter case, each observed by the profile the server granted it
ExternalExtensionSystemTestExternally packaged capabilities and platform-agent operations
PartialScenarioLifecycleSystemTestIndividual starts in a wired two-proxy, three-server environment; setup once, retained endpoints/workspaces, agent reconnection, and cleanup

Exercise exact protocol workers

For packet or worker changes, run the worker contracts before live coverage. They start a real worker for every release in mcprotocol-releases.toml on the release's locked runtime closure, the [[release.artifact]] modules Gradle resolves exactly as listed, and check the protocol number, the selected client segment and that every built-in capability is installed, none reported unavailable. ClientSegmentLinkageTest reads the linkage manifests of the built client segments and checks them against every release they serve, as the worker does:

./gradlew :anvil-player:player-library:library-mcprotocol:mcprotocol-common:test --tests '*McProtocolWorkerContractTest'

Retain coverage for every release. Adding a Minecraft version describes the release row, its pinned closure, segments, worker contracts, platform data, and the live matrix that a new version needs.

Kicking, reconnect, and identity changes also require PlayerIdentityReconnectSystemTest for both Paper versions it runs. Platform provider or forwarding changes require every affected server/proxy combination.

Keep verified data equal to the matrix

anvil-testkit/tests/server/live-matrix.toml lists the live matrix: the Paper build of every release key, the releases that also run on Spigot and behind Velocity and BungeeCord with their Spigot checksums, the proxies' builds, and the NeoForge releases. CompatibilityScenarioFactory builds the scenarios from it, and the build registers one releaseTest-<minecraft> task per release from it. The verified data of MCProtocolLib's releases and of each platform lists exactly the combinations the matrix runs. CompatibilityScenarioFactoryTest plans the matrix without starting a server and fails when they differ; every build runs it through the matrixTest task, without -Panvil.testMode=full:

./gradlew :anvil-testkit:tests:server:matrixTest

Run the suites in CI

The live workflow reads the releases from live-matrix.toml and runs one job per release, so a new release gets its own job without a workflow change. Three jobs run the platform suite, for Spigot and NeoForge, for Velocity and for BungeeCord, and one job runs the behaviour suite. All of them run at once.

Inspect framework environments in the IDE

Open the Anvil repository with the IntelliJ plugin and open its Anvil tool window. It imports the project model when needed and loads the scenario definitions automatically. The anvil-testkit/tests/server module applies the standard Anvil plugin to its src/anvil compatibility definitions. The plugin itself is resolved at the build's anvilVersion, so publish it first with ./gradlew publishToMavenLocal; the Anvil runtime it adds is substituted with the current source projects.

Preparation compiles those definitions and resolves the declared fixture variants from the independent anvil-testkit/fixtures build. Its root-composite invocation substitutes current public Anvil source projects. The declaration forwards exact anvil.testkit.fixture.* paths as tracked artifact inputs; JUnit task properties are not copied implicitly. The definitions are the direct Paper 1.21.11 and 26.1.2 environments; the rest of the live matrix is built by CompatibilityScenarioFactory from live-matrix.toml for JUnit only and is not listed in the IDE. Loading the definitions starts no Minecraft processes. Selecting Run starts the chosen environment with the module's declared properties and artifacts. Selecting a component exposes its individual Start action; the prepared environment retains the full wiring while its other JVMs remain stopped.

These scenario definitions do not create players automatically. Individual JUnit methods create their players and method-scoped fixtures, and still use the live-test commands above. Starting an IDE environment does not run those methods or their assertions.

Investigate a failure

Read the test failure and captured process output first. Startup, restart, or cleanup failures can retain diagnostic workspaces under the server module's build/anvil directory; inspect each process's anvil-console.log. Check the distribution pin, Java requirement, readiness, and forwarding configuration before increasing timeouts.

The JUnit integration supplies failed test outcomes to finalization. Tests that own a prepared context directly, including PartialScenarioLifecycleSystemTest, pass their success flag to finish(successful) so failed caller work reaches retention policy. Collect the diagnostics relevant to your assertion in the test output. Consumer journeys run separately after local publication.

On this page