You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by th...@apache.org on 2026/10/03 12:12:06 UTC
(tapestry-5) 01/03: TAP5-2809: Gradle integration test convention use mutex
This is an automated email from the ASF dual-hosted git repository.
thiagohp pushed a commit to branch javax
in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit c7937aa2e65a3d205144b3564f2e7e003253a070
Author: Ben Weidig <be...@netzgut.net>
AuthorDate: Sun Jul 26 14:29:24 2026 +0200
TAP5-2809: Gradle integration test convention use mutex
---
buildSrc/README.md | 21 +++++++++++++++++++++
.../main/groovy/t5build/IntegrationTestMutex.groovy | 20 ++++++++++++++++++++
.../groovy/tapestry.testing-base-convention.gradle | 13 +++++++++++++
.../main/groovy/tapestry.testng-convention.gradle | 15 +++++++++++++++
4 files changed, 69 insertions(+)
diff --git a/buildSrc/README.md b/buildSrc/README.md
index d5c83b7f8..e09ca7200 100644
--- a/buildSrc/README.md
+++ b/buildSrc/README.md
@@ -46,6 +46,16 @@ Configures every `Test` task in the project via `tasks.withType(Test)`:
* Standard system properties: file encoding, locale, CI flag, Selenium wait timeout
* Consistent test logging: full exception format, pass/skip/fail events, progress counter
+Integration tests bind 9090/8443 by default (`@TapestryTestConfiguration`).
+To move a single run off those ports, when something else on the machine already holds them:
+
+```bash
+./gradlew :tapestry-jpa:testNG -Dtapestry.port=9191 -Dtapestry.ssl-port=8544
+```
+
+Gradle's `-D` only reaches the daemon, so the convention forwards these two to the test workers.
+`SeleniumTestCase` reads them as a fallback when `testng.xml` declares no such parameter.
+
### `tapestry.junit5-convention`
Use when the module's test sources use JUnit 5 Jupiter (`@Test`, `@ExtendWith`, ...).
@@ -131,6 +141,17 @@ The `testng.xml` suite file for these modules should contain **only** integratio
Unit tests are handled by the `test` task via testng-engine by Jupiter and must not appear in `testng.xml`.
+The task **must** be named `testNG`: the convention gives every task with that name a permit on the
+`integrationTestMutex` build service, described below.
+A differently named task gets no permit and will race the others for the ports.
+
+#### Serializing integration tests
+
+Every Selenium suite binds the same ports, so no two may run at once.
+Rather than disabling parallel builds (not a simple flag on project-level), the convention registers
+an `IntegrationTestMutex` build service with `maxParallelUsages = 1` and declares it on each `testNG` task.
+Gradle then admits one integration test task at a time while compilation, asset generation and the unit test suites still run in parallel.
+
---
## Helper classes (`t5build` package)
diff --git a/buildSrc/src/main/groovy/t5build/IntegrationTestMutex.groovy b/buildSrc/src/main/groovy/t5build/IntegrationTestMutex.groovy
new file mode 100644
index 000000000..245bcc743
--- /dev/null
+++ b/buildSrc/src/main/groovy/t5build/IntegrationTestMutex.groovy
@@ -0,0 +1,20 @@
+package t5build
+
+import org.gradle.api.services.BuildService
+import org.gradle.api.services.BuildServiceParameters
+
+/**
+ * A build service with no behaviour, used purely as a mutex.
+ *
+ * <p>Selenium integration tests bind fixed ports (9090/8443 by default, see
+ * {@code @TapestryTestConfiguration}), so two modules testing at the same time fight over one
+ * socket and the loser dies with "Failed to bind".
+ * <p>Registering this service with {@code maxParallelUsages = 1} and declaring it on
+ * the integration test tasks tells Gradle to allow only one of them at a time, whereas
+ * compilation, asset generation and the unit test suites carry on in parallel.
+ *
+ * @see tapestry.testng-convention.gradle
+ */
+abstract class IntegrationTestMutex implements BuildService<BuildServiceParameters.None>
+{
+}
\ No newline at end of file
diff --git a/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle b/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle
index c4f1a1f06..6c3595490 100644
--- a/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle
+++ b/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle
@@ -38,6 +38,19 @@ tasks.withType(Test).configureEach { testTask ->
'selenium.wait.timeout': System.getProperty('selenium.wait.timeout', '15')
)
+ // Integration tests default to 9090/8443 (see @TapestryTestConfiguration).
+ // The integration test mutex in tapestry.testng-convention keeps two suites from wanting them at the same time.
+ // These let a single run move off the defaults when something else on the machine holds them:
+ // ./gradlew :tapestry-jpa:testNG -Dtapestry.port=9191 -Dtapestry.ssl-port=8544
+ // Gradle's -D only reaches the daemon, so they have to be forwarded to the workers explicitly.
+ // SeleniumTestCase reads them as a fallback when testng.xml declares no such parameter.
+ ['tapestry.port', 'tapestry.ssl-port'].each { key ->
+ def value = System.getProperty(key)
+ if (value != null) {
+ systemProperty key, value
+ }
+ }
+
environment.LANG = 'en_US.UTF-8'
testLogging {
diff --git a/buildSrc/src/main/groovy/tapestry.testng-convention.gradle b/buildSrc/src/main/groovy/tapestry.testng-convention.gradle
index 083069931..70d0c7636 100644
--- a/buildSrc/src/main/groovy/tapestry.testng-convention.gradle
+++ b/buildSrc/src/main/groovy/tapestry.testng-convention.gradle
@@ -14,6 +14,8 @@
// tapestry.testing-base-convention platform runner, BOM, logging
// └─ tapestry.testng-convention + TestNG + EasyMock + testng-engine (this file)
+import t5build.IntegrationTestMutex
+
plugins {
id 'tapestry.testing-base-convention'
}
@@ -23,3 +25,16 @@ dependencies {
testImplementation libs.easymock
testRuntimeOnly libs.testng.engine
}
+
+// Every Selenium suite binds the same ports, so no two of them may run at once.
+// Gradle's build services express exactly that: one permit, handed to one integration
+// test task at a time, while everything else in the build still runs in parallel.
+def integrationTestMutex = gradle.sharedServices.registerIfAbsent('integrationTestMutex', IntegrationTestMutex) {
+ maxParallelUsages = 1
+}
+
+tasks.withType(Test).configureEach {
+ if (name == 'testNG') {
+ usesService(integrationTestMutex)
+ }
+}