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 <[email protected]> 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) + } +}
