Skip to content

docs(build): put bootJar before test in the local verification order - #81

Merged
htcom-code merged 1 commit into
mainfrom
fix/local-verification-order
Sep 21, 2026
Merged

htcom-code merged 1 commit into
mainfrom
fix/local-verification-order

Conversation

@htcom-code

Copy link
Copy Markdown
Owner

What & why

Found while verifying #80. The operations guide's "good example" told you to run
clean test first and bootJar second. The container and OS-isolation tests
mount the fat -boot.jar and self-skip without it
(assumeTrue(bootJarExists())), and clean deletes it — so that order never ran
those 16 tests. The run still reported green: green over 580 tests instead of
596, with nothing in the output saying which reading applied.

Measured on this branch, same machine, Docker up throughout:

order tests skipped
clean test → bootJar (as documented) 596 16
clean bootJar → test (this PR) 596 0

CI is unaffected and is not changed here. Its split is deliberate — the
test job runs without a bootJar so those tests skip there, and a dedicated
isolation-test job builds the jar and runs exactly them
(.github/workflows/ci.yml says so in a comment). The gap was local-only, where
there is no second job to fall back on. A developer running the documented
command before pushing simply did not exercise those 16 tests, and had no way to
notice.

Three surfaces carried the stale order, so all three move together:

  • docs/guide/11-operations.md + .ko.md — the stated source of truth. The
    reversed order is now the good example, the old one is shown as a bad example
    with its consequence, and the CI difference is written down so nobody
    "fixes" the workflow to match.
  • .github/PULL_REQUEST_TEMPLATE.md — the checklist asked for clean test
    and then accepted "Docker-dependent tests run or knowingly skipped (Docker
    absent)"
    . That box could be ticked truthfully while the tests had skipped for
    a reason that has nothing to do with Docker — which is exactly what happened on
    feat(web): ack a console stream connection with a ready frame #80. It now asks whether they actually ran, and for the reason if not.
  • build.gradle — test logs a note when build/libs holds no
    -boot.jar, naming the consequence and the fix. It does not fail the
    build, because CI takes that path deliberately on every run; it just stops the
    gap from hiding inside a skip count nobody reads.
NOTE: no -boot.jar in build/libs, so the container/OS-isolation tests will self-skip.
Run './gradlew bootJar' first (as a separate invocation) to include them.
CI covers them in the isolation-test job.

The OOM constraint that motivated the original wording is untouched: these are
still two separate gradle invocations, only reordered.

The new order has a cost, and the guide now says so

One local invocation now runs Docker alongside everything else, so it is heavier
than CI's split. Worker-spawn health checks (WorkerProcessIsolation.waitHealthy)
can time out under that load. Of two full runs here, one failed
WorkerPoolTest.hot_swap_removes_the_emptied_old_worker_synchronously with
worker health timeout: port 58759; it passed when re-run alone, and the second
full run was clean (596 / 0 failures / 0 skipped). Local maxRetries is 0 by
design so flakiness stays visible, so the guide now says to re-run the class by
itself before treating such a failure as real.

Type of change

  • fix — bug fix (no new API)
  • feat — new capability (dynamic loading, gates, isolation, MCP, debug, data access)
  • perf — performance, no observable behaviour change
  • docs — documentation only
  • refactor / chore / test / ci — no behaviour change (the build.gradle note)

Correctness & safety

No production code path is touched. The only non-documentation change is a
doFirst on the test task that logs a line; it adds no dependency between
test and bootJar (which would reintroduce the OOM the separation exists to
avoid) and cannot fail a build.

  • N/A — Unload path, promotion gates, config binding: not touched.

Verification

  • gradle clean bootJar assembles
  • gradle test passes — 596 tests, 0 failures, 0 skipped
  • Docs updated — docs/guide/11-operations.md + .ko.md
  • Container/isolation tests actually ran — that is the point of this PR; the
    16 that silently skipped under the old order now execute.
  • The note was verified to fire: ./gradlew clean then ./gradlew test
    prints it and the build still succeeds.

Related

Follow-up to #80, where the old order caused 16 tests to skip unnoticed.

The guide's "good example" ran `clean test` first and `bootJar` second. The
container and OS-isolation tests mount the fat -boot.jar and self-skip without
it, and `clean` deletes it -- so that order never ran those 16 tests. The run
still reported green, over 580 tests instead of 596, with nothing in the output
saying so. Measured on this branch: 16 skipped in the documented order, 0 in the
reversed one.

CI is not affected and is not changed here. Its split is deliberate: the `test`
job runs without a bootJar so those tests skip, and a dedicated `isolation-test`
job builds the jar and runs exactly them. The gap was local-only, where there is
no second job to fall back on.

Three surfaces carried the stale order, so all three move together: the guide
(en + ko), which is the stated source of truth, and the PR template, whose
checklist asked for `clean test` and then accepted "Docker tests run or knowingly
skipped" -- a box you could tick truthfully while never learning the tests had
skipped for a reason unrelated to Docker. That box now asks whether they actually
ran, and for the reason if not.

The build says it out loud as well: `test` now logs a note when build/libs holds
no -boot.jar, naming the consequence and the fix. It does not fail the build --
CI hits this path deliberately on every run -- but the gap no longer hides inside
a skip count nobody reads.

Documented the cost of the new order too, since it is real: one local invocation
now runs Docker alongside everything else, and worker-spawn health checks can
time out under that load. One of two full runs here failed
WorkerPoolTest.hot_swap_removes_the_emptied_old_worker_synchronously that way;
it passed alone, and the second full run was clean. Local maxRetries is 0 by
design, so the guide says to re-run the class before believing such a failure.

Co-Authored-By: htjulia <htjulia1@gmail.com>
@htcom-code
htcom-code merged commit 6192f0b into main Sep 21, 2026
7 checks passed
@htcom-code
htcom-code deleted the fix/local-verification-order branch September 21, 2026 06:07
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