Skip to content

[v3] Run ExternalMode (stand) suite in CI in addition to LocalMode - #63

Merged
Drownek merged 6 commits into
Drownek:v3-devfrom
monikon22:feat/61-stand-ci-testing
Aug 28, 2026
Merged

[v3] Run ExternalMode (stand) suite in CI in addition to LocalMode#63
Drownek merged 6 commits into
Drownek:v3-devfrom
monikon22:feat/61-stand-ci-testing

Conversation

@monikon22

@monikon22 monikon22 commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Closes #61.

test-example-plugin only ever ran LocalMode — stand (ExternalMode) is includeInMatrix: false because it needs an already-running server, so plugwrightTest skips it and the RCON console channel, account-pool leasing and justCreated registration flow had zero CI coverage.

What changed

  • example_plugin/src/test/e2e/stand-run/start.sh (new, tracked): the launcher example_plugin/README.md already told developers to hand-write under generated/local/run/. That directory is gitignored, so CI had nothing to copy in — this commits the same script README documents, outside generated/.

  • .gitattributes (new): pins *.sh to LF so a Windows checkout doesn't corrupt the shebang.

  • example_plugin/README.md: one-line pointer to the tracked copy.

  • .github/workflows/ci.yml: stand now runs as its own job, test-example-plugin-stand, in parallel with test-example-plugin (unchanged, keeps its name so an existing required-status-check on it still matches). Running on a separate runner means it can't reuse the local job's generated/local/run/, so it:

    1. Provisions its own Paper server via plugwright-action's gradle-args: plugwrightProvisionLocal plugwrightCompileTests (provisioning alone isn't enough — plugwrightPingStand/plugwrightTestStand don't depend on plugwrightCompileTests themselves, they just expect node_modules already has what console { rcon {} } and the npm(...) plugin refs need; the old sequential job got this for free as a side effect of plugwrightTest's own dependency chain).
    2. Installs a real JDK 21 (actions/setup-java@v4) before touching start.sh — Paper 1.21.11 needs it, but the Gradle daemon above runs on 17, and start.sh just execs whatever java is on PATH.
    3. Copies the tracked start.sh into the run dir, launches it backgrounded, saves its PID.
    4. Retry-loop ./gradlew plugwrightPingStand (up to 30×2s) until the server answers — the built-in readiness probe (connects, probes the RCON console channel, leases + authenticates an account), not a raw port check.
    5. ./gradlew plugwrightTestStand — blocking, same as LocalMode.
    6. if: always(): kill the backgrounded server by PID.
    7. if: failure(): upload the server log + build/reports/plugwright/stand.* as an artifact.

    PLUGWRIGHT_RCON_PASSWORD / PLUGWRIGHT_BOT_PASSWORD are set at job level as literal plugwright — test-only credentials for a server the job itself starts and tears down within the same run, matching local's existing default.

No change to plugwright-action itself — its existing gradle-args input covered everything needed.

Testing

Ran the sequence locally first: plugwrightProvisionLocal → copy tracked start.sh in → background start → ping retry-loop (ready on attempt 3) → plugwrightTestStand: 44/44 passed, 6 expected skips, 0 failed → kill by PID confirmed the process exits.

CI itself went through a few iterations once the two jobs actually ran on separate runners (each caught something the single-job version had been masking as a side effect of task ordering):

  • JDK 17-only PATH → UnsupportedClassVersionError starting Paper 1.21.11 → fixed by installing JDK 21 before start.sh.
  • plugwrightProvisionLocal alone doesn't install @plugwright/console-rcon → every ping failed with "no console channel could be reached" → fixed by also running plugwrightCompileTests.

Final green run: test-example-plugin pass in 4m58s, test-example-plugin-stand pass in 4m52s, running concurrently — down from ~7m13s sequential.

…ted/

generated/ is gitignored, so CI has nothing to launch the stand Paper
server with. Commit the launcher example_plugin/README.md already
told developers to write by hand, under stand-run/, and pin *.sh to
LF via .gitattributes so a Windows checkout doesn't break the
shebang.
test-example-plugin only ever exercised LocalMode - the stand
(ExternalMode) env is excluded from the matrix since it needs an
already-running server, so the RCON console channel, account-pool
leasing and the justCreated registration flow had zero CI coverage.

The LocalMode run plugwright-action already does leaves Paper, cache
and libraries under generated/local/run/ - stand points at the same
localhost:25565. Copy in the tracked launcher, start it in the
background, retry plugwrightPingStand until it answers, then run
plugwrightTestStand. Kill the server and upload its log on failure
regardless of outcome.

Closes Drownek#61.
@monikon22
monikon22 marked this pull request as draft August 28, 2026 12:47
start.sh just runs whatever "java" is on PATH, which the earlier
plugwright-action step pinned to 17 for the Gradle daemon. Paper
1.21.11 needs 21 (example_plugin/build.gradle.kts pins the toolchain
there) - LocalMode never hit this because Gradle resolves and
downloads that toolchain JDK itself for its own server launch, but a
plain shell script has no such resolution.

CI run 33172008840 confirmed the crash: UnsupportedClassVersionError,
class file version 65.0 vs runtime's 61.0.
test-example-plugin and test-example-plugin-stand now run on separate
runners concurrently instead of stand chaining off the end of a single
sequential job. Stand can no longer reuse the local job's
generated/local/run/ (different runner, different filesystem), so it
provisions its own Paper server via plugwright-action's gradle-args
input (plugwrightProvisionLocal only - not the full plugwrightTest,
which would also run and duplicate the local suite). Everything after
that (JDK 21 setup, start.sh, ping retry-loop, plugwrightTestStand,
teardown, failure log upload) is unchanged, just moved into the new
job. Trades a duplicate Paper/plugin download for roughly half the
wall-clock time versus running sequentially in one job.

job names: test-example-plugin keeps its existing name (a maintainer
required-status-check on it, if any, keeps matching); the new job is
test-example-plugin-stand.
plugwrightPingStand/plugwrightTestStand don't depend on
plugwrightCompileTests (ExternalMode registers no prepareTask -
registerTasks in ExternalMode.kt assumes the stand is already up and
node_modules already has what it needs). The old sequential job got
this for free as a side effect of plugwrightTest's dependsOn chain
running first; the new parallel stand job only ran
plugwrightProvisionLocal, so @plugwright/console-rcon was never
installed and every ping failed with 'no console channel could be
reached'.

Verified locally: a clean node_modules, then ./gradlew
plugwrightCompileTests, installs console-rcon/auth-authme/runner as
expected.
The console-rcon fix's replace missed the pre-existing gradle-args
line, leaving two under the same 'with:' block. Plain YAML parsers
silently keep the last one (which is why local yaml.safe_load passed),
but GitHub Actions' own parser rejects it outright - the run failed
in 0s with zero jobs registered, no logs at all.
@monikon22
monikon22 marked this pull request as ready for review August 28, 2026 13:22
@monikon22 monikon22 changed the title [v3] Run ExternalMode (stand) suite in CI after LocalMode [v3] Run ExternalMode (stand) suite in CI in addition to LocalMode Aug 28, 2026
@Drownek
Drownek merged commit 1e58e04 into Drownek:v3-dev Aug 28, 2026
2 checks passed
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.

2 participants