-
-
Notifications
You must be signed in to change notification settings - Fork 0
Testing and Javadoc
How to reproduce a test run, what the -Werror doclint policy actually enforces and where it
applies, and why the test heap is pinned. All of it is configured in the root
build.gradle.kts and
reaches every subproject except falco-bom, which has no sources to test or document (see Build
Setup). What the tests assert is not on this page; the per-module test counts and what
they cover are in Project Status.
./gradlew test # compile and run the tests
./gradlew check # the above, plus the per-module verification wired in below
./gradlew build # the above, plus assembling the artefacts
check is the interesting one, because what it pulls in differs per module:
| Module | What check runs beyond test
|
|---|---|
falco-anvil, falco-light, falco-instance
|
jacocoTestReport, as a finalizer of test
|
falco-demo |
jacocoTestReport, and javadoc — wired in explicitly, see below |
falco-benchmarks |
compileJmhJava — the benchmarks are compiled, never executed (see Benchmarks and Demo) |
falco-bom |
nothing; a java-platform project has no test task |
The run is reproducible in the sense that matters for a build: the JDK is not whichever one launched
Gradle. The root build pins a Java 25 toolchain and options.release = 25 (see Build
Setup), and .github/workflows/build-pr.yml requests Temurin 25 from the shared
gradle-build-pr.yml@v2.4.0 workflow with run-tests: true, so a pull request runs the same
compiler and the same test JVM version a local ./gradlew check does.
Every test JVM is started with one system property:
jvmArgs("-Dminestom.inside-test=true")It is Minestom's own flag, not one this project invents. At 2026.06.20-26.1.2 it is read into
ServerFlag.INSIDE_TEST (server/ServerFlag.java:79) and has two effects the tests depend on: a
dynamic registry never freezes, because DynamicRegistryImpl.canFreeze() returns false while it is
set (server/registry/DynamicRegistryImpl.java:328), and ConnectionManager spawns a player
synchronously and drops its virtual-thread assertions (server/network/ConnectionManager.java:174,
:192, :227, :354). Without it a test that registers a block or spawns a player after
MinecraftServer.init() behaves differently from the same code in a test harness that sets it.
tasks.withType<Javadoc>().configureEach {
(options as StandardJavadocDocletOptions).addBooleanOption("Werror", true)
}This is the only Javadoc option the build sets. Everything else is the standard doclet's default, and the defaults are what define the policy:
-
doclint is on with all groups (
accessibility,html,missing,reference,syntax) — the default of the standard doclet, not something this build enables. -
the visibility level is
-protected, also the default. So the rule is public and protected API, not literally every member: a private field with no comment is not a doclint warning and does not fail anything. -
-Werrorturns every warning into an error, so a missing@param, a broken@link, a malformed<p>or an undocumented public method fails the task and therefore the build. -
only
mainis covered. Gradle'sjavadoctask documents the main source set; there is no Javadoc task oversrc/test/javaorsrc/jmh/java, so the policy says nothing about test or benchmark sources.
The convention is only as good as its enforcement: without -Werror, a missing comment produces a
doclint warning that shows up once in a CI log and is then never read again. Turning warnings into
build failures is what makes the rule hold in practice rather than in intent only.
-Werror is configured for every Javadoc task in every module that has one, but a task that never
runs enforces nothing. Whether an ordinary ./gradlew build reaches it differs per module:
| Module | Javadoc runs during build? |
Why |
|---|---|---|
falco-anvil, falco-light, falco-instance
|
yes |
withJavadocJar() wires javadoc into assemble (see Publishing) |
falco-demo |
yes |
check depends on javadoc, wired explicitly in its own build file |
falco-benchmarks |
no | neither mechanism applies; only compileJmhJava is added to check
|
falco-bom |
n/a | no Java plugin, so no Javadoc task exists |
falco-demo is never published, so it has no withJavadocJar(), and left alone the "Javadoc on
every public and protected element" convention would have held for the three library modules and
quietly not for it. falco-demo/build.gradle.kts restores it:
tasks.named("check") {
dependsOn(tasks.named("javadoc"))
}falco-benchmarks has no equivalent line, so its Javadoc is not checked by a normal build. That is a
gap, not a decision documented anywhere in the build.
tasks.withType<Test>().configureEach {
useJUnitPlatform()
maxHeapSize = "1g"
jvmArgs("-Dminestom.inside-test=true")
testLogging {
events("passed", "skipped", "failed")
}
finalizedBy(tasks.named("jacocoTestReport"))
}The chunk loader tests allocate payloads of roughly one mebibyte each to cover the external chunk
file path — RegionFileConcurrencyTest sizes them as MAX_SECTORS_PER_CHUNK * SECTOR_SIZE + 1,
one byte past what a region file's location entry can address. With org.gradle.parallel=true in
gradle.properties, several such test JVMs run alongside each other and alongside the Gradle daemon
during one build. Without an explicit heap size the test JVM can die from memory pressure under that
load, and the recorded reason for pinning it is that the failure surfaced misleadingly — as an
EOFException from a truncated read rather than as an out-of-memory error or a clean test failure.
Setting maxHeapSize = "1g" explicitly removes that flakiness.
maxHeapSize is a ceiling applied to each test JVM separately, not to the build as a whole.
Every subproject that runs tests also finalizes them with a JaCoCo report:
tasks.withType<JacocoReport>().configureEach {
dependsOn(tasks.withType<Test>())
reports {
xml.required.set(true)
csv.required.set(true)
}
}The finalizedBy in the Test block and the dependsOn here are two halves of the same wiring:
the finalizer makes a report follow every test run, and the dependency makes an explicitly requested
jacocoTestReport run the tests first rather than report on stale execution data. XML and CSV are
requested because they are the formats a coverage service consumes; the HTML report stays on for
reading locally.