Skip to content

[#13516] improvement(ci): Split core tests into explicit database lanes - #13517

Merged
jerryshao merged 26 commits into
apache:mainfrom
nevzheng:codex/13516-core-database-test-lanes
Oct 15, 2026
Merged

jerryshao merged 26 commits into
apache:mainfrom
nevzheng:codex/13516-core-database-test-lanes

Conversation

@nevzheng

@nevzheng nevzheng commented Sep 27, 2026 •

Copy link
Copy Markdown
Collaborator

What changes were proposed in this pull request?

Split core tests into four Gradle lanes, each run as its own CI job, so unit tests and each database backend report, fail and time independently.

  • coreUnitTest runs tests with no database backend. coreH2Test, coreMySQLTest and corePostgreSQLTest each run one backend, sequentially. A class joins a lane with @CoreBackend.H2/MySQL/PostgreSQL/All.
  • TestCoreLaneMembership fails coreUnitTest when a Docker-tagged class has no backend tag, so a class can't silently drop out of every lane.
  • The coverage job merges the four lanes' raw JaCoCo data into one core report.
  • Three plain @Test methods move from TestJobMetaService to TestJobMetaServiceValidation: they need no database, and in a @CoreBackend.All class they would run three times.
  • :core:test still works locally and prints a deprecation warning.
  • docs/how-to-test.md describes the lanes and how to check which lane a class runs in.

Why are the changes needed?

:core:test combines unit tests with all three database backends in one task. Failures, timing and coverage can't be told apart per backend, and the follow-up concurrency work (#13518, #13530, #13553) has no safe boundary to build on. Running the lanes in parallel also shortens the critical path (see Timing).

Fix: #13516

Related to: #13515

Does this PR introduce any user-facing change?

No production API, storage format or configuration changes. Test-only changes:

  • New Gradle tasks coreUnitTest, coreH2Test, coreMySQLTest, corePostgreSQLTest, and the test-only system property gravitino.core.test.backend.
  • :core:test is deprecated: it still runs all core tests locally but prints a warning pointing to the lanes.
  • Core @TestTemplate cases are now reported as testMethod[H2 Backend] instead of [H2 Backend], in JUnit XML and HTML for the new lanes and for legacy :core:test. Test history keyed on the old names starts fresh, and exact-match --tests filters need the new name. Trailing-wildcard filters such as *[H2 Backend] still match.
  • CI checks: build (17) and the BackendIT checks keep their names, but build (17) now skips :core:test, so core results appear only under the new core-test (17, unit|h2|mysql|postgresql) checks. coverage is now its own check. .asf.yaml requires no status checks by name, so branch protection needs no change. Merge order for the follow-up PRs is on [EPIC] Improve database-backed test structure and execution #13515.

How was this patch tested?

Locally (macOS, JDK 17, Docker via OrbStack) on the PR head:

  • ./gradlew :core:spotlessCheck :core:compileTestJava -PskipWeb=true -PskipDockerTests=true
  • ./gradlew :core:coreUnitTest -PskipWeb=true -PskipDockerTests=true: 2,203 tests, 0 failures.
  • ./gradlew :core:coreH2Test -PskipWeb=true -PskipDockerTests=true: 588 tests, 0 failures; no Docker container was started.
  • ./gradlew :core:corePostgreSQLTest -PskipWeb=true -PskipDockerTests=false: 588 tests, 0 failures, 3 skipped.
  • ./gradlew :core:coreMySQLTest -PskipWeb=true -PskipDockerTests=false: 588 tests, 0 failures, 2 skipped.
  • The three database lanes ran the same 588 tests once backend names are normalized, and no class ran in both the unit lane and a database lane. All 312 core test classes JUnit discovers ran in a lane.
  • TestCoreLaneMembership fails, naming the four TestEntityStorage* classes, when @CoreBackend.All is removed from AbstractEntityStorageTest.
  • actionlint .github/workflows/build.yml
Timing (CI)

Lane jobs from run 38280654558 on 656282cc6. Later commits remove code and comments and leave lane selection unchanged; the lanes' JUnit and JaCoCo outputs now use Gradle's default paths.

Job Wall time
build (17) on main, core included (1, 2, 3) 1h28m – 1h46m
build (17) on this PR, core skipped 49m30s
core-test (17, unit) 6m04s
core-test (17, h2) 6m59s
core-test (17, postgresql) 19m11s
core-test (17, mysql) 27m20s
coverage 1m28s

The lanes run in parallel with build, so the critical path is build plus coverage, about 51m.

@github-actions

github-actions Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

Code Coverage Report

Overall Project 71.98% +0.5% 🟢
Files changed 95.04% 🟢

Module Coverage
aliyun 19.74% 🔴
api 54.68% 🟢
authorization-common 85.96% 🟢
authorization-ranger 14.44% 🔴
aws 53.54% 🟢
aws-bundle 0.0% 🔴
azure 32.1% 🔴
azure-bundle 0.0% 🔴
catalog-common 29.49% 🔴
catalog-fileset 82.37% 🟢
catalog-glue 73.24% 🟢
catalog-hive 83.63% 🟢
catalog-jdbc-common 57.62% 🟢
catalog-jdbc-doris 87.56% 🟢
catalog-jdbc-mysql 81.91% 🟢
catalog-jdbc-postgresql 84.02% 🟢
catalog-jdbc-starrocks 79.16% 🟢
catalog-kafka 75.21% 🟢
catalog-lakehouse-generic 80.67% 🟢
catalog-lakehouse-hudi 79.1% 🟢
catalog-lakehouse-iceberg 85.93% 🟢
catalog-lakehouse-paimon 84.36% 🟢
catalog-model 77.99% 🟢
cli 44.62% 🟢
client-java 79.32% 🟢
client-java-runtime 0.0% 🔴
common 60.49% 🟢
core 87.37% +0.41% 🟢
filesystem-hadoop3 76.89% 🟢
flink 55.36% 🟢
flink-common 32.2% 🔴
flink-runtime 0.0% 🔴
gcp 32.2% 🔴
hadoop-auth 68.0% 🟢
hadoop-common 17.84% 🔴
hive-metastore-common 62.3% 🟢
hive-metastore3-libs 58.8% 🟢
iceberg-aliyun-bundle 0.0% 🔴
iceberg-common 66.53% 🟢
iceberg-rest-server 77.01% 🟢
idp-basic 86.77% 🟢
integration-test-common 0.0% 🔴
jobs 70.23% 🟢
k8s-job-executor 99.81% 🟢
lance-common 39.9% 🔴
lance-rest-server 69.51% 🟢
lineage 59.39% 🟢
optimizer 84.2% 🟢
optimizer-api 40.87% 🟢
server 91.44% +0.24% 🟢
server-common 84.04% +3.02% 🟢
spark 61.79% 🟢
tencent 81.78% 🟢
tms 79.75% 🟢
trino-connector 63.31% 🟢
Files
Module File Coverage
core SemanticModelDispatcher.java 100.0% 🟢
SemanticModelHookDispatcher.java 100.0% 🟢
SemanticModelEventDispatcher.java 100.0% 🟢
OperationType.java 100.0% 🟢
CompatibilityUtils.java 99.44% 🟢
SemanticModelOperationDispatcher.java 98.18% 🟢
AuditLog.java 97.83% 🟢
OssieDocumentConverter.java 96.73% 🟢
SemanticModelNormalizeDispatcher.java 96.3% 🟢
AuthorizationRequestContext.java 80.95% 🟢
server SemanticModelOperations.java 99.19% 🟢
server-common AuthorizationExpressionConverter.java 98.36% 🟢
JcasbinLoadedRolesCache.java 96.3% 🟢
JcasbinAuthorizer.java 90.58% 🟢

@jerryshao jerryshao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: blocking issues

Reviewed 3058fb44945f4df06a2a01e482006462a73f5997 against origin/main (merge base 19e3a555). The branch still carries the #13495 commits, so I concentrated on the #13516 delta: core/build.gradle.kts, the includeDockerTaggedTests hook in root build.gradle.kts, the new backend selector and tags, dev/ci/test-shards.sh, dev/ci/core_test_identity.py, and the new build.yml jobs.

The overall shape is good: lanes are derived from one shard table, the database lanes are forced sequential, JaCoCo exec files are collected per lane and merged once, and the identity reconciliation fails closed. Two things need attention before merge.

Blocking. The includeDockerTaggedTests opt-in is never observed by the root test configuration, because Gradle runs configureEach add-actions before the action passed to tasks.register. :core:coreH2Test therefore keeps the inherited excludeTags("gravitino-docker-test") whenever dockerTest is false, which is exactly how CI runs the core-h2 shard. The H2 lane then loses ~52 AbstractEntityStorageTest cases plus TestJdbcPartitionStatisticStorageIT$H2Test, and core-test-contract fails its H2-vs-MySQL/PostgreSQL identity check. I reproduced the ordering on Gradle 8.2 rather than inferring it; details are in the inline comment on build.gradle.kts.

Tests. Coverage of the new logic is good: TestBackendTestSelector covers the selector's absent/normalized/invalid paths and both provider shapes, and dev/ci/tests/test_core_test_identity.py covers normalization, the unit-lane foreign-marker rejection and reconciliation against committed fixtures. Two gaps:

  • Nothing exercises the Gradle wiring itself. The defect above is invisible to every test in this PR and to every local command listed in the description; it only surfaces in the core-h2 CI shard. The real fix is to make each lane's tag set independent of -PskipDockerTests; a cheap extra guard would be a core-test-contract assertion that the H2 manifest contains the TestEntityStorage* identities.
  • compare-legacy is implemented and unit-tested (dev/ci/core_test_identity.py:543-562) but no workflow calls it. I grepped .github/ and dev/ci/ and found no invocation. The description lists "verifies that legacy execution is the exact multiset union of the split lanes" as a delivered property, while the appendix marks the legacy partition comparison as TBD. Could you either wire a compare-legacy step in (a manually dispatched job would do) or soften that bullet, so the description matches what CI actually enforces?

Findings that could not be anchored inline: none beyond the two notes above.

Review drafted with Claude, requested by @jerryshao.

Comment thread build.gradle.kts
Comment thread .github/workflows/build.yml Outdated
Comment thread core/build.gradle.kts Outdated
@nevzheng nevzheng self-assigned this Sep 28, 2026
nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Sep 28, 2026
- Revert build.yml timeout from 90 to 120 minutes; 90 was too tight
  given the MySQL lane alone measures ~69 min locally with no margin.
- Rename the coreH2Test/coreMySQLTest/corePostgreSQLTest
  inputs.property key from includeDockerTaggedTests to
  coreTestIncludesDockerTaggedTests, distinguishing it from the
  unrelated extensions.extraProperties[includeDockerTaggedTests]
  flag consumed by root build.gradle.kts's shared test setup.
- Drop the hardcoded "()" from BackendTestExtension's
  getDisplayName(): @testtemplate methods can declare real
  parameters, so an empty parameter list misrepresented the
  method's signature in JUnit XML/HTML reports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
@nevzheng

nevzheng commented Sep 28, 2026 •

Copy link
Copy Markdown
Collaborator Author

@jerryshao PTAL

Changes:

  • 654cfe33a — timeout 90→120min, renamed the inputs.property key to coreTestIncludesDockerTaggedTests (distinct from the extraProperties flag), dropped the hardcoded () in BackendTestExtension#getDisplayName().

  • efee6a4c9 — replaced the per-backend excludeTags() scheme with @DatabaseTest/BackendLaneCondition (see below).

  • excludeTags(otherTwoBackends) allowed two silent failure modes with zero build-time check: a class tagged with two backends excluded from every lane, or a class missing its tag running redundantly in all three. Replaced it with one typed source of truth:

  • DatabaseTest (backends(), defaults to all 3) + BackendLaneCondition (ExecutionCondition reading the gravitino.core.test.backend property lanes already set)

  • TestDatabaseTestClassification — build-time guard, no DB/Docker, catches empty backends() or leftover raw tags

  • core/build.gradle.kts's 3-way excludeTags table → one includeTags(coreDatabaseTestTag)

Makes the bug class unrepresentable rather than just detectable post-execution.

Listing (before/after equivalence, :core:coreH2Test):

Classes Tests Skipped Executed Failures
Before 40 520 0 520 0
After 42 534 14 520 0

Same 520 executed, same outcomes. The 14 "new" tests were always excluded from Gradle's own discovery (invisible); now they're a reported skip with a reason string — e.g. MySQLTest/PostgreSQLTest correctly disabled under the H2 lane, 0.004s, no container attempted.

Also added TestBackendLaneCondition (5/5, direct unit coverage of the condition across all-backend/single-backend/multi-backend cases).

Nevin

@nevzheng
nevzheng requested a review from jerryshao September 28, 2026 09:55
@nevzheng

Copy link
Copy Markdown
Collaborator Author

need redesign. will reply later

@nevzheng

Copy link
Copy Markdown
Collaborator Author

@jerryshao PTAL — here's what changed since your initial review, and how it's been tested.

What changed:

  • Replaced the single gatekeeper/master tag + boolean tag-expression scheme with a typed, namespaced annotation API: CoreBackend.H2 / .MySQL / .PostgreSQL / .All. No raw @tag("...") string literals in core/src/test anymore — a class runs under exactly the lanes it's annotated for, nothing implicit.
  • Removed the dead compare-legacy subcommand from dev/ci/core_test_identity.py (unwired, unused by CI). manifest/reconcile are unchanged and still the CI-enforced contract.
  • Added ./gradlew :core:coreTestLaneOf -PclassName= — a discovery-only local check that prints which lane(s) a class runs in (or an explicit warning if it's Docker-tagged but carries no backend annotation), so contributors can self-check before pushing instead of guessing.
  • Deprecated :core:test in place (still works, now warns and points at the four real lanes and coreTestLaneOf) rather than removing it, since other tooling may still target it by name.
  • Added design-docs/testing/core-db-split.md, an SPIP documenting the final design, the alternatives we tried and rejected along the way (an ExecutionCondition-based redesign that broke CI because skipped tests still leave a trace in JUnit XML; a parameterized-annotation form rejected because it'd break reconcile's exact-identity-equality requirement; a flat naming scheme rejected after a real reproduced compile-error collision with existing nested test classes).

How it's been tested:

  • Real ./gradlew :core:coreUnitTest / :core:coreH2Test runs against the current code, confirmed via actual JUnit XML output (not just reasoning about it).
  • TestCoreDatabaseLaneAnnotations pins the annotation contract as a regression test — exact tag expansion, stacking, inheritance, and that none of the four annotations carry any meta-annotation outside {Documented, Inherited, Retention, Target, Tag, Tags} (i.e., structurally guarantees no execution-time hook can sneak back in).
  • Two independent adversarial reviews (one on CI/build correctness, one on contributor ergonomics) turned up a real bug before merge: a parameterized test's default JUnit display name was leaking a backend token into its own unit-lane XML, tripping manifest's foreign-marker check. Fixed, then re-verified by running the real normalize_identity check across all 1968 coreUnitTest testcases — zero marker errors.
  • MySQL/PostgreSQL Docker-backed lanes: [you mentioned you'd verify these independently — fill in once you have]

nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Sep 28, 2026
Stack: apache#13517 (lanes) -> apache#13518 (fixture reuse) -> apache#13530 (adaptive
forks, one container per worker) -> this change.

apache#13530 gets its wall-clock win from Gradle test-worker forks, but pays
for it with one physical MySQL/PostgreSQL container per fork. This
adds SharedDbContainerService, a Gradle BuildService that starts one
MySQL and one PostgreSQL container for the whole build (via the
docker CLI, since the main Gradle process has no Testcontainers on
its classpath), and provisions a brand-new, uniquely-named database
per DefaultBackendFactory.create() call instead of a fresh container.

Falls back to the existing per-fork-container path
(BaseIT#startAndInitMySQLBackend()/startAndInitPGBackend()) when the
shared-container system properties aren't published, e.g. an IDE run.
apache#13530's own DatabaseIsolation/BackendSetupCallback/BackendResource
reuse-and-poison caching is untouched - this only changes how a
backend is provisioned, not when one is reused.

Reviewed twice independently (two blind Fable passes); both
converged on the same real issues (stale-container cleanup not
covering a killed-daemon leak, a test clobbering a Gradle-owned
system property, under-tested PostgreSQL duplicate-database handling)
plus additional individual findings, all fixed:
- Stale-container cleanup now tracks the owning Gradle daemon's PID
  and reaps a still-running container only when that process is dead,
  instead of never reaping a running container at all.
- SharedDbContainerService's docker-CLI parsing (host-port parsing,
  line splitting, PID-liveness check) extracted to static methods and
  unit-tested without needing Docker.
- DatabaseProvisioning's PostgreSQL exists-check/duplicate-tolerant
  create sequence extracted to Statement-level methods and
  unit-tested with Mockito (42P04 handling, non-duplicate rethrow).
- Fixed a test that cleared org.gradle.test.worker instead of saving
  and restoring it, matching the sibling telemetry test's pattern.
- Documented the 63-char name cap and exception-type change for
  existing MySQLContainer/PostgreSQLContainer callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE

@jerryshao jerryshao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: needs more information

Re-review of the 11 commits since 3058fb44.

Previously raised:

  • [Important] includeDockerTaggedTests never observed, so the core-h2 lane silently loses every Docker-tagged test — no longer reproduces on this head. CI has since run (run 36466794673): build (17, core-h2) passed with -PskipDockerTests=true, :core:coreH2Test took 216.8s there against your local 3m40.981s for the full lane, and core-test-contract passed — that job enforces strict h2 == mysql == postgresql identity equality (dev/ci/core_test_identity.py:418-424), so the four @CoreBackend.All classes did run in the H2 lane. Why the root Docker exclusion did not bite is the one thing I could not establish; see the question on core/build.gradle.kts:180.
  • [Question] timeout-minutes 90 → 120 — fixed (.github/workflows/build.yml:162).
  • [Nit] inputs.property("includeDockerTaggedTests") colliding with the extraProperties flag — fixed, renamed to coreTestIncludesDockerTaggedTests with a comment separating the two mechanisms.
  • [Nit] hardcoded () in BackendTestExtension#getDisplayName — fixed (BackendTestExtension.java:145-151).
  • [Tests] compare-legacy implemented but unwired — resolved by removing it (bed4a93), along with its fixture and unit tests; argparse still requires a subcommand, so the else branch in main is reachable only by reconcile. Two description follow-ups, though: the bullet "verifies that legacy execution is the exact multiset union of the split lanes" is no longer a property of this branch, and "9 tests passed" is now 8 (python3 -B -m unittest discover -s dev/ci/tests -p 'test_core_test_identity.py' → 8 tests, OK, run here).
  • [Tests] nothing exercises the lane wiring itself — partly addressed. TestCoreDatabaseLaneAnnotations pins tag expansion, stacking, inheritance and the absence of any non-@Tag meta-annotation, and core-test-contract now catches divergence between lanes. What is still unguarded is a drop that hits all three lanes equally, or a class that lands in no lane at all — see the comment on core/build.gradle.kts:174.

The redesign itself reads well: one backend→tag table, includeTags only, no raw lane @Tag strings left in core/src/test, and no execution-time condition (the SPIP records that attempt and why it was reverted).

Tests: adequate for the new logic. TestCoreDatabaseLaneAnnotations covers all four annotations plus stacking, subclass inheritance and the meta-annotation shape; the Python suite passes here; the lane split itself is now backed by a real four-lane CI run. The two gaps above are the remainder: no build-time guard against an unlaned class, and the -PskipDockerTests interaction is unpinned.

Timing: the after-CI column in the description is still "Pending", but run 36466794673 supplies it — core-unit 6m54s, core-h2 6m33s, core-postgresql 17m40s, core-mysql 25m26s of job wall time, against the 49m35s combined-core baseline you cite, i.e. a ~24m critical-path reduction with core-mysql as the long pole. Worth filling in, since it is a stronger claim than the local model.

Findings that could not be anchored inline: only the description items above.

Review drafted with Claude, requested by @jerryshao.

Comment thread core/build.gradle.kts
Comment thread core/build.gradle.kts
Comment thread core/src/test/java/org/apache/gravitino/storage/relational/CoreTestLaneOf.java Outdated
Comment thread core/build.gradle.kts Outdated
@nevzheng

Copy link
Copy Markdown
Collaborator Author

@jerryshao PTAL. All four threads from your Sep 29 re-review are addressed, one commit each:

  • Orphan-lane guard (:174): 9122429cb adds TestCoreLaneMembership in coreUnitTest. It fails when a Docker-tagged class has no CoreBackend tag, and I checked it fails when @CoreBackend.All is dropped from AbstractEntityStorageTest. The design doc is updated in 0e66fd6db.
  • H2 Docker exclusion (:180): no ordering dependency. The flag is read inside doFirst. Documented in 67457136f.
  • coreTestLaneOf nested classes (CoreTestLaneOf:89): a5ab8df63 reports member test classes.
  • :core:test comment (:257): corrected in eb4e20a79.
  • Description: stale bullet removed, 8 tests, and after-CI timings filled in. The core critical path is 49m35s → 25m26s.

Nevin
Sent from my 🤖 (Claude Opus 5.5)

@nevzheng
nevzheng requested a review from jerryshao September 30, 2026 17:50
@jerryshao

Copy link
Copy Markdown
Contributor

I would suggest that we simplify the code. Current PR has lots of code that are not directly related to the PR, like design doc, test fixtures, Python code, and shell scripts. I don't think they're super necessary, it makes others hard to maintain. I would suggest we only keep the core changes.

@jerryshao jerryshao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is well-engineered, and I want to say so specifically because several things I expected to be problems turned out to be deliberate and correct:

  • others emits -x :core:test, and the four new lanes are tasks.register<Test> that are not wired into check, so root build genuinely cannot run them a second time — the comment in print_others holds.
  • The artifact path design is right: mixing core/build/... and build/reports/... in one path: forces upload-artifact's least-common-ancestor back to the workspace root, so after download core-test-evidence/unit/core/build/jacoco/coreUnitTest.exec is exactly what install expects. Same trick with build.gradle.kts anchoring the jacoco-* artifacts, and it's documented inline.
  • coverage-comment.yml keeps working: the coverage steps moved to a separate job but stay in the same workflow run, so the workflow_run trigger and run-id still resolve.
  • Skipped tests still emit <testcase>, so PostgreSQL's "517 passed + 2 skipped" and H2's 519 are identity-equal and reconcile won't false-alarm.
  • The TestJobMetaServiceValidation split is legitimate — JobMetaService throws NoSuchEntityException on malformed ids before any DB access, so those three tests really need no backend.
  • CoreTestLaneOf.declaresTests correctly excludes abstract classes, and effectiveTags only walks enclosing classes for non-static nested classes, which matches JUnit (a @Nested static class isn't a JUnit nested class and is discovered as a top-level one).

Four comments inline, none blocking. The one I'd most like to see changed is the loose backend-token regex in core_test_identity.py, which is a trap for future contributors rather than a bug today.

Two things not worth their own comment: backend-integration-test-action.yml drops timeout-minutes from 120 to 90 with no backend-it timing evidence in this PR (the timing table only covers the core lanes) — that's #13495's change, but the 25% cut deserves a datapoint there. And TestCoreLaneMembership's findAllClassesInPackage("org.apache.gravitino", ...) classloads every class under that package; JUnit's scanner catches per-class Throwable so it's safe, it just adds a fixed cost to coreUnitTest.

Comment thread dev/ci/core_test_identity.py Outdated
Comment thread .github/workflows/build.yml Outdated
Comment thread .github/workflows/build.yml Outdated
@nevzheng
nevzheng force-pushed the codex/13516-core-database-test-lanes branch from 0e66fd6 to 656282c Compare October 12, 2026 16:54
nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Oct 12, 2026
- Revert build.yml timeout from 90 to 120 minutes; 90 was too tight
  given the MySQL lane alone measures ~69 min locally with no margin.
- Rename the coreH2Test/coreMySQLTest/corePostgreSQLTest
  inputs.property key from includeDockerTaggedTests to
  coreTestIncludesDockerTaggedTests, distinguishing it from the
  unrelated extensions.extraProperties[includeDockerTaggedTests]
  flag consumed by root build.gradle.kts's shared test setup.
- Drop the hardcoded "()" from BackendTestExtension's
  getDisplayName(): @testtemplate methods can declare real
  parameters, so an empty parameter list misrepresented the
  method's signature in JUnit XML/HTML reports.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
@nevzheng

Copy link
Copy Markdown
Collaborator Author

I would suggest that we simplify the code. [...] I would suggest we only keep the core changes.

note: Done in 6797513, 11ac69a and 4e8e79d. The PR now keeps only the four core:*Test lanes, CoreBackend, TestCoreLaneMembership and the core-test jobs: +727 across 13 files. Removed: the design doc, the Python identity tool with its tests and fixtures, the core-test-contract job, coreTestLaneOf, the annotation self-test and Gradle lines that restated defaults; the shell scripts belonged to #13495 and left with the rebase. TestCoreLaneMembership still fails the unit lane when a Docker-tagged class has no backend lane.

@nevzheng

nevzheng commented Oct 13, 2026 •

Copy link
Copy Markdown
Collaborator Author

@jerryshao PTAL, and please let me know specifically any blocking issues or asks. Let's get this merged; I have follow-ups (#13518, #13530, #13553) waiting on it. A lot changed since your last review, so here is the summary.

Change log

Major

Other

  • CoreBackend now documents that the lane, not the class, decides whether Docker is needed (h2 never does).
  • The description is rewritten: CI check names, the testcase rename, and timing.
  • docs/how-to-test.md now describes the lanes and how to check which lane a class runs in.

Replies to your comments

All 12 inline threads are resolved.

Evidence

  • Local, on 4e8e79d: coreUnitTest 2,203 tests, coreH2Test 588, corePostgreSQLTest 588, coreMySQLTest 588, all with 0 failures. The h2 lane started no Docker container.
  • Lane parity: h2, MySQL and PostgreSQL ran the same 588 tests once backend names are normalized. No class ran in both the unit lane and a database lane, and all 312 core test classes JUnit discovers ran in a lane.
  • Guard: removing @CoreBackend.All from AbstractEntityStorageTest fails TestCoreLaneMembership, naming the four TestEntityStorage* classes.
  • CI timing (run 38280654558, before the trims; lane selection unchanged since): build (17) drops from 1h28m–1h46m on main to 49m, and the lanes run alongside it (mysql is the longest at 27m).

CI on 4e8e79d is still queued on the ASF runners; I'll follow up here if anything fails.

nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
Stack: apache#13517 (lanes) -> apache#13518 (fixture reuse) -> apache#13530 (adaptive
forks, one container per worker) -> this change.

apache#13530 gets its wall-clock win from Gradle test-worker forks, but pays
for it with one physical MySQL/PostgreSQL container per fork. This
adds SharedDbContainerService, a Gradle BuildService that starts one
MySQL and one PostgreSQL container for the whole build (via the
docker CLI, since the main Gradle process has no Testcontainers on
its classpath), and provisions a brand-new, uniquely-named database
per DefaultBackendFactory.create() call instead of a fresh container.

Falls back to the existing per-fork-container path
(BaseIT#startAndInitMySQLBackend()/startAndInitPGBackend()) when the
shared-container system properties aren't published, e.g. an IDE run.
apache#13530's own DatabaseIsolation/BackendSetupCallback/BackendResource
reuse-and-poison caching is untouched - this only changes how a
backend is provisioned, not when one is reused.

Reviewed twice independently (two blind Fable passes); both
converged on the same real issues (stale-container cleanup not
covering a killed-daemon leak, a test clobbering a Gradle-owned
system property, under-tested PostgreSQL duplicate-database handling)
plus additional individual findings, all fixed:
- Stale-container cleanup now tracks the owning Gradle daemon's PID
  and reaps a still-running container only when that process is dead,
  instead of never reaping a running container at all.
- SharedDbContainerService's docker-CLI parsing (host-port parsing,
  line splitting, PID-liveness check) extracted to static methods and
  unit-tested without needing Docker.
- DatabaseProvisioning's PostgreSQL exists-check/duplicate-tolerant
  create sequence extracted to Statement-level methods and
  unit-tested with Mockito (42P04 handling, non-duplicate rethrow).
- Fixed a test that cleared org.gradle.test.worker instead of saving
  and restoring it, matching the sibling telemetry test's pattern.
- Documented the 63-char name cap and exception-type change for
  existing MySQLContainer/PostgreSQLContainer callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
Stack: apache#13517 (lanes) -> apache#13518 (fixture reuse) -> apache#13530 (adaptive
forks, one container per worker) -> this change.

apache#13530 gets its wall-clock win from Gradle test-worker forks, but pays
for it with one physical MySQL/PostgreSQL container per fork. This
adds SharedDbContainerService, a Gradle BuildService that starts one
MySQL and one PostgreSQL container for the whole build (via the
docker CLI, since the main Gradle process has no Testcontainers on
its classpath), and provisions a brand-new, uniquely-named database
per DefaultBackendFactory.create() call instead of a fresh container.

Falls back to the existing per-fork-container path
(BaseIT#startAndInitMySQLBackend()/startAndInitPGBackend()) when the
shared-container system properties aren't published, e.g. an IDE run.
apache#13530's own DatabaseIsolation/BackendSetupCallback/BackendResource
reuse-and-poison caching is untouched - this only changes how a
backend is provisioned, not when one is reused.

Reviewed twice independently (two blind Fable passes); both
converged on the same real issues (stale-container cleanup not
covering a killed-daemon leak, a test clobbering a Gradle-owned
system property, under-tested PostgreSQL duplicate-database handling)
plus additional individual findings, all fixed:
- Stale-container cleanup now tracks the owning Gradle daemon's PID
  and reaps a still-running container only when that process is dead,
  instead of never reaping a running container at all.
- SharedDbContainerService's docker-CLI parsing (host-port parsing,
  line splitting, PID-liveness check) extracted to static methods and
  unit-tested without needing Docker.
- DatabaseProvisioning's PostgreSQL exists-check/duplicate-tolerant
  create sequence extracted to Statement-level methods and
  unit-tested with Mockito (42P04 handling, non-duplicate rethrow).
- Fixed a test that cleared org.gradle.test.worker instead of saving
  and restoring it, matching the sibling telemetry test's pattern.
- Documented the 63-char name cap and exception-type change for
  existing MySQLContainer/PostgreSQLContainer callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
claude and others added 15 commits October 13, 2026 16:42
…eBackend

Raw @tag("gravitino-core-h2-test") string literals (and their multi/-mysql/
-postgresql siblings) were typo-prone: a misspelled tag compiles fine and
the class just silently never runs in any lane. @AllBackendsTest already
solved this for the all-three-backends case; extend the same discovery-time-
safe pattern to the individual-backend case.

CoreBackend.H2/.MySQL/.PostgreSQL/.All replace both the raw @tag literals
and @AllBackendsTest. Flat names (@CoreH2Test etc.) were considered and
rejected: they'd collide with existing classes of those exact names
(TestJdbcPartitionStatisticStorageIT$H2Test/$MySQLTest/$PostgreSQLTest) -
not just confusingly, but as an actual compile error (an unqualified
annotation import shadowed by a same-named nested class). Namespacing under
CoreBackend makes that collision structurally impossible (CoreBackend.H2 is
always qualified) and matches this package's existing "Backend" vocabulary
(BackendTestExtension, BackendTestSelector). A parameterized single
annotation (@corebackend(backends = {H2, MYSQL})) was also considered and
rejected: JUnit's @tag resolution only ever reads an annotation type's own
fixed meta-annotations, never a per-usage attribute value, so it would need
an execution-time mechanism to interpret - the same category of mistake the
already-reverted @DatabaseTest/ExecutionCondition redesign made.

Each nested annotation stays a plain composed @tag with no @ExtendWith;
TestCoreDatabaseLaneAnnotations pins the contract directly (exact tag
expansion via the same AnnotationSupport lookup Jupiter's engine uses at
discovery time, inheritance, stacking, and that none of the four carry an
execution-time meta-annotation).

Also adds `coreTestLaneOf`, a discovery-only local check
(./gradlew :core:coreTestLaneOf -PclassName=<FQCN>) that prints which
lane(s) a class's tags put it in without running anything, and warns when a
Docker-tagged class carries no backend tag (the one gap this annotation
redesign doesn't structurally close: forgetting every backend tag, not just
one of several). And a build-script comment on coreBackendTestTags spelling
out the four lane-membership shapes with examples.

Verified against real :core:coreH2Test/:core:coreUnitTest/:core:coreTestLaneOf
runs: TestJdbcPartitionStatisticStorageIT's per-backend isolation still
holds (only the matching nested class's XML is produced, manifest passes),
and coreTestLaneOf correctly reports lanes for a real @CoreBackend.All class
and warns for a synthetic Docker-tagged, no-backend-tag orphan class.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
:core:test is the java plugin's built-in test task, kept registered
for tooling backward-compat but no longer the right way to run core
tests. Its tasks.test block now warns on every direct invocation,
naming the four real lanes (coreUnitTest/coreH2Test/coreMySQLTest/
corePostgreSQLTest) and coreTestLaneOf. No CI change needed since
dev/ci/test-shards.sh already excludes it.

Adds design-docs/testing/core-db-split.md, an SPIP (Heilmeier
Catechism) documenting the final lane-split design retroactively,
including the alternatives that were tried and reverted along the
way and a contributor how-to (Appendix C) in place of a
docs/how-to-test.md section, since that doc is repo-wide and never
mentioned core specifically.
TestCoreDatabaseLaneAnnotations' @ParameterizedTest used the default
display name, which embeds each case's expected-tags argument (e.g.
"[gravitino-core-h2-test]") into its own JUnit XML. That test runs in
coreUnitTest, so core_test_identity.py's unit-lane manifest check
rejected it as a foreign backend marker - reproduced directly,
confirmed with a full 1968-testcase sweep through the real
normalize_identity function after the fix, zero marker errors.
Fixed by naming on the class under test only.

Also, found via two independent Fable reviews:
- CoreBackend's Javadoc misattributed core_test_identity.py's
  reconcile step to core/build.gradle.kts, and its lane-membership
  bullets didn't mention the docker-tag-only orphan case. Fixed both.
- The SPIP's Appendix C taught a partial-backend tagging example
  (@CoreBackend.H2 @CoreBackend.MySQL) that fails CI's reconcile step
  unless normalized sibling classes exist for the omitted backends,
  which only one existing class shape satisfies. Replaced it with
  guidance to default to @CoreBackend.All and an explicit warning
  about the constraint.
A class tagged gravitino-docker-test with no CoreBackend tag is excluded
from coreUnitTest and from every database lane, and reconcile only
compares the database lanes with each other, so it could drop out of all
three at once with CI green. TestCoreLaneMembership runs in coreUnitTest
and fails, naming each such class.
The root setTestEnvironment reads the flag inside param.doFirst, at
execution time, after every register/configureEach action has run, so
the core database lanes keep their Docker-tagged tests regardless of
configuration order. Comment both ends so the read stays in doFirst.
Only the build suite excludes :core:test. BackendIT's others shard still
realizes it through the root test task under -PskipTests, where it
matches nothing in core, so the deprecation warning can appear there.
For a class with no tests of its own, such as
TestJdbcPartitionStatisticStorageIT, coreTestLaneOf now reports each
member test class and its lanes instead of warning that the class runs
nowhere. Non-static @nested classes also pick up their enclosing
classes' tags. TestCoreLaneMembership reuses the same helpers.
TestCoreLaneMembership now fails coreUnitTest for a Docker-tagged class with no backend tag, so the doc no longer describes that case as unguarded.
Wire coreUnitTest/coreH2Test/coreMySQLTest/corePostgreSQLTest into a
core-test matrix job in build.yml directly, so the PR no longer depends
on the Backend IT sharding in apache#13495 (dev/ci/test-shards.sh). The build
job skips :core:test and uploads its JaCoCo reports; a coverage job
merges them with the four core lane .exec files after
core-test-contract reconciles the lanes.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019TX7XCnuCMGSJ7xX2iubTH
Drop the design doc, the Python test-identity tool with its tests and
fixtures, the core-test-contract job and manifest steps, and the
coreTestLaneOf helper task. TestCoreLaneMembership keeps the orphan-lane
guard; its two tag helpers move into the test itself.
CoreBackend tags choose the lane; MySQL and PostgreSQL need Docker and
H2 does not. gravitino-docker-test on a backend class only keeps it out
of coreUnitTest and of legacy :core:test, so an H2-tagged class must not
need a container.
Say that an untagged Docker class runs in no lane task (legacy
:core:test still runs it with Docker on), say plainly why lane
annotations must not carry an execution-time hook, and name what the
includeDockerTaggedTests flag actually controls.
Review asked to keep only the core changes, so remove what doesn't
earn its keep:

- TestCoreDatabaseLaneAnnotations only re-tested JUnit's own @tag
  meta-annotation and @inherited handling. The lanes themselves and
  TestCoreLaneMembership already prove the tags select the right tests.
- The lane rules were spelled out three times (core/build.gradle.kts,
  CoreBackend, the :core:test block). CoreBackend's Javadoc is now the
  one place; the Gradle file points to it.
- The coreTestIncludesDockerTaggedTests input property only repeated
  what the coreTestBackend input already tells Gradle's up-to-date check.
… trivia

Review asked to keep only the core changes:

- Gradle already writes each Test task's JUnit XML to
  build/test-results/<task> and its JaCoCo data to build/jacoco/<task>.exec,
  so the lanes no longer set those paths. The coreTestSuite and
  coreTestBackend inputs only repeated what includeTags and the backend
  system property already give the up-to-date check, and the three
  imports are Kotlin DSL defaults.
- validateCoreSuiteCoverage checked that the four .exec files exist, which
  the coverage job already checks before it copies them.
- TestBackendTestSelector no longer pins the six-row legacy matrix or the
  display-name string through a mocked ExtensionContext; the remaining
  tests cover selection, validation and the selected-backend matrix.
Contributors had no guide to the new lanes: name the four tasks and when
Docker is needed, how a class joins a lane through @corebackend, and how
to check a class runs in the lane you expect.
@nevzheng
nevzheng requested a review from jerryshao October 13, 2026 23:44
@nevzheng
nevzheng force-pushed the codex/13516-core-database-test-lanes branch from 1147573 to 483bee3 Compare October 13, 2026 23:50
nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Oct 13, 2026
Stack: apache#13517 (lanes) -> apache#13518 (fixture reuse) -> apache#13530 (adaptive
forks, one container per worker) -> this change.

apache#13530 gets its wall-clock win from Gradle test-worker forks, but pays
for it with one physical MySQL/PostgreSQL container per fork. This
adds SharedDbContainerService, a Gradle BuildService that starts one
MySQL and one PostgreSQL container for the whole build (via the
docker CLI, since the main Gradle process has no Testcontainers on
its classpath), and provisions a brand-new, uniquely-named database
per DefaultBackendFactory.create() call instead of a fresh container.

Falls back to the existing per-fork-container path
(BaseIT#startAndInitMySQLBackend()/startAndInitPGBackend()) when the
shared-container system properties aren't published, e.g. an IDE run.
apache#13530's own DatabaseIsolation/BackendSetupCallback/BackendResource
reuse-and-poison caching is untouched - this only changes how a
backend is provisioned, not when one is reused.

Reviewed twice independently (two blind Fable passes); both
converged on the same real issues (stale-container cleanup not
covering a killed-daemon leak, a test clobbering a Gradle-owned
system property, under-tested PostgreSQL duplicate-database handling)
plus additional individual findings, all fixed:
- Stale-container cleanup now tracks the owning Gradle daemon's PID
  and reaps a still-running container only when that process is dead,
  instead of never reaping a running container at all.
- SharedDbContainerService's docker-CLI parsing (host-port parsing,
  line splitting, PID-liveness check) extracted to static methods and
  unit-tested without needing Docker.
- DatabaseProvisioning's PostgreSQL exists-check/duplicate-tolerant
  create sequence extracted to Statement-level methods and
  unit-tested with Mockito (42P04 handling, non-duplicate rethrow).
- Fixed a test that cleared org.gradle.test.worker instead of saving
  and restoring it, matching the sibling telemetry test's pattern.
- Documented the 63-char name cap and exception-type change for
  existing MySQLContainer/PostgreSQLContainer callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
apache#13478 added docs/policies.md as an input and gravitino.repositoryRoot as
a system property to :core:test. TestRestrictedRegoGoldenFile is a unit
test, so CI runs it in coreUnitTest: give that task the same two
settings. The database lanes never run it and get neither.
@nevzheng
nevzheng force-pushed the codex/13516-core-database-test-lanes branch from 483bee3 to 5481579 Compare October 14, 2026 02:42
nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Oct 14, 2026
Stack: apache#13517 (lanes) -> apache#13518 (fixture reuse) -> apache#13530 (adaptive
forks, one container per worker) -> this change.

apache#13530 gets its wall-clock win from Gradle test-worker forks, but pays
for it with one physical MySQL/PostgreSQL container per fork. This
adds SharedDbContainerService, a Gradle BuildService that starts one
MySQL and one PostgreSQL container for the whole build (via the
docker CLI, since the main Gradle process has no Testcontainers on
its classpath), and provisions a brand-new, uniquely-named database
per DefaultBackendFactory.create() call instead of a fresh container.

Falls back to the existing per-fork-container path
(BaseIT#startAndInitMySQLBackend()/startAndInitPGBackend()) when the
shared-container system properties aren't published, e.g. an IDE run.
apache#13530's own DatabaseIsolation/BackendSetupCallback/BackendResource
reuse-and-poison caching is untouched - this only changes how a
backend is provisioned, not when one is reused.

Reviewed twice independently (two blind Fable passes); both
converged on the same real issues (stale-container cleanup not
covering a killed-daemon leak, a test clobbering a Gradle-owned
system property, under-tested PostgreSQL duplicate-database handling)
plus additional individual findings, all fixed:
- Stale-container cleanup now tracks the owning Gradle daemon's PID
  and reaps a still-running container only when that process is dead,
  instead of never reaping a running container at all.
- SharedDbContainerService's docker-CLI parsing (host-port parsing,
  line splitting, PID-liveness check) extracted to static methods and
  unit-tested without needing Docker.
- DatabaseProvisioning's PostgreSQL exists-check/duplicate-tolerant
  create sequence extracted to Statement-level methods and
  unit-tested with Mockito (42P04 handling, non-duplicate rethrow).
- Fixed a test that cleared org.gradle.test.worker instead of saving
  and restoring it, matching the sibling telemetry test's pattern.
- Documented the 63-char name cap and exception-type change for
  existing MySQLContainer/PostgreSQLContainer callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE

@jerryshao jerryshao left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: ship it

Re-review after the rebase and the scope cut. My last review was on 0e66fd6d, which the rebase removed from the branch, so there is no usable 0e66fd6d...HEAD range; I reviewed the whole change at 8cc5fbf9 against origin/main (f58ea843) instead, and checked each earlier finding against the current tree.

Previously raised:

  • [Oct 8] Loose BACKEND_TOKEN_RE in dev/ci/core_test_identity.py - gone. The file is no longer in the tree (git diff --stat origin/main...HEAD lists 14 files, none under dev/ci/), so nothing parses testcase names any more. Resolved by removal.
  • [Oct 8] Check-name cutover and branch protection - resolved, and I verified the claim rather than taking it: .asf.yaml:48-49 has required_status_checks: strict: true with no contexts: list, so no check is required by name and INFRA has nothing to reconfigure. The rebase also dropped #13495, so build (17) and the BackendIT names are unchanged; the new names are core-test (17, <lane>) and coverage.
  • [Oct 8] h2 lane running with dockerTest=false while the other two see true - the constraint is now recorded in CoreBackend.java:36-38 ("the lane, not the class, decides whether Docker is needed"). The container-level-conditional edge it protected against left with reconcile. Accepted, but see the question on core/build.gradle.kts:157, which is the part of this that got less guarded, not more.
  • [Oct 8] @TestTemplate rename blast radius - fixed in the description; the user-facing section now spells out testMethod[H2 Backend], the loss of test history and the --tests filter change.
  • [Sep 29] Orphan-lane guard - still in place and still the right shape (TestCoreLaneMembership), with one residual hole; see the nit on that file.
  • [Sep 29] coreTestLaneOf nested-class verdict - obsolete, the task was removed in the cut. docs/how-to-test.md:51-57 replaces it with a --tests probe, which is accurate: Gradle's filter fails on no match by default.

I also re-checked the three tests moved out of TestJobMetaService: JobMetaService.deleteJob calls parseJobRunId first (JobMetaService.java:163-164) and getJobPO parses before touching SessionUtils (:186-193), and parseJobRunId throws NoSuchEntityException on a malformed id, so TestJobMetaServiceValidation really needs no backend. No unused imports are left behind in TestJobMetaService (NoSuchEntityException, JobHandle, NameIdentifierUtil are all still used). Of the 38 direct subclasses of TestJDBCBackend, none still declares a plain @Test method and none has a further subclass, so that cleanup is complete rather than partial.

Tests: adequate for the new logic. TestBackendTestSelector covers absent/normalized/invalid selection and the storageProvider filter; TestCoreLaneMembership covers the orphan-class case. What is still unpinned is the Gradle wiring: nothing fails if the h2 lane stops picking up Docker-tagged classes (the question below), and the orphan guard reads class-level tags only (the nit below).

One caveat on the evidence. The four-lane run you cite, 38280654558, is on 656282cc6, and its job list still contains core-test-contract plus a "Generate core test manifest" step in each lane - i.e. it validates the lane selection, but not the build.yml on this head, which dropped that job and moved the lane outputs to Gradle's default paths. Every job of that run that was not skipped, coverage included, did succeed, and I traced the current paths by hand: core/build/test-results/<task>, core/build/jacoco/<task>.exec (both Gradle defaults) and build/reports/tests/core/<task> (set by root build.gradle.kts:1019-1021) match what "Validate core test evidence" asserts, and the mixed core/... plus build/... upload keeps the artifact anchored at the workspace root so the coverage job's find -path "*/core/build/jacoco/<task>.exec" resolves. Worth a green run on this head before merge, nothing more.

Not worth their own comments: maxParallelForks = 1 and junit.jupiter.execution.parallel.enabled=false (core/build.gradle.kts:160-161) restate today's defaults - there is no junit-platform.properties anywhere in the repo and Gradle's default is one fork - so they are documentation today and load-bearing only once #13518 turns parallelism on; fine either way, I mention it only because the cut removed other default-restating lines. -PskipITs on the lane jobs is a no-op for :core (only the flink/authorization modules read that property), harmless and consistent with the build job.

Findings that could not be anchored inline: none.

Review drafted with Claude, requested by @jerryshao.

Comment thread core/build.gradle.kts
Comment thread build.gradle.kts
@nevzheng

nevzheng commented Oct 14, 2026 •

Copy link
Copy Markdown
Collaborator Author

@jerryshao thanks for the approval! CI is green on your merge commit. Could you shepherd this to merge? The branch needs an update from main first, and that will dismiss the approval if I push it.

If you'd like any change before merge, could you list it specifically: the file, what to change, and whether it blocks? I'm otherwise treating your three latest comments as non-blocking.

@nevzheng
nevzheng requested a review from jerryshao October 14, 2026 18:31
nevzheng pushed a commit to nevzheng/gravitino that referenced this pull request Oct 14, 2026
Stack: apache#13517 (lanes) -> apache#13518 (fixture reuse) -> apache#13530 (adaptive
forks, one container per worker) -> this change.

apache#13530 gets its wall-clock win from Gradle test-worker forks, but pays
for it with one physical MySQL/PostgreSQL container per fork. This
adds SharedDbContainerService, a Gradle BuildService that starts one
MySQL and one PostgreSQL container for the whole build (via the
docker CLI, since the main Gradle process has no Testcontainers on
its classpath), and provisions a brand-new, uniquely-named database
per DefaultBackendFactory.create() call instead of a fresh container.

Falls back to the existing per-fork-container path
(BaseIT#startAndInitMySQLBackend()/startAndInitPGBackend()) when the
shared-container system properties aren't published, e.g. an IDE run.
apache#13530's own DatabaseIsolation/BackendSetupCallback/BackendResource
reuse-and-poison caching is untouched - this only changes how a
backend is provisioned, not when one is reused.

Reviewed twice independently (two blind Fable passes); both
converged on the same real issues (stale-container cleanup not
covering a killed-daemon leak, a test clobbering a Gradle-owned
system property, under-tested PostgreSQL duplicate-database handling)
plus additional individual findings, all fixed:
- Stale-container cleanup now tracks the owning Gradle daemon's PID
  and reaps a still-running container only when that process is dead,
  instead of never reaping a running container at all.
- SharedDbContainerService's docker-CLI parsing (host-port parsing,
  line splitting, PID-liveness check) extracted to static methods and
  unit-tested without needing Docker.
- DatabaseProvisioning's PostgreSQL exists-check/duplicate-tolerant
  create sequence extracted to Statement-level methods and
  unit-tested with Mockito (42P04 handling, non-duplicate rethrow).
- Fixed a test that cleared org.gradle.test.worker instead of saving
  and restoring it, matching the sibling telemetry test's pattern.
- Documented the 63-char name cap and exception-type change for
  existing MySQLContainer/PostgreSQLContainer callers.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_015k3E9XykKTzbTWaBPwqTyE
@jerryshao
jerryshao merged commit 105fe71 into apache:main Oct 15, 2026
44 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.

[Subtask] Split core tests into explicit database lanes

3 participants