Skip to content

test(e2e): cover every supported Minecraft version with a runtime smoke suite - #852

Closed
darbyjack wants to merge 9 commits into
masterfrom
test/minecraft-version-smoke-coverage
Closed

darbyjack wants to merge 9 commits into
masterfrom
test/minecraft-version-smoke-coverage

Conversation

@darbyjack

@darbyjack darbyjack commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

What was missing

The E2E suite had exactly one environment, on 1.21.8, so nothing ran against the versions the plugin claims to support. The tooling could not have done it as it stood, and the two blockers are worth writing down because neither is obvious from the error you get.

Plugwright's local mode starts the server with the JVM of the project's Java toolchain, which is one setting for the whole build. A Paper 1.8.8 server does not run on Java 21.

Plugwright's provision task downloads the oldest build the Paper API lists for a version. For 1.8.8 and 1.12.2 that build is a paperclip jar whose patch data points at a Mojang URL that no longer serves the vanilla server jar, so the server exits before it loads a plugin:

[MC] Error downloading original jar
[MC] java.io.FileNotFoundException: https://s3.amazonaws.com/Minecraft.Download/versions/1.8.8/minecraft_server.1.8.8.jar
Test run failed: Error: Server exited with code 1 before becoming ready

With the download fixed and the server on Java 11, every connection the server accepted died on the socket:

[MC] java.lang.RuntimeException: Unable to access address of buffer
[MC] 	at io.netty.channel.epoll.Native.read(Native Method)

That one is Netty 4.0.23, which 1.8.8 ships. It decides whether it can use direct buffers by reading the address of a heap buffer, and on Java 9 and later that address no longer reads as zero, so it gives up on the unsafe path. Without it the native transport cannot work.

What this adds

A smoke suite, src/test/e2e/tests/smoke.spec.ts, that runs the same plugin, the same staged config and the same three dependencies on every supported version. Seven tests: the plugin and its hard dependencies enabled, Vault's economy and permission providers registered, the economy moving money, a command answering, a guild being created, and the two the recovery work needs. It is much smaller than the full suite on purpose, since the full suite is written against a modern server.

Six environments in build.gradle.kts, one per version:

Environment Minecraft JDK Runs
smoke-1.8.8 1.8.8 11 the smoke spec
smoke-1.12.2 1.12.2 11 the smoke spec
smoke-1.16.5 1.16.5 16 the smoke spec
smoke-1.21.8 1.21.8 21 the smoke spec
smoke-26.1.2 26.1.2 25 the smoke spec
smoke-26.3 26.3 25 a server-level check

Three things had to be worked around per environment, all in the build script:

  • A launcher script per version, so the server runs on the JDK that version needs. The same script drops the --nogui flag the runner always passes, which the CraftBukkit builds on 1.8.8 and 1.12.2 reject as an unknown option.
  • The newest Paper build per version, downloaded into the run directory before the provision task looks for a server jar.
  • use-native-transport=false in server.properties for 1.8.8, which puts it on the NIO transport every Windows server uses.

The legacy environments also run EssentialsX 2.20.1 rather than 2.22.0. EssentialsX 2.21 and newer need an Adventure API that Paper only started shipping with 1.18, and on 1.16.5 the plugin fails to enable with NoClassDefFoundError: net/kyori/adventure/text/minimessage/tag/resolver/TagResolver.

1.12.2 joins supportedMinecraftVersions on Java 11, so runServer1_12_2 exists as well.

Results

./gradlew plugwrightSmokeTest
Environment Result
1.8.8 10 passed, 0 failed
1.12.2 10 passed, 0 failed
1.16.5 10 passed, 0 failed
1.21.8 10 passed, 0 failed
26.1.2 10 passed, 0 failed
26.3 server-level check passed

Ten is three preflight tests plus seven smoke tests. The whole run took 4m41s.

./gradlew plugwrightTest

151 tests: 149 passed, 0 failed, 2 skipped, in 7m05s. That is the 141 existing tests, the 3 preflight tests and the 7 smoke tests. The two that are skipped are the recovery checks below, which have nothing staged to look at on the local environment.

./gradlew test testJava11 spotlessCheck
./gradlew check

Both succeeded. check includes the 1.8.8 Material probe, which still compiles two Material constants against the 1.8.8 API.

Compatibility

Nothing under src/main changed. The plugin still targets Java 11 bytecode, the Material probe still runs, and no API, dependency or configuration key was touched. The environments differ only in the server they run against, the JDK that server runs on, and the EssentialsX build on the three legacy ones.

The smoke spec runs on the 1.21.8 environment too, so a smoke failure on the current version is as loud as one on 1.8.8.

Limitations

26.3 has no bot-driven test. minecraft-data, the protocol table mineflayer reads, has no entry for 26.3, and the server refuses a 26.1 client as an outdated one. That environment gets a server-level check instead: it starts the server and asserts that Guilds and its hard dependencies enabled, that Guilds did not disable itself, and that it found the economy and the permission provider. It cannot prove that the command layer works, because that needs a player. 26.1.2 shares the 26.1 protocol, which is the newest one minecraft-data knows, so the bot connects to that one.

The 141 full-suite tests still only run on 1.21.8. The smoke suite proves the plugin loads and answers on the old versions, not that every feature works there.

The legacy environments start the server through a shell script, so they do not run on Windows.

A new smoke job in .github/workflows/e2e.yml runs the smoke suite on pull requests. The existing job is unchanged.

The recovery path

The plugin reads four folders under plugins/Guilds when it starts: data, arenas, challenges and cooldowns. The recovery work writes copies of files it could not read into a fifth one, plugins/Guilds/backups/corrupt/<timestamp>/<data-type>/. The unit tests behind that work prove the provider does not enumerate the backup directory. They cannot prove it on a real server, where the reading happens during startup and where a 1.8.8 server behaves differently from a 1.21.8 one.

Two guild files are staged into each smoke environment, both complete and valid guild records:

File What the plugin has to do with it
plugins/Guilds/data/8d3f1a67-….json load it, the way it loads any guild in the data folder
plugins/Guilds/backups/corrupt/20240101-000000/guilds/1f6b9c2e-….json leave it alone, because the backup directory is not a data folder

The smoke spec asserts both, so a plugin that read the wrong folder would fail on every version. The 26.3 environment has no bot to ask, so its server-level check asks the console instead: the guild in the backup directory has to be unknown to the plugin, and the guild in the data folder has to be known to it.

The local environment gets neither file. A guild in its data folder would change what the full suite sees, so the two checks are restricted to the smoke environments and are reported as skipped on the local one. A new smoke environment has to be added to SMOKE_ENVIRONMENTS in tests/smoke.spec.ts as well, or those two checks are skipped on it.

The storage changes in flight were checked against this suite on a throwaway copy of the branch that carries them, with this build script and these specs on top:

Environment Result with the storage changes
1.8.8 10 passed, 0 failed
1.12.2 10 passed, 0 failed
1.16.5 10 passed, 0 failed
1.21.8 10 passed, 0 failed
26.1.2 10 passed, 0 failed
26.3 server-level check passed

The shared helper CorruptDataBackup uses java.io, java.nio.file, java.security and java.time only. Every one of those APIs is in Java 8, and the project compiles it to Java 11 bytecode, so it runs on the 1.8.8 server's Java 11 JVM like the rest of the plugin.

The coverage inventory, docs/e2e-coverage.md, now carries the merged totals: 162 tests across 14
spec files. The section headings hold the merged numbers, and the section for the startup-loading
spec is a heading with a note, because its table belongs to the pull request that adds the spec
file.

A flaky test in the suite

homes.spec.ts fails on about half the open pull requests, and on master itself in run
37211243837. The bot's socket is already closed when the command is about to be sent, and the
server log says why:

[05:21:21 WARN]: pw_31f6 was kicked for floating too long!
[05:21:21 INFO]: pw_31f6 lost connection: Flying is not enabled on this server

The test teleports the bot to a spot in mid-air and then waits out a cooldown. Paper refuses a
player whose client reports that it is not on the ground for 80 ticks, which is four seconds,
unless the server allows flight: MinecraftServer.isFlightAllowed() is what makes Paper skip the
check. The suite drives a throwaway server, so that server now allows flight. Nothing in a test
changes, and the plugin is not involved.

This is not a keepalive problem. The runner has none for an idle bot, and a bot the client gives
up on ends with keepAliveError rather than with a closed socket. The socket is closed by the
server.

Two smaller things go with it. A kicked bot now reports the reason the server gave instead of
"the connection had already ended (socketClosed)", because the end event used to overwrite the
kick reason with the fact that the socket closed. And the 1.8.8 smoke environment keeps its own
server property, use-native-transport=false, which this change moves next to the new one.

The homes spec passes with the fix in place: three runs in a row, 11 of 11 each time, including
"a second teleport home cannot happen until the cooldown expires".

Master's own failure is a separate thing. There the server was 64 ticks behind, the "blocked"
command reached it 3.2 seconds late, and the three-second cooldown of the time had expired
before the command was processed, so the server correctly teleported the player. The staged
config now uses fifteen seconds for the cooldowns, with a comment saying why.

t3 added 5 commits October 22, 2026 06:49
…e suite

The full Plugwright suite only runs on 1.21.8, so nothing proved that the
plugin loads and answers on the versions it claims to support. The oldest
Paper builds need a few things the tooling does not give them per
environment, and this wires those up:

- a launcher script per version, so the server runs on the JDK that version
  needs and the legacy CraftBukkit builds do not see the --nogui flag they
  reject
- the newest Paper build per version, because the oldest one the API lists
  for 1.8.8 and 1.12.2 is a paperclip jar that fetches the vanilla server
  jar from a URL Mojang retired
- use-native-transport=false for 1.8.8, whose Netty 4.0 cannot use direct
  buffers on a Java 11 JVM
- EssentialsX 2.20.1 for the legacy versions, because 2.21 and newer need an
  Adventure API that Paper only ships from 1.18 on

1.12.2 joins the supported versions list on Java 11.
t3 added 4 commits October 22, 2026 12:41
The plugin reads four folders under plugins/Guilds when it starts, and the
recovery work writes copies of unreadable files into a fifth one. Nothing
proved that a file in the backup directory stays out of the plugin's records
on a real server, where the reading happens at startup and the versions
differ.

Two guild files are staged into each smoke environment: one in the data
folder, which the plugin has to load, and one in the backup directory, which
it has to leave alone. Both are complete, valid guild records, so a plugin
that read the wrong folder would load them. The smoke spec asserts both.

The 26.3 environment has no bot to ask, so its server-level check asks the
console instead.
The coverage inventory now counts 162 tests across 14 spec files, which is
what the branches add up to. The section headings carry the merged numbers,
and the section for the startup-loading spec is a placeholder: its table
belongs to the pull request that adds the spec file.
A bot the server kicked is reported as "the connection had already ended
(socketClosed)", which says nothing about what the server objected to. The
kick carries a reason — "Flying is not enabled on this server", for a bot a
teleport left in mid-air — and the end event overwrites it with the fact that
the socket closed. Keeping the two apart makes the next occurrence readable.
Paper refuses a player whose client reports that it is not on the ground for
80 ticks, which is four seconds, unless the server allows flight. The
homes spec teleports its bot into the air and then waits out a cooldown, and
the server kicked the bot with "Flying is not enabled on this server" while
it waited: the bot's socket was already closed when the command was about to
be sent.

The suite drives a throwaway server, so that server allows flight. Nothing in
a test changes, and the plugin is not involved.
@darbyjack darbyjack closed this Oct 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant