Hi Robert, Sorry for the delayed response. I agree with the detailed rationale that you exposed here and think this is a very nice addition to the Polaris project. I also reviewed your PR [4732] and just approved.
Looking forward to using the new server runner! Thanks, Alex On Mon, Jun 15, 2026 at 12:05 PM Robert Stupp <[email protected]> wrote: > > Hi all, > > I wanted to give some context for PR #4732, which adds the > `gradle/server-test-runner` plugin, and for follow-up work that migrates > some Spark, Ranger, OPA, and runtime Spark integration tests onto it. > > The short version: > This is not intended to introduce another general testing framework. > It is a small Gradle utility for one recurring Polaris test problem: > Some integration tests need to exercise a real Polaris server, but they > should not have to become Quarkus application tests themselves. > > Today, tests in areas like Spark, Ranger, and OPA tend to blur two concerns: > > 1. building and booting a Polaris server; and > 2. running client/extension-specific integration tests against that server. > > That coupling has a few practical costs: > > - Test modules often need Quarkus test/application wiring even when the > test is > really about a client, extension, Spark runtime, or authorization > integration. > - Quarkus dependencies and BOM constraints can leak into test classpaths > that are > already sensitive, especially Spark/Iceberg/Hadoop/Ranger classpaths. > - Moving integration tests into the module that owns the behavior is harder, > because the module may then need to carry extra Quarkus runtime-test > shape. > - We build more Quarkus test applications than we need, which hurts > feedback time. > - IntelliJ and Gradle model the tests less cleanly than ordinary > `JvmTestSuite` > based integration tests. > > The plugin addresses that by externalizing the Polaris server lifecycle > from the test JVM. > A test task declares a server artifact, usually the `quarkus-run.jar` from > `:polaris-server`: > > ```kotlin > dependencies { > polarisServer(project(path = ":polaris-server", configuration = > "quarkusRunner")) > } > > tasks.named<Test>("intTest") { > withPolarisServer(configurations.polarisServer) { > > systemProperties.put("polaris.features.\"ALLOW_INSECURE_STORAGE_TYPES\"", > "true") > } > } > ``` > > At execution time, the plugin starts that server in a separate JVM with > random HTTP and management ports, waits until Quarkus reports the listen > URLs, passes the ports into the test JVM via system properties, and stops > the server when the test task finishes. > The tests themselves stay ordinary JVM tests. > > This gives us a cleaner split: > > - `:polaris-server` remains responsible for building a runnable Polaris > server. > - Spark/Ranger/OPA tests can live with the code they exercise. > - The test JVM only gets the dependencies it needs as a client/test. > - The server JVM gets Polaris runtime dependencies and configuration. > - Each test task can have an isolated server instance and isolated config. > > Follow-up PRs would then: > > - Spark integration tests move into the Spark projects and run against a > Polaris server artifact. > - Ranger integration tests live in the Ranger extension. > - OPA integration tests live in the OPA extension. > - Runtime Spark tests still add task-specific setup, such as RustFS/Hive > support, without baking those details into the plugin. > > The startup-action SPI is deliberately small. > It exists for test tasks that need to start external resources or compute > dynamic server configuration before Polaris starts. > For example, OPA can start an OPA container, and runtime Spark tests > can prepare RustFS/Hive-specific properties. > The action is loaded from an isolated classpath and only receives mutable > server system properties/environment plus string parameters. > > Why a Gradle plugin instead of repeated build-script snippets? > > The lifecycle has enough sharp edges that copying it into each module would > be worse: > > - selecting exactly one runnable server artifact; > - starting the process with `quarkus.http.port=0` and > `quarkus.management.port=0`; > - detecting the actual ports from Quarkus output; > - passing those ports to the test JVM; > - cleaning up the process on normal completion and build-service cleanup; > - declaring inputs for server config/startup action classpaths; > - avoiding ad hoc task hooks in every consuming module. > > Centralizing that in one small plugin gives us one place to test and review > those details. > > There is a tradeoff: > This adds custom Gradle build logic. > I think that tradeoff is reasonable because the alternative is not "no > complexity"; the alternative is the same lifecycle complexity spread across > Spark, Ranger, OPA, and future server-backed integration tests. > Keeping it under `gradle/server-test-runner` also makes the scope explicit: > It is internal build/test infrastructure, not a Polaris user-facing API. > > I also do not think this should replace all Quarkus tests. > Tests that are specifically validating CDI wiring, Quarkus test resources, > augmentation-time > configuration, or in-process service behavior should stay as Quarkus tests. > The server runner is for tests whose natural boundary is "start a Polaris > server and interact with it as an external client or extension integration." > > My proposed direction is: > > 1. Keep the plugin in PR #4732 as a focused internal test utility. > 2. Use it for the migrated Spark/Ranger/OPA/runtime Spark integration tests > where > the separation is clear. > 3. Avoid expanding it into a general process-runner abstraction. > 4. Keep startup hooks narrow and task-specific. > 5. Continue to use Quarkus tests where in-process Quarkus behavior is what > we are > actually testing. > > I think this gives us better module ownership, cleaner dependency > boundaries, and > faster feedback without changing the Polaris runtime behavior under test. > > Thoughts?
