This is an automated email from the ASF dual-hosted git repository. benw pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
commit 1f3dd17f2a9c9f28bf165a555d30305e84a07591 Author: Ben Weidig <[email protected]> AuthorDate: Wed Apr 15 09:28:07 2026 +0200 TAP5-2809: run TestNG unit tests via Jupiter, integration tests native, more cleanup As using both JUnit and TestNG in the same project can be troublesome, we now created a deliberate split at integration testing level. Unit tests are run via the Jupiter testng-runner, whereas integration tests are still run with "native" TestNG, as SeleniumTestCase uses TestNG-only feature for its lifecycles. Furthermore, the Gradle files have been cleaned up and added some documentation to improve maintainability. --- Jenkinsfile | 25 ++-- Jenkinsfile.integration-variants | 2 +- build.gradle | 63 +++++----- buildSrc/README.md | 139 +++++++++++++++++++++ buildSrc/src/main/groovy/t5build/SSshExec.groovy | 21 ---- buildSrc/src/main/groovy/t5build/Scp.groovy | 36 ------ buildSrc/src/main/groovy/t5build/SshTask.groovy | 94 -------------- .../main/groovy/t5build/TapestryBuildLogic.groovy | 22 ---- .../main/groovy/tapestry.java-convention.gradle | 14 ++- .../tapestry.junit4-legacy-convention.gradle | 8 ++ .../main/groovy/tapestry.junit5-convention.gradle | 14 ++- .../groovy/tapestry.junit5-spock-convention.gradle | 4 + .../src/main/groovy/tapestry.ssh-convention.gradle | 15 --- .../groovy/tapestry.testing-base-convention.gradle | 25 +++- .../main/groovy/tapestry.testng-convention.gradle | 41 +++--- gradle/libs.versions.toml | 8 +- quickstart/build.gradle | 8 +- settings.gradle | 5 +- tapestry-beanvalidator/build.gradle | 17 ++- tapestry-cdi/build.gradle | 1 - tapestry-core/build.gradle | 127 ++++++++----------- tapestry-core/src/test/resources/testng.xml | 44 ------- tapestry-hibernate-core/build.gradle | 2 +- tapestry-hibernate/build.gradle | 17 ++- tapestry-hibernate/src/test/resources/testng.xml | 5 - tapestry-http/build.gradle | 17 ++- tapestry-http/src/test/resources/testng.xml | 11 ++ tapestry-ioc/build.gradle | 2 +- tapestry-jmx/build.gradle | 15 +++ tapestry-jpa/build.gradle | 15 +++ .../jpa/JpaTransactionAdvisorImplTest.java | 27 ++-- .../org/apache/tapestry5/jpa/test/JpaTest.java | 17 +-- tapestry-kaptcha/build.gradle | 15 +++ .../kaptcha/components/KaptchaUnitTest.java | 4 +- tapestry-kaptcha/src/test/resources/testng.xml | 1 - tapestry-spring/build.gradle | 17 ++- tapestry-spring/src/test/resources/testng.xml | 6 - tapestry-test/build.gradle | 15 +++ tapestry-upload/build.gradle | 15 +++ tapestry-upload/src/test/resources/testng.xml | 2 - tapestry-webresources/build.gradle | 18 ++- 41 files changed, 515 insertions(+), 439 deletions(-) diff --git a/Jenkinsfile b/Jenkinsfile index 1965762bd..c3689bff7 100644 --- a/Jenkinsfile +++ b/Jenkinsfile @@ -2,7 +2,7 @@ pipeline { agent none environment { - GRADLE_OPTS = '-Dci=true -Dfile.encoding=UTF-8 -Dselenium.wait.timeout=30' + GRADLE_OPTS = '-Dci=true -Dfile.encoding=UTF-8 -Dselenium.wait.timeout=60' } options { @@ -20,7 +20,11 @@ pipeline { } } - agent { node { label 'ubuntu' } } + agent { + node { + label 'ubuntu' + } + } tools { jdk "${JDK_VERSION}" @@ -47,7 +51,14 @@ pipeline { } post { always { - // Copy all reports and test results into a matrix-named folder + // JUnit runs inside a matrix cell, so Jenkins automatically scopes the results + junit( + testResults: "**/build/test-results/**/*.xml", + allowEmptyResults: true + ) + + // archiveArtifacts has no per-cell namespacing, so we copy them into a + // JDK-named folder first to avoid cells overwriting each other sh """ find . \\( -path '*/build/reports' -o -path '*/build/test-results' \\) \ -not -path './build/matrix-artifacts/*' -type d | while IFS= read -r src; do @@ -56,14 +67,6 @@ pipeline { cp -r -- "\$src/." "\$dest/" done """ - - // Point JUnit to the XMLs inside the new folder - junit( - testResults: "build/matrix-artifacts/${JDK_VERSION}/**/test-results/**/*.xml", - allowEmptyResults: true - ) - - // Archive matrix artifacts archiveArtifacts( artifacts: "build/matrix-artifacts/${JDK_VERSION}/**/*", allowEmptyArchive: true diff --git a/Jenkinsfile.integration-variants b/Jenkinsfile.integration-variants index 6108572a5..011a1b477 100644 --- a/Jenkinsfile.integration-variants +++ b/Jenkinsfile.integration-variants @@ -5,7 +5,7 @@ pipeline { environment { // Disable the daemon to prevent parallal issues - GRADLE_OPTS = '-Dci=true -Dfile.encoding=UTF-8 -Dorg.gradle.daemon=false' + GRADLE_OPTS = '-Dci=true -Dfile.encoding=UTF-8 -Dselenium.wait.timeout=60 -Dorg.gradle.daemon=false' } tools { diff --git a/build.gradle b/build.gradle index b174ebceb..7c35bf751 100755 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,4 @@ import t5build.GenerateChecksums -import t5build.TapestryBuildLogic import org.apache.tools.ant.filters.ReplaceTokens plugins { @@ -43,15 +42,16 @@ ext { 'tapestry-core', ] + // Set via GRADLE_OPTS='-Dci=true' in CI (see Jenkinsfile). + // Controls version suffix, deploy target, and if integration tests will run headless continuousIntegrationBuild = Boolean.getBoolean('ci') + stagingUrl = 'https://repository.apache.org/service/local/staging/deploy/maven2/' snapshotUrl = 'https://repository.apache.org/content/repositories/snapshots' doSign = !project.hasProperty('noSign') && project.hasProperty('signing.keyId') // apacheDeployUserName and apacheDeployPassword should be specified in ~/.gradle/gradle.properties - - def deployFlavor = TapestryBuildLogic.isSnapshot(project) ? 'snapshot' : 'apache' - + def deployFlavor = continuousIntegrationBuild ? 'snapshot' : 'apache' deployUsername = providers.gradleProperty("${deployFlavor}DeployUserName") deployPassword = providers.gradleProperty("${deployFlavor}DeployPassword") archiveDeployFolder = providers.gradleProperty('apacheArchivesFolder') @@ -64,18 +64,18 @@ ext { } // Provided so that the CI server can override the normal version number for nightly builds. -version = TapestryBuildLogic.tapestryVersion(project) +version = continuousIntegrationBuild ? tapestryMajorVersion + '-SNAPSHOT' : tapestryMajorVersion + tapestryMinorVersion // Specific to top-level build, not set for subprojects: configurations { javadoc - published.extendsFrom archives, meta + published.extendsFrom archives, meta if (doSign) { published.extendsFrom signatures } - binaries // additional dependencies included in the binary archive + binaries } dependencies { @@ -127,9 +127,8 @@ subprojects { } configurations { - // published -- what gets uploaded to the Nexus repository + // published: what gets uploaded to the Nexus repository published.extendsFrom archives, meta - if (rootProject.doSign) { published.extendsFrom signatures } @@ -147,8 +146,9 @@ subprojects { pom { name = project.name - // TODO: find some way to get the subproject description here. - // description = + description = providers.provider { + project.description + } url = 'https://tapestry.apache.org/' licenses { license { @@ -159,13 +159,12 @@ subprojects { scm { connection = 'scm:git:https://gitbox.apache.org/repos/asf/tapestry-5.git' developerConnection = 'scm:git://gitbox.apache.org/repos/asf/tapestry-5.git' - url = 'https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git;a=summary' + url = 'https://git-wip-us.apache.org/repos/asf?p=tapestry-5.git' } // Changes the generated pom.xml so its dependencies on suffixed artifacts // get properly updated with suffixed artifact ids withXml { - def artifactIdQName = new groovy.namespace.QName( - 'http://maven.apache.org/POM/4.0.0', 'artifactId') + def artifactIdQName = new groovy.namespace.QName('http://maven.apache.org/POM/4.0.0', 'artifactId') def node = asNode() def dependencies = node.get('dependencies')[0] if (dependencies != null) { @@ -180,6 +179,7 @@ subprojects { } } } + if (canDeploy) { repositories { mavenLocal() @@ -206,8 +206,9 @@ subprojects { } } + // Modules not in suffixedArtifactNames have no -jakarta counterpart + // and should not be published when artifactSuffix is empty (i.e. the javax branch) def actuallyPublish = !artifactSuffix.isEmpty() || suffixedArtifactNames.contains(project.name) - // println 'XXXXXX Actually publish? ' + actuallyPublish + ' project ' + project.name + ' ' + '!artifactSuffix.isEmpty() ' + !artifactSuffix.isEmpty() if (!actuallyPublish) { tasks.withType(PublishToMavenRepository).configureEach { it.enabled = false @@ -230,7 +231,7 @@ subprojects { // Cribbed from https://github.com/hibernate/hibernate-core/blob/master/release/release.gradle#L19 tasks.register('aggregateJavadoc', Javadoc) { - group = 'Documentation' + group = 'documentation' description = 'Build the aggregated JavaDocs for all modules' dependsOn configurations.javadoc @@ -299,14 +300,14 @@ tasks.register('aggregateJavadoc', Javadoc) { } tasks.register('typeScriptDocs') { - group = 'Documentation' + group = 'documentation' description = 'Builds typedoc documentation for all TypeScript sources' dependsOn(':tapestry-core:generateTypeScriptDocs') } tasks.register('combinedJacocoReport', JacocoReport) { - group = 'Verification' + group = 'verification' description = 'Generates combined JaCoCo coverage report for all subprojects' def excludedProjects = [ @@ -325,7 +326,7 @@ tasks.register('combinedJacocoReport', JacocoReport) { def subprojectsToConsider = subprojects.findAll { !excludedProjects.contains(it.name) } - dependsOn subprojectsToConsider.test + dependsOn subprojectsToConsider.collect { it.tasks.named('check') } def classFiles = files(subprojectsToConsider.sourceSets.main.output).asFileTree.matching { exclude 'org/apache/tapestry5/internal/plastic/asm/**' @@ -334,9 +335,12 @@ tasks.register('combinedJacocoReport', JacocoReport) { additionalSourceDirs.from(files(subprojectsToConsider.sourceSets.main.allSource.srcDirs)) sourceDirectories.from(files(subprojectsToConsider.sourceSets.main.allSource.srcDirs)) - executionData.from(files(subprojectsToConsider.jacocoTestReport.executionData).filter { - it.exists() + // Glob picks up all exec files regardless of task name: test.exec, testNG.exec, etc. + executionData.from(subprojectsToConsider.collect { p -> + p.fileTree(p.layout.buildDirectory) { include 'jacoco/*.exec' } }) + + // Reuse the JaCoCo agent classpath that each subproject's jacocoTestReport task already resolved. jacocoClasspath = files(subprojectsToConsider.jacocoTestReport.jacocoClasspath) reports { @@ -353,7 +357,7 @@ tasks.register('combinedJacocoReport', JacocoReport) { } tasks.register('continuousIntegration') { - group = 'Verification' + group = 'verification' description = 'Runs a full CI build: assembles all artifacts, runs all checks, and generates aggregate reports.' def dependants = [ @@ -361,20 +365,16 @@ tasks.register('continuousIntegration') { 'tapestry-core:testWithJqueryAndRequireJsDisabled', 'tapestry-core:testWithPrototypeAndRequireJsEnabled', subprojects.check, // jQuery and Require.js enabled - combinedJacocoReport + combinedJacocoReport, + aggregateJavadoc ] - // tapestry-javadoc doesn't work with Java 8 anymore. That's why it's only added if != 8. - if (JavaVersion.current() != JavaVersion.VERSION_1_8) { - dependants << aggregateJavadoc - } - dependsOn(dependants) } tasks.register('zippedSources', Zip) { - group = 'Release artifact' + group = 'release artifact' description = "Creates a combined Zip file of all sub-project's sources" dependsOn 'tapestry-core:compileTypeScript' @@ -399,7 +399,7 @@ tasks.register('zippedSources', Zip) { } tasks.register('zippedApidoc', Zip) { - group = 'Release artifact' + group = 'release artifact' description = "Zip archive of the project's aggregate JavaDoc and TypeScript documentation" dependsOn typeScriptDocs @@ -470,7 +470,6 @@ if (canDeploy) { uploads.extendsFrom archives, signatures } - artifacts { archives zippedApidoc, zippedSources, zippedBinaries } @@ -520,7 +519,7 @@ if (canDeploy) { } tasks.register('updateBootstrap') { - group = 'Maintenance' + group = 'maintenance' description = 'Updates the included Bootstrap dependencies from GitHub' doLast { diff --git a/buildSrc/README.md b/buildSrc/README.md new file mode 100644 index 000000000..d5c83b7f8 --- /dev/null +++ b/buildSrc/README.md @@ -0,0 +1,139 @@ +# buildSrc: Tapestry Build Conventions + +Gradle convention plugins and build utilities shared across all subprojects. +All convention files live in `src/main/groovy/` as precompiled script plugins. + +--- + +## Convention hierarchy + +```text +tapestry.java-convention applied to every subproject by root build.gradle + └─ tapestry.testing-base-convention JUnit Platform runner, shared test config + ├─ tapestry.junit5-convention + Jupiter API + engine + │ ├─ tapestry.junit5-spock-convention + Spock BOM / spock-core + │ └─ tapestry.junit4-legacy-convention + Vintage engine (JUnit 4 tests) + └─ tapestry.testng-convention + TestNG + EasyMock + testng-engine +``` + +--- + +## Conventions + +### `tapestry.java-convention` + +Applied automatically to every subproject by the root `build.gradle`. + +* Java 11 source/target compatibility +* `provided` configuration (compile-only scope, like Maven `<scope>provided</scope>`) +* Dependency version constraints to keep library versions consistent across modules +* JAR manifest: `Automatic-Module-Name` (JPMS) and `LICENSE`/`NOTICE` in `META-INF` + +--- + +### `tapestry.testing-base-convention` + +Foundation for all test conventions. + +**Do not apply directly** + +Use one of the specialized conventions below. + +Configures every `Test` task in the project via `tasks.withType(Test)`: + +* JUnit Platform as the runner (required by all engines: Jupiter, TestNG, Vintage) +* `junit-platform-launcher` and the JUnit BOM on the runtime classpath +* Standard system properties: file encoding, locale, CI flag, Selenium wait timeout +* Consistent test logging: full exception format, pass/skip/fail events, progress counter + +### `tapestry.junit5-convention` + +Use when the module's test sources use JUnit 5 Jupiter (`@Test`, `@ExtendWith`, ...). + +Adds Jupiter API to the test compile classpath and the Jupiter engine at runtime. + +```groovy +plugins { + id 'tapestry.junit5-convention' +} +``` + +### `tapestry.junit5-spock-convention` + +Use when the module writes tests in Spock (Groovy BDD framework). + +Extends `junit5-convention` with the Spock BOM and `spock-core`. +Spock runs on the JUnit Platform — no additional runner config needed. + +```groovy +plugins { + id 'tapestry.junit5-spock-convention' +} +``` + +### `tapestry.junit4-legacy-convention` + +Use **only** for modules that still have JUnit 4 test sources (`@RunWith`, `@Rule`, ...). + +Extends `junit5-convention` with the JUnit Vintage engine, so legacy tests run alongside any Jupiter tests without touching test source code. +Prefer migrating tests over adding this convention to new modules. + +```groovy +plugins { + id 'tapestry.junit4-legacy-convention' +} +``` + +### `tapestry.testng-convention` + +Use when the module's test sources use TestNG annotations (`@Test`, `@BeforeMethod`, `@DataProvider`, ...). + +Adds TestNG and EasyMock to the test compile classpath. +The `testng-engine` dependency at runtime lets the standard `test` task (JUnit Platform) discover and run TestNG unit tests automatically. + +```groovy +plugins { + id 'tapestry.testng-convention' +} +``` + +#### Modules with Selenium integration tests + +When a module has both TestNG unit tests and Selenium integration tests, _native_ TestNG **must** be used for the integration tests! + +This is because Selenium tests currently rely on `@BeforeTest` / `ITestContext` scoping that only works correctly with the native TestNG runner and a `testng.xml` suite file. +Each `<test>` element gets its own server instance with the right webapp. + +The required additions to the module's `build.gradle`: + +```groovy +// Exclude integration classes from the JUnit Platform 'test' task +tasks.named('test') { + exclude '**/integration/**' // adjust pattern to match your integration tests +} + +// Native TestNG task: uses testng.xml for correct ITestContext grouping +tasks.register('testNG', Test) { + group = 'verification' + + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +// Include in the check lifecycle +tasks.named('check') { + dependsOn 'testNG' +} +``` + +The `testng.xml` suite file for these modules should contain **only** integration `<test>` elements. + +Unit tests are handled by the `test` task via testng-engine by Jupiter and must not appear in `testng.xml`. + +--- + +## Helper classes (`t5build` package) + +**`GenerateChecksums`**: custom Gradle task type that generates MD5/SHA-256 checksum +files for release archives. diff --git a/buildSrc/src/main/groovy/t5build/SSshExec.groovy b/buildSrc/src/main/groovy/t5build/SSshExec.groovy deleted file mode 100644 index e985b4591..000000000 --- a/buildSrc/src/main/groovy/t5build/SSshExec.groovy +++ /dev/null @@ -1,21 +0,0 @@ -package t5build - -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.TaskAction - -class SshExec extends SshTask { - - @Input - List<String[]> commandLines = [] - - void commandLine(String... commandLine) { - commandLines << commandLine - } - - @TaskAction - void doActions() { - commandLines.each { commandLine -> - ssh(*commandLine) - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/t5build/Scp.groovy b/buildSrc/src/main/groovy/t5build/Scp.groovy deleted file mode 100644 index 3280cfb9d..000000000 --- a/buildSrc/src/main/groovy/t5build/Scp.groovy +++ /dev/null @@ -1,36 +0,0 @@ -package t5build - -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.SkipWhenEmpty -import org.gradle.api.tasks.TaskAction -import java.io.File - -class Scp extends SshTask { - - @InputFiles @SkipWhenEmpty - def source - - @Input - String destination - - @Input - boolean isDir = false - - @TaskAction - void doActions() { - if (isDir) { - scpDir(source, destination) - return - } - project.files(source).each { doFile(it) } - } - - private void doFile(File file) { - if (file.isDirectory()) { - file.eachFile { doFile(it) } - } else { - scpFile(file, destination) - } - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/t5build/SshTask.groovy b/buildSrc/src/main/groovy/t5build/SshTask.groovy deleted file mode 100644 index 769b32adf..000000000 --- a/buildSrc/src/main/groovy/t5build/SshTask.groovy +++ /dev/null @@ -1,94 +0,0 @@ -package t5build - -import org.gradle.api.DefaultTask -import org.gradle.api.file.FileCollection -import org.gradle.api.logging.LogLevel -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal - -abstract class SshTask extends DefaultTask { - - @InputFiles - FileCollection sshAntClasspath - - @Input - String host - - @Input - String userName - - // TODO: Passwords should not be plain @Input. - @Input - String password - - @Input - boolean verbose = false - - private boolean antInited = false - - protected void initAnt() { - if (antInited) { - return - } - ant.taskdef(name: 'scp', - classname: 'org.apache.tools.ant.taskdefs.optional.ssh.Scp', - classpath: sshAntClasspath.asPath, - loaderref: 'ssh') - - ant.taskdef(name: 'sshexec', - classname: 'org.apache.tools.ant.taskdefs.optional.ssh.SSHExec', - classpath: sshAntClasspath.asPath, - loaderref: 'ssh') - antInited = true - } - - protected void withInfoLogging(Closure action) { - def oldLogLevel = getLogging().getLevel() - getLogging().setLevel([LogLevel.INFO, oldLogLevel].min()) - try { - action() - } finally { - if (oldLogLevel != null) { - getLogging().setLevel(oldLogLevel) - } - } - } - - protected void scpFile(Object source, String destination) { - initAnt() - withInfoLogging { - // TODO: This keyfile is hardcoded and uses an old algorithm (dsa) - ant.scp(localFile: project.files(source).singleFile, - remoteToFile: "${userName}@${host}:${destination}", - keyfile: "${System.properties['user.home']}/.ssh/id_dsa", - verbose: verbose) - } - } - - protected void scpDir(Object source, String destination) { - initAnt() - withInfoLogging { - ant.sshexec(host: host, - username: userName, - password: password, - command: "mkdir -p ${destination}") - - ant.scp(remoteTodir: "${userName}@${host}:${destination}", - keyfile: "${System.properties['user.home']}/.ssh/id_dsa", - verbose: verbose) { - project.files(source).addToAntBuilder(ant, 'fileSet', FileCollection.AntType.FileSet) - } - } - } - - protected void ssh(Object... commandLine) { - initAnt() - withInfoLogging { - ant.sshexec(host: host, - username: userName, - password: password, - command: commandLine.join(' ')) - } - } -} diff --git a/buildSrc/src/main/groovy/t5build/TapestryBuildLogic.groovy b/buildSrc/src/main/groovy/t5build/TapestryBuildLogic.groovy deleted file mode 100644 index 13f9b935a..000000000 --- a/buildSrc/src/main/groovy/t5build/TapestryBuildLogic.groovy +++ /dev/null @@ -1,22 +0,0 @@ -package t5build - -import org.gradle.api.Project - -class TapestryBuildLogic { - - static boolean isSnapshot(Project project) { - return tapestryVersion(project).endsWith('SNAPSHOT') - } - - static boolean isWindows() { - return System.properties['os.name'].toLowerCase().contains('windows') - } - - static String tapestryVersion(Project project) { - String major = project.rootProject.ext.tapestryMajorVersion - String minor = project.rootProject.ext.tapestryMinorVersion - - boolean isCiBuild = project.rootProject.hasProperty('continuousIntegrationBuild') && project.rootProject.continuousIntegrationBuild - return isCiBuild ? major + '-SNAPSHOT' : major + minor - } -} \ No newline at end of file diff --git a/buildSrc/src/main/groovy/tapestry.java-convention.gradle b/buildSrc/src/main/groovy/tapestry.java-convention.gradle index 974633a7c..862bef814 100644 --- a/buildSrc/src/main/groovy/tapestry.java-convention.gradle +++ b/buildSrc/src/main/groovy/tapestry.java-convention.gradle @@ -1,3 +1,9 @@ +// Base convention applied to every subproject in root build.gradle via `subprojects {}` +// - Java 11 source/target compatibility +// - 'provided' configuration (compile-only scope, like Maven <scope>provided</scope>) +// - Dependency version constraints to keep the classpath consistent across modules +// - JAR manifest: Automatic-Module-Name for JPMS, LICENSE/NOTICE in META-INF + plugins { id 'java-library' id 'eclipse' @@ -17,7 +23,10 @@ tasks.withType(JavaCompile).configureEach { } configurations { + // 'provided': on compile + test classpaths but excluded from the runtime jar/war. provided + + // 'meta': collects artifacts (e.g. sourcesJar) that should be published alongside the main jar. meta } @@ -45,7 +54,7 @@ dependencies { implementation libs.commons.codec implementation libs.commons.io implementation libs.commons.logging - implementation("${libs.hsqldb.get().module.group}:${libs.hsqldb.get().module.name}:${libs.hsqldb.get().version}:jdk8") + implementation libs.hsqldb implementation libs.xml.apis } } @@ -57,7 +66,8 @@ tasks.withType(Jar).configureEach { into 'META-INF' } - // JPMS compatibility + // Stable module name for JPMS (e.g. tapestry-ioc → org.apache.tapestry.ioc). + // Keeps the module name consistent even if the JAR file is renamed. manifest { attributes("Automatic-Module-Name": "org.apache.tapestry.${project.name}" .replace('tapestry-', '') diff --git a/buildSrc/src/main/groovy/tapestry.junit4-legacy-convention.gradle b/buildSrc/src/main/groovy/tapestry.junit4-legacy-convention.gradle index ac37c9b3a..fc7e39272 100644 --- a/buildSrc/src/main/groovy/tapestry.junit4-legacy-convention.gradle +++ b/buildSrc/src/main/groovy/tapestry.junit4-legacy-convention.gradle @@ -1,3 +1,11 @@ +// Use ONLY for existing modules still using JUnit 4 test sources (@RunWith, @Rule, etc.). +// +// Adds the JUnit Vintage engine so those tests run on the JUnit Platform alongside +// any Jupiter tests in the same module. +// No test source changes required. +// +// Prefer migrating tests to JUnit 5 over adding this convention to new modules. + plugins { id 'tapestry.junit5-convention' } diff --git a/buildSrc/src/main/groovy/tapestry.junit5-convention.gradle b/buildSrc/src/main/groovy/tapestry.junit5-convention.gradle index 59c163eb6..ee2ecdaea 100644 --- a/buildSrc/src/main/groovy/tapestry.junit5-convention.gradle +++ b/buildSrc/src/main/groovy/tapestry.junit5-convention.gradle @@ -1,14 +1,16 @@ +// Use for modules whose test sources use JUnit 5 (Jupiter) annotations (@Test, @ExtendWith, …). +// +// Convention hierarchy: +// tapestry.testing-base-convention platform runner, BOM, logging +// └─ tapestry.junit5-convention + Jupiter API + engine (this file) +// ├─ tapestry.junit5-spock-convention + Spock +// └─ tapestry.junit4-legacy-convention + Vintage engine for JUnit 4 + plugins { id 'tapestry.testing-base-convention' } dependencies { - testImplementation platform(libs.junit.bom) testImplementation libs.bundles.junit.jupiter.essentials testRuntimeOnly libs.junit.jupiter.engine - testRuntimeOnly libs.junit.platform.launcher -} - -tasks.withType(Test).configureEach { - useJUnitPlatform() } diff --git a/buildSrc/src/main/groovy/tapestry.junit5-spock-convention.gradle b/buildSrc/src/main/groovy/tapestry.junit5-spock-convention.gradle index 1570598ef..3c35d1a43 100644 --- a/buildSrc/src/main/groovy/tapestry.junit5-spock-convention.gradle +++ b/buildSrc/src/main/groovy/tapestry.junit5-spock-convention.gradle @@ -1,3 +1,7 @@ +// Use for modules that write tests in Spock (Groovy-based BDD framework). +// Extends tapestry.junit5-convention with the Spock BOM and spock-core. +// Spock runs on the JUnit Platform, so no additional runner config is needed. + plugins { id 'tapestry.junit5-convention' id 'groovy' diff --git a/buildSrc/src/main/groovy/tapestry.ssh-convention.gradle b/buildSrc/src/main/groovy/tapestry.ssh-convention.gradle deleted file mode 100644 index 2f6aa7c8a..000000000 --- a/buildSrc/src/main/groovy/tapestry.ssh-convention.gradle +++ /dev/null @@ -1,15 +0,0 @@ -import t5build.SshTask - -// This configuration will hold the ant-jsch.jar at runtime -configurations { - sshAntTask -} - -dependencies { - sshAntTask libs.ant.jsch -} - -// Configure all SshTask instances to use the files from the configuration -tasks.withType(SshTask).configureEach { - it.sshAntClasspath = configurations.sshAntTask -} diff --git a/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle b/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle index 7a105f7e2..58cff9d52 100644 --- a/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle +++ b/buildSrc/src/main/groovy/tapestry.testing-base-convention.gradle @@ -1,12 +1,29 @@ +// Shared test infrastructure for all testing conventions. +// Applied indirectly via tapestry.junit5-convention and tapestry.testng-convention. +// +// DO NOT USE DIRECTLY! +// +// Configures every Test task in the project (via tasks.withType(Test)): +// - JUnit Platform as the test runner (required by both Jupiter and testng-engine) +// - junit-platform-launcher + JUnit BOM on the runtime classpath +// - Standard system properties: encoding, locale, CI flag, Selenium timeout +// - Consistent test logging (full exception format, pass/skip/fail events, progress counter) +// +// The JUnit BOM ensures all JUnit Platform artifacts (launcher, engines) use the same version, +// even in modules that only use TestNG and never write a single @Test(jupiter) method. + plugins { id 'java-library' } dependencies { + testImplementation platform(libs.junit.bom) testRuntimeOnly libs.slf4j.simple + testRuntimeOnly libs.junit.platform.launcher } tasks.withType(Test).configureEach { testTask -> + useJUnitPlatform() maxHeapSize = '600M' systemProperties( @@ -26,6 +43,8 @@ tasks.withType(Test).configureEach { testTask -> showStandardStreams = true } + // The code below is a helper to output test progress during testing + def total = 0, passed = 0, failed = 0, skipped = 0 afterTest { descriptor, result -> @@ -50,11 +69,11 @@ tasks.withType(Test).configureEach { testTask -> // The root suite has a null parent. We only want to log the final summary. if (descriptor.parent == null) { total = passed + failed + skipped - // Using project.path plus task gives a clear identifier like ":tapestry-core:test" - def taskIdentifier = "${project.path}:${name}" - // Don't log if no tests were run + // Only log if we actually have tested something if (total > 0) { + // Using project.path plus task gives a clear identifier like ":tapestry-core:test" + def taskIdentifier = "${project.path}:${name}" logger.lifecycle "------------------------------------------------------------------------" logger.lifecycle "Test Results for ${taskIdentifier}" logger.lifecycle " Tests run: ${total}, Passed: ${passed}, Failed: ${failed}, Skipped: ${skipped}" diff --git a/buildSrc/src/main/groovy/tapestry.testng-convention.gradle b/buildSrc/src/main/groovy/tapestry.testng-convention.gradle index 9ca8e29ac..083069931 100644 --- a/buildSrc/src/main/groovy/tapestry.testng-convention.gradle +++ b/buildSrc/src/main/groovy/tapestry.testng-convention.gradle @@ -1,3 +1,19 @@ +// Use for modules whose test sources use TestNG annotations (@Test, @BeforeMethod, ...). +// +// Adds TestNG + EasyMock to the test compile classpath, and testng-engine at runtime +// so the standard 'test' task (JUnit Platform) discovers and runs TestNG unit tests +// without any configuration change. +// +// Modules that ALSO have Selenium integration tests must additionally declare: +// - A 'testNG' task using useTestNG { suiteXmlFiles << ... } for native TestNG execution +// (needed for correct @BeforeTest / ITestContext scoping across multiple test apps) +// - An exclusion on the 'test' task to skip integration classes +// - tasks.named('check') { dependsOn 'testNG' } +// +// Convention hierarchy: +// tapestry.testing-base-convention platform runner, BOM, logging +// └─ tapestry.testng-convention + TestNG + EasyMock + testng-engine (this file) + plugins { id 'tapestry.testing-base-convention' } @@ -5,28 +21,5 @@ plugins { dependencies { testImplementation libs.testng testImplementation libs.easymock -} - -def testNGTask = tasks.register('testNG', Test) { - group = 'verification' - description = 'Runs TetstNG tests.' - - def suiteFiles = [ - 'src/test/resources/testng.xml', - 'src/test/conf/testng.xml' - ].collect { path -> layout.projectDirectory.file(path).asFile } - - def existingSuite = suiteFiles.find { it.exists() } - - if (existingSuite) { - useTestNG { - suites(existingSuite) - } - } else { - useTestNG() - } -} - -tasks.named('check') { - dependsOn(testNGTask) + testRuntimeOnly libs.testng.engine } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 1384d14f2..6fc521197 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -71,8 +71,7 @@ rhino = "1.7.7.2" # TESTING -junit5 = "5.14.2" -junit4 = "4.13.2" +junit5 = "5.14.3" testng = "7.5.1" easymock = "5.6.0" @@ -198,9 +197,8 @@ junit-jupiter-engine = { module = "org.junit.jupiter:junit-jupiter-engine" } junit-vintage-engine = { module = "org.junit.vintage:junit-vintage-engine" } junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" } -junit4 = { module = "junit:junit", version.ref = "junit4" } - -testng = { module = "org.testng:testng", version.ref = "testng" } +testng = { module = "org.testng:testng", version.ref = "testng" } +testng-engine = { module = "org.junit.support:testng-engine", version = "1.1.0" } easymock = { module = "org.easymock:easymock", version.ref = "easymock" } diff --git a/quickstart/build.gradle b/quickstart/build.gradle index 76bdbfb39..199f93414 100644 --- a/quickstart/build.gradle +++ b/quickstart/build.gradle @@ -2,7 +2,7 @@ import org.apache.tools.ant.filters.FixCrLfFilter import org.apache.tools.ant.filters.ReplaceTokens tasks.register('copyGradleWrapper', Copy) { - group = 'Build Setup' + group = 'build setup' description = 'Copies Gradle wrapper files to archetype resources' def srcDir = layout.buildDirectory.dir('wrapper') @@ -15,7 +15,7 @@ tasks.register('copyGradleWrapper', Copy) { } tasks.register('addGradleWrapper', Exec) { - group = 'Build Setup' + group = 'build setup' description = 'Generates Gradle wrapper in temporary directory' workingDir = layout.buildDirectory.dir('wrapper') @@ -34,14 +34,14 @@ tasks.register('addGradleWrapper', Exec) { } tasks.register('addWrappers') { - group = 'Build Setup' + group = 'build setup' description = 'Coordinates wrapper generation tasks' dependsOn addGradleWrapper } tasks.register('processFiltered', Copy) { - group = 'Build' + group = 'build' description = 'Processes filtered resources with token replacement' def srcDir = layout.projectDirectory.dir('src/main/resources-filtered') diff --git a/settings.gradle b/settings.gradle index 1cc4d555c..46841ef36 100644 --- a/settings.gradle +++ b/settings.gradle @@ -10,7 +10,10 @@ dependencyResolutionManagement { mavenLocal { content { // https://github.com/gradle/gradle/issues/18519 - excludeGroup "io.netty" + // Netty-related dependencies are os/arch-dependent, but gradle might fail + // to resolve the correct one if finding anything in the local Maven, as + // it stops looking further. + excludeGroup 'io.netty' } } mavenCentral() diff --git a/tapestry-beanvalidator/build.gradle b/tapestry-beanvalidator/build.gradle index bee604967..183d0027a 100644 --- a/tapestry-beanvalidator/build.gradle +++ b/tapestry-beanvalidator/build.gradle @@ -2,8 +2,6 @@ plugins { id 'tapestry.testng-convention' } -import t5build.* - description = 'Support for JSR-303 Bean Validation via the Hibernate validator implementation' dependencies { @@ -27,6 +25,21 @@ tasks.register('runTestApp303', JavaExec) { classpath += project.sourceSets.test.runtimeClasspath } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.beanvalidator.modules.BeanValidatorModule' diff --git a/tapestry-cdi/build.gradle b/tapestry-cdi/build.gradle index 85bf10ac8..ce32f4d26 100644 --- a/tapestry-cdi/build.gradle +++ b/tapestry-cdi/build.gradle @@ -1,5 +1,4 @@ import org.gradle.plugins.ide.idea.model.* -import t5build.* plugins { id 'tapestry.junit4-legacy-convention' diff --git a/tapestry-core/build.gradle b/tapestry-core/build.gradle index 06cd89d35..0b0afd063 100644 --- a/tapestry-core/build.gradle +++ b/tapestry-core/build.gradle @@ -1,18 +1,15 @@ -import org.gradle.api.tasks.testing.Test -import t5build.TapestryBuildLogic -import org.apache.tools.ant.filters.ReplaceTokens - plugins { - id 'tapestry.testing-base-convention' id 'tapestry.junit5-convention' id 'tapestry.testng-convention' } - description = 'Central module for Tapestry, containing all core services and components' def npmWorkingDir = 'src/main/typescript/' -def npmExec = TapestryBuildLogic.isWindows() ? 'npm.cmd' : 'npm' +def npmExec = 'npm' +if (System.properties['os.name'].toLowerCase().contains('windows')) { + npmExec += '.cmd' +} dependencies { api project(':tapestry-ioc') @@ -34,11 +31,11 @@ dependencies { testImplementation libs.commons.httpclient testImplementation project(":tapestry-spock") - testRuntimeOnly("${libs.hsqldb.get().module.group}:${libs.hsqldb.get().module.name}:${libs.hsqldb.get().version}:jdk8") + testRuntimeOnly libs.hsqldb } tasks.register('npmInstall', Exec) { - group = 'TypeScript' + group = 'typescript' description = 'Runs npm install' workingDir = npmWorkingDir @@ -46,7 +43,7 @@ tasks.register('npmInstall', Exec) { } tasks.register('compileTypeScriptToAmd', Exec) { - group = 'TypeScript' + group = 'typescript' description = 'Compiles TypeScript to AMD modules' dependsOn npmInstall @@ -56,7 +53,7 @@ tasks.register('compileTypeScriptToAmd', Exec) { } tasks.register('compileTypeScriptToEsModule', Exec) { - group = 'TypeScript' + group = 'typescript' description = 'Compiles TypeScript to ES modules' dependsOn npmInstall @@ -65,8 +62,11 @@ tasks.register('compileTypeScriptToEsModule', Exec) { commandLine npmExec, 'run', 'build-es-module' } +// Lifecycle task that triggers both TypeScript compilations. +// Typed as Delete only because Gradle requires a concrete type +// and this task performs no actions of its own. tasks.register('compileTypeScript', Delete) { - group = 'TypeScript' + group = 'typescript' description = 'Compiles all TypeScript variants' dependsOn compileTypeScriptToAmd, compileTypeScriptToEsModule @@ -77,7 +77,7 @@ tasks.named('sourcesJar') { } tasks.register('generateTypeScriptDocs', Exec) { - group = 'TypeScript' + group = 'typescript' description = 'Generates TypeScript documentation' dependsOn npmInstall @@ -87,7 +87,7 @@ tasks.register('generateTypeScriptDocs', Exec) { } tasks.register('cleanTypeScriptFiles', Delete) { - group = 'TypeScript' + group = 'typescript' description = 'Cleans generated TypeScript files' delete fileTree('src/main/resources/META-INF/assets/es-modules/t5/core') { @@ -108,12 +108,18 @@ tasks.named('clean') { } -// Not sure why this is necessary: +// Groovy test classes extend Java test base classes, so Java must be compiled first. +// Gradle does not infer this ordering automatically in mixed-language source sets. tasks.named('compileTestGroovy') { dependsOn compileTestJava } test { + useJUnitPlatform() + + // Don't run integration tests via Jupiter testng-runner + exclude '**/integration/**' + // Needed to have XMLTokenStreamTests.testStreamEncoding() passing on Java 9+ jvmArgs('--add-opens=java.base/java.nio.charset=ALL-UNNAMED') @@ -121,6 +127,19 @@ test { systemProperty 'user.language', 'en' } +// This task runs the default integration tests that are defined in the testng.xml +tasks.register('testNG', Test) { + group = 'verification' + + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + [ 'app1', 'app2', @@ -131,7 +150,7 @@ test { 'appfolder' ].each { appName -> tasks.register("runTest${appName.capitalize()}", JavaExec) { - group = 'Application' + group = 'application' description = "Starts the ${appName} integration test app, useful when debugging." mainClass = 'org.apache.tapestry5.test.JettyRunner' @@ -140,65 +159,16 @@ test { } } -// Default is jQuery and Require.js enabled, so here are the tests for the -// other combinations. -// Tests need to run both JUnit and TestNG, so we need to register both -// and connect them all. - -def registerTestPair(String baseName, Closure configuration) { - - // Logic to configure a task with the user-provided closure - def configureTask = { Test task -> - // Create a copy of the closure to avoid side effects - Closure configClone = configuration.clone() - configClone.delegate = task - configClone.resolveStrategy = Closure.DELEGATE_FIRST - configClone.call() - } - - def jUnit = tasks.register("${baseName}JUnit", Test) { - group = 'verification' - description = "Runs JUnit tests for ${baseName}" +// The default testNG run uses jQuery + Require.js (the most common production config). +// These three tasks exercise the other combinations. They are NOT wired into 'check' +// and run only via the 'continuousIntegration' task in the root build. - useJUnitPlatform() +tasks.register('testWithJqueryAndRequireJsDisabled', Test) { + group = 'verification' + description = 'Runs tests with jQuery and Require.js disabled' - binaryResultsDirectory = layout.buildDirectory.dir("test-results/$name") - reports.html.outputLocation = layout.buildDirectory.dir("reports/tests/$name") + useTestNG() - configureTask(it) - } - - def testNG = tasks.register("${baseName}TestNG", Test) { - group = 'verification' - description = "Rest TestNG tests for ${baseName}" - - // Logic to find TestNG suite file - def suiteFile = [ - 'src/test/resources/testng.xml', - 'src/test/conf/testng.xml' - ].collect { layout.projectDirectory.file(it).asFile }.find { it.exists() } - - useTestNG { - if (suiteFile) { - suites(suiteFile) - } - } - - binaryResultsDirectory = layout.buildDirectory.dir("test-results/$name") - reports.html.outputLocation = layout.buildDirectory.dir("reports/tests/$name") - - configureTask(it) - } - - tasks.register(baseName) { - group = 'verification' - description = "Runs JUnit and TestNG tests for ${baseName}" - - dependsOn jUnit, testNG - } -} - -registerTestPair('testWithJqueryAndRequireJsDisabled') { systemProperties( 'tapestry.javascript-infrastructure-provider': 'jquery', 'tapestry.require-js-enabled': 'false', @@ -207,7 +177,11 @@ registerTestPair('testWithJqueryAndRequireJsDisabled') { ) } -registerTestPair('testWithPrototypeAndRequireJsEnabled') { +tasks.register('testWithPrototypeAndRequireJsEnabled', Test) { + group = 'verification' + description = 'Runs tests with Prototype and Require.js enabled' + useTestNG() + systemProperties( 'tapestry.javascript-infrastructure-provider': 'prototype', 'tapestry.require-js-enabled': 'true', @@ -216,7 +190,12 @@ registerTestPair('testWithPrototypeAndRequireJsEnabled') { ) } -registerTestPair('testWithPrototypeAndRequireJsDisabled') { +tasks.register('testWithPrototypeAndRequireJsDisabled', Test) { + group = 'verification' + description = 'Runs tests with Prototype and Require.js disabled' + + useTestNG() + systemProperties( 'tapestry.javascript-infrastructure-provider': 'prototype', 'tapestry.require-js-enabled': 'false', diff --git a/tapestry-core/src/test/resources/testng.xml b/tapestry-core/src/test/resources/testng.xml index e7ffac3f7..b46d6f256 100644 --- a/tapestry-core/src/test/resources/testng.xml +++ b/tapestry-core/src/test/resources/testng.xml @@ -11,50 +11,6 @@ name="Tapestry Core" annotations="1.5" verbose="2"> - <test - name="Component Unit Tests" - enabled="true"> - <packages> - <package name="org.apache.tapestry5.pagetester" /> - <package name="org.apache.tapestry5.integration.pagelevel" /> - <package name="org.apache.tapestry5.corelib.base" /> - <package name="org.apache.tapestry5.corelib.components" /> - <package name="org.apache.tapestry5.corelib.mixins" /> - <package name="org.apache.tapestry5.corelib.internal" /> - </packages> - </test> - - <test - name="Service Unit Tests" - enabled="true"> - <packages> - <package name="org.apache.tapestry5.root" /> - <package name="org.apache.tapestry5.dom" /> - <package name="org.apache.tapestry5.runtime" /> - <package name="org.apache.tapestry5.services" /> - <package name="org.apache.tapestry5.services.javascript" /> - <package name="org.apache.tapestry5.util" /> - <package name="org.apache.tapestry5.validator" /> - <package name="org.apache.tapestry5.internal" /> - <package name="org.apache.tapestry5.internal.beaneditor" /> - <package name="org.apache.tapestry5.internal.bindings" /> - <package name="org.apache.tapestry5.internal.grid" /> - <package name="org.apache.tapestry5.internal.model" /> - <package name="org.apache.tapestry5.internal.pageload" /> - <package name="org.apache.tapestry5.internal.services" /> - <package name="org.apache.tapestry5.internal.services.assets" /> - <package name="org.apache.tapestry5.internal.services.messages" /> - <package name="org.apache.tapestry5.internal.services.meta" /> - <package name="org.apache.tapestry5.internal.services.templates" /> - <package name="org.apache.tapestry5.internal.structure" /> - <package name="org.apache.tapestry5.internal.test" /> - <package name="org.apache.tapestry5.internal.transform" /> - <package name="org.apache.tapestry5.internal.translator" /> - <package name="org.apache.tapestry5.internal.util" /> - <package name="org.apache.tapestry5.internal.services.ajax" /> - </packages> - </test> - <!-- We break these out by which of the test applications they apply to. We have a bunch. --> <test name="Core Integration Tests" diff --git a/tapestry-hibernate-core/build.gradle b/tapestry-hibernate-core/build.gradle index 48aa01612..60ef293d3 100644 --- a/tapestry-hibernate-core/build.gradle +++ b/tapestry-hibernate-core/build.gradle @@ -12,7 +12,7 @@ dependencies { implementation libs.jaxb.runtime testImplementation project(':tapestry-test') - testRuntimeOnly("${libs.hsqldb.get().module.group}:${libs.hsqldb.get().module.name}:${libs.hsqldb.get().version}:jdk8") + testRuntimeOnly libs.hsqldb } jar { diff --git a/tapestry-hibernate/build.gradle b/tapestry-hibernate/build.gradle index f6e08dd8b..a2a9d32d7 100644 --- a/tapestry-hibernate/build.gradle +++ b/tapestry-hibernate/build.gradle @@ -11,7 +11,7 @@ dependencies { implementation libs.jboss.logging testImplementation project(':tapestry-test') - testRuntimeOnly("${libs.hsqldb.get().module.group}:${libs.hsqldb.get().module.name}:${libs.hsqldb.get().version}:jdk8") + testRuntimeOnly libs.hsqldb } tasks.register('runTestApp0', JavaExec) { @@ -22,6 +22,21 @@ tasks.register('runTestApp0', JavaExec) { classpath += project.sourceSets.test.runtimeClasspath } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.hibernate.web.modules.HibernateModule' diff --git a/tapestry-hibernate/src/test/resources/testng.xml b/tapestry-hibernate/src/test/resources/testng.xml index 58f4ffac1..d18fa8361 100644 --- a/tapestry-hibernate/src/test/resources/testng.xml +++ b/tapestry-hibernate/src/test/resources/testng.xml @@ -17,11 +17,6 @@ <suite name="Tapestry Hibernate" parallel="false" thread-count="10" annotations="1.5" verbose="2"> <parameter name="tapestry.integration-webapp" value="src/test/webapp"/> - <test name="Tapestry Hibernate Internal APIs"> - <packages> - <package name="org.apache.tapestry5.hibernate.web.internal"/> - </packages> - </test> <test name="Tapestry Hibernate Integration Tests"> <packages> <package name="org.apache.tapestry5.hibernate.web.integration"/> diff --git a/tapestry-http/build.gradle b/tapestry-http/build.gradle index edcdc56fa..c4d535e30 100644 --- a/tapestry-http/build.gradle +++ b/tapestry-http/build.gradle @@ -19,7 +19,7 @@ dependencies { testImplementation project(':tapestry-test') testImplementation project(':tapestry-test-constants') - testRuntimeOnly("${libs.hsqldb.get().module.group}:${libs.hsqldb.get().module.name}:${libs.hsqldb.get().version}:jdk8") + testRuntimeOnly libs.hsqldb testRuntimeOnly libs.slf4j.simple } @@ -33,3 +33,18 @@ tasks.named('jar', Jar) { filter(ReplaceTokens, tokens: [version: project.parent.version]) } } + +tasks.named('test') { + exclude '**/TapestryHttpIntegrationTests*' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} diff --git a/tapestry-http/src/test/resources/testng.xml b/tapestry-http/src/test/resources/testng.xml new file mode 100644 index 000000000..edd62c552 --- /dev/null +++ b/tapestry-http/src/test/resources/testng.xml @@ -0,0 +1,11 @@ +<!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> + +<suite name="Tapestry HTTP" annotations="1.5" verbose="2"> + + <test name="Tapestry HTTP Integration Tests" enabled="true"> + <packages> + <package name="org.apache.tapestry5.http.test" /> + </packages> + </test> + +</suite> diff --git a/tapestry-ioc/build.gradle b/tapestry-ioc/build.gradle index 722487afe..ba8d0d8b2 100644 --- a/tapestry-ioc/build.gradle +++ b/tapestry-ioc/build.gradle @@ -18,7 +18,7 @@ dependencies { testImplementation libs.commons.lang3 testImplementation libs.hibernate.core - testRuntimeOnly("${libs.hsqldb.get().module.group}:${libs.hsqldb.get().module.name}:${libs.hsqldb.get().version}:jdk8") + testRuntimeOnly libs.hsqldb } tasks.named('test', Test) { diff --git a/tapestry-jmx/build.gradle b/tapestry-jmx/build.gradle index 00694090c..b4d80f37e 100644 --- a/tapestry-jmx/build.gradle +++ b/tapestry-jmx/build.gradle @@ -10,6 +10,21 @@ dependencies { testImplementation project(':tapestry-test') } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + jar { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.jmx.modules.JmxModule' diff --git a/tapestry-jpa/build.gradle b/tapestry-jpa/build.gradle index 0f7ff48e5..7b0b7121d 100644 --- a/tapestry-jpa/build.gradle +++ b/tapestry-jpa/build.gradle @@ -26,6 +26,21 @@ dependencies { } } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.jpa.modules.JpaModule' diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java index 558ea8dbc..e766b7276 100644 --- a/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java +++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/internal/jpa/JpaTransactionAdvisorImplTest.java @@ -37,6 +37,9 @@ import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; +// Disabled as JUnit would pick it up, but TestNG ignored it due to missing in testng.xml +// https://github.com/apache/tapestry-5/commit/246e677097f249ad2352ddc595ef43cc257f8e27 +@Test(enabled = false) public class JpaTransactionAdvisorImplTest extends IOCTestCase { private static final String UNIT_NAME = "FooUnit"; @@ -62,7 +65,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase registry = null; } - @Test + @Test(enabled = false) public void undecorated() { final VoidService delegate = newMock(VoidService.class); @@ -84,7 +87,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void persistence_unit_name_missing() { final VoidService delegate = newMock(VoidService.class); @@ -118,7 +121,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void persistence_unit_name_missing_single_unit_configured() { final VoidService delegate = newMock(VoidService.class); @@ -147,7 +150,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void persistence_unit_missing() { final VoidService delegate = newMock(VoidService.class); @@ -181,7 +184,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void persistence_unit_missing_single_unit_configured() { final VoidService delegate = newMock(VoidService.class); @@ -210,7 +213,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void transaction_inactive() { final VoidService delegate = newMock(VoidService.class); @@ -238,7 +241,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void void_method() { final VoidService delegate = newMock(VoidService.class); @@ -266,7 +269,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void void_method_with_param() { final VoidService delegate = newMock(VoidService.class); @@ -292,7 +295,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void runtime_exception_will_abort_transaction() throws Exception { final Performer delegate = newMock(Performer.class); @@ -328,7 +331,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void checked_exception_will_commit_transaction() throws Exception { final Performer delegate = newMock(Performer.class); @@ -369,7 +372,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void return_type_method() { final ReturnTypeService delegate = newTestService(); @@ -395,7 +398,7 @@ public class JpaTransactionAdvisorImplTest extends IOCTestCase verify(); } - @Test + @Test(enabled = false) public void return_type_method_with_param() { final ReturnTypeService delegate = newTestService(); diff --git a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java index 6c7493f0d..9a27ae889 100644 --- a/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java +++ b/tapestry-jpa/src/test/java/org/apache/tapestry5/jpa/test/JpaTest.java @@ -47,6 +47,9 @@ import org.testng.annotations.AfterMethod; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; +// Disabled as JUnit would pick it up, but TestNG ignored it due to missing in testng.xml +// https://github.com/apache/tapestry-5/commit/246e677097f249ad2352ddc595ef43cc257f8e27 +@Test(enabled = false) public class JpaTest { @@ -149,7 +152,7 @@ public class JpaTest return em.createQuery(query).getResultList(); } - @Test + @Test(enabled = false) public void commitBothInNestedTransaction() { topLevelService.createThingOneAndTwo("one", "two"); @@ -158,19 +161,19 @@ public class JpaTest assertTrue(getEntityManager().find(VersionedThing.class, 1).getVersion() > 0); } - @Test(expectedExceptions = RollbackException.class) + @Test(expectedExceptions = RollbackException.class, enabled = false) public void rollbackNestedFails() { topLevelService.createThingOneAndTwo("one", null); } - @Test(expectedExceptions = RollbackException.class) + @Test(expectedExceptions = RollbackException.class, enabled = false) public void rollbackTopFails() { topLevelService.createThingOneAndTwo(null, "two"); } - @Test + @Test(enabled = false) public void sequentialCommitUsingInvokeAfterCommit() { topLevelService.createThingOneThenTwo("one", "two"); @@ -179,7 +182,7 @@ public class JpaTest assertTrue(getEntityManager().find(VersionedThing.class, 1).getVersion() > 1); } - @Test + @Test(enabled = false) public void sequentialCommitUsingInvokeAfterCommitAndCommitAfterAnnotation() { topLevelService.createThingOneThenTwoWithNestedCommitAfter("one", "two"); @@ -188,7 +191,7 @@ public class JpaTest assertTrue(getEntityManager().find(VersionedThing.class, 1).getVersion() > 1); } - @Test + @Test(enabled = false) public void sequentialRollbackAndAbortUsingInvokeAfterCommit() { try @@ -203,7 +206,7 @@ public class JpaTest assertEquals(0, getInstances(ThingTwo.class).size()); } - @Test + @Test(enabled = false) public void trySomething() { ThingOne thingOne = new ThingOne(); diff --git a/tapestry-kaptcha/build.gradle b/tapestry-kaptcha/build.gradle index 355af5daa..df8ee9637 100644 --- a/tapestry-kaptcha/build.gradle +++ b/tapestry-kaptcha/build.gradle @@ -14,6 +14,21 @@ dependencies { testImplementation project(':tapestry-test') } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.kaptcha.modules.KaptchaModule' diff --git a/tapestry-kaptcha/src/test/java/org/apache/tapestry5/kaptcha/components/KaptchaUnitTest.java b/tapestry-kaptcha/src/test/java/org/apache/tapestry5/kaptcha/components/KaptchaUnitTest.java index 6346c1653..87d756af2 100644 --- a/tapestry-kaptcha/src/test/java/org/apache/tapestry5/kaptcha/components/KaptchaUnitTest.java +++ b/tapestry-kaptcha/src/test/java/org/apache/tapestry5/kaptcha/components/KaptchaUnitTest.java @@ -20,13 +20,13 @@ import org.apache.tapestry5.test.PageTester; import org.testng.Assert; import org.testng.annotations.Test; -@Test +@Test(enabled = false) public class KaptchaUnitTest { @SuppressWarnings("unchecked") - @Test + @Test(enabled = false) public void cache_control_header() { PageTester tester = new PageTester("kaptcha.demo", "app"); diff --git a/tapestry-kaptcha/src/test/resources/testng.xml b/tapestry-kaptcha/src/test/resources/testng.xml index 1875fe4ad..ac2e62756 100644 --- a/tapestry-kaptcha/src/test/resources/testng.xml +++ b/tapestry-kaptcha/src/test/resources/testng.xml @@ -3,7 +3,6 @@ <test name="Integration Tests"> <parameter name="tapestry.web-app-folder" value="src/test/webapp"/> <packages> - <package name="org.apache.tapestry5.kaptcha"/> <package name="org.apache.tapestry5.kaptcha.integration"/> </packages> </test> diff --git a/tapestry-spring/build.gradle b/tapestry-spring/build.gradle index 9d069604c..0b412cd7b 100644 --- a/tapestry-spring/build.gradle +++ b/tapestry-spring/build.gradle @@ -31,7 +31,7 @@ dependencies { ['', '1'].each { suffix -> tasks.register("runTestApp${suffix}", JavaExec) { - group = 'Application' + group = 'application' description = "Start tapestry-spring integration test app${suffix}, useful when debugging failing integration tests" mainClass = 'org.apache.tapestry5.test.JettyRunner' args '-d', "src/test/webapp${suffix}", '-p', '8080' @@ -39,6 +39,21 @@ dependencies { } } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.spring.modules.SpringModule' diff --git a/tapestry-spring/src/test/resources/testng.xml b/tapestry-spring/src/test/resources/testng.xml index 076743557..d40d02bf2 100644 --- a/tapestry-spring/src/test/resources/testng.xml +++ b/tapestry-spring/src/test/resources/testng.xml @@ -16,12 +16,6 @@ --> <suite name="Tapesty Spring Integration" parallel="false" thread-count="10" annotations="1.5" verbose="2"> - <test name="Tapestry Spring Unit Tests"> - <packages> - <package name="org.apache.tapestry5.spring"/> - <package name="org.apache.tapestry5.internal.spring"/> - </packages> - </test> <test name="Tapestry Spring Integration - Standard"> <packages> <package name="org.apache.tapestry5.spring.integration.standard"/> diff --git a/tapestry-test/build.gradle b/tapestry-test/build.gradle index 70b34c377..281b3f9c0 100644 --- a/tapestry-test/build.gradle +++ b/tapestry-test/build.gradle @@ -25,3 +25,18 @@ dependencies { api libs.testng api libs.easymock } + +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} diff --git a/tapestry-upload/build.gradle b/tapestry-upload/build.gradle index fdd1458d1..83dc5e204 100644 --- a/tapestry-upload/build.gradle +++ b/tapestry-upload/build.gradle @@ -16,6 +16,21 @@ dependencies { testImplementation project(':tapestry-test') } +tasks.named('test') { + exclude '**/integration/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + useTestNG { + suiteXmlFiles << project.file('src/test/resources/testng.xml') + } +} + +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.upload.modules.UploadModule' diff --git a/tapestry-upload/src/test/resources/testng.xml b/tapestry-upload/src/test/resources/testng.xml index a6ed19248..d6d82a9ec 100755 --- a/tapestry-upload/src/test/resources/testng.xml +++ b/tapestry-upload/src/test/resources/testng.xml @@ -19,8 +19,6 @@ <test name="Tapesty Upload Component"> <parameter name="tapestry.web-app-folder" value="src/test/webapp"/> <packages> - <package name="org.apache.tapestry5.upload.components"/> - <package name="org.apache.tapestry5.upload.internal.services"/> <package name="org.apache.tapestry5.upload.integration"/> </packages> </test> diff --git a/tapestry-webresources/build.gradle b/tapestry-webresources/build.gradle index 4588f1334..4082fd0e3 100644 --- a/tapestry-webresources/build.gradle +++ b/tapestry-webresources/build.gradle @@ -24,7 +24,19 @@ dependencies { } -test { +tasks.named('test') { + useJUnitPlatform() + exclude '**/services/web/**' +} + +tasks.register('testNG', Test) { + group = 'verification' + description = 'Runs Selenium integration tests via native TestNG' + + useTestNG() + + include '**/services/web/**' + systemProperties( 'tapestry.page-load-timeout': '60', 'tapestry.compiled-asset-cache-dir': "$buildDir/compiled-asset-cache", @@ -34,6 +46,10 @@ test { ) } +tasks.named('check') { + dependsOn 'testNG' +} + tasks.named('jar', Jar) { manifest { attributes 'Tapestry-Module-Classes': 'org.apache.tapestry5.webresources.modules.WebResourcesModule'
