├── .gitattributes ├── .github ├── renovate.json5 └── workflows │ └── main.yml ├── .gitignore ├── LICENSE.md ├── README.adoc ├── build.gradle.kts ├── gradle.properties ├── gradle ├── libs.versions.toml ├── spotless │ ├── eclipse-public-license-2.0.java │ ├── junit-eclipse-formatter-settings.xml │ └── junit-eclipse.importorder └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src ├── main ├── java │ └── org │ │ └── junit │ │ └── support │ │ └── testng │ │ └── engine │ │ ├── ClassDescriptor.java │ │ ├── ConfiguringListener.java │ │ ├── DefaultListener.java │ │ ├── DiscoveryListener.java │ │ ├── ExecutionListener.java │ │ ├── InvocationDescriptor.java │ │ ├── IsTestNGTestClass.java │ │ ├── LoggingListener.java │ │ ├── MethodDescriptor.java │ │ ├── MethodSignature.java │ │ ├── TestAnnotationUtils.java │ │ ├── TestClassRegistry.java │ │ ├── TestDescriptorFactory.java │ │ ├── TestNGEngineDescriptor.java │ │ ├── TestNGSelectorResolver.java │ │ └── TestNGTestEngine.java └── resources │ └── META-INF │ └── services │ └── org.junit.platform.engine.TestEngine ├── module └── java │ └── module-info.java ├── test ├── java │ └── org │ │ └── junit │ │ └── support │ │ └── testng │ │ └── engine │ │ ├── AbstractIntegrationTests.java │ │ ├── ConfigurationMethodIntegrationTests.java │ │ ├── ConfigurationParametersIntegrationTests.java │ │ ├── DataProviderIntegrationTests.java │ │ ├── DiscoveryIntegrationTests.java │ │ ├── ReportingIntegrationTests.java │ │ ├── RequiresTestNGVersion.java │ │ ├── TestContext.java │ │ └── TestNGVersionAppendingDisplayNameGenerator.java └── resources │ ├── junit-platform.properties │ └── log4j2-test.xml └── testFixtures └── java └── example ├── basics ├── AnonymousClassTestCase.java ├── ClassLevelOnlyAnnotationTestCase.java ├── CustomAttributeTestCase.java ├── DefaultVisibilityBaseTestCase.java ├── DefaultVisibilityWithDeclaredMethodTestCase.java ├── DefaultVisibilityWithoutDeclaredMethodTestCase.java ├── DryRunTestCase.java ├── ExpectedExceptionsTestCase.java ├── IgnoredTestCase.java ├── InheritedClassLevelOnlyAnnotationTestCase.java ├── InheritingSubClassTestCase.java ├── JUnitTestCase.java ├── NestedTestClass.java ├── ParallelExecutionTestCase.java ├── RetriedTestCase.java ├── SimpleTestCase.java ├── SuccessPercentageTestCase.java ├── TimeoutTestCase.java └── TwoMethodsTestCase.java ├── configuration ├── methods │ ├── AbortedBeforeClassConfigurationMethodTestCase.java │ ├── FailingAfterClassConfigurationMethodTestCase.java │ ├── FailingAfterMethodConfigurationMethodTestCase.java │ ├── FailingAfterSuiteConfigurationMethodTestCase.java │ ├── FailingAfterTestConfigurationMethodTestCase.java │ ├── FailingBeforeClassConfigurationMethodTestCase.java │ ├── FailingBeforeMethodConfigurationMethodTestCase.java │ ├── FailingBeforeSuiteConfigurationMethodTestCase.java │ ├── FailingBeforeTestConfigurationMethodTestCase.java │ └── GroupsConfigurationMethodsTestCase.java └── parameters │ ├── DataProviderThreadCountTestCase.java │ ├── InvocationTrackingListener.java │ ├── ParallelMethodsTestCase.java │ ├── PreserveOrderTestCase.java │ ├── ReturnValuesTestCase.java │ ├── SystemPropertyProvidingListener.java │ └── SystemPropertyReadingTestCase.java └── dataproviders ├── DataProviderMethodEmptyListTestCase.java ├── DataProviderMethodErrorHandlingTestCase.java ├── DataProviderMethodTestCase.java ├── DataProviders.java ├── FactoryMethodTestCase.java ├── FactoryWithDataProviderTestCase.java └── ParallelDataProviderTestCase.java /.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /.github/renovate.json5: -------------------------------------------------------------------------------- 1 | { 2 | $schema: 'https://docs.renovatebot.com/renovate-schema.json', 3 | extends: [ 4 | 'github>junit-team/renovate-config', 5 | ], 6 | packageRules: [ 7 | { 8 | allowedVersions: '(,5.8)', 9 | description: [ 10 | 'Stick to 5.7.x as required baseline for JUnit', 11 | ], 12 | matchPackageNames: [ 13 | 'org.junit:{/,}**' 14 | ] 15 | }, 16 | ], 17 | } 18 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | pull_request: 8 | branches: 9 | - '*' 10 | schedule: 11 | - cron: '0 0 * * *' 12 | workflow_dispatch: 13 | 14 | jobs: 15 | build: 16 | strategy: 17 | matrix: 18 | jdk: 19 | - version: 17 20 | - version: 21 21 | - version: 24 22 | distribution: oracle 23 | runs-on: ubuntu-latest 24 | name: "Build (JDK ${{ matrix.jdk.version }})" 25 | steps: 26 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 27 | - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 28 | id: setup-gradle-jdk 29 | with: 30 | distribution: liberica 31 | java-version: 17 32 | - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 33 | if: matrix.jdk.version != '17' 34 | with: 35 | distribution: ${{ matrix.jdk.distribution || 'liberica' }} 36 | java-version: ${{ matrix.jdk.version }} 37 | - name: 'Prepare TOOLCHAIN_JDK env var' 38 | if: matrix.jdk.version != '17' 39 | shell: bash 40 | run: echo "TOOLCHAIN_JDK=$JAVA_HOME" >> $GITHUB_ENV 41 | - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 42 | with: 43 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 44 | - shell: bash 45 | env: 46 | DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 47 | JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} 48 | run: | 49 | ./gradlew \ 50 | -Porg.gradle.java.installations.auto-download=false \ 51 | "-Dscan.value.GitHub job=${{ github.job }}" \ 52 | -Dscan.tag.JDK_${{ matrix.jdk.version }} \ 53 | -PjavaToolchainVersion=${{ matrix.jdk.version }} \ 54 | --scan \ 55 | --refresh-dependencies \ 56 | javaToolchains \ 57 | build 58 | ea-build: 59 | strategy: 60 | matrix: 61 | jdk: [ '25' ] 62 | runs-on: ubuntu-latest 63 | name: "Early Access Build (JDK ${{ matrix.jdk }})" 64 | steps: 65 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 66 | - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 67 | id: setup-gradle-jdk 68 | with: 69 | distribution: liberica 70 | java-version: 17 71 | - uses: oracle-actions/setup-java@b1546e588c27008e88bfcabda44d11c22316b9b8 # v1.4.2 72 | with: 73 | website: jdk.java.net 74 | release: ${{ matrix.jdk }} 75 | version: latest 76 | - name: 'Prepare TOOLCHAIN_JDK env var' 77 | shell: bash 78 | run: echo "TOOLCHAIN_JDK=$JAVA_HOME" >> $GITHUB_ENV 79 | - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 80 | with: 81 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 82 | - shell: bash 83 | env: 84 | DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 85 | JAVA_HOME: ${{ steps.setup-gradle-jdk.outputs.path }} 86 | run: | 87 | ./gradlew \ 88 | -Porg.gradle.java.installations.auto-download=false \ 89 | "-Dscan.value.GitHub job=${{ github.job }}" \ 90 | -Dscan.tag.JDK_${{ matrix.jdk }} \ 91 | -PjavaToolchainVersion=${{ matrix.jdk }} \ 92 | --scan \ 93 | --refresh-dependencies \ 94 | javaToolchains \ 95 | build 96 | publish: 97 | name: Publish Snapshots 98 | needs: build 99 | runs-on: ubuntu-latest 100 | if: github.event_name == 'push' && github.repository == 'junit-team/testng-engine' && (startsWith(github.ref, 'refs/heads/releases/') || github.ref == 'refs/heads/main') 101 | steps: 102 | - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 103 | - uses: actions/setup-java@c5195efecf7bdfc987ee8bae7a71cb8b11521c00 # v4.7.1 104 | with: 105 | distribution: liberica 106 | java-version: 17 107 | - uses: gradle/actions/setup-gradle@8379f6a1328ee0e06e2bb424dadb7b159856a326 # v4.4.0 108 | with: 109 | cache-encryption-key: ${{ secrets.GRADLE_ENCRYPTION_KEY }} 110 | - shell: bash 111 | env: 112 | DEVELOCITY_ACCESS_KEY: ${{ secrets.DEVELOCITY_ACCESS_KEY }} 113 | ORG_GRADLE_PROJECT_mavenCentralUsername: ${{ secrets.MAVEN_CENTRAL_USERNAME }} 114 | ORG_GRADLE_PROJECT_mavenCentralPassword: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} 115 | run: | 116 | ./gradlew \ 117 | -Porg.gradle.java.installations.auto-download=false \ 118 | "-Dscan.value.GitHub job=${{ github.job }}" \ 119 | -Dscan.tag.publish \ 120 | --scan \ 121 | javaToolchains \ 122 | publishAllPublicationsToMavenCentralSnapshotsRepository \ 123 | -x check \ 124 | --no-configuration-cache 125 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | 7 | # Ignore SDKMAN config 8 | /.sdkmanrc 9 | /.idea/ 10 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Eclipse Public License - v 2.0 2 | ============================== 3 | 4 | THE ACCOMPANYING PROGRAM IS PROVIDED UNDER THE TERMS OF THIS ECLIPSE PUBLIC LICENSE (“AGREEMENT”). ANY USE, REPRODUCTION OR DISTRIBUTION OF THE PROGRAM CONSTITUTES RECIPIENT'S ACCEPTANCE OF THIS AGREEMENT. 5 | 6 | ### 1. Definitions 7 | 8 | “Contribution” means: 9 | * **a)** in the case of the initial Contributor, the initial content Distributed under this Agreement, and 10 | * **b)** in the case of each subsequent Contributor: 11 | * **i)** changes to the Program, and 12 | * **ii)** additions to the Program; 13 | where such changes and/or additions to the Program originate from and are Distributed by that particular Contributor. A Contribution “originates” from a Contributor if it was added to the Program by such Contributor itself or anyone acting on such Contributor's behalf. Contributions do not include changes or additions to the Program that are not Modified Works. 14 | 15 | “Contributor” means any person or entity that Distributes the Program. 16 | 17 | “Licensed Patents” mean patent claims licensable by a Contributor which are necessarily infringed by the use or sale of its Contribution alone or when combined with the Program. 18 | 19 | “Program” means the Contributions Distributed in accordance with this Agreement. 20 | 21 | “Recipient” means anyone who receives the Program under this Agreement or any Secondary License (as applicable), including Contributors. 22 | 23 | “Derivative Works” shall mean any work, whether in Source Code or other form, that is based on (or derived from) the Program and for which the editorial revisions, annotations, elaborations, or other modifications represent, as a whole, an original work of authorship. 24 | 25 | “Modified Works” shall mean any work in Source Code or other form that results from an addition to, deletion from, or modification of the contents of the Program, including, for purposes of clarity any new file in Source Code form that contains any contents of the Program. Modified Works shall not include works that contain only declarations, interfaces, types, classes, structures, or files of the Program solely in each case in order to link to, bind by name, or subclass the Program or Modified Works thereof. 26 | 27 | “Distribute” means the acts of **a)** distributing or **b)** making available in any manner that enables the transfer of a copy. 28 | 29 | “Source Code” means the form of a Program preferred for making modifications, including but not limited to software source code, documentation source, and configuration files. 30 | 31 | “Secondary License” means either the GNU General Public License, Version 2.0, or any later versions of that license, including any exceptions or additional permissions as identified by the initial Contributor. 32 | 33 | ### 2. Grant of Rights 34 | 35 | **a)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free copyright license to reproduce, prepare Derivative Works of, publicly display, publicly perform, Distribute and sublicense the Contribution of such Contributor, if any, and such Derivative Works. 36 | 37 | **b)** Subject to the terms of this Agreement, each Contributor hereby grants Recipient a non-exclusive, worldwide, royalty-free patent license under Licensed Patents to make, use, sell, offer to sell, import and otherwise transfer the Contribution of such Contributor, if any, in Source Code or other form. This patent license shall apply to the combination of the Contribution and the Program if, at the time the Contribution is added by the Contributor, such addition of the Contribution causes such combination to be covered by the Licensed Patents. The patent license shall not apply to any other combinations which include the Contribution. No hardware per se is licensed hereunder. 38 | 39 | **c)** Recipient understands that although each Contributor grants the licenses to its Contributions set forth herein, no assurances are provided by any Contributor that the Program does not infringe the patent or other intellectual property rights of any other entity. Each Contributor disclaims any liability to Recipient for claims brought by any other entity based on infringement of intellectual property rights or otherwise. As a condition to exercising the rights and licenses granted hereunder, each Recipient hereby assumes sole responsibility to secure any other intellectual property rights needed, if any. For example, if a third party patent license is required to allow Recipient to Distribute the Program, it is Recipient's responsibility to acquire that license before distributing the Program. 40 | 41 | **d)** Each Contributor represents that to its knowledge it has sufficient copyright rights in its Contribution, if any, to grant the copyright license set forth in this Agreement. 42 | 43 | **e)** Notwithstanding the terms of any Secondary License, no Contributor makes additional grants to any Recipient (other than those set forth in this Agreement) as a result of such Recipient's receipt of the Program under the terms of a Secondary License (if permitted under the terms of Section 3). 44 | 45 | ### 3. Requirements 46 | 47 | **3.1** If a Contributor Distributes the Program in any form, then: 48 | 49 | * **a)** the Program must also be made available as Source Code, in accordance with section 3.2, and the Contributor must accompany the Program with a statement that the Source Code for the Program is available under this Agreement, and informs Recipients how to obtain it in a reasonable manner on or through a medium customarily used for software exchange; and 50 | 51 | * **b)** the Contributor may Distribute the Program under a license different than this Agreement, provided that such license: 52 | * **i)** effectively disclaims on behalf of all other Contributors all warranties and conditions, express and implied, including warranties or conditions of title and non-infringement, and implied warranties or conditions of merchantability and fitness for a particular purpose; 53 | * **ii)** effectively excludes on behalf of all other Contributors all liability for damages, including direct, indirect, special, incidental and consequential damages, such as lost profits; 54 | * **iii)** does not attempt to limit or alter the recipients' rights in the Source Code under section 3.2; and 55 | * **iv)** requires any subsequent distribution of the Program by any party to be under a license that satisfies the requirements of this section 3. 56 | 57 | **3.2** When the Program is Distributed as Source Code: 58 | 59 | * **a)** it must be made available under this Agreement, or if the Program **(i)** is combined with other material in a separate file or files made available under a Secondary License, and **(ii)** the initial Contributor attached to the Source Code the notice described in Exhibit A of this Agreement, then the Program may be made available under the terms of such Secondary Licenses, and 60 | * **b)** a copy of this Agreement must be included with each copy of the Program. 61 | 62 | **3.3** Contributors may not remove or alter any copyright, patent, trademark, attribution notices, disclaimers of warranty, or limitations of liability (“notices”) contained within the Program from any copy of the Program which they Distribute, provided that Contributors may add their own appropriate notices. 63 | 64 | ### 4. Commercial Distribution 65 | 66 | Commercial distributors of software may accept certain responsibilities with respect to end users, business partners and the like. While this license is intended to facilitate the commercial use of the Program, the Contributor who includes the Program in a commercial product offering should do so in a manner which does not create potential liability for other Contributors. Therefore, if a Contributor includes the Program in a commercial product offering, such Contributor (“Commercial Contributor”) hereby agrees to defend and indemnify every other Contributor (“Indemnified Contributor”) against any losses, damages and costs (collectively “Losses”) arising from claims, lawsuits and other legal actions brought by a third party against the Indemnified Contributor to the extent caused by the acts or omissions of such Commercial Contributor in connection with its distribution of the Program in a commercial product offering. The obligations in this section do not apply to any claims or Losses relating to any actual or alleged intellectual property infringement. In order to qualify, an Indemnified Contributor must: **a)** promptly notify the Commercial Contributor in writing of such claim, and **b)** allow the Commercial Contributor to control, and cooperate with the Commercial Contributor in, the defense and any related settlement negotiations. The Indemnified Contributor may participate in any such claim at its own expense. 67 | 68 | For example, a Contributor might include the Program in a commercial product offering, Product X. That Contributor is then a Commercial Contributor. If that Commercial Contributor then makes performance claims, or offers warranties related to Product X, those performance claims and warranties are such Commercial Contributor's responsibility alone. Under this section, the Commercial Contributor would have to defend claims against the other Contributors related to those performance claims and warranties, and if a court requires any other Contributor to pay any damages as a result, the Commercial Contributor must pay those damages. 69 | 70 | ### 5. No Warranty 71 | 72 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, THE PROGRAM IS PROVIDED ON AN “AS IS” BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED INCLUDING, WITHOUT LIMITATION, ANY WARRANTIES OR CONDITIONS OF TITLE, NON-INFRINGEMENT, MERCHANTABILITY OR FITNESS FOR A PARTICULAR PURPOSE. Each Recipient is solely responsible for determining the appropriateness of using and distributing the Program and assumes all risks associated with its exercise of rights under this Agreement, including but not limited to the risks and costs of program errors, compliance with applicable laws, damage to or loss of data, programs or equipment, and unavailability or interruption of operations. 73 | 74 | ### 6. Disclaimer of Liability 75 | 76 | EXCEPT AS EXPRESSLY SET FORTH IN THIS AGREEMENT, AND TO THE EXTENT PERMITTED BY APPLICABLE LAW, NEITHER RECIPIENT NOR ANY CONTRIBUTORS SHALL HAVE ANY LIABILITY FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING WITHOUT LIMITATION LOST PROFITS), HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OR DISTRIBUTION OF THE PROGRAM OR THE EXERCISE OF ANY RIGHTS GRANTED HEREUNDER, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 77 | 78 | ### 7. General 79 | 80 | If any provision of this Agreement is invalid or unenforceable under applicable law, it shall not affect the validity or enforceability of the remainder of the terms of this Agreement, and without further action by the parties hereto, such provision shall be reformed to the minimum extent necessary to make such provision valid and enforceable. 81 | 82 | If Recipient institutes patent litigation against any entity (including a cross-claim or counterclaim in a lawsuit) alleging that the Program itself (excluding combinations of the Program with other software or hardware) infringes such Recipient's patent(s), then such Recipient's rights granted under Section 2(b) shall terminate as of the date such litigation is filed. 83 | 84 | All Recipient's rights under this Agreement shall terminate if it fails to comply with any of the material terms or conditions of this Agreement and does not cure such failure in a reasonable period of time after becoming aware of such noncompliance. If all Recipient's rights under this Agreement terminate, Recipient agrees to cease use and distribution of the Program as soon as reasonably practicable. However, Recipient's obligations under this Agreement and any licenses granted by Recipient relating to the Program shall continue and survive. 85 | 86 | Everyone is permitted to copy and distribute copies of this Agreement, but in order to avoid inconsistency the Agreement is copyrighted and may only be modified in the following manner. The Agreement Steward reserves the right to publish new versions (including revisions) of this Agreement from time to time. No one other than the Agreement Steward has the right to modify this Agreement. The Eclipse Foundation is the initial Agreement Steward. The Eclipse Foundation may assign the responsibility to serve as the Agreement Steward to a suitable separate entity. Each new version of the Agreement will be given a distinguishing version number. The Program (including Contributions) may always be Distributed subject to the version of the Agreement under which it was received. In addition, after a new version of the Agreement is published, Contributor may elect to Distribute the Program (including its Contributions) under the new version. 87 | 88 | Except as expressly stated in Sections 2(a) and 2(b) above, Recipient receives no rights or licenses to the intellectual property of any Contributor under this Agreement, whether expressly, by implication, estoppel or otherwise. All rights in the Program not expressly granted under this Agreement are reserved. Nothing in this Agreement is intended to be enforceable by any entity that is not a Contributor or Recipient. No third-party beneficiary rights are created under this Agreement. 89 | 90 | #### Exhibit A - Form of Secondary Licenses Notice 91 | 92 | > “This Source Code may also be made available under the following Secondary Licenses when the conditions for such availability set forth in the Eclipse Public License, v. 2.0 are satisfied: {name license(s), version(s), and exceptions or additional permissions here}.” 93 | 94 | Simply including a copy of this Agreement, including this Exhibit A is not sufficient to license the Source Code under Secondary Licenses. 95 | 96 | If it is not possible or desirable to put the notice in a particular file, then You may include the notice in a location (such as a LICENSE file in a relevant directory) where a recipient would be likely to look for such a notice. 97 | 98 | You may add additional accurate notices of copyright ownership. 99 | -------------------------------------------------------------------------------- /README.adoc: -------------------------------------------------------------------------------- 1 | = TestNG Engine for the JUnit Platform 2 | :jcommander-version: 1.72 3 | :jquery-version: 3.5.1 4 | :junit-platform-version: 1.12.1 5 | :surefire-version: 3.5.2 6 | :testng-min-version: 6.14.3 7 | :testng-max-version: 7.10.2 8 | :testng-engine-version: 1.0.6 9 | 10 | Allows executing https://testng.org[TestNG] tests on the JUnit Platform 11 | 12 | == Usage 13 | 14 | .Console Launcher 15 | [%collapsible] 16 | ==== 17 | When running without a build tool, you need to download the following jars from Maven Central: 18 | 19 | * https://search.maven.org/remotecontent?filepath=org/junit/platform/junit-platform-console-standalone/{junit-platform-version}/junit-platform-console-standalone-{junit-platform-version}.jar[junit-platform-console-standalone-{junit-platform-version}.jar] 20 | * https://search.maven.org/remotecontent?filepath=org/junit/support/testng-engine/{testng-engine-version}/testng-engine-{testng-engine-version}.jar[testng-engine-{testng-engine-version}.jar] 21 | * https://search.maven.org/remotecontent?filepath=org/testng/testng/{testng-max-version}/testng-{testng-max-version}.jar[testng-{testng-max-version}.jar] 22 | * https://search.maven.org/remotecontent?filepath=com/beust/jcommander/{jcommander-version}/jcommander-{jcommander-version}.jar[jcommander-{jcommander-version}.jar] 23 | * https://search.maven.org/remotecontent?filepath=org/webjars/jquery/{jquery-version}/jquery-{jquery-version}.jar[jquery-{jquery-version}.jar] 24 | 25 | The following samples assume the above jars have been downloaded to the local `lib` folder and production and test classes to `bin/main` and `bin/test`, respectively. 26 | 27 | [source,subs="attributes+"] 28 | ---- 29 | $ java -cp 'lib/*' org.junit.platform.console.ConsoleLauncher \ 30 | -cp bin/main -cp bin/test \ 31 | --include-engine=testng --scan-classpath=bin/test 32 | 33 | Thanks for using JUnit! Support its development at https://junit.org/sponsoring 34 | 35 | ╷ 36 | └─ TestNG ✔ 37 | └─ CalculatorTests ✔ 38 | ├─ add(int, int, int) ✔ 39 | │ ├─ [0] 0, 1, 1 ✔ 40 | │ ├─ [1] 1, 2, 3 ✔ 41 | │ ├─ [2] 49, 51, 100 ✔ 42 | │ └─ [3] 1, 100, 101 ✔ 43 | └─ addsTwoNumbers ✔ 44 | 2021-07-04T17:43:52.223145 description = `1 + 1 = 2` 45 | 46 | Test run finished after 38 ms 47 | [ 3 containers found ] 48 | [ 0 containers skipped ] 49 | [ 3 containers started ] 50 | [ 0 containers aborted ] 51 | [ 3 containers successful ] 52 | [ 0 containers failed ] 53 | [ 5 tests found ] 54 | [ 0 tests skipped ] 55 | [ 5 tests started ] 56 | [ 0 tests aborted ] 57 | [ 5 tests successful ] 58 | [ 0 tests failed ] 59 | ---- 60 | 61 | ==== 62 | 63 | .Gradle 64 | [%collapsible] 65 | ==== 66 | [source,kotlin,subs="attributes+"] 67 | .build.gradle[.kts] 68 | ---- 69 | dependencies { 70 | testImplementation("org.testng:testng:{testng-max-version}") 71 | testRuntimeOnly("org.junit.support:testng-engine:{testng-engine-version}") // <.> 72 | } 73 | tasks.test { 74 | useJUnitPlatform() // <.> 75 | } 76 | ---- 77 | <.> Add the engine as an extra dependency for running tests 78 | <.> Configure the test task to use the JUnit Platform 79 | ==== 80 | 81 | .Maven 82 | [%collapsible] 83 | ==== 84 | [source,xml,subs="attributes+"] 85 | .pom.xml 86 | ---- 87 | 88 | 89 | 90 | 91 | org.testng 92 | testng 93 | {testng-max-version} 94 | test 95 | 96 | 97 | org.junit.support 98 | testng-engine 99 | {testng-engine-version} 100 | test 101 | 102 | 103 | 104 | 105 | 106 | maven-surefire-plugin 107 | {surefire-version} 108 | 109 | 110 | 111 | 112 | 113 | ---- 114 | ==== 115 | 116 | === Supported versions 117 | 118 | ==== TestNG 119 | 120 | The engine supports TestNG version {testng-min-version} and above. 121 | 122 | ==== JUnit Platform 123 | 124 | The engine requires at least JUnit Platform 1.5.x. 125 | 126 | === Configuration Parameters 127 | 128 | The following JUnit Platform https://junit.org/junit5/docs/current/user-guide/#running-tests-config-params[configuration parameters] are supported. 129 | 130 | ==== Execution 131 | 132 | `testng.allowReturnValues` (boolean):: 133 | whether methods annotated with `@Test` that have return values should be considered test methods (default: `false`; see https://testng.org/doc/documentation-main.html#test-methods[documentation]) 134 | + 135 | `testng.dataProviderThreadCount` (integer):: 136 | maximum number of threads to use for running data providers in parallel, if enabled via `@DataProvider(parallel = true)` (default: `10`; see https://testng.org/doc/documentation-main.html#parameters-dataproviders[documentation]) 137 | + 138 | `testng.excludedGroups` (comma-separated list):: 139 | groups to exclude (see <>) 140 | + 141 | `testng.groups` (comma-separated list):: 142 | groups to be run (see <>) 143 | + 144 | `testng.parallel` (methods|tests|classes|instances|none):: 145 | TestNG's parallel execution mode for running tests in separate threads (default: `"none"`; see https://testng.org/doc/documentation-main.html#parallel-tests[documentation]) 146 | + 147 | `testng.preserveOrder` (boolean):: 148 | whether classes and methods should be run in a predictable order (default: `true`; see https://testng.org/doc/documentation-main.html#testng-xml[documentation]) 149 | + 150 | `testng.threadCount` (integer):: 151 | maximum number of threads for running tests in parallel, if enabled via `testng.parallel` (default: `5`; see https://testng.org/doc/documentation-main.html#parallel-tests[documentation]) 152 | 153 | ==== Reporting 154 | 155 | `testng.listeners` (comma-separated list of fully-qualified class names):: 156 | custom listeners that should be registered when executing tests (default: `""`; see https://testng.org/doc/documentation-main.html#testng-listeners[documentation]) 157 | + 158 | `testng.outputDirectory` (file path):: 159 | the output directory for reports (default: `"test-output"`; see https://testng.org/doc/documentation-main.html#running-testng[documentation]) 160 | + 161 | `testng.useDefaultListeners` (boolean):: 162 | whether TestNG's default report generating listeners should be used (default: `false`; see https://testng.org/doc/documentation-main.html#running-testng[documentation]) 163 | + 164 | `testng.verbose` (integer):: 165 | TestNG's level of verbosity for console output (default: `0`) 166 | 167 | === Generating TestNG reports 168 | 169 | .Console Launcher 170 | [%collapsible] 171 | ==== 172 | [source] 173 | ---- 174 | $ java -cp 'lib/*' org.junit.platform.console.ConsoleLauncher \ 175 | -cp bin/main -cp bin/test \ 176 | --include-engine=testng --scan-classpath=bin/test \ 177 | --config=testng.useDefaultListeners=true \ 178 | --config=testng.outputDirectory=test-reports 179 | ---- 180 | ==== 181 | 182 | .Gradle 183 | [%collapsible] 184 | ==== 185 | [source,kotlin,subs="attributes+"] 186 | .build.gradle[.kts] 187 | ---- 188 | tasks.test { 189 | useJUnitPlatform() 190 | systemProperty("testng.useDefaultListeners", "true") 191 | 192 | val testNGReportsDir = layout.buildDirectory.dir("reports/testng") 193 | outputs.dir(testNGReportsDir).withPropertyName("testng-reports") 194 | jvmArgumentProviders += CommandLineArgumentProvider { 195 | listOf("-Dtestng.outputDirectory=${testNGReportsDir.get().asFile.absolutePath}") 196 | } 197 | } 198 | ---- 199 | ==== 200 | 201 | .Maven 202 | [%collapsible] 203 | ==== 204 | [source,xml,subs="attributes+"] 205 | ---- 206 | 207 | 208 | 209 | 210 | 211 | maven-surefire-plugin 212 | {surefire-version} 213 | 214 | 215 | 216 | testng.useDefaultListeners = true 217 | testng.outputDirectory = ${project.build.directory}/testng-reports 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | ---- 227 | ==== 228 | 229 | === Registering custom listeners 230 | 231 | .Console Launcher 232 | [%collapsible] 233 | ==== 234 | [source] 235 | ---- 236 | $ java -cp 'lib/*' org.junit.platform.console.ConsoleLauncher \ 237 | -cp bin/main -cp bin/test \ 238 | --include-engine=testng --scan-classpath=bin/test \ 239 | --config=testng.listeners=com.acme.MyCustomListener1,com.acme.MyCustomListener2 240 | ---- 241 | ==== 242 | 243 | .Gradle 244 | [%collapsible] 245 | ==== 246 | [source,kotlin,subs="attributes+"] 247 | .build.gradle[.kts] 248 | ---- 249 | tasks.test { 250 | useJUnitPlatform() 251 | systemProperty("testng.listeners", "com.acme.MyCustomListener1, com.acme.MyCustomListener2") 252 | } 253 | ---- 254 | ==== 255 | 256 | .Maven 257 | [%collapsible] 258 | ==== 259 | [source,xml,subs="attributes+"] 260 | ---- 261 | 262 | 263 | 264 | 265 | 266 | maven-surefire-plugin 267 | {surefire-version} 268 | 269 | 270 | 271 | testng.listeners = com.acme.MyCustomListener1, com.acme.MyCustomListener2 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | ---- 281 | ==== 282 | 283 | 284 | == Limitations 285 | 286 | [#groups_vs_tags] 287 | === Groups vs. Tags 288 | 289 | https://testng.org/doc/documentation-main.html#test-groups[Groups] declared via the `@Test` annotation on test classes and methods are exposed as tags to the JUnit Platform. 290 | Hence, you can use https://junit.org/junit5/docs/current/user-guide/#running-tests-tag-expressions[tag filter expressions] to include or exclude certain groups. 291 | For example, given the following test class, including the tag `included` and excluding `excluded` will run test `a` and `c` but not `b` and `d`. 292 | 293 | [source,java] 294 | ---- 295 | import org.testng.annotations.Test; 296 | 297 | public class TestWithGroups { 298 | @Test(groups = "included") 299 | public void a() {} 300 | @Test(groups = {"included", "excluded"}) 301 | public void b() {} 302 | @Test(groups = "included") 303 | public void c() {} 304 | @Test 305 | public void d() {} 306 | } 307 | ---- 308 | 309 | However, since tags and therefore groups are filtered rather than selected, `@BeforeGroups` and `@AfterGroups` configuration methods are not executed on some versions of TestNG. 310 | Moreover, tag filters do not support wildcards such as `integration.*`. 311 | Therefore, instead of using tags, you can use the `testng.groups` and `testng.excludedGroups` configuration parameters to specify the groups that should be selected. 312 | 313 | [WARNING] 314 | ==== 315 | Ideally, you should use either tags or the group configuration parameters. 316 | If you specify both, please ensure they match. 317 | ==== 318 | 319 | ==== Using Tags 320 | 321 | .Console Launcher 322 | [%collapsible] 323 | ==== 324 | [source] 325 | ---- 326 | $ java -cp 'lib/*' org.junit.platform.console.ConsoleLauncher \ 327 | -cp bin/main -cp bin/test \ 328 | --include-engine=testng --scan-classpath=bin/test \ 329 | --include-tag=included --exclude-tag=excluded 330 | ---- 331 | ==== 332 | 333 | .Gradle 334 | [%collapsible] 335 | ==== 336 | [source,kotlin,subs="attributes+"] 337 | .build.gradle[.kts] 338 | ---- 339 | tasks.test { 340 | useJUnitPlatform { 341 | includeTags("included") 342 | excludeTags("excluded") 343 | } 344 | } 345 | ---- 346 | ==== 347 | 348 | .Maven 349 | [%collapsible] 350 | ==== 351 | [source,xml,subs="attributes+"] 352 | ---- 353 | 354 | 355 | 356 | 357 | 358 | maven-surefire-plugin 359 | {surefire-version} 360 | 361 | included 362 | excluded 363 | 364 | 365 | 366 | 367 | 368 | 369 | ---- 370 | ==== 371 | 372 | ==== Using Configuration Parameters 373 | 374 | .Console Launcher 375 | [%collapsible] 376 | ==== 377 | [source] 378 | ---- 379 | $ java -cp 'lib/*' org.junit.platform.console.ConsoleLauncher \ 380 | -cp bin/main -cp bin/test \ 381 | --include-engine=testng --scan-classpath=bin/test \ 382 | --config=testng.groups=included --config=testng.excludedGroups=excluded 383 | ---- 384 | ==== 385 | 386 | .Gradle 387 | [%collapsible] 388 | ==== 389 | [source,kotlin,subs="attributes+"] 390 | .build.gradle[.kts] 391 | ---- 392 | tasks.test { 393 | useJUnitPlatform() 394 | systemProperty("testng.groups", "included") 395 | systemProperty("testng.excludedGroups", "excluded") 396 | } 397 | ---- 398 | ==== 399 | 400 | .Maven 401 | [%collapsible] 402 | ==== 403 | [source,xml,subs="attributes+"] 404 | ---- 405 | 406 | 407 | 408 | 409 | 410 | maven-surefire-plugin 411 | {surefire-version} 412 | 413 | 414 | 415 | testng.groups = included 416 | testng.excludedGroups = excluded 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | ---- 426 | ==== 427 | 428 | === Suites 429 | 430 | The engine's main intention is integration with build tools like Gradle and Maven. 431 | Hence, custom suites specified via `testng.xml` files are not supported. 432 | However, you can use https://junit.org/junit5/docs/current/user-guide/#junit-platform-suite-engine[JUnit's suite support] with this engine. 433 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | import com.diffplug.spotless.LineEnding 2 | import java.util.EnumSet 3 | import org.gradle.api.tasks.PathSensitivity.RELATIVE 4 | import org.gradle.api.tasks.testing.logging.TestLogEvent 5 | 6 | plugins { 7 | `java-library` 8 | `java-test-fixtures` 9 | `maven-publish` 10 | signing 11 | id("com.diffplug.spotless") version "7.0.4" 12 | id("com.gradleup.nmcp") version "0.1.5" 13 | } 14 | 15 | val javaToolchainVersion = providers.gradleProperty("javaToolchainVersion") 16 | .map { JavaLanguageVersion.of(it) } 17 | .orElse(JavaLanguageVersion.of(17)) 18 | .get() 19 | 20 | java { 21 | toolchain.languageVersion.set(javaToolchainVersion) 22 | withJavadocJar() 23 | withSourcesJar() 24 | } 25 | 26 | repositories { 27 | mavenCentral() 28 | maven(url = "https://oss.sonatype.org/content/repositories/snapshots/") { 29 | mavenContent { 30 | includeModule("org.testng", "testng") 31 | snapshotsOnly() 32 | } 33 | } 34 | } 35 | 36 | val moduleSourceSet = sourceSets.create("module") { 37 | compileClasspath += sourceSets.main.get().output 38 | } 39 | 40 | configurations { 41 | named(moduleSourceSet.compileClasspathConfigurationName) { 42 | extendsFrom(compileClasspath.get()) 43 | } 44 | } 45 | 46 | val supportedTestNGVersions = listOf( 47 | "6.14.3" to 8, 48 | "7.0.0" to 8, 49 | "7.1.0" to 8, 50 | "7.3.0" to 8, 51 | "7.4.0" to 8, 52 | "7.5.1" to 8, 53 | "7.6.1" to 11, 54 | "7.7.1" to 11, 55 | "7.8.0" to 11, 56 | "7.9.0" to 11, 57 | "7.10.2" to 11, 58 | libs.versions.latestTestNG.get() to 11, 59 | ).associateBy({ Version(it.first) }, { JavaLanguageVersion.of(it.second) }) 60 | 61 | val lastJdk8CompatibleRelease = supportedTestNGVersions.entries.last { it.value == JavaLanguageVersion.of(8) }.key 62 | 63 | val snapshotTestNGVersion = Version(libs.versions.snapshotTestNG.get()) 64 | 65 | val allTestNGVersions = supportedTestNGVersions.keys + listOf(snapshotTestNGVersion) 66 | 67 | val testRuntimeClasspath: Configuration by configurations.getting 68 | val testNGTestConfigurationsByVersion = allTestNGVersions.associateWith { version -> 69 | configurations.create("testRuntimeClasspath_${version.suffix}") { 70 | extendsFrom(testRuntimeClasspath) 71 | } 72 | } 73 | val testFixturesRuntimeClasspath: Configuration by configurations.getting 74 | val testNGTestFixturesConfigurationsByVersion = allTestNGVersions.associateWith { version -> 75 | configurations.create("testFixturesRuntimeClasspath_${version.suffix}") { 76 | extendsFrom(testFixturesRuntimeClasspath) 77 | } 78 | } 79 | 80 | dependencies { 81 | api(platform(libs.junit.bom)) 82 | api("org.junit.platform:junit-platform-engine") 83 | 84 | implementation("org.testng:testng") { 85 | version { 86 | require(supportedTestNGVersions.keys.first().value) 87 | prefer(supportedTestNGVersions.keys.last().value) 88 | } 89 | } 90 | 91 | compileOnly("org.testng:testng:${lastJdk8CompatibleRelease}") 92 | testCompileOnly("org.testng:testng:${lastJdk8CompatibleRelease}") 93 | testFixturesCompileOnly("org.testng:testng:${lastJdk8CompatibleRelease}") 94 | 95 | constraints { 96 | testNGTestConfigurationsByVersion.forEach { (version, configuration) -> 97 | configuration("org.testng:testng") { 98 | version { 99 | strictly(version.value) 100 | } 101 | } 102 | } 103 | testNGTestFixturesConfigurationsByVersion.forEach { (version, configuration) -> 104 | configuration("org.testng:testng") { 105 | version { 106 | strictly(version.value) 107 | } 108 | } 109 | } 110 | } 111 | 112 | testImplementation("org.junit.jupiter:junit-jupiter") 113 | testImplementation("org.junit.platform:junit-platform-testkit") 114 | testImplementation(libs.mockito.junit.jupiter) 115 | testImplementation(libs.assertj.core) 116 | testImplementation(libs.maven.artifact) { 117 | because("ComparableVersion is used to reason about tested TestNG version") 118 | } 119 | testImplementation(libs.commons.lang3) 120 | 121 | testRuntimeOnly(platform(libs.log4j.bom)) 122 | testRuntimeOnly("org.apache.logging.log4j:log4j-core") 123 | testRuntimeOnly("org.apache.logging.log4j:log4j-jul") 124 | 125 | testFixturesImplementation(libs.junit4) 126 | testFixturesRuntimeOnly("org.junit.platform:junit-platform-console") 127 | } 128 | 129 | tasks { 130 | listOf(compileJava, compileTestFixturesJava).forEach { task -> 131 | task.configure { 132 | options.release.set(8) 133 | if (javaToolchainVersion >= JavaLanguageVersion.of(20)) { 134 | // `--release=8` is deprecated on JDK 20 and later 135 | options.compilerArgs.add("-Xlint:-options") 136 | } 137 | } 138 | } 139 | compileTestJava { 140 | options.release.set(17) 141 | } 142 | named(moduleSourceSet.compileJavaTaskName) { 143 | options.release.set(9) 144 | options.compilerArgs.addAll(listOf("--module-version", "${project.version}")) 145 | val files = files(sourceSets.main.map { it.java.srcDirs }) 146 | inputs.files(files).withPropertyName("mainSrcDirs").withPathSensitivity(RELATIVE) 147 | options.compilerArgumentProviders += CommandLineArgumentProvider { 148 | listOf("--patch-module", "org.junit.support.testng.engine=${files.asPath}") 149 | } 150 | } 151 | withType().configureEach { 152 | options.compilerArgs.addAll(listOf("-Xlint:all,-requires-automatic", "-Werror")) 153 | } 154 | jar { 155 | from(moduleSourceSet.output) { 156 | include("module-info.class") 157 | } 158 | } 159 | withType().configureEach { 160 | from(rootDir) { 161 | include("LICENSE.md") 162 | into("META-INF") 163 | } 164 | } 165 | testNGTestFixturesConfigurationsByVersion.forEach { (version, configuration) -> 166 | val javaMinVersionLauncher = project.the().launcherFor { 167 | languageVersion.set(supportedTestNGVersions[version]) 168 | } 169 | register("testFixturesTestNG_${version.suffix}") { 170 | javaLauncher.set(javaMinVersionLauncher) 171 | classpath = configuration + sourceSets.testFixtures.get().output 172 | testClassesDirs = sourceSets.testFixtures.get().output 173 | useTestNG { 174 | listeners.add("example.configuration.parameters.SystemPropertyProvidingListener") 175 | } 176 | } 177 | register("testFixturesJUnitPlatform_${version.suffix}") { 178 | javaLauncher.set(javaMinVersionLauncher) 179 | classpath = configuration + sourceSets.testFixtures.get().output 180 | testClassesDirs = sourceSets.testFixtures.get().output 181 | useJUnitPlatform { 182 | includeEngines("testng") 183 | } 184 | systemProperty("testng.listeners", "example.configuration.parameters.SystemPropertyProvidingListener") 185 | testLogging { 186 | events = EnumSet.allOf(TestLogEvent::class.java) 187 | } 188 | } 189 | register("testFixturesConsoleLauncher_${version.suffix}") { 190 | javaLauncher.set(javaMinVersionLauncher) 191 | classpath = configuration + sourceSets.testFixtures.get().output 192 | mainClass.set("org.junit.platform.console.ConsoleLauncher") 193 | args( 194 | "--scan-classpath", sourceSets.testFixtures.get().output.asPath, 195 | "--fail-if-no-tests", 196 | "--disable-banner", 197 | "--include-classname", ".*TestCase", 198 | "--exclude-package", "example.configuration", 199 | "--include-engine", "testng" 200 | ) 201 | isIgnoreExitValue = true 202 | } 203 | } 204 | val testTasks = testNGTestConfigurationsByVersion.map { (version, configuration) -> 205 | register("test_${version.suffix}") { 206 | classpath = configuration + sourceSets.test.get().output 207 | testClassesDirs = files(testing.suites.named("test").map { it.sources.output.classesDirs }) 208 | group = JavaBasePlugin.VERIFICATION_GROUP 209 | useJUnitPlatform { 210 | includeEngines("junit-jupiter") 211 | } 212 | systemProperty("testng.version", version.value) 213 | systemProperty("java.util.logging.manager", "org.apache.logging.log4j.jul.LogManager") 214 | } 215 | } 216 | test { 217 | enabled = false 218 | dependsOn(testTasks) 219 | } 220 | } 221 | 222 | spotless { 223 | val licenseHeaderFile = file("gradle/spotless/eclipse-public-license-2.0.java") 224 | java { 225 | licenseHeaderFile(licenseHeaderFile) 226 | importOrderFile(rootProject.file("gradle/spotless/junit-eclipse.importorder")) 227 | eclipse().configFile(rootProject.file("gradle/spotless/junit-eclipse-formatter-settings.xml")) 228 | if (!JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_15)) { 229 | // Doesn't work with Java 15 text blocks, see https://github.com/diffplug/spotless/issues/713 230 | removeUnusedImports() 231 | } 232 | trimTrailingWhitespace() 233 | endWithNewline() 234 | } 235 | format("javaMisc") { 236 | target("src/**/package-info.java", "src/**/module-info.java") 237 | licenseHeaderFile(licenseHeaderFile, "/\\*\\*") 238 | } 239 | // https://github.com/diffplug/spotless/issues/1644 240 | lineEndings = LineEnding.PLATFORM_NATIVE 241 | } 242 | 243 | publishing { 244 | publications { 245 | create("maven") { 246 | from(components["java"].apply { 247 | this as AdhocComponentWithVariants 248 | withVariantsFromConfiguration(configurations.testFixturesApiElements.get()) { skip() } 249 | withVariantsFromConfiguration(configurations.testFixturesRuntimeElements.get()) { skip() } 250 | }) 251 | pom { 252 | name.set("TestNG Engine for the JUnit Platform") 253 | description.set(project.description) 254 | url.set("https://junit.org/junit5/") 255 | scm { 256 | connection.set("scm:git:git://github.com/junit-team/testng-engine.git") 257 | developerConnection.set("scm:git:git://github.com/junit-team/testng-engine.git") 258 | url.set("https://github.com/junit-team/testng-engine") 259 | } 260 | licenses { 261 | license { 262 | name.set("Eclipse Public License v2.0") 263 | url.set("https://www.eclipse.org/legal/epl-v20.html") 264 | } 265 | } 266 | developers { 267 | developer { 268 | id.set("junit-team") 269 | name.set("JUnit team") 270 | email.set("team@junit.org") 271 | } 272 | } 273 | } 274 | } 275 | } 276 | repositories { 277 | maven { 278 | name = "mavenCentralSnapshots" 279 | url = uri("https://central.sonatype.com/repository/maven-snapshots/") 280 | credentials { 281 | username = providers.gradleProperty("mavenCentralUsername").orNull 282 | password = providers.gradleProperty("mavenCentralPassword").orNull 283 | } 284 | } 285 | } 286 | } 287 | 288 | nmcp { 289 | centralPortal { 290 | username = providers.gradleProperty("mavenCentralUsername") 291 | password = providers.gradleProperty("mavenCentralPassword") 292 | publishingType = providers.gradleProperty("mavenCentralPublishingType").orElse("USER_MANAGED") 293 | } 294 | } 295 | 296 | signing { 297 | useGpgCmd() 298 | sign(publishing.publications) 299 | } 300 | 301 | tasks.withType().configureEach { 302 | enabled = !project.version.toString().contains("SNAPSHOT") 303 | } 304 | 305 | data class Version(val value: String) { 306 | 307 | companion object { 308 | private val pattern = "[^\\w]".toRegex() 309 | } 310 | 311 | val suffix: String by lazy { 312 | value.replace(pattern, "_") 313 | } 314 | 315 | fun isSnapshot() : Boolean = value.endsWith("-SNAPSHOT") 316 | 317 | override fun toString() = value 318 | } 319 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | group = org.junit.support 2 | version = 1.0.6-SNAPSHOT 3 | description = Allows executing TestNG tests on the JUnit Platform 4 | 5 | org.gradle.java.installations.fromEnv=TOOLCHAIN_JDK 6 | org.gradle.configuration-cache=true 7 | org.gradle.configuration-cache.parallel=true 8 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | latestTestNG = "7.11.0" # Keep in sync with TestContext.java and README.adoc 3 | snapshotTestNG = "7.12.0-SNAPSHOT" 4 | 5 | [libraries] 6 | assertj-core = { module = "org.assertj:assertj-core", version = "3.27.3" } 7 | commons-lang3 = { module = "org.apache.commons:commons-lang3", version = "3.17.0" } 8 | junit4 = { module = "junit:junit", version = "4.13.2" } 9 | junit-bom = { module = "org.junit:junit-bom", version = "5.7.2" } 10 | log4j-bom = { module = "org.apache.logging.log4j:log4j-bom", version = "2.24.3" } 11 | maven-artifact = { module = "org.apache.maven:maven-artifact", version = "3.9.10" } 12 | mockito-junit-jupiter = { module = "org.mockito:mockito-junit-jupiter", version = "5.18.0" } 13 | testng = { module = "org.testng:testng", version.ref = "latestTestNG" } 14 | -------------------------------------------------------------------------------- /gradle/spotless/eclipse-public-license-2.0.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | -------------------------------------------------------------------------------- /gradle/spotless/junit-eclipse.importorder: -------------------------------------------------------------------------------- 1 | #Organize Import Order 2 | 0=java 3 | 1=javax 4 | 2=jdk 5 | 3=aQute 6 | 4=junit 7 | 5=de 8 | 6=com 9 | 7=example 10 | 8=extensions 11 | 9=io 12 | 10=org 13 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/junit-team/testng-engine/4eb60d782dc6e41663fa65f759a68622dd50fb1e/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionSha256Sum=7197a12f450794931532469d4ff21a59ea2c1cd59a3ec3f89c035c3c420a6999 4 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.2-bin.zip 5 | networkTimeout=10000 6 | validateDistributionUrl=true 7 | zipStoreBase=GRADLE_USER_HOME 8 | zipStorePath=wrapper/dists 9 | -------------------------------------------------------------------------------- /gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit 90 | 91 | # Use the maximum available, or set MAX_FD != -1 to use that value. 92 | MAX_FD=maximum 93 | 94 | warn () { 95 | echo "$*" 96 | } >&2 97 | 98 | die () { 99 | echo 100 | echo "$*" 101 | echo 102 | exit 1 103 | } >&2 104 | 105 | # OS specific support (must be 'true' or 'false'). 106 | cygwin=false 107 | msys=false 108 | darwin=false 109 | nonstop=false 110 | case "$( uname )" in #( 111 | CYGWIN* ) cygwin=true ;; #( 112 | Darwin* ) darwin=true ;; #( 113 | MSYS* | MINGW* ) msys=true ;; #( 114 | NONSTOP* ) nonstop=true ;; 115 | esac 116 | 117 | CLASSPATH="\\\"\\\"" 118 | 119 | 120 | # Determine the Java command to use to start the JVM. 121 | if [ -n "$JAVA_HOME" ] ; then 122 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 123 | # IBM's JDK on AIX uses strange locations for the executables 124 | JAVACMD=$JAVA_HOME/jre/sh/java 125 | else 126 | JAVACMD=$JAVA_HOME/bin/java 127 | fi 128 | if [ ! -x "$JAVACMD" ] ; then 129 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 130 | 131 | Please set the JAVA_HOME variable in your environment to match the 132 | location of your Java installation." 133 | fi 134 | else 135 | JAVACMD=java 136 | if ! command -v java >/dev/null 2>&1 137 | then 138 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 139 | 140 | Please set the JAVA_HOME variable in your environment to match the 141 | location of your Java installation." 142 | fi 143 | fi 144 | 145 | # Increase the maximum file descriptors if we can. 146 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 147 | case $MAX_FD in #( 148 | max*) 149 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 150 | # shellcheck disable=SC2039,SC3045 151 | MAX_FD=$( ulimit -H -n ) || 152 | warn "Could not query maximum file descriptor limit" 153 | esac 154 | case $MAX_FD in #( 155 | '' | soft) :;; #( 156 | *) 157 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 158 | # shellcheck disable=SC2039,SC3045 159 | ulimit -n "$MAX_FD" || 160 | warn "Could not set maximum file descriptor limit to $MAX_FD" 161 | esac 162 | fi 163 | 164 | # Collect all arguments for the java command, stacking in reverse order: 165 | # * args from the command line 166 | # * the main class name 167 | # * -classpath 168 | # * -D...appname settings 169 | # * --module-path (only if needed) 170 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 171 | 172 | # For Cygwin or MSYS, switch paths to Windows format before running java 173 | if "$cygwin" || "$msys" ; then 174 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 175 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 176 | 177 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 178 | 179 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 180 | for arg do 181 | if 182 | case $arg in #( 183 | -*) false ;; # don't mess with options #( 184 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 185 | [ -e "$t" ] ;; #( 186 | *) false ;; 187 | esac 188 | then 189 | arg=$( cygpath --path --ignore --mixed "$arg" ) 190 | fi 191 | # Roll the args list around exactly as many times as the number of 192 | # args, so each arg winds up back in the position where it started, but 193 | # possibly modified. 194 | # 195 | # NB: a `for` loop captures its iteration list before it begins, so 196 | # changing the positional parameters here affects neither the number of 197 | # iterations, nor the values presented in `arg`. 198 | shift # remove old arg 199 | set -- "$@" "$arg" # push replacement arg 200 | done 201 | fi 202 | 203 | 204 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 205 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 206 | 207 | # Collect all arguments for the java command: 208 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 209 | # and any embedded shellness will be escaped. 210 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 211 | # treated as '${Hostname}' itself on the command line. 212 | 213 | set -- \ 214 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 215 | -classpath "$CLASSPATH" \ 216 | -jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \ 217 | "$@" 218 | 219 | # Stop when "xargs" is not available. 220 | if ! command -v xargs >/dev/null 2>&1 221 | then 222 | die "xargs is not available" 223 | fi 224 | 225 | # Use "xargs" to parse quoted args. 226 | # 227 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 228 | # 229 | # In Bash we could simply go: 230 | # 231 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 232 | # set -- "${ARGS[@]}" "$@" 233 | # 234 | # but POSIX shell has neither arrays nor command substitution, so instead we 235 | # post-process each arg (as a line of input to sed) to backslash-escape any 236 | # character that might be a shell metacharacter, then use eval to reverse 237 | # that process (while maintaining the separation between arguments), and wrap 238 | # the whole thing up as a single "set" statement. 239 | # 240 | # This will of course break if any of these variables contains a newline or 241 | # an unmatched quote. 242 | # 243 | 244 | eval "set -- $( 245 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 246 | xargs -n1 | 247 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 248 | tr '\n' ' ' 249 | )" '"$@"' 250 | 251 | exec "$JAVACMD" "$@" 252 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH= 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.gradle.develocity") version "4.0.2" 3 | id("com.gradle.common-custom-user-data-gradle-plugin") version "2.3" 4 | } 5 | 6 | rootProject.name = "testng-engine" 7 | 8 | develocity { 9 | buildScan { 10 | val isCiServer = System.getenv("CI") != null 11 | 12 | server = "https://ge.junit.org" 13 | uploadInBackground = !isCiServer 14 | 15 | obfuscation { 16 | if (isCiServer) { 17 | username { "github" } 18 | } else { 19 | hostname { null } 20 | ipAddresses { emptyList() } 21 | } 22 | } 23 | 24 | publishing.onlyIf { it.isAuthenticated } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/ClassDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.Collections.emptySet; 14 | import static java.util.Collections.unmodifiableSet; 15 | 16 | import java.util.LinkedHashSet; 17 | import java.util.Optional; 18 | import java.util.Set; 19 | import java.util.concurrent.ConcurrentHashMap; 20 | import java.util.concurrent.ConcurrentMap; 21 | import java.util.concurrent.atomic.AtomicInteger; 22 | import java.util.stream.Stream; 23 | 24 | import org.junit.platform.engine.TestDescriptor; 25 | import org.junit.platform.engine.TestTag; 26 | import org.junit.platform.engine.UniqueId; 27 | import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; 28 | import org.junit.platform.engine.support.descriptor.ClassSource; 29 | import org.junit.platform.engine.support.descriptor.MethodSource; 30 | import org.testng.ITestResult; 31 | 32 | class ClassDescriptor extends AbstractTestDescriptor { 33 | 34 | static final String SEGMENT_TYPE = "class"; 35 | 36 | private final ConcurrentMap methodsById = new ConcurrentHashMap<>(); 37 | private final Class testClass; 38 | private final Set tags; 39 | final AtomicInteger remainingIterations = new AtomicInteger(); 40 | ExecutionStrategy executionStrategy = new IncludeMethodsExecutionStrategy(); 41 | 42 | ClassDescriptor(UniqueId uniqueId, Class testClass, Set tags) { 43 | super(uniqueId, determineDisplayName(testClass), ClassSource.from(testClass)); 44 | this.testClass = testClass; 45 | this.tags = tags; 46 | } 47 | 48 | private static String determineDisplayName(Class testClass) { 49 | String simpleName = testClass.getSimpleName(); 50 | return simpleName.isEmpty() ? testClass.getName() : simpleName; 51 | } 52 | 53 | @Override 54 | public String getLegacyReportingName() { 55 | return testClass.getName(); 56 | } 57 | 58 | Class getTestClass() { 59 | return testClass; 60 | } 61 | 62 | @Override 63 | public Type getType() { 64 | return Type.CONTAINER; 65 | } 66 | 67 | @Override 68 | public Set getTags() { 69 | return unmodifiableSet(tags); 70 | } 71 | 72 | @Override 73 | public void addChild(TestDescriptor child) { 74 | methodsById.put(toChildKey(child), (MethodDescriptor) child); 75 | super.addChild(child); 76 | } 77 | 78 | @Override 79 | public void removeChild(TestDescriptor child) { 80 | methodsById.remove(toChildKey(child)); 81 | super.removeChild(child); 82 | } 83 | 84 | private String toChildKey(TestDescriptor child) { 85 | return child.getUniqueId().getLastSegment().getValue(); 86 | } 87 | 88 | public Optional findMethodDescriptor(ITestResult result) { 89 | return Optional.ofNullable( 90 | methodsById.get(MethodDescriptor.toMethodId(result, MethodSignature.from(result.getMethod())))); 91 | } 92 | 93 | public void includeTestMethod(String methodName) { 94 | executionStrategy = executionStrategy.includeMethod(methodName); 95 | } 96 | 97 | public void selectEntireClass() { 98 | executionStrategy = executionStrategy.selectEntireClass(); 99 | } 100 | 101 | public void prepareExecution() { 102 | executionStrategy = new IncludeMethodsExecutionStrategy(getChildren().stream() // 103 | .map(child -> (MethodDescriptor) child) // 104 | .map(MethodDescriptor::getMethodSource) // 105 | .map(MethodSource::getMethodName)); 106 | } 107 | 108 | interface ExecutionStrategy { 109 | Optional> getTestClass(); 110 | 111 | Set getTestMethods(); 112 | 113 | ExecutionStrategy selectEntireClass(); 114 | 115 | ExecutionStrategy includeMethod(String methodName); 116 | } 117 | 118 | class EntireClassExecutionStrategy implements ExecutionStrategy { 119 | 120 | @Override 121 | public Optional> getTestClass() { 122 | return Optional.of(testClass); 123 | } 124 | 125 | @Override 126 | public Set getTestMethods() { 127 | return emptySet(); 128 | } 129 | 130 | @Override 131 | public ExecutionStrategy selectEntireClass() { 132 | return this; 133 | } 134 | 135 | @Override 136 | public ExecutionStrategy includeMethod(String methodName) { 137 | return this; 138 | } 139 | } 140 | 141 | class IncludeMethodsExecutionStrategy implements ExecutionStrategy { 142 | 143 | private final Set testMethods = new LinkedHashSet<>(); 144 | 145 | public IncludeMethodsExecutionStrategy() { 146 | } 147 | 148 | public IncludeMethodsExecutionStrategy(Stream testMethods) { 149 | testMethods.forEach(this.testMethods::add); 150 | } 151 | 152 | @Override 153 | public Optional> getTestClass() { 154 | return Optional.empty(); 155 | } 156 | 157 | @Override 158 | public Set getTestMethods() { 159 | return testMethods; 160 | } 161 | 162 | @Override 163 | public ExecutionStrategy selectEntireClass() { 164 | return new EntireClassExecutionStrategy(); 165 | } 166 | 167 | @Override 168 | public ExecutionStrategy includeMethod(String methodName) { 169 | testMethods.add(methodName); 170 | return this; 171 | } 172 | } 173 | } 174 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/ConfiguringListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.util.List; 14 | 15 | import org.junit.platform.engine.ConfigurationParameters; 16 | import org.testng.xml.XmlSuite; 17 | 18 | class ConfiguringListener extends DefaultListener { 19 | 20 | private final ConfigurationParameters configurationParameters; 21 | 22 | ConfiguringListener(ConfigurationParameters configurationParameters) { 23 | this.configurationParameters = configurationParameters; 24 | } 25 | 26 | @Override 27 | public void alter(List suites) { 28 | configurationParameters.getBoolean("testng.allowReturnValues") // 29 | .ifPresent(allowReturnValues -> suites.forEach(it -> it.setAllowReturnValues(allowReturnValues))); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/DefaultListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.util.List; 14 | 15 | import org.testng.IAlterSuiteListener; 16 | import org.testng.IClassListener; 17 | import org.testng.IConfigurationListener; 18 | import org.testng.ITestClass; 19 | import org.testng.ITestContext; 20 | import org.testng.ITestListener; 21 | import org.testng.ITestNGMethod; 22 | import org.testng.ITestResult; 23 | import org.testng.xml.XmlSuite; 24 | 25 | abstract class DefaultListener implements IClassListener, ITestListener, IConfigurationListener, IAlterSuiteListener { 26 | 27 | @Override 28 | public void alter(List suites) { 29 | } 30 | 31 | @Override 32 | public void onBeforeClass(ITestClass testClass) { 33 | } 34 | 35 | @Override 36 | public void onAfterClass(ITestClass testClass) { 37 | } 38 | 39 | @Override 40 | public void onTestStart(ITestResult result) { 41 | } 42 | 43 | @Override 44 | public void onTestSuccess(ITestResult result) { 45 | } 46 | 47 | @Override 48 | public void onTestFailure(ITestResult result) { 49 | } 50 | 51 | @Override 52 | public void onTestSkipped(ITestResult result) { 53 | } 54 | 55 | @Override 56 | public void onTestFailedButWithinSuccessPercentage(ITestResult result) { 57 | } 58 | 59 | @Override 60 | public void onTestFailedWithTimeout(ITestResult result) { 61 | } 62 | 63 | @Override 64 | public void onStart(ITestContext context) { 65 | } 66 | 67 | @Override 68 | public void onFinish(ITestContext context) { 69 | } 70 | 71 | @Override 72 | public void onConfigurationSuccess(ITestResult tr) { 73 | } 74 | 75 | @Override 76 | public void onConfigurationSuccess(ITestResult tr, ITestNGMethod tm) { 77 | } 78 | 79 | @Override 80 | public void onConfigurationFailure(ITestResult tr) { 81 | } 82 | 83 | @Override 84 | public void onConfigurationFailure(ITestResult tr, ITestNGMethod tm) { 85 | } 86 | 87 | @Override 88 | public void onConfigurationSkip(ITestResult tr) { 89 | } 90 | 91 | @Override 92 | public void onConfigurationSkip(ITestResult tr, ITestNGMethod tm) { 93 | } 94 | 95 | @Override 96 | public void beforeConfiguration(ITestResult tr) { 97 | } 98 | 99 | @Override 100 | public void beforeConfiguration(ITestResult tr, ITestNGMethod tm) { 101 | } 102 | } 103 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/DiscoveryListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.util.HashSet; 14 | import java.util.Optional; 15 | import java.util.Set; 16 | import java.util.function.Predicate; 17 | 18 | import org.junit.platform.engine.EngineDiscoveryRequest; 19 | import org.junit.platform.engine.Filter; 20 | import org.junit.platform.engine.TestDescriptor; 21 | import org.junit.platform.engine.discovery.ClassNameFilter; 22 | import org.testng.ITestClass; 23 | import org.testng.ITestResult; 24 | 25 | class DiscoveryListener extends DefaultListener { 26 | 27 | private final TestClassRegistry testClassRegistry = new TestClassRegistry(); 28 | private final TestNGEngineDescriptor engineDescriptor; 29 | private final Predicate classNameFilter; 30 | 31 | public DiscoveryListener(EngineDiscoveryRequest request, TestNGEngineDescriptor engineDescriptor) { 32 | this.classNameFilter = Filter.composeFilters(request.getFiltersByType(ClassNameFilter.class)).toPredicate(); 33 | this.engineDescriptor = engineDescriptor; 34 | } 35 | 36 | public void finalizeDiscovery() { 37 | Set classDescriptors = new HashSet<>(engineDescriptor.getClassDescriptors()); 38 | classDescriptors.removeAll(testClassRegistry.getClassDescriptors()); 39 | classDescriptors.forEach(TestDescriptor::removeFromHierarchy); 40 | } 41 | 42 | @Override 43 | public void onBeforeClass(ITestClass testClass) { 44 | testClassRegistry.start(testClass.getRealClass(), realClass -> { 45 | ClassDescriptor classDescriptor = engineDescriptor.findClassDescriptor(realClass); 46 | if (classDescriptor == null && classNameFilter.test(realClass.getName())) { 47 | classDescriptor = engineDescriptor.getTestDescriptorFactory().createClassDescriptor(engineDescriptor, 48 | realClass); 49 | engineDescriptor.addChild(classDescriptor); 50 | } 51 | return classDescriptor; 52 | }); 53 | } 54 | 55 | @Override 56 | public void onAfterClass(ITestClass testClass) { 57 | testClassRegistry.finish(testClass.getRealClass(), __ -> true, 58 | classDescriptor -> classDescriptor.remainingIterations.incrementAndGet()); 59 | } 60 | 61 | @Override 62 | public void onTestStart(ITestResult result) { 63 | addMethodDescriptor(result); 64 | } 65 | 66 | @Override 67 | public void onTestSkipped(ITestResult result) { 68 | addMethodDescriptor(result); 69 | } 70 | 71 | @Override 72 | public void onTestFailure(ITestResult result) { 73 | getClassDescriptor(result).ifPresent(classDescriptor -> addMethodDescriptor(result, classDescriptor)); 74 | } 75 | 76 | private void addMethodDescriptor(ITestResult result) { 77 | ClassDescriptor classDescriptor = getClassDescriptor(result) // 78 | .orElseThrow(() -> new IllegalStateException("Missing class descriptor for " + result.getTestClass())); 79 | addMethodDescriptor(result, classDescriptor); 80 | } 81 | 82 | private Optional getClassDescriptor(ITestResult result) { 83 | return testClassRegistry.get(result.getTestClass().getRealClass()); 84 | } 85 | 86 | private void addMethodDescriptor(ITestResult result, ClassDescriptor classDescriptor) { 87 | if (!classDescriptor.findMethodDescriptor(result).isPresent()) { 88 | classDescriptor.addChild( 89 | engineDescriptor.getTestDescriptorFactory().createMethodDescriptor(classDescriptor, result)); 90 | } 91 | } 92 | 93 | } 94 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/ExecutionListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.Collections.emptyMap; 14 | import static java.util.Objects.requireNonNull; 15 | import static java.util.stream.Collectors.toMap; 16 | import static org.junit.platform.engine.TestExecutionResult.aborted; 17 | import static org.junit.platform.engine.TestExecutionResult.failed; 18 | import static org.junit.platform.engine.TestExecutionResult.successful; 19 | 20 | import java.util.Arrays; 21 | import java.util.Iterator; 22 | import java.util.Map; 23 | import java.util.Objects; 24 | import java.util.Optional; 25 | import java.util.Set; 26 | import java.util.concurrent.ConcurrentHashMap; 27 | import java.util.concurrent.ConcurrentMap; 28 | import java.util.concurrent.CountDownLatch; 29 | import java.util.concurrent.atomic.AtomicInteger; 30 | import java.util.stream.Stream; 31 | 32 | import org.junit.platform.engine.EngineExecutionListener; 33 | import org.junit.platform.engine.TestExecutionResult; 34 | import org.junit.platform.engine.reporting.ReportEntry; 35 | import org.testng.ITestClass; 36 | import org.testng.ITestNGMethod; 37 | import org.testng.ITestResult; 38 | import org.testng.annotations.CustomAttribute; 39 | 40 | class ExecutionListener extends DefaultListener { 41 | 42 | private final TestClassRegistry testClassRegistry = new TestClassRegistry(); 43 | private final Map inProgressTestMethods = new ConcurrentHashMap<>(); 44 | 45 | private final Set engineLevelFailureResults = ConcurrentHashMap.newKeySet(); 46 | private final Map> classLevelFailureResults = new ConcurrentHashMap<>(); 47 | 48 | private final EngineExecutionListener delegate; 49 | private final TestNGEngineDescriptor engineDescriptor; 50 | 51 | ExecutionListener(EngineExecutionListener delegate, TestNGEngineDescriptor engineDescriptor) { 52 | this.delegate = delegate; 53 | this.engineDescriptor = engineDescriptor; 54 | } 55 | 56 | @Override 57 | public void onBeforeClass(ITestClass testClass) { 58 | ClassDescriptor classDescriptor = requireNonNull(engineDescriptor.findClassDescriptor(testClass.getRealClass()), 59 | "Missing class descriptor"); 60 | testClassRegistry.start(testClass.getRealClass(), __ -> { 61 | delegate.executionStarted(classDescriptor); 62 | return classDescriptor; 63 | }); 64 | } 65 | 66 | @Override 67 | public void onConfigurationFailure(ITestResult result) { 68 | handleConfigurationResult(result); 69 | } 70 | 71 | @Override 72 | public void onConfigurationSkip(ITestResult result) { 73 | handleConfigurationResult(result); 74 | } 75 | 76 | private void handleConfigurationResult(ITestResult result) { 77 | Optional classDescriptor = testClassRegistry.get(result.getTestClass().getRealClass()); 78 | if (classDescriptor.isPresent()) { 79 | classLevelFailureResults.computeIfAbsent(classDescriptor.get(), __ -> ConcurrentHashMap.newKeySet()) // 80 | .add(result); 81 | } 82 | else { 83 | engineLevelFailureResults.add(result); 84 | } 85 | } 86 | 87 | @Override 88 | public void onAfterClass(ITestClass testClass) { 89 | testClassRegistry.finish(testClass.getRealClass(), 90 | classDescriptor -> classDescriptor.remainingIterations.decrementAndGet() == 0, classDescriptor -> { 91 | finishMethodsNotYetReportedAsFinished(testClass); 92 | Set results = classLevelFailureResults.remove(classDescriptor); 93 | delegate.executionFinished(classDescriptor, toTestExecutionResult(results)); 94 | }); 95 | } 96 | 97 | @Override 98 | public void onTestStart(ITestResult result) { 99 | MethodProgress progress = startMethodProgress(result); 100 | int invocationIndex = progress.invocationIndex.getAndIncrement(); 101 | if (invocationIndex == 0) { 102 | reportStarted(result, progress); 103 | } 104 | if (progress.descriptor.getType().isContainer()) { 105 | try { 106 | progress.reportedAsStarted.await(); 107 | } 108 | catch (InterruptedException e) { 109 | throw new RuntimeException("Interrupted while waiting for test method to be reported as started"); 110 | } 111 | createInvocationAndReportStarted(progress, invocationIndex, result); 112 | } 113 | } 114 | 115 | @Override 116 | public void onTestSuccess(ITestResult result) { 117 | reportFinished(result, successful()); 118 | } 119 | 120 | @Override 121 | public void onTestSkipped(ITestResult result) { 122 | MethodProgress progress = inProgressTestMethods.get(result.getMethod()); 123 | if (progress != null || result.getThrowable() != null) { 124 | if (progress == null) { 125 | reportStarted(result, startMethodProgress(result)); 126 | } 127 | reportFinished(result, aborted(result.getThrowable())); 128 | } 129 | else { 130 | MethodDescriptor methodDescriptor = findOrCreateMethodDescriptor(result); 131 | delegate.executionSkipped(methodDescriptor, ""); 132 | } 133 | } 134 | 135 | @Override 136 | public void onTestFailure(ITestResult result) { 137 | if (!inProgressTestMethods.containsKey(result.getMethod())) { 138 | reportStarted(result, startMethodProgress(result)); 139 | } 140 | reportFinished(result, failed(result.getThrowable())); 141 | } 142 | 143 | @Override 144 | public void onTestFailedButWithinSuccessPercentage(ITestResult result) { 145 | onTestSuccess(result); 146 | } 147 | 148 | @Override 149 | public void onTestFailedWithTimeout(ITestResult result) { 150 | onTestFailure(result); 151 | } 152 | 153 | private MethodProgress startMethodProgress(ITestResult result) { 154 | MethodDescriptor methodDescriptor = findOrCreateMethodDescriptor(result); 155 | return inProgressTestMethods.computeIfAbsent(result.getMethod(), 156 | __ -> new MethodProgress(result.getMethod(), methodDescriptor)); 157 | } 158 | 159 | private void finishMethodsNotYetReportedAsFinished(ITestClass testClass) { 160 | for (ITestNGMethod testMethod : testClass.getTestMethods()) { 161 | MethodProgress progress = inProgressTestMethods.remove(testMethod); 162 | if (progress != null) { 163 | delegate.executionFinished(progress.descriptor, successful()); 164 | } 165 | } 166 | } 167 | 168 | private void reportStarted(ITestResult result, MethodProgress progress) { 169 | delegate.executionStarted(progress.descriptor); 170 | progress.reportedAsStarted.countDown(); 171 | String description = result.getMethod().getDescription(); 172 | if (description != null && !description.trim().isEmpty()) { 173 | delegate.reportingEntryPublished(progress.descriptor, ReportEntry.from("description", description.trim())); 174 | } 175 | Map attributes = getAttributes(result); 176 | if (!attributes.isEmpty()) { 177 | delegate.reportingEntryPublished(progress.descriptor, ReportEntry.from(attributes)); 178 | } 179 | } 180 | 181 | private void reportFinished(ITestResult result, TestExecutionResult executionResult) { 182 | MethodProgress progress = inProgressTestMethods.get(result.getMethod()); 183 | if (progress.descriptor.getType().isContainer() && progress.invocations.containsKey(result)) { 184 | InvocationDescriptor invocationDescriptor = progress.invocations.remove(result); 185 | delegate.executionFinished(invocationDescriptor, executionResult); 186 | } 187 | else { 188 | inProgressTestMethods.remove(result.getMethod()); 189 | delegate.executionFinished(progress.descriptor, executionResult); 190 | } 191 | } 192 | 193 | private MethodDescriptor findOrCreateMethodDescriptor(ITestResult result) { 194 | ClassDescriptor classDescriptor = testClassRegistry.get(result.getTestClass().getRealClass()) // 195 | .orElseThrow(() -> new IllegalStateException("Missing class descriptor for " + result.getTestClass())); 196 | Optional methodDescriptor = classDescriptor.findMethodDescriptor(result); 197 | if (methodDescriptor.isPresent()) { 198 | return methodDescriptor.get(); 199 | } 200 | MethodDescriptor dynamicMethodDescriptor = getTestDescriptorFactory() // 201 | .createMethodDescriptor(classDescriptor, result); 202 | classDescriptor.addChild(dynamicMethodDescriptor); 203 | delegate.dynamicTestRegistered(dynamicMethodDescriptor); 204 | return dynamicMethodDescriptor; 205 | } 206 | 207 | private void createInvocationAndReportStarted(MethodProgress progress, int invocationIndex, ITestResult result) { 208 | InvocationDescriptor invocationDescriptor = getTestDescriptorFactory().createInvocationDescriptor( 209 | progress.descriptor, result, invocationIndex); 210 | progress.invocations.put(result, invocationDescriptor); 211 | progress.descriptor.addChild(invocationDescriptor); 212 | delegate.dynamicTestRegistered(invocationDescriptor); 213 | delegate.executionStarted(invocationDescriptor); 214 | } 215 | 216 | private TestDescriptorFactory getTestDescriptorFactory() { 217 | return engineDescriptor.getTestDescriptorFactory(); 218 | } 219 | 220 | public TestExecutionResult toEngineResult() { 221 | return toTestExecutionResult(engineLevelFailureResults); 222 | } 223 | 224 | private TestExecutionResult toTestExecutionResult(Set results) { 225 | return results == null || results.isEmpty() ? successful() : abortedOrFailed(results); 226 | } 227 | 228 | private static TestExecutionResult abortedOrFailed(Set results) { 229 | return results.stream().allMatch(it -> it.getStatus() == ITestResult.SKIP) // 230 | ? aborted(chain(throwables(results))) // 231 | : failed(chain(throwables(results))); 232 | } 233 | 234 | private static Stream throwables(Set results) { 235 | return results.stream().map(ITestResult::getThrowable).filter(Objects::nonNull); 236 | } 237 | 238 | private static Throwable chain(Stream failures) { 239 | Iterator iterator = failures.iterator(); 240 | Throwable throwable = null; 241 | if (iterator.hasNext()) { 242 | throwable = iterator.next(); 243 | iterator.forEachRemaining(throwable::addSuppressed); 244 | } 245 | return throwable; 246 | } 247 | 248 | static class MethodProgress { 249 | final ITestNGMethod method; 250 | final MethodDescriptor descriptor; 251 | final ConcurrentMap invocations = new ConcurrentHashMap<>(); 252 | final AtomicInteger invocationIndex = new AtomicInteger(); 253 | final CountDownLatch reportedAsStarted = new CountDownLatch(1); 254 | 255 | public MethodProgress(ITestNGMethod method, MethodDescriptor descriptor) { 256 | this.method = method; 257 | this.descriptor = descriptor; 258 | } 259 | } 260 | 261 | private Map getAttributes(ITestResult result) { 262 | try { 263 | CustomAttribute[] attributes = result.getMethod().getAttributes(); 264 | if (attributes.length > 0) { 265 | return Arrays.stream(attributes) // 266 | .collect(toMap(CustomAttribute::name, attr -> String.join(", ", attr.values()))); 267 | } 268 | } 269 | catch (NoSuchMethodError ignore) { 270 | } 271 | return emptyMap(); 272 | } 273 | } 274 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/InvocationDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import org.junit.platform.engine.UniqueId; 14 | import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; 15 | import org.junit.platform.engine.support.descriptor.MethodSource; 16 | 17 | class InvocationDescriptor extends AbstractTestDescriptor { 18 | 19 | static final String SEGMENT_TYPE = "invoc"; 20 | private final String legacyReportingName; 21 | 22 | InvocationDescriptor(UniqueId uniqueId, String displayName, String legacyReportingName, MethodSource source) { 23 | super(uniqueId, displayName, source); 24 | this.legacyReportingName = legacyReportingName; 25 | } 26 | 27 | @Override 28 | public String getLegacyReportingName() { 29 | return legacyReportingName; 30 | } 31 | 32 | @Override 33 | public Type getType() { 34 | return Type.TEST; 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/IsTestNGTestClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.lang.reflect.Method; 14 | import java.util.List; 15 | import java.util.function.Predicate; 16 | 17 | import org.junit.platform.commons.support.HierarchyTraversalMode; 18 | import org.junit.platform.commons.support.ReflectionSupport; 19 | 20 | class IsTestNGTestClass implements Predicate> { 21 | 22 | @Override 23 | public boolean test(Class candidateClass) { 24 | return TestAnnotationUtils.isAnnotatedInHierarchy(candidateClass) 25 | || hasMethodWithTestAnnotation(candidateClass); 26 | } 27 | 28 | private boolean hasMethodWithTestAnnotation(Class candidateClass) { 29 | List testMethods = ReflectionSupport.findMethods(candidateClass, 30 | TestAnnotationUtils::isAnnotatedDirectly, HierarchyTraversalMode.BOTTOM_UP); 31 | return !testMethods.isEmpty(); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/LoggingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.util.List; 14 | import java.util.function.Supplier; 15 | import java.util.logging.Logger; 16 | 17 | import org.testng.ITestClass; 18 | import org.testng.ITestContext; 19 | import org.testng.ITestNGMethod; 20 | import org.testng.ITestResult; 21 | import org.testng.xml.XmlSuite; 22 | 23 | class LoggingListener extends DefaultListener { 24 | 25 | static final LoggingListener INSTANCE = new LoggingListener(); 26 | private static final Logger LOGGER = Logger.getLogger(LoggingListener.class.getName()); 27 | 28 | private LoggingListener() { 29 | } 30 | 31 | @Override 32 | public void alter(List suites) { 33 | log(() -> "alter: " + suites); 34 | } 35 | 36 | @Override 37 | public void onBeforeClass(ITestClass testClass) { 38 | log(() -> "onBeforeClass: " + testClass); 39 | } 40 | 41 | @Override 42 | public void onAfterClass(ITestClass testClass) { 43 | log(() -> "onAfterClass: " + testClass); 44 | } 45 | 46 | @Override 47 | public void onTestStart(ITestResult result) { 48 | log(() -> "onTestStart: " + result); 49 | } 50 | 51 | @Override 52 | public void onTestSuccess(ITestResult result) { 53 | log(() -> "onTestSuccess: " + result); 54 | } 55 | 56 | @Override 57 | public void onTestFailure(ITestResult result) { 58 | log(() -> "onTestFailure: " + result); 59 | } 60 | 61 | @Override 62 | public void onTestSkipped(ITestResult result) { 63 | log(() -> "onTestSkipped: " + result); 64 | } 65 | 66 | @Override 67 | public void onTestFailedButWithinSuccessPercentage(ITestResult result) { 68 | log(() -> "onTestFailedButWithinSuccessPercentage: " + result); 69 | } 70 | 71 | @Override 72 | public void onTestFailedWithTimeout(ITestResult result) { 73 | log(() -> "onTestFailedWithTimeout: " + result); 74 | } 75 | 76 | @Override 77 | public void onStart(ITestContext context) { 78 | log(() -> "onStart: " + context); 79 | } 80 | 81 | @Override 82 | public void onFinish(ITestContext context) { 83 | log(() -> "onFinish: " + context); 84 | } 85 | 86 | @Override 87 | public void onConfigurationSuccess(ITestResult tr) { 88 | log(() -> "onConfigurationSuccess: " + tr); 89 | } 90 | 91 | @Override 92 | public void onConfigurationSuccess(ITestResult tr, ITestNGMethod tm) { 93 | log(() -> "onConfigurationSuccess: " + tr + ", " + tm); 94 | } 95 | 96 | @Override 97 | public void onConfigurationFailure(ITestResult tr) { 98 | log(() -> "onConfigurationFailure: " + tr); 99 | } 100 | 101 | @Override 102 | public void onConfigurationFailure(ITestResult tr, ITestNGMethod tm) { 103 | log(() -> "onConfigurationFailure: " + tr + ", " + tm); 104 | } 105 | 106 | @Override 107 | public void onConfigurationSkip(ITestResult tr) { 108 | log(() -> "onConfigurationSkip: " + tr); 109 | } 110 | 111 | @Override 112 | public void onConfigurationSkip(ITestResult tr, ITestNGMethod tm) { 113 | log(() -> "onConfigurationSkip: " + tr + ", " + tm); 114 | } 115 | 116 | @Override 117 | public void beforeConfiguration(ITestResult tr) { 118 | log(() -> "beforeConfiguration: " + tr); 119 | } 120 | 121 | @Override 122 | public void beforeConfiguration(ITestResult tr, ITestNGMethod tm) { 123 | log(() -> "beforeConfiguration: " + tr + ", " + tm); 124 | } 125 | 126 | private void log(Supplier message) { 127 | LOGGER.fine(message); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/MethodDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.Collections.unmodifiableSet; 14 | import static org.junit.platform.commons.support.ClassSupport.nullSafeToString; 15 | 16 | import java.util.Set; 17 | 18 | import org.junit.platform.engine.TestTag; 19 | import org.junit.platform.engine.UniqueId; 20 | import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; 21 | import org.junit.platform.engine.support.descriptor.MethodSource; 22 | import org.testng.ITestResult; 23 | import org.testng.internal.IParameterInfo; 24 | 25 | class MethodDescriptor extends AbstractTestDescriptor { 26 | 27 | static final String SEGMENT_TYPE = "method"; 28 | 29 | final MethodSignature methodSignature; 30 | private final Set tags; 31 | private final Type type; 32 | 33 | protected MethodDescriptor(UniqueId uniqueId, String displayName, Class sourceClass, 34 | MethodSignature methodSignature, Set tags, Type type) { 35 | super(uniqueId, displayName, toMethodSource(sourceClass, methodSignature)); 36 | this.methodSignature = methodSignature; 37 | this.tags = tags; 38 | this.type = type; 39 | } 40 | 41 | @Override 42 | public Set getTags() { 43 | return unmodifiableSet(tags); 44 | } 45 | 46 | private static MethodSource toMethodSource(Class sourceClass, MethodSignature methodSignature) { 47 | return MethodSource.from(sourceClass.getName(), methodSignature.methodName, 48 | nullSafeToString(methodSignature.parameterTypes)); 49 | } 50 | 51 | static String toMethodId(ITestResult result, MethodSignature methodSignature) { 52 | String id = methodSignature.stringRepresentation; 53 | Object[] instances = result.getTestClass().getInstances(true); 54 | if (instances.length > 1) { 55 | Object instance = result.getInstance(); 56 | int instanceIndex = 0; 57 | for (int i = 0; i < instances.length; i++) { 58 | if (unwrap(instances[i]) == instance) { 59 | instanceIndex = i; 60 | break; 61 | } 62 | } 63 | id = id + "@" + instanceIndex; 64 | } 65 | return id; 66 | } 67 | 68 | private static Object unwrap(Object instance) { 69 | try { 70 | return IParameterInfo.embeddedInstance(instance); 71 | } 72 | catch (NoClassDefFoundError ignored) { 73 | return instance; 74 | } 75 | } 76 | 77 | @Override 78 | public Type getType() { 79 | return type; 80 | } 81 | 82 | @Override 83 | public boolean mayRegisterTests() { 84 | return type == Type.CONTAINER; 85 | } 86 | 87 | @SuppressWarnings("OptionalGetWithoutIsPresent") 88 | MethodSource getMethodSource() { 89 | return getSource().map(it -> (MethodSource) it).get(); 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/MethodSignature.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static org.junit.platform.commons.support.ClassSupport.nullSafeToString; 14 | 15 | import org.testng.ITestNGMethod; 16 | 17 | class MethodSignature { 18 | 19 | final String methodName; 20 | final Class[] parameterTypes; 21 | final String stringRepresentation; 22 | 23 | static MethodSignature from(ITestNGMethod method) { 24 | return new MethodSignature(method.getMethodName(), getParameterTypes(method)); 25 | } 26 | 27 | public static Class[] getParameterTypes(ITestNGMethod method) { 28 | try { 29 | return method.getParameterTypes(); 30 | } 31 | catch (NoSuchMethodError e) { 32 | return method.getConstructorOrMethod().getParameterTypes(); 33 | } 34 | } 35 | 36 | private MethodSignature(String methodName, Class[] parameterTypes) { 37 | this.methodName = methodName; 38 | this.parameterTypes = parameterTypes; 39 | this.stringRepresentation = String.format("%s(%s)", methodName, nullSafeToString(parameterTypes)); 40 | } 41 | 42 | @Override 43 | public String toString() { 44 | return stringRepresentation; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/TestAnnotationUtils.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.Spliterators.spliteratorUnknownSize; 14 | 15 | import java.lang.reflect.Method; 16 | import java.util.Arrays; 17 | import java.util.Iterator; 18 | import java.util.Objects; 19 | import java.util.Optional; 20 | import java.util.Spliterator; 21 | import java.util.stream.Stream; 22 | import java.util.stream.StreamSupport; 23 | 24 | import org.testng.ITestNGMethod; 25 | import org.testng.annotations.Test; 26 | 27 | /** 28 | * Replicates how TestNG looks up test annotations and their attributes. 29 | */ 30 | class TestAnnotationUtils { 31 | 32 | static boolean isAnnotatedInHierarchy(Class clazz) { 33 | return findAnnotationInHierarchy(clazz).isPresent(); 34 | } 35 | 36 | static boolean isAnnotatedDirectly(Method method) { 37 | return method.getAnnotation(Test.class) != null; 38 | } 39 | 40 | public static Class getRetryAnalyzer(ITestNGMethod method) { 41 | return getAnnotation(method).retryAnalyzer(); 42 | } 43 | 44 | static Optional getDataProvider(ITestNGMethod method) { 45 | String dataProvider = getAnnotation(method).dataProvider().trim(); 46 | return dataProvider.isEmpty() ? Optional.empty() : Optional.of(dataProvider); 47 | } 48 | 49 | static Optional> getDataProviderClass(ITestNGMethod method) { 50 | return Stream.concat(Stream.of(getAnnotationDirectly(method)), collectTestAnnotations(method.getRealClass())) // 51 | .filter(Objects::nonNull) // 52 | .map(Test::dataProviderClass) // 53 | .filter(clazz -> clazz != Object.class) // 54 | .findFirst(); 55 | } 56 | 57 | private static Test getAnnotation(ITestNGMethod method) { 58 | Test result = getAnnotationDirectly(method); 59 | if (result == null) { 60 | return findAnnotationInHierarchy(method.getTestClass().getRealClass()) // 61 | .orElseThrow(IllegalStateException::new); 62 | } 63 | return result; 64 | } 65 | 66 | private static Test getAnnotationDirectly(ITestNGMethod method) { 67 | return method.getConstructorOrMethod().getMethod().getAnnotation(Test.class); 68 | } 69 | 70 | private static Optional findAnnotationInHierarchy(Class clazz) { 71 | return collectTestAnnotations(clazz).findFirst(); 72 | } 73 | 74 | static Stream collectGroups(Class testClass) { 75 | return collectTestAnnotations(testClass) // 76 | .flatMap(annotation -> Arrays.stream(annotation.groups())); 77 | } 78 | 79 | private static Stream collectTestAnnotations(Class testClass) { 80 | return getClassHierarchy(testClass) // 81 | .map(clazz -> clazz.getAnnotation(Test.class)) // 82 | .filter(Objects::nonNull); 83 | } 84 | 85 | private static Stream> getClassHierarchy(Class testClass) { 86 | Iterator> iterator = new Iterator>() { 87 | Class next = testClass; 88 | 89 | @Override 90 | public boolean hasNext() { 91 | return next != Object.class && next != null; 92 | } 93 | 94 | @Override 95 | public Class next() { 96 | Class result = next; 97 | next = next.getSuperclass(); 98 | return result; 99 | } 100 | }; 101 | return StreamSupport.stream(spliteratorUnknownSize(iterator, Spliterator.ORDERED), false); 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/TestClassRegistry.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.util.Map; 14 | import java.util.Optional; 15 | import java.util.Set; 16 | import java.util.concurrent.ConcurrentHashMap; 17 | import java.util.concurrent.atomic.AtomicInteger; 18 | import java.util.function.Consumer; 19 | import java.util.function.Function; 20 | import java.util.function.Predicate; 21 | 22 | class TestClassRegistry { 23 | 24 | private final Set classDescriptors = ConcurrentHashMap.newKeySet(); 25 | private final Map, Entry> testClasses = new ConcurrentHashMap<>(); 26 | 27 | void start(Class testClass, Function, ClassDescriptor> onFirst) { 28 | Entry entry = testClasses.computeIfAbsent(testClass, __ -> { 29 | ClassDescriptor classDescriptor = onFirst.apply(testClass); 30 | if (classDescriptor != null) { 31 | classDescriptors.add(classDescriptor); 32 | return new Entry(classDescriptor); 33 | } 34 | return null; 35 | }); 36 | if (entry != null) { 37 | entry.inProgress.incrementAndGet(); 38 | } 39 | } 40 | 41 | Optional get(Class testClass) { 42 | Entry entry = testClasses.get(testClass); 43 | return Optional.ofNullable(entry).map(it -> it.descriptor); 44 | } 45 | 46 | void finish(Class testClass, Predicate last, Consumer onLast) { 47 | testClasses.compute(testClass, (__, value) -> { 48 | if (value == null) { 49 | return null; 50 | } 51 | int inProgress = value.inProgress.decrementAndGet(); 52 | if (inProgress == 0 && last.test(value.descriptor)) { 53 | onLast.accept(value.descriptor); 54 | return null; 55 | } 56 | return value; 57 | }); 58 | } 59 | 60 | Set getClassDescriptors() { 61 | return classDescriptors; 62 | } 63 | 64 | private static class Entry { 65 | 66 | final ClassDescriptor descriptor; 67 | final AtomicInteger inProgress = new AtomicInteger(0); 68 | 69 | Entry(ClassDescriptor descriptor) { 70 | this.descriptor = descriptor; 71 | } 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/TestDescriptorFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.stream.Collectors.joining; 14 | import static java.util.stream.Collectors.toSet; 15 | import static org.junit.platform.engine.TestDescriptor.Type.CONTAINER; 16 | import static org.junit.platform.engine.TestDescriptor.Type.TEST; 17 | import static org.junit.support.testng.engine.MethodDescriptor.toMethodId; 18 | 19 | import java.util.Arrays; 20 | import java.util.Map; 21 | import java.util.Set; 22 | import java.util.concurrent.ConcurrentHashMap; 23 | 24 | import org.junit.platform.engine.TestDescriptor; 25 | import org.junit.platform.engine.TestDescriptor.Type; 26 | import org.junit.platform.engine.TestTag; 27 | import org.junit.platform.engine.UniqueId; 28 | import org.testng.ITestNGMethod; 29 | import org.testng.ITestResult; 30 | import org.testng.internal.annotations.DisabledRetryAnalyzer; 31 | 32 | class TestDescriptorFactory { 33 | 34 | private final Map testTags = new ConcurrentHashMap<>(); 35 | 36 | ClassDescriptor createClassDescriptor(TestDescriptor parent, Class testClass) { 37 | UniqueId uniqueId = parent.getUniqueId().append(ClassDescriptor.SEGMENT_TYPE, testClass.getName()); 38 | Set tags = TestAnnotationUtils.collectGroups(testClass) // 39 | .map(this::createTag) // 40 | .collect(toSet()); 41 | return new ClassDescriptor(uniqueId, testClass, tags); 42 | } 43 | 44 | MethodDescriptor createMethodDescriptor(ClassDescriptor parent, ITestResult result) { 45 | ITestNGMethod method = result.getMethod(); 46 | MethodSignature methodSignature = MethodSignature.from(method); 47 | StringBuilder name = new StringBuilder(methodSignature.parameterTypes.length > 0 // 48 | ? methodSignature.stringRepresentation // 49 | : result.getName()); 50 | appendInvocationIndex(name, getFactoryMethodInvocationIndex(result)); 51 | appendParameterValues(name, getFactoryParameters(result)); 52 | UniqueId uniqueId = parent.getUniqueId().append(MethodDescriptor.SEGMENT_TYPE, 53 | toMethodId(result, methodSignature)); 54 | Class sourceClass = method.getTestClass().getRealClass(); 55 | Set tags = Arrays.stream(method.getGroups()).map(this::createTag).collect(toSet()); 56 | Type type = reportsInvocations(method) ? CONTAINER : TEST; 57 | return new MethodDescriptor(uniqueId, name.toString(), sourceClass, methodSignature, tags, type); 58 | } 59 | 60 | private static Object[] getFactoryParameters(ITestResult result) { 61 | try { 62 | return result.getFactoryParameters(); 63 | } 64 | catch (NoSuchMethodError ignore) { 65 | // getFactoryParameters() was added in 7.0 66 | return null; 67 | } 68 | } 69 | 70 | private static Integer getFactoryMethodInvocationIndex(ITestResult result) { 71 | long[] instanceHashCodes = result.getTestClass().getInstanceHashCodes(); 72 | if (instanceHashCodes.length > 1) { 73 | long hashCode = result.getInstance().hashCode(); 74 | for (int i = 0; i < instanceHashCodes.length; ++i) { 75 | if (instanceHashCodes[i] == hashCode) { 76 | return i; 77 | } 78 | } 79 | } 80 | 81 | return null; 82 | } 83 | 84 | static void appendInvocationIndex(StringBuilder builder, Integer invocationIndex) { 85 | if (invocationIndex != null) { 86 | builder.append("[").append(invocationIndex).append("]"); 87 | } 88 | } 89 | 90 | static void appendParameterValues(StringBuilder builder, Object[] parameters) { 91 | if (parameters != null && parameters.length > 0) { 92 | builder.append("(").append(Arrays.stream(parameters).map(String::valueOf).collect(joining(", "))).append( 93 | ")"); 94 | } 95 | } 96 | 97 | private boolean reportsInvocations(ITestNGMethod method) { 98 | return isDataDriven(method) // 99 | || method.getInvocationCount() > 1 // 100 | || method.getThreadPoolSize() > 0 // 101 | || getRetryAnalyzerClass(method) != getDefaultRetryAnalyzer(); 102 | } 103 | 104 | private Class getRetryAnalyzerClass(ITestNGMethod method) { 105 | try { 106 | return method.getRetryAnalyzerClass(); 107 | } 108 | catch (NoSuchMethodError ignore) { 109 | return TestAnnotationUtils.getRetryAnalyzer(method); 110 | } 111 | } 112 | 113 | private Class getDefaultRetryAnalyzer() { 114 | try { 115 | return DisabledRetryAnalyzer.class; 116 | } 117 | catch (NoClassDefFoundError ignore) { 118 | return Class.class; 119 | } 120 | } 121 | 122 | private boolean isDataDriven(ITestNGMethod method) { 123 | try { 124 | return method.isDataDriven(); 125 | } 126 | catch (NoSuchMethodError ignore) { 127 | return TestAnnotationUtils.getDataProvider(method).isPresent() // 128 | || TestAnnotationUtils.getDataProviderClass(method).isPresent(); 129 | } 130 | } 131 | 132 | InvocationDescriptor createInvocationDescriptor(MethodDescriptor parent, ITestResult result, int invocationIndex) { 133 | UniqueId uniqueId = parent.getUniqueId().append(InvocationDescriptor.SEGMENT_TYPE, 134 | String.valueOf(invocationIndex)); 135 | Object[] parameters = result.getParameters(); 136 | String displayName; 137 | if (parameters.length > 0) { 138 | String paramList = Arrays.stream(parameters).map(String::valueOf).collect(joining(", ")); 139 | displayName = String.format("[%d] %s", invocationIndex, paramList); 140 | } 141 | else { 142 | displayName = String.format("[%d]", invocationIndex); 143 | } 144 | String legacyReportingName = String.format("%s[%d]", parent.getLegacyReportingName(), invocationIndex); 145 | return new InvocationDescriptor(uniqueId, displayName, legacyReportingName, parent.getMethodSource()); 146 | } 147 | 148 | private TestTag createTag(String value) { 149 | return testTags.computeIfAbsent(value, TestTag::create); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/TestNGEngineDescriptor.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.stream.Collectors.toList; 14 | import static java.util.stream.Collectors.toSet; 15 | 16 | import java.util.HashMap; 17 | import java.util.List; 18 | import java.util.Map; 19 | import java.util.Objects; 20 | import java.util.Set; 21 | import java.util.stream.Stream; 22 | 23 | import org.junit.platform.engine.TestDescriptor; 24 | import org.junit.platform.engine.UniqueId; 25 | import org.junit.platform.engine.support.descriptor.EngineDescriptor; 26 | 27 | class TestNGEngineDescriptor extends EngineDescriptor { 28 | 29 | private final TestDescriptorFactory testDescriptorFactory = new TestDescriptorFactory(); 30 | private final Map, ClassDescriptor> classDescriptorsByTestClass = new HashMap<>(); 31 | 32 | public TestNGEngineDescriptor(UniqueId uniqueId) { 33 | super(uniqueId, "TestNG"); 34 | } 35 | 36 | public TestDescriptorFactory getTestDescriptorFactory() { 37 | return testDescriptorFactory; 38 | } 39 | 40 | @Override 41 | public void addChild(TestDescriptor child) { 42 | ClassDescriptor classDescriptor = (ClassDescriptor) child; 43 | classDescriptorsByTestClass.put(classDescriptor.getTestClass(), classDescriptor); 44 | super.addChild(child); 45 | } 46 | 47 | @Override 48 | public void removeChild(TestDescriptor child) { 49 | classDescriptorsByTestClass.remove(((ClassDescriptor) child).getTestClass()); 50 | super.removeChild(child); 51 | } 52 | 53 | public ClassDescriptor findClassDescriptor(Class testClass) { 54 | return classDescriptorsByTestClass.get(testClass); 55 | } 56 | 57 | Set getClassDescriptors() { 58 | return classDescriptors().collect(toSet()); 59 | } 60 | 61 | Class[] getTestClasses() { 62 | return classDescriptors() // 63 | .map(it -> it.executionStrategy.getTestClass().orElse(null)) // 64 | .filter(Objects::nonNull).toArray(Class[]::new); 65 | } 66 | 67 | List getQualifiedMethodNames() { 68 | return classDescriptors() // 69 | .flatMap(it -> it.executionStrategy.getTestMethods().stream() // 70 | .map(methodName -> it.getTestClass().getName() + "." + methodName)) // 71 | .collect(toList()); 72 | } 73 | 74 | void prepareExecution() { 75 | classDescriptors().forEach(ClassDescriptor::prepareExecution); 76 | } 77 | 78 | private Stream classDescriptors() { 79 | return getChildren().stream().map(child -> (ClassDescriptor) child); 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/TestNGSelectorResolver.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.Collections.emptySet; 14 | import static java.util.Collections.singleton; 15 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 16 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; 17 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; 18 | 19 | import java.util.Optional; 20 | import java.util.function.Predicate; 21 | 22 | import org.junit.platform.engine.UniqueId; 23 | import org.junit.platform.engine.UniqueId.Segment; 24 | import org.junit.platform.engine.discovery.ClassSelector; 25 | import org.junit.platform.engine.discovery.MethodSelector; 26 | import org.junit.platform.engine.discovery.UniqueIdSelector; 27 | import org.junit.platform.engine.support.discovery.SelectorResolver; 28 | 29 | class TestNGSelectorResolver implements SelectorResolver { 30 | 31 | private final Predicate classNameFilter; 32 | private final TestDescriptorFactory testDescriptorFactory; 33 | 34 | TestNGSelectorResolver(Predicate classNameFilter, TestDescriptorFactory testDescriptorFactory) { 35 | this.classNameFilter = classNameFilter; 36 | this.testDescriptorFactory = testDescriptorFactory; 37 | } 38 | 39 | @Override 40 | public Resolution resolve(ClassSelector selector, Context context) { 41 | if (!classNameFilter.test(selector.getClassName())) { 42 | return Resolution.unresolved(); 43 | } 44 | return context.addToParent( 45 | parent -> Optional.of(testDescriptorFactory.createClassDescriptor(parent, selector.getJavaClass()))) // 46 | .map(classDescriptor -> Match.exact(classDescriptor, () -> { 47 | classDescriptor.selectEntireClass(); 48 | return emptySet(); 49 | })) // 50 | .map(Resolution::match) // 51 | .orElse(Resolution.unresolved()); 52 | } 53 | 54 | @Override 55 | public Resolution resolve(MethodSelector selector, Context context) { 56 | return context.resolve(selectClass(selector.getJavaClass())) // 57 | .map(parent -> { 58 | ((ClassDescriptor) parent).includeTestMethod(selector.getMethodName()); 59 | return parent; 60 | }) // 61 | .map(Match::partial) // 62 | .map(Resolution::match) // 63 | .orElse(Resolution.unresolved()); 64 | } 65 | 66 | @Override 67 | public Resolution resolve(UniqueIdSelector selector, Context context) { 68 | UniqueId uniqueId = selector.getUniqueId(); 69 | Segment lastSegment = uniqueId.getLastSegment(); 70 | if (ClassDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { 71 | return Resolution.selectors(singleton(selectClass(lastSegment.getValue()))); 72 | } 73 | if (MethodDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { 74 | String methodName = lastSegment.getValue(); 75 | int i = methodName.indexOf('('); 76 | if (i != -1) { 77 | methodName = methodName.substring(0, i); 78 | } 79 | Segment previousSegment = uniqueId.removeLastSegment().getLastSegment(); 80 | if (ClassDescriptor.SEGMENT_TYPE.equals(previousSegment.getType())) { 81 | String className = previousSegment.getValue(); 82 | return Resolution.selectors(singleton(selectMethod(className, methodName))); 83 | } 84 | } 85 | if (InvocationDescriptor.SEGMENT_TYPE.equals(lastSegment.getType())) { 86 | return Resolution.selectors(singleton(selectUniqueId(uniqueId.removeLastSegment()))); 87 | } 88 | return Resolution.unresolved(); 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/junit/support/testng/engine/TestNGTestEngine.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static org.junit.support.testng.engine.TestNGTestEngine.Configurer.testClasses; 14 | import static org.junit.support.testng.engine.TestNGTestEngine.Configurer.testMethods; 15 | import static org.testng.internal.RuntimeBehavior.TESTNG_MODE_DRYRUN; 16 | 17 | import java.util.List; 18 | 19 | import org.junit.platform.engine.ConfigurationParameters; 20 | import org.junit.platform.engine.EngineDiscoveryRequest; 21 | import org.junit.platform.engine.EngineExecutionListener; 22 | import org.junit.platform.engine.ExecutionRequest; 23 | import org.junit.platform.engine.TestDescriptor; 24 | import org.junit.platform.engine.TestEngine; 25 | import org.junit.platform.engine.UniqueId; 26 | import org.junit.platform.engine.support.discovery.EngineDiscoveryRequestResolver; 27 | import org.testng.CommandLineArgs; 28 | import org.testng.ITestNGListener; 29 | import org.testng.TestNG; 30 | import org.testng.annotations.DataProvider; 31 | import org.testng.xml.XmlSuite.ParallelMode; 32 | 33 | /** 34 | * The TestNG {@link TestEngine} for running TestNG tests on the JUnit Platform. 35 | * 36 | * @since 1.0 37 | */ 38 | public class TestNGTestEngine implements TestEngine { 39 | 40 | private static final EngineDiscoveryRequestResolver DISCOVERY_REQUEST_RESOLVER = EngineDiscoveryRequestResolver. builder() // 41 | .addClassContainerSelectorResolver(new IsTestNGTestClass()) // 42 | .addSelectorResolver(ctx -> new TestNGSelectorResolver(ctx.getClassNameFilter(), 43 | ctx.getEngineDescriptor().getTestDescriptorFactory())) // 44 | .build(); 45 | 46 | /** 47 | * Create a new instance (typically called by the JUnit Platform via ServiceLoader). 48 | */ 49 | public TestNGTestEngine() { 50 | } 51 | 52 | @Override 53 | public String getId() { 54 | return "testng"; 55 | } 56 | 57 | /** 58 | * Discover TestNG tests based on the supplied {@linkplain EngineDiscoveryRequest request}. 59 | *

60 | * Supports the following {@linkplain org.junit.platform.engine.DiscoverySelector selectors}: 61 | *

    62 | *
  • {@link org.junit.platform.engine.discovery.ClasspathRootSelector}
  • 63 | *
  • {@link org.junit.platform.engine.discovery.ClassSelector}
  • 64 | *
  • {@link org.junit.platform.engine.discovery.MethodSelector}
  • 65 | *
  • {@link org.junit.platform.engine.discovery.ModuleSelector}
  • 66 | *
  • {@link org.junit.platform.engine.discovery.PackageSelector}
  • 67 | *
  • {@link org.junit.platform.engine.discovery.UniqueIdSelector}
  • 68 | *
69 | *

70 | * Custom test suites specified via {@code testng.xml} files are not supported. 71 | *

72 | * Supports the following {@linkplain org.junit.platform.engine.Filter filters}: 73 | *

    74 | *
  • {@link org.junit.platform.engine.discovery.ClassNameFilter}
  • 75 | *
  • {@link org.junit.platform.engine.discovery.PackageNameFilter}
  • 76 | *
  • Any post-discovery filter, e.g. for included/excluded tags
  • 77 | *
78 | *

79 | * The implementation collects a list of potential test classes and method names and uses 80 | * TestNG's dry-run mode to determine which of them are TestNG test classes and methods. Since 81 | * TestNG can only run either classes or methods, it will be executed twice in the edge case 82 | * that class and method selectors are part of the discovery request. 83 | */ 84 | @Override 85 | public TestDescriptor discover(EngineDiscoveryRequest request, UniqueId uniqueId) { 86 | TestNGEngineDescriptor engineDescriptor = new TestNGEngineDescriptor(uniqueId); 87 | 88 | DISCOVERY_REQUEST_RESOLVER.resolve(request, engineDescriptor); 89 | Class[] testClasses = engineDescriptor.getTestClasses(); 90 | List methodNames = engineDescriptor.getQualifiedMethodNames(); 91 | 92 | ConfigurationParameters configurationParameters = request.getConfigurationParameters(); 93 | DiscoveryListener listener = new DiscoveryListener(request, engineDescriptor); 94 | 95 | if (testClasses.length > 0) { 96 | withTemporarySystemProperty(TESTNG_MODE_DRYRUN, "true", 97 | () -> configureAndRun(configurationParameters, listener, testClasses(testClasses), Phase.DISCOVERY)); 98 | } 99 | 100 | if (!methodNames.isEmpty()) { 101 | withTemporarySystemProperty(TESTNG_MODE_DRYRUN, "true", 102 | () -> configureAndRun(configurationParameters, listener, testMethods(methodNames), Phase.DISCOVERY)); 103 | } 104 | 105 | listener.finalizeDiscovery(); 106 | 107 | return engineDescriptor; 108 | } 109 | 110 | /** 111 | * Execute the previously discovered TestNG tests in the supplied {@linkplain ExecutionRequest request}. 112 | *

113 | * Supports the following configuration parameters: 114 | *

Execution

115 | *
116 | *
{@code testng.allowReturnValues} (file path)
117 | *
whether methods annotated with {@code @Test} that have return values should be considered test methods (default: {@code false})
118 | * 119 | *
{@code testng.dataProviderThreadCount} (file path)
120 | *
maximum number of threads to use for running data providers in parallel, if enabled via {@link DataProvider#parallel()} (default: {@code 10})
121 | * 122 | *
{@code testng.parallel} (methods|tests|classes|instances|none)
123 | *
TestNG's parallel execution mode for running tests in separate threads (default: {@code "none"})
124 | * 125 | *
{@code testng.preserveOrder} (boolean)
126 | *
whether classes and methods should be run in a predictable order (default: {@code true})
127 | * 128 | *
{@code testng.threadCount} (boolean)
129 | *
maximum number of threads for running tests in parallel, if enabled via {@code testng.parallel} (default: {@code 5})
130 | *
131 | *

Reporting

132 | *
133 | *
{@code testng.listeners} (comma-separated list of fully-qualified class names)
134 | *
custom listeners that should be registered when executing tests (default: {@code ""})
135 | * 136 | *
{@code testng.outputDirectory} (file path)
137 | *
the output directory for reports (default: {@code "test-output"})
138 | * 139 | *
{@code testng.useDefaultListeners} (boolean)
140 | *
whether TestNG's default report generating listeners should be used (default: {@code false})
141 | * 142 | *
{@code testng.verbose} (integer)
143 | *
TestNG's level of verbosity (default: {@code 0})
144 | *
145 | *

146 | * The implementation configures TestNG as if the discovered methods were specified on the 147 | * command line. 148 | *

149 | * Data providers test methods are reported as a nested structure, i.e. individual invocations 150 | * are reported underneath the test methods along with their parameters: 151 | *


152 | 	 * └─ TestNG ✔
153 | 	 *    └─ DataProviderMethodTestCase ✔
154 | 	 *       └─ test(java.lang.String) ✔
155 | 	 *          ├─ [0] a ✔
156 | 	 *          └─ [1] b ✔
157 | 	 * 
158 | */ 159 | @Override 160 | public void execute(ExecutionRequest request) { 161 | EngineExecutionListener listener = request.getEngineExecutionListener(); 162 | TestNGEngineDescriptor engineDescriptor = (TestNGEngineDescriptor) request.getRootTestDescriptor(); 163 | listener.executionStarted(engineDescriptor); 164 | engineDescriptor.prepareExecution(); 165 | ExecutionListener executionListener = new ExecutionListener(listener, engineDescriptor); 166 | List methodNames = engineDescriptor.getQualifiedMethodNames(); 167 | if (!methodNames.isEmpty()) { 168 | configureAndRun(request.getConfigurationParameters(), executionListener, testMethods(methodNames), 169 | Phase.EXECUTION); 170 | } 171 | listener.executionFinished(engineDescriptor, executionListener.toEngineResult()); 172 | } 173 | 174 | private static void configureAndRun(ConfigurationParameters configurationParameters, ITestNGListener listener, 175 | Configurer... configurers) { 176 | CommandLineArgs commandLineArgs = new CommandLineArgs(); 177 | for (Configurer configurer : configurers) { 178 | configurer.configure(commandLineArgs, configurationParameters); 179 | } 180 | configurationParameters.get("testng.groups").ifPresent(it -> commandLineArgs.groups = it); 181 | configurationParameters.get("testng.excludedGroups").ifPresent(it -> commandLineArgs.excludedGroups = it); 182 | ConfigurableTestNG testNG = new ConfigurableTestNG(); 183 | testNG.configure(commandLineArgs); 184 | for (Configurer configurer : configurers) { 185 | configurer.configure(testNG, configurationParameters); 186 | } 187 | testNG.addListener(LoggingListener.INSTANCE); 188 | testNG.addListener(new ConfiguringListener(configurationParameters)); 189 | testNG.addListener(listener); 190 | testNG.run(); 191 | } 192 | 193 | @SuppressWarnings("SameParameterValue") 194 | private static void withTemporarySystemProperty(String key, String value, Runnable action) { 195 | String originalValue = System.getProperty(key); 196 | System.setProperty(key, value); 197 | try { 198 | action.run(); 199 | } 200 | finally { 201 | if (originalValue == null) { 202 | System.getProperties().remove(key); 203 | } 204 | else { 205 | System.setProperty(key, originalValue); 206 | } 207 | } 208 | } 209 | 210 | interface Configurer { 211 | 212 | static Configurer testClasses(Class[] testClasses) { 213 | return new Configurer() { 214 | @Override 215 | public void configure(TestNG testNG, ConfigurationParameters config) { 216 | testNG.setTestClasses(testClasses); 217 | } 218 | }; 219 | } 220 | 221 | static Configurer testMethods(List methodNames) { 222 | return new Configurer() { 223 | @Override 224 | public void configure(CommandLineArgs commandLineArgs, ConfigurationParameters config) { 225 | commandLineArgs.commandLineMethods = methodNames; 226 | } 227 | }; 228 | } 229 | 230 | default void configure(TestNG testNG, ConfigurationParameters config) { 231 | } 232 | 233 | default void configure(CommandLineArgs commandLineArgs, ConfigurationParameters config) { 234 | } 235 | 236 | } 237 | 238 | enum Phase implements Configurer { 239 | 240 | DISCOVERY { 241 | @Override 242 | public void configure(TestNG testNG, ConfigurationParameters config) { 243 | testNG.setVerbose(0); 244 | testNG.setUseDefaultListeners(false); 245 | } 246 | }, 247 | 248 | EXECUTION { 249 | @Override 250 | public void configure(TestNG testNG, ConfigurationParameters config) { 251 | testNG.setVerbose(config.get("testng.verbose", Integer::valueOf).orElse(0)); 252 | testNG.setUseDefaultListeners(config.getBoolean("testng.useDefaultListeners").orElse(false)); 253 | config.get("testng.outputDirectory") // 254 | .ifPresent(testNG::setOutputDirectory); 255 | config.getBoolean("testng.preserveOrder") // 256 | .ifPresent(testNG::setPreserveOrder); 257 | config.get("testng.parallel", ParallelMode::getValidParallel) // 258 | .ifPresent(testNG::setParallel); 259 | config.get("testng.threadCount", Integer::parseInt) // 260 | .ifPresent(testNG::setThreadCount); 261 | config.get("testng.dataProviderThreadCount", Integer::parseInt) // 262 | .ifPresent(testNG::setDataProviderThreadCount); 263 | } 264 | 265 | @Override 266 | public void configure(CommandLineArgs commandLineArgs, ConfigurationParameters config) { 267 | config.get("testng.listeners") // 268 | .ifPresent(listeners -> commandLineArgs.listener = listeners); 269 | } 270 | }; 271 | } 272 | 273 | /** 274 | * Needed to make {@link #configure(CommandLineArgs)} accessible. 275 | */ 276 | private static class ConfigurableTestNG extends TestNG { 277 | @Override 278 | protected void configure(CommandLineArgs cla) { 279 | super.configure(cla); 280 | } 281 | } 282 | } 283 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/org.junit.platform.engine.TestEngine: -------------------------------------------------------------------------------- 1 | org.junit.support.testng.engine.TestNGTestEngine 2 | -------------------------------------------------------------------------------- /src/module/java/module-info.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | /** 12 | * Provides the TestNG {@linkplain org.junit.platform.engine.TestEngine} implementation. 13 | * 14 | * @since 1.0 15 | * @provides org.junit.platform.engine.TestEngine 16 | * @see org.junit.support.testng.engine.TestNGTestEngine 17 | */ 18 | module org.junit.support.testng.engine { 19 | requires org.junit.platform.engine; 20 | requires org.testng; 21 | requires java.logging; 22 | provides org.junit.platform.engine.TestEngine with org.junit.support.testng.engine.TestNGTestEngine; 23 | } 24 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/AbstractIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.function.Predicate.isEqual; 14 | import static org.assertj.core.api.Assertions.allOf; 15 | import static org.junit.platform.commons.util.FunctionUtils.where; 16 | import static org.junit.platform.engine.TestExecutionResult.Status.ABORTED; 17 | import static org.junit.platform.testkit.engine.Event.byTestDescriptor; 18 | import static org.junit.platform.testkit.engine.EventConditions.container; 19 | import static org.junit.platform.testkit.engine.EventConditions.displayName; 20 | import static org.junit.platform.testkit.engine.EventConditions.event; 21 | import static org.junit.platform.testkit.engine.EventConditions.finished; 22 | import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; 23 | import static org.junit.platform.testkit.engine.TestExecutionResultConditions.status; 24 | 25 | import java.nio.file.Path; 26 | import java.util.Optional; 27 | 28 | import org.assertj.core.api.Condition; 29 | import org.junit.jupiter.api.io.TempDir; 30 | import org.junit.platform.engine.TestDescriptor; 31 | import org.junit.platform.engine.TestExecutionResult; 32 | import org.junit.platform.testkit.engine.EngineTestKit; 33 | import org.junit.platform.testkit.engine.Event; 34 | 35 | abstract class AbstractIntegrationTests { 36 | 37 | @TempDir 38 | Path tempDir; 39 | 40 | EngineTestKit.Builder testNGEngine() { 41 | return EngineTestKit.engine("testng") // 42 | .configurationParameter("testng.verbose", "10") // 43 | .configurationParameter("testng.useDefaultListeners", "false") // 44 | .configurationParameter("testng.outputDirectory", tempDir.toString()); 45 | } 46 | 47 | static Condition testClass(Class testClass) { 48 | return container(event(displayName(testClass.getSimpleName()), uniqueIdSubstring(testClass.getName()), 49 | legacyReportingName(testClass.getName()))); 50 | } 51 | 52 | static Condition legacyReportingName(String legacyReportingName) { 53 | return new Condition<>( 54 | byTestDescriptor(where(TestDescriptor::getLegacyReportingName, isEqual(legacyReportingName))), 55 | "descriptor with legacy reporting name '%s'", legacyReportingName); 56 | } 57 | 58 | static Condition abortedWithoutReason() { 59 | return finished(allOf(status(ABORTED), emptyThrowable())); 60 | } 61 | 62 | static Condition emptyThrowable() { 63 | return new Condition<>(where(TestExecutionResult::getThrowable, Optional::isEmpty), "throwable is empty"); 64 | } 65 | 66 | } 67 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/ConfigurationMethodIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 15 | import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; 16 | import static org.junit.platform.testkit.engine.EventConditions.engine; 17 | import static org.junit.platform.testkit.engine.EventConditions.event; 18 | import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; 19 | import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; 20 | import static org.junit.platform.testkit.engine.EventConditions.started; 21 | import static org.junit.platform.testkit.engine.EventConditions.test; 22 | import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; 23 | 24 | import example.configuration.methods.FailingAfterClassConfigurationMethodTestCase; 25 | import example.configuration.methods.FailingAfterMethodConfigurationMethodTestCase; 26 | import example.configuration.methods.FailingAfterSuiteConfigurationMethodTestCase; 27 | import example.configuration.methods.FailingAfterTestConfigurationMethodTestCase; 28 | import example.configuration.methods.FailingBeforeClassConfigurationMethodTestCase; 29 | import example.configuration.methods.FailingBeforeMethodConfigurationMethodTestCase; 30 | import example.configuration.methods.FailingBeforeSuiteConfigurationMethodTestCase; 31 | import example.configuration.methods.FailingBeforeTestConfigurationMethodTestCase; 32 | import example.configuration.methods.GroupsConfigurationMethodsTestCase; 33 | 34 | import org.junit.jupiter.api.Test; 35 | import org.junit.jupiter.params.ParameterizedTest; 36 | import org.junit.jupiter.params.provider.ValueSource; 37 | 38 | class ConfigurationMethodIntegrationTests extends AbstractIntegrationTests { 39 | 40 | @Test 41 | void reportsFailureFromBeforeClassMethod() { 42 | var results = testNGEngine().selectors( 43 | selectClass(FailingBeforeClassConfigurationMethodTestCase.class)).execute(); 44 | 45 | results.allEvents().assertEventsMatchLooselyInOrder( // 46 | event(testClass(FailingBeforeClassConfigurationMethodTestCase.class), started()), // 47 | event(testClass(FailingBeforeClassConfigurationMethodTestCase.class), 48 | finishedWithFailure(message("boom")))); 49 | } 50 | 51 | @Test 52 | void reportsFailureFromBeforeMethodConfigurationMethodAsAbortedWithThrowable() { 53 | var testClass = FailingBeforeMethodConfigurationMethodTestCase.class; 54 | 55 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 56 | 57 | results.allEvents().assertEventsMatchLooselyInOrder( // 58 | event(testClass(testClass), started()), // 59 | event(test("method:a()"), started()), // 60 | event(test("method:a()"), finishedSuccessfully()), // 61 | event(test("method:b()"), started()), // 62 | event(test("method:b()"), abortedWithReason(message("boom"))), // 63 | event(testClass(testClass), finishedWithFailure(message("boom")))); 64 | } 65 | 66 | @ParameterizedTest 67 | @ValueSource(classes = { FailingBeforeTestConfigurationMethodTestCase.class, 68 | FailingBeforeSuiteConfigurationMethodTestCase.class }) 69 | void reportsFailureFromEarlyEngineLevelConfigurationMethodAsAborted(Class testClass) { 70 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 71 | 72 | results.allEvents().debug().assertEventsMatchExactly( // 73 | event(engine(), started()), // 74 | event(testClass(testClass), started()), // 75 | event(test("method:test()"), started()), // 76 | event(test("method:test()"), abortedWithReason(message("boom"))), // 77 | event(testClass(testClass), abortedWithoutReason()), // 78 | event(engine(), finishedWithFailure(message("boom")))); 79 | } 80 | 81 | @Test 82 | void reportsFailureFromAfterMethodConfigurationMethodAsClassLevelFailure() { 83 | var testClass = FailingAfterMethodConfigurationMethodTestCase.class; 84 | 85 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 86 | 87 | results.allEvents().assertEventsMatchLooselyInOrder( // 88 | event(testClass(testClass), started()), // 89 | event(test("method:test()"), started()), // 90 | event(test("method:test()"), finishedSuccessfully()), // 91 | event(testClass(testClass), finishedWithFailure(message("boom")))); 92 | } 93 | 94 | @ParameterizedTest 95 | @ValueSource(classes = { FailingAfterClassConfigurationMethodTestCase.class, 96 | FailingAfterTestConfigurationMethodTestCase.class, FailingAfterSuiteConfigurationMethodTestCase.class }) 97 | void reportsFailureFromLateEngineLevelConfigurationMethodAsEngineLevelFailure(Class testClass) { 98 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 99 | 100 | results.allEvents().assertEventsMatchExactly( // 101 | event(engine(), started()), // 102 | event(testClass(testClass), started()), // 103 | event(test("method:test()"), started()), // 104 | event(test("method:test()"), finishedSuccessfully()), // 105 | event(testClass(testClass), finishedSuccessfully()), // 106 | event(engine(), finishedWithFailure(message("boom")))); 107 | } 108 | 109 | @Test 110 | void runsGroupsIncludingConfigurationMethods() { 111 | Class testClass = GroupsConfigurationMethodsTestCase.class; 112 | GroupsConfigurationMethodsTestCase.EVENTS.clear(); 113 | 114 | var results = testNGEngine() // 115 | .selectors(selectClass(testClass)) // 116 | .configurationParameter("testng.groups", "group1") // 117 | .configurationParameter("testng.excludedGroups", "group2") // 118 | .execute(); 119 | 120 | results.allEvents().assertEventsMatchExactly( // 121 | event(engine(), started()), // 122 | event(testClass(testClass), started()), // 123 | event(test("method:testGroup1()"), started()), // 124 | event(test("method:testGroup1()"), finishedSuccessfully()), // 125 | event(testClass(testClass), finishedSuccessfully()), // 126 | event(engine(), finishedSuccessfully())); 127 | 128 | assertThat(GroupsConfigurationMethodsTestCase.EVENTS) // 129 | .containsExactly("beforeGroup1", "testGroup1", "afterGroup1"); 130 | } 131 | 132 | } 133 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/ConfigurationParametersIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static org.assertj.core.api.Assertions.assertThat; 14 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 15 | import static org.junit.platform.testkit.engine.EventConditions.event; 16 | import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; 17 | import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; 18 | import static org.junit.platform.testkit.engine.EventConditions.started; 19 | import static org.junit.platform.testkit.engine.EventConditions.test; 20 | 21 | import example.configuration.parameters.DataProviderThreadCountTestCase; 22 | import example.configuration.parameters.InvocationTrackingListener; 23 | import example.configuration.parameters.ParallelMethodsTestCase; 24 | import example.configuration.parameters.PreserveOrderTestCase; 25 | import example.configuration.parameters.ReturnValuesTestCase; 26 | import example.configuration.parameters.SystemPropertyProvidingListener; 27 | import example.configuration.parameters.SystemPropertyReadingTestCase; 28 | 29 | import org.junit.jupiter.api.Test; 30 | import org.junit.jupiter.params.ParameterizedTest; 31 | import org.junit.jupiter.params.provider.ValueSource; 32 | 33 | class ConfigurationParametersIntegrationTests extends AbstractIntegrationTests { 34 | 35 | @Test 36 | void registersCustomListeners() { 37 | var testClass = SystemPropertyReadingTestCase.class; 38 | InvocationTrackingListener.invoked = false; 39 | 40 | var results = testNGEngine() // 41 | .selectors(selectClass(testClass)) // 42 | .configurationParameter("testng.listeners", SystemPropertyProvidingListener.class.getName() + " , " 43 | + InvocationTrackingListener.class.getName()) // 44 | .execute(); 45 | 46 | assertThat(InvocationTrackingListener.invoked).isTrue(); 47 | results.allEvents().assertEventsMatchLooselyInOrder( // 48 | event(testClass(testClass), started()), // 49 | event(test("method:test()"), started()), // 50 | event(test("method:test()"), finishedSuccessfully()), // 51 | event(testClass(testClass), finishedSuccessfully())); 52 | } 53 | 54 | @Test 55 | void executesTestMethodsWithReturnValuesWhenEnabledViaConfigurationParameter() { 56 | var testClass = ReturnValuesTestCase.class; 57 | 58 | var results = testNGEngine() // 59 | .selectors(selectClass(testClass)) // 60 | .configurationParameter("testng.allowReturnValues", "true") // 61 | .execute(); 62 | 63 | results.allEvents().assertEventsMatchLooselyInOrder( // 64 | event(testClass(testClass), started()), // 65 | event(test("method:test()"), started()), // 66 | event(test("method:test()"), finishedSuccessfully()), // 67 | event(testClass(testClass), finishedSuccessfully())); 68 | } 69 | 70 | @ParameterizedTest 71 | @ValueSource(booleans = { true, false }) 72 | void configuresPreserveOrderOnXmlTest(boolean preserveOrder) { 73 | var testClass = PreserveOrderTestCase.class; 74 | 75 | var results = testNGEngine() // 76 | .selectors(selectClass(testClass)) // 77 | .configurationParameter("testng.preserveOrder", String.valueOf(preserveOrder)) // 78 | .execute(); 79 | 80 | results.allEvents().assertEventsMatchLooselyInOrder( // 81 | event(testClass(testClass), started()), // 82 | event(test("method:test(org.testng.ITestContext)"), started()), // 83 | event(test("method:test(org.testng.ITestContext)"), 84 | preserveOrder ? finishedSuccessfully() : finishedWithFailure()), // 85 | event(testClass(testClass), finishedSuccessfully())); 86 | } 87 | 88 | @Test 89 | void configuresParallelMode() { 90 | var testClass = ParallelMethodsTestCase.class; 91 | 92 | var results = testNGEngine() // 93 | .selectors(selectClass(testClass)) // 94 | .configurationParameter("testng.parallel", "methods") // 95 | .execute(); 96 | 97 | results.testEvents().debug().assertStatistics(stats -> stats.succeeded(2)); 98 | } 99 | 100 | @Test 101 | void configuresThreadCount() { 102 | var testClass = ParallelMethodsTestCase.class; 103 | 104 | var results = testNGEngine() // 105 | .selectors(selectClass(testClass)) // 106 | .configurationParameter("testng.parallel", "methods") // 107 | .configurationParameter("testng.threadCount", "1") // 108 | .execute(); 109 | 110 | results.testEvents().debug().assertStatistics(stats -> stats.succeeded(1).failed(1)); 111 | } 112 | 113 | @Test 114 | void configuresDataProviderThreadCount() { 115 | var testClass = DataProviderThreadCountTestCase.class; 116 | var numInvocations = DataProviderThreadCountTestCase.NUM_INVOCATIONS; 117 | 118 | var results = testNGEngine() // 119 | .selectors(selectClass(testClass)) // 120 | .configurationParameter("testng.dataProviderThreadCount", String.valueOf(numInvocations)) // 121 | .execute(); 122 | 123 | results.testEvents().debug().assertStatistics(stats -> stats.succeeded(numInvocations)); 124 | } 125 | } 126 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/DataProviderIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.util.function.Function.identity; 14 | import static java.util.stream.Collectors.toMap; 15 | import static org.assertj.core.api.Assertions.assertThat; 16 | import static org.junit.platform.commons.util.CollectionUtils.getOnlyElement; 17 | import static org.junit.platform.engine.TestDescriptor.Type.CONTAINER; 18 | import static org.junit.platform.engine.TestDescriptor.Type.TEST; 19 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 20 | import static org.junit.platform.launcher.core.LauncherDiscoveryRequestBuilder.request; 21 | import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; 22 | import static org.junit.platform.testkit.engine.EventConditions.container; 23 | import static org.junit.platform.testkit.engine.EventConditions.displayName; 24 | import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; 25 | import static org.junit.platform.testkit.engine.EventConditions.engine; 26 | import static org.junit.platform.testkit.engine.EventConditions.event; 27 | import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; 28 | import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; 29 | import static org.junit.platform.testkit.engine.EventConditions.started; 30 | import static org.junit.platform.testkit.engine.EventConditions.test; 31 | import static org.junit.platform.testkit.engine.EventConditions.uniqueIdSubstring; 32 | import static org.junit.platform.testkit.engine.TestExecutionResultConditions.cause; 33 | import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; 34 | import static org.junit.support.testng.engine.TestContext.testNGVersion; 35 | 36 | import example.dataproviders.*; 37 | 38 | import org.apache.maven.artifact.versioning.ComparableVersion; 39 | import org.junit.jupiter.api.Test; 40 | import org.junit.platform.engine.TestDescriptor; 41 | import org.junit.platform.engine.UniqueId; 42 | import org.junit.platform.engine.support.descriptor.MethodSource; 43 | 44 | class DataProviderIntegrationTests extends AbstractIntegrationTests { 45 | 46 | @Test 47 | void discoversDataProviderTestMethods() { 48 | var request = request().selectors(selectClass(DataProviderMethodTestCase.class)).build(); 49 | 50 | var rootDescriptor = new TestNGTestEngine().discover(request, UniqueId.forEngine("testng")); 51 | 52 | var classDescriptor = getOnlyElement(rootDescriptor.getChildren()); 53 | assertThat(classDescriptor.getChildren()).hasSize(3); 54 | 55 | var methodDescriptors = classDescriptor.getChildren().stream() // 56 | .collect(toMap(TestDescriptor::getDisplayName, identity())); 57 | assertThat(methodDescriptors.keySet()).containsExactlyInAnyOrder("test", "test(java.lang.String)", "test(int)"); 58 | methodDescriptors.forEach((displayName, methodDescriptor) -> { 59 | assertThat(methodDescriptor.getLegacyReportingName()).isEqualTo(displayName); 60 | assertThat(methodDescriptor.getChildren()).isEmpty(); 61 | }); 62 | 63 | assertThat(methodDescriptors.get("test").getType()) // 64 | .isEqualTo(TEST); 65 | assertThat(methodDescriptors.get("test").getSource()) // 66 | .contains(MethodSource.from(DataProviderMethodTestCase.class.getName(), "test", "")); 67 | assertThat(methodDescriptors.get("test").getUniqueId().getLastSegment().getValue()) // 68 | .isEqualTo("test()"); 69 | 70 | assertThat(methodDescriptors.get("test(java.lang.String)").getType()) // 71 | .isEqualTo(CONTAINER); 72 | assertThat(methodDescriptors.get("test(java.lang.String)").getSource()) // 73 | .contains( 74 | MethodSource.from(DataProviderMethodTestCase.class.getName(), "test", String.class.getName())); 75 | assertThat(methodDescriptors.get("test(java.lang.String)").getUniqueId().getLastSegment().getValue()) // 76 | .isEqualTo("test(java.lang.String)"); 77 | 78 | assertThat(methodDescriptors.get("test(int)").getType()) // 79 | .isEqualTo(CONTAINER); 80 | assertThat(methodDescriptors.get("test(int)").getSource()) // 81 | .contains(MethodSource.from(DataProviderMethodTestCase.class.getName(), "test", int.class.getName())); 82 | assertThat(methodDescriptors.get("test(int)").getUniqueId().getLastSegment().getValue()) // 83 | .isEqualTo("test(int)"); 84 | } 85 | 86 | @Test 87 | void executesDataProviderTestMethods() { 88 | var results = testNGEngine().selectors(selectClass(DataProviderMethodTestCase.class)).execute(); 89 | 90 | results.allEvents().assertEventsMatchLooselyInOrder( // 91 | event(testClass(DataProviderMethodTestCase.class), started()), // 92 | event(test("method:test()"), started()), // 93 | event(test("method:test()"), finishedWithFailure(message("parameterless"))), // 94 | event(testClass(DataProviderMethodTestCase.class), finishedSuccessfully())); 95 | results.allEvents().assertEventsMatchLooselyInOrder( // 96 | event(testClass(DataProviderMethodTestCase.class), started()), // 97 | event(container("method:test(java.lang.String)"), started()), // 98 | event(uniqueIdSubstring("method:test(java.lang.String)"), dynamicTestRegistered("invoc:0"), 99 | displayName("[0] a"), legacyReportingName("test(java.lang.String)[0]")), // 100 | event(uniqueIdSubstring("method:test(java.lang.String)"), test("invoc:0"), started()), // 101 | event(uniqueIdSubstring("method:test(java.lang.String)"), test("invoc:0"), 102 | finishedWithFailure(message("a"))), // 103 | event(uniqueIdSubstring("method:test(java.lang.String)"), dynamicTestRegistered("invoc:1"), 104 | displayName("[1] b"), legacyReportingName("test(java.lang.String)[1]")), // 105 | event(uniqueIdSubstring("method:test(java.lang.String)"), test("invoc:1"), started()), // 106 | event(uniqueIdSubstring("method:test(java.lang.String)"), test("invoc:1"), 107 | finishedWithFailure(message("b"))), // 108 | event(container("method:test(java.lang.String)"), finishedSuccessfully()), // 109 | event(testClass(DataProviderMethodTestCase.class), finishedSuccessfully())); 110 | results.allEvents().assertEventsMatchLooselyInOrder( // 111 | event(testClass(DataProviderMethodTestCase.class), started()), // 112 | event(container("method:test(int)"), started()), // 113 | event(uniqueIdSubstring("method:test(int)"), dynamicTestRegistered("invoc:0"), displayName("[0] 1"), 114 | legacyReportingName("test(int)[0]")), // 115 | event(uniqueIdSubstring("method:test(int)"), test("invoc:0"), started()), // 116 | event(uniqueIdSubstring("method:test(int)"), test("invoc:0"), finishedWithFailure(message("1"))), // 117 | event(uniqueIdSubstring("method:test(int)"), dynamicTestRegistered("invoc:1"), displayName("[1] 2"), 118 | legacyReportingName("test(int)[1]")), // 119 | event(uniqueIdSubstring("method:test(int)"), test("invoc:1"), started()), // 120 | event(uniqueIdSubstring("method:test(int)"), test("invoc:1"), finishedWithFailure(message("2"))), // 121 | event(container("method:test(int)"), finishedSuccessfully()), // 122 | event(testClass(DataProviderMethodTestCase.class), finishedSuccessfully())); 123 | } 124 | 125 | @Test 126 | void discoversFactoryWithDataProviderTestClass() { 127 | var request = request().selectors(selectClass(FactoryWithDataProviderTestCase.class)).build(); 128 | 129 | var rootDescriptor = new TestNGTestEngine().discover(request, UniqueId.forEngine("testng")); 130 | 131 | var classDescriptor = getOnlyElement(rootDescriptor.getChildren()); 132 | assertThat(classDescriptor.getChildren()).hasSize(4); 133 | 134 | var methodDescriptors = classDescriptor.getChildren().stream() // 135 | .collect(toMap(descriptor -> descriptor.getUniqueId().getLastSegment().getValue(), identity())); 136 | assertThat(methodDescriptors.keySet()).containsExactlyInAnyOrder("a()@0", "a()@1", "b()@0", "b()@1"); 137 | } 138 | 139 | @Test 140 | void executesFactoryWithDataProviderTestClass() { 141 | var results = testNGEngine().selectors(selectClass(FactoryWithDataProviderTestCase.class)).execute(); 142 | 143 | var capturesFactoryParameters = testNGVersion().compareTo(new ComparableVersion("7.0")) >= 0; 144 | var firstParamSuffix = capturesFactoryParameters ? "(a)" : ""; 145 | var secondParamSuffix = capturesFactoryParameters ? "(b)" : ""; 146 | 147 | results.containerEvents().assertEventsMatchLooselyInOrder( // 148 | event(engine(), started()), // 149 | event(testClass(FactoryWithDataProviderTestCase.class), started()), // 150 | event(testClass(FactoryWithDataProviderTestCase.class), finishedSuccessfully()), // 151 | event(engine(), finishedSuccessfully())); 152 | results.allEvents().debug().assertEventsMatchLooselyInOrder( // 153 | event(testClass(FactoryWithDataProviderTestCase.class), started()), // 154 | event(test("method:a()@0"), started(), displayName("a[0]" + firstParamSuffix)), // 155 | event(test("method:a()@0"), finishedWithFailure(message("a"))), // 156 | event(testClass(FactoryWithDataProviderTestCase.class), finishedSuccessfully())); 157 | results.allEvents().assertEventsMatchLooselyInOrder( // 158 | event(testClass(FactoryWithDataProviderTestCase.class), started()), // 159 | event(test("method:a()@1"), started(), displayName("a[1]" + secondParamSuffix)), // 160 | event(test("method:a()@1"), finishedWithFailure(message("b"))), // 161 | event(testClass(FactoryWithDataProviderTestCase.class), finishedSuccessfully())); 162 | results.allEvents().assertEventsMatchLooselyInOrder( // 163 | event(testClass(FactoryWithDataProviderTestCase.class), started()), // 164 | event(test("method:b()@0"), started(), displayName("b[0]" + firstParamSuffix)), // 165 | event(test("method:b()@0"), finishedWithFailure(message("a"))), // 166 | event(testClass(FactoryWithDataProviderTestCase.class), finishedSuccessfully())); 167 | results.allEvents().assertEventsMatchLooselyInOrder( // 168 | event(testClass(FactoryWithDataProviderTestCase.class), started()), // 169 | event(test("method:b()@1"), started(), displayName("b[1]" + secondParamSuffix)), // 170 | event(test("method:b()@1"), finishedWithFailure(message("b"))), // 171 | event(testClass(FactoryWithDataProviderTestCase.class), finishedSuccessfully())); 172 | } 173 | 174 | @Test 175 | void executesFactoryMethodTestClass() { 176 | var results = testNGEngine().selectors(selectClass(FactoryMethodTestCase.class)).execute(); 177 | 178 | results.allEvents().debug(); 179 | 180 | results.containerEvents().assertEventsMatchExactly( // 181 | event(engine(), started()), // 182 | event(testClass(FactoryMethodTestCase.class), started()), // 183 | event(testClass(FactoryMethodTestCase.class), finishedSuccessfully()), // 184 | event(engine(), finishedSuccessfully())); 185 | results.allEvents().assertEventsMatchLooselyInOrder( // 186 | event(testClass(FactoryMethodTestCase.class), started()), // 187 | event(test("method:test()@0"), started()), // 188 | event(test("method:test()@0"), finishedSuccessfully()), // 189 | event(testClass(FactoryMethodTestCase.class), finishedSuccessfully())); 190 | results.allEvents().assertEventsMatchLooselyInOrder( // 191 | event(testClass(FactoryMethodTestCase.class), started()), // 192 | event(test("method:test()@1"), started()), // 193 | event(test("method:test()@1"), finishedSuccessfully()), // 194 | event(testClass(FactoryMethodTestCase.class), finishedSuccessfully())); 195 | } 196 | 197 | @Test 198 | @RequiresTestNGVersion(maxExclusive = "7.6") 199 | void reportsExceptionInDataProviderMethodAsAborted() { 200 | var testClass = DataProviderMethodErrorHandlingTestCase.class; 201 | 202 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 203 | 204 | results.allEvents().assertEventsMatchLooselyInOrder( // 205 | event(testClass(testClass), started()), // 206 | event(container("method:test(int)"), started()), // 207 | event(container("method:test(int)"), abortedWithReason(cause(message("exception in data provider")))), // 208 | event(testClass(testClass), finishedSuccessfully())); 209 | } 210 | 211 | @Test 212 | @RequiresTestNGVersion(min = "7.6") 213 | void reportsExceptionInDataProviderMethodAsFailed() { 214 | var testClass = DataProviderMethodErrorHandlingTestCase.class; 215 | 216 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 217 | 218 | results.allEvents().debug().assertEventsMatchLooselyInOrder( // 219 | event(testClass(testClass), started()), // 220 | event(container("method:test(int)"), started()), // 221 | event(container("method:test(int)"), finishedWithFailure(cause(message("exception in data provider")))), // 222 | event(testClass(testClass), finishedSuccessfully())); 223 | } 224 | 225 | @Test 226 | void reportsNoEventsForDataProviderWithZeroInvocations() { 227 | var testClass = DataProviderMethodEmptyListTestCase.class; 228 | 229 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 230 | 231 | results.allEvents().assertEventsMatchExactly( // 232 | event(engine(), started()), // 233 | event(engine(), finishedSuccessfully())); 234 | } 235 | 236 | @Test 237 | void reportsParallelDataProviderCorrectly() { 238 | var testClass = ParallelDataProviderTestCase.class; 239 | 240 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 241 | 242 | results.containerEvents().debug().assertEventsMatchLooselyInOrder( // 243 | event(testClass(testClass), started()), // 244 | event(container("method:test(java.lang.Integer)"), started()), // 245 | event(container("method:test(java.lang.Integer)"), finishedSuccessfully()), // 246 | event(testClass(testClass), finishedSuccessfully())); 247 | results.testEvents().assertStatistics( 248 | stats -> stats.dynamicallyRegistered(11).started(11).succeeded(11).finished(11)); 249 | } 250 | } 251 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/ReportingIntegrationTests.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static org.junit.platform.commons.util.StringUtils.isBlank; 14 | import static org.junit.platform.engine.FilterResult.excluded; 15 | import static org.junit.platform.engine.FilterResult.includedIf; 16 | import static org.junit.platform.engine.discovery.ClassNameFilter.excludeClassNamePatterns; 17 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectClass; 18 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectMethod; 19 | import static org.junit.platform.engine.discovery.DiscoverySelectors.selectUniqueId; 20 | import static org.junit.platform.testkit.engine.EventConditions.abortedWithReason; 21 | import static org.junit.platform.testkit.engine.EventConditions.container; 22 | import static org.junit.platform.testkit.engine.EventConditions.displayName; 23 | import static org.junit.platform.testkit.engine.EventConditions.dynamicTestRegistered; 24 | import static org.junit.platform.testkit.engine.EventConditions.engine; 25 | import static org.junit.platform.testkit.engine.EventConditions.event; 26 | import static org.junit.platform.testkit.engine.EventConditions.finishedSuccessfully; 27 | import static org.junit.platform.testkit.engine.EventConditions.finishedWithFailure; 28 | import static org.junit.platform.testkit.engine.EventConditions.reportEntry; 29 | import static org.junit.platform.testkit.engine.EventConditions.started; 30 | import static org.junit.platform.testkit.engine.EventConditions.test; 31 | import static org.junit.platform.testkit.engine.TestExecutionResultConditions.instanceOf; 32 | import static org.junit.platform.testkit.engine.TestExecutionResultConditions.message; 33 | 34 | import java.util.Map; 35 | 36 | import example.basics.CustomAttributeTestCase; 37 | import example.basics.ExpectedExceptionsTestCase; 38 | import example.basics.InheritingSubClassTestCase; 39 | import example.basics.NestedTestClass; 40 | import example.basics.ParallelExecutionTestCase; 41 | import example.basics.RetriedTestCase; 42 | import example.basics.SimpleTestCase; 43 | import example.basics.SuccessPercentageTestCase; 44 | import example.basics.TimeoutTestCase; 45 | import example.configuration.methods.AbortedBeforeClassConfigurationMethodTestCase; 46 | import example.configuration.methods.FailingBeforeClassConfigurationMethodTestCase; 47 | import example.dataproviders.DataProviderMethodTestCase; 48 | 49 | import org.junit.jupiter.api.Test; 50 | import org.junit.jupiter.params.ParameterizedTest; 51 | import org.junit.jupiter.params.provider.ValueSource; 52 | import org.junit.platform.engine.Filter; 53 | import org.junit.platform.engine.UniqueId; 54 | import org.junit.platform.engine.support.descriptor.MethodSource; 55 | import org.junit.platform.launcher.PostDiscoveryFilter; 56 | import org.testng.SkipException; 57 | import org.testng.internal.thread.ThreadTimeoutException; 58 | 59 | class ReportingIntegrationTests extends AbstractIntegrationTests { 60 | 61 | @ParameterizedTest 62 | @ValueSource(classes = { SimpleTestCase.class, InheritingSubClassTestCase.class }) 63 | void executesSuccessfulTests(Class testClass) { 64 | var results = testNGEngine().selectors(selectMethod(testClass, "successful")).execute(); 65 | 66 | results.allEvents().assertEventsMatchExactly( // 67 | event(engine(), started()), // 68 | event(testClass(testClass), started()), // 69 | event(test("method:successful()"), started()), // 70 | event(test("method:successful()"), reportEntry(Map.of("description", "a test that passes"))), // 71 | event(test("method:successful()"), finishedSuccessfully()), // 72 | event(testClass(testClass), finishedSuccessfully()), // 73 | event(engine(), finishedSuccessfully())); 74 | } 75 | 76 | @Test 77 | void executesFailingTests() { 78 | var results = testNGEngine().selectors(selectClass(SimpleTestCase.class)).execute(); 79 | 80 | results.allEvents().assertEventsMatchLooselyInOrder( // 81 | event(testClass(SimpleTestCase.class), started()), // 82 | event(test("method:failing()"), started()), // 83 | event(test("method:failing()"), finishedWithFailure(message("boom"))), // 84 | event(testClass(SimpleTestCase.class), finishedSuccessfully())); 85 | } 86 | 87 | @Test 88 | void executesAbortedTests() { 89 | var results = testNGEngine().selectors(selectClass(SimpleTestCase.class)).execute(); 90 | 91 | results.allEvents().assertEventsMatchLooselyInOrder( // 92 | event(testClass(SimpleTestCase.class), started()), // 93 | event(test("method:aborted()"), started()), // 94 | event(test("method:aborted()"), abortedWithReason(instanceOf(SkipException.class), message("not today"))), // 95 | event(testClass(SimpleTestCase.class), finishedSuccessfully())); 96 | } 97 | 98 | @Test 99 | void reportsMethodsSkippedDueToFailingDependencyAsAborted() { 100 | var results = testNGEngine().selectors(selectClass(SimpleTestCase.class)).execute(); 101 | 102 | results.allEvents().assertEventsMatchLooselyInOrder( // 103 | event(testClass(SimpleTestCase.class), started()), // 104 | event(test("method:skippedDueToFailingDependency()"), started()), // 105 | event(test("method:skippedDueToFailingDependency()"), 106 | abortedWithReason(message(it -> it.contains("depends on not successfully finished methods")))), // 107 | event(testClass(SimpleTestCase.class), finishedSuccessfully())); 108 | } 109 | 110 | @Test 111 | void reportsFailureFromBeforeClassMethod() { 112 | var results = testNGEngine().selectors( 113 | selectClass(FailingBeforeClassConfigurationMethodTestCase.class)).execute(); 114 | 115 | results.allEvents().assertEventsMatchLooselyInOrder( // 116 | event(testClass(FailingBeforeClassConfigurationMethodTestCase.class), started()), // 117 | event(testClass(FailingBeforeClassConfigurationMethodTestCase.class), 118 | finishedWithFailure(message("boom")))); 119 | } 120 | 121 | @Test 122 | void reportsAbortedBeforeClassMethod() { 123 | var results = testNGEngine().selectors( 124 | selectClass(AbortedBeforeClassConfigurationMethodTestCase.class)).execute(); 125 | 126 | results.allEvents().debug().assertEventsMatchLooselyInOrder( // 127 | event(testClass(AbortedBeforeClassConfigurationMethodTestCase.class), started()), // 128 | event(testClass(AbortedBeforeClassConfigurationMethodTestCase.class), 129 | abortedWithReason(message("not today")))); 130 | } 131 | 132 | @Test 133 | void executesNoTestWhenPostDiscoveryFilterExcludesEverything() { 134 | var testClass = SimpleTestCase.class; 135 | 136 | var results = testNGEngine() // 137 | .selectors(selectClass(testClass)) // 138 | .filters((PostDiscoveryFilter) descriptor -> excluded("not today")) // 139 | .execute(); 140 | 141 | results.allEvents().assertEventsMatchExactly( // 142 | event(engine(), started()), // 143 | event(engine(), finishedSuccessfully())); 144 | } 145 | 146 | @Test 147 | void reportsPreviouslyExcludedTestsThatAreExecutedDueToHavingTheSameMethodNameAsDynamicTests() { 148 | PostDiscoveryFilter onlyParameterlessMethods = descriptor -> { 149 | var source = descriptor.getSource().orElse(null); 150 | return includedIf( 151 | !(source instanceof MethodSource) || isBlank(((MethodSource) source).getMethodParameterTypes())); 152 | }; 153 | 154 | var results = testNGEngine() // 155 | .selectors(selectClass(DataProviderMethodTestCase.class)) // 156 | .filters(onlyParameterlessMethods) // 157 | .execute(); 158 | 159 | results.containerEvents().assertStatistics(stats -> stats // 160 | .dynamicallyRegistered(2) // 161 | .started(1 + 1 + 2) // 162 | .succeeded(1 + 1 + 2) // 163 | .finished(1 + 1 + 2)); 164 | 165 | results.testEvents().assertStatistics(stats -> stats // 166 | .dynamicallyRegistered(2 + 2) // 167 | .started(1 + 2 + 2) // 168 | .failed(1 + 2 + 2) // 169 | .succeeded(0) // 170 | .finished(1 + 2 + 2)); 171 | } 172 | 173 | @Test 174 | void reportsSuccessPercentageTestCase() { 175 | var testClass = SuccessPercentageTestCase.class; 176 | 177 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 178 | 179 | results.allEvents().assertEventsMatchLooselyInOrder( // 180 | event(testClass(testClass), started()), // 181 | event(container("method:test()"), started()), // 182 | event(dynamicTestRegistered("invoc:0"), displayName("[0]")), // 183 | event(test("invoc:0"), started()), // 184 | event(test("invoc:0"), finishedSuccessfully()), // 185 | event(dynamicTestRegistered("invoc:1"), displayName("[1]")), // 186 | event(test("invoc:1"), started()), // 187 | event(test("invoc:1"), finishedWithFailure(message("boom"))), // 188 | event(dynamicTestRegistered("invoc:2"), displayName("[2]")), // 189 | event(test("invoc:2"), started()), // 190 | event(test("invoc:2"), finishedSuccessfully()), // 191 | event(dynamicTestRegistered("invoc:3"), displayName("[3]")), // 192 | event(test("invoc:3"), started()), // 193 | event(test("invoc:3"), finishedSuccessfully()), // 194 | event(container("method:test()"), finishedSuccessfully()), // 195 | event(testClass(testClass), finishedSuccessfully())); 196 | } 197 | 198 | @Test 199 | void executesAllInvocationsForInvocationUniqueIdSelector() { 200 | var uniqueId = UniqueId.forEngine("testng") // 201 | .append(ClassDescriptor.SEGMENT_TYPE, SuccessPercentageTestCase.class.getName()) // 202 | .append(MethodDescriptor.SEGMENT_TYPE, "test()") // 203 | .append(InvocationDescriptor.SEGMENT_TYPE, "0"); 204 | 205 | var results = testNGEngine().selectors(selectUniqueId(uniqueId)).execute(); 206 | 207 | results.testEvents().assertStatistics(stats -> stats.finished(4)); 208 | } 209 | 210 | @Test 211 | void reportsRetriedTestsCorrectly() { 212 | var testClass = RetriedTestCase.class; 213 | 214 | var results = testNGEngine().selectors(selectMethod(testClass, "test")).execute(); 215 | 216 | results.allEvents().assertEventsMatchLooselyInOrder( // 217 | event(testClass(testClass), started()), // 218 | event(container("method:test()"), started()), // 219 | event(dynamicTestRegistered("invoc:0"), displayName("[0]")), // 220 | event(test("invoc:0"), started()), // 221 | event(test("invoc:0"), abortedWithReason(message("retry"))), // 222 | event(dynamicTestRegistered("invoc:1"), displayName("[1]")), // 223 | event(test("invoc:1"), started()), // 224 | event(test("invoc:1"), finishedSuccessfully()), // 225 | event(container("method:test()"), finishedSuccessfully()), // 226 | event(testClass(testClass), finishedSuccessfully())); 227 | } 228 | 229 | @Test 230 | void reportsRetriedTestsWithDataProvidersCorrectly() { 231 | var testClass = RetriedTestCase.class; 232 | 233 | var results = testNGEngine().selectors(selectMethod(testClass, "dataProviderTest")).execute(); 234 | 235 | results.allEvents().assertEventsMatchLooselyInOrder( // 236 | event(testClass(testClass), started()), // 237 | event(container("method:dataProviderTest(int)"), started()), // 238 | event(dynamicTestRegistered("invoc:0"), displayName("[0] 1")), // 239 | event(test("invoc:0"), started()), // 240 | event(test("invoc:0"), abortedWithReason(message("retry @ 1"))), // 241 | event(dynamicTestRegistered("invoc:1"), displayName("[1] 1")), // 242 | event(test("invoc:1"), started()), // 243 | event(test("invoc:1"), finishedSuccessfully()), // 244 | event(dynamicTestRegistered("invoc:2"), displayName("[2] 2")), // 245 | event(test("invoc:2"), started()), // 246 | event(test("invoc:2"), finishedSuccessfully()), // 247 | event(container("method:dataProviderTest(int)"), finishedSuccessfully()), // 248 | event(testClass(testClass), finishedSuccessfully())); 249 | } 250 | 251 | @ParameterizedTest 252 | @ValueSource(strings = { "timeOut", "invocationTimeOut" }) 253 | void reportsTimedOutTestsAsFailures(String methodName) { 254 | var testClass = TimeoutTestCase.class; 255 | 256 | var results = testNGEngine().selectors(selectMethod(testClass, methodName)).execute(); 257 | 258 | results.allEvents().assertEventsMatchLooselyInOrder( // 259 | event(testClass(testClass), started()), // 260 | event(test("method:%s()".formatted(methodName)), started()), // 261 | event(test("method:%s()".formatted(methodName)), 262 | finishedWithFailure(instanceOf(ThreadTimeoutException.class))), // 263 | event(testClass(testClass), finishedSuccessfully())); 264 | } 265 | 266 | @Test 267 | void reportsTestThrowingExpectedExceptionAsSuccessful() { 268 | var testClass = ExpectedExceptionsTestCase.class; 269 | 270 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 271 | 272 | results.allEvents().assertEventsMatchLooselyInOrder( // 273 | event(testClass(testClass), started()), // 274 | event(test("method:test()"), started()), // 275 | event(test("method:test()"), finishedSuccessfully()), // 276 | event(testClass(testClass), finishedSuccessfully())); 277 | } 278 | 279 | @Test 280 | @RequiresTestNGVersion(min = "7.0") // introduced in 7.0 281 | void reportsCustomAttributesAsReportEntries() { 282 | var testClass = CustomAttributeTestCase.class; 283 | 284 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 285 | 286 | results.allEvents().assertEventsMatchLooselyInOrder( // 287 | event(testClass(testClass), started()), // 288 | event(test("method:test()"), started()), // 289 | event(test("method:test()"), reportEntry(Map.of("foo", "bar, baz"))), // 290 | event(test("method:test()"), finishedSuccessfully()), // 291 | event(testClass(testClass), finishedSuccessfully())); 292 | } 293 | 294 | @Test 295 | void reportsParallelInvocations() { 296 | var testClass = ParallelExecutionTestCase.class; 297 | 298 | var results = testNGEngine().selectors(selectClass(testClass)).execute(); 299 | 300 | results.containerEvents().assertStatistics(stats -> stats.started(3).finished(3)); 301 | results.testEvents().assertStatistics(stats -> stats.started(10).finished(10)); 302 | 303 | results.allEvents().debug().assertEventsMatchLooselyInOrder( // 304 | event(testClass(testClass), started()), // 305 | event(container("method:test()"), started()), // 306 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 307 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 308 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 309 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 310 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 311 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 312 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 313 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 314 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 315 | event(test("method:test()"), dynamicTestRegistered("invoc")), // 316 | event(container("method:test()"), finishedSuccessfully()), // 317 | event(testClass(testClass), finishedSuccessfully())); 318 | } 319 | 320 | @Test 321 | void onlyExecutesNestedTestClassesThatMatchClassNameFilter() { 322 | var selectedTestClass = NestedTestClass.class; 323 | 324 | var results = testNGEngine() // 325 | .selectors(selectClass(selectedTestClass)) // 326 | .filters((Filter) excludeClassNamePatterns(".*A$")) // 327 | .execute(); 328 | 329 | results.containerEvents().assertStatistics(stats -> stats.started(2).finished(2)); 330 | results.testEvents().assertStatistics(stats -> stats.started(1).finished(1)); 331 | } 332 | 333 | } 334 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/RequiresTestNGVersion.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static java.lang.annotation.ElementType.METHOD; 14 | import static java.lang.annotation.RetentionPolicy.RUNTIME; 15 | import static java.util.function.Predicate.isEqual; 16 | import static org.junit.jupiter.api.extension.ConditionEvaluationResult.disabled; 17 | import static org.junit.jupiter.api.extension.ConditionEvaluationResult.enabled; 18 | import static org.junit.support.testng.engine.TestContext.testNGVersion; 19 | 20 | import java.lang.annotation.Retention; 21 | import java.lang.annotation.Target; 22 | import java.util.Arrays; 23 | 24 | import org.apache.maven.artifact.versioning.ComparableVersion; 25 | import org.junit.jupiter.api.extension.ConditionEvaluationResult; 26 | import org.junit.jupiter.api.extension.ExecutionCondition; 27 | import org.junit.jupiter.api.extension.ExtendWith; 28 | import org.junit.jupiter.api.extension.ExtensionContext; 29 | import org.junit.platform.commons.support.AnnotationSupport; 30 | 31 | @Retention(RUNTIME) 32 | @Target(METHOD) 33 | @ExtendWith(RequiresTestNGVersion.Extension.class) 34 | @interface RequiresTestNGVersion { 35 | 36 | String min() default ""; 37 | 38 | String maxExclusive() default ""; 39 | 40 | String[] except() default {}; 41 | 42 | class Extension implements ExecutionCondition { 43 | @Override 44 | public ConditionEvaluationResult evaluateExecutionCondition(ExtensionContext context) { 45 | return AnnotationSupport.findAnnotation(context.getElement(), RequiresTestNGVersion.class).map( 46 | this::satisfiesRequirements).orElse(enabled("no TestNG version requirements")); 47 | } 48 | 49 | private ConditionEvaluationResult satisfiesRequirements(RequiresTestNGVersion requirements) { 50 | var actualVersion = testNGVersion(); 51 | if (!requirements.maxExclusive().isBlank() 52 | && actualVersion.compareTo(new ComparableVersion(requirements.maxExclusive())) >= 0) { 53 | return disabled( 54 | "maxExclusive constraint not met: %s > %s".formatted(actualVersion, requirements.maxExclusive())); 55 | } 56 | if (!requirements.min().isBlank() 57 | && actualVersion.compareTo(new ComparableVersion(requirements.min())) < 0) { 58 | return disabled("min constraint not met: %s < %s".formatted(actualVersion, requirements.min())); 59 | } 60 | if (Arrays.stream(requirements.except()).map(ComparableVersion::new).anyMatch(isEqual(actualVersion))) { 61 | return disabled("except constraint not met: %s is contained in %s".formatted(actualVersion, 62 | Arrays.toString(requirements.except()))); 63 | } 64 | return enabled("satisfies all TestNG version requirements"); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/TestContext.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import static org.apache.commons.lang3.StringUtils.removeEnd; 14 | 15 | import org.apache.maven.artifact.versioning.ComparableVersion; 16 | 17 | class TestContext { 18 | 19 | static ComparableVersion testNGVersion() { 20 | return new ComparableVersion(removeEnd(System.getProperty("testng.version", "7.10.2"), "-SNAPSHOT")); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/test/java/org/junit/support/testng/engine/TestNGVersionAppendingDisplayNameGenerator.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package org.junit.support.testng.engine; 12 | 13 | import java.lang.reflect.Method; 14 | import java.text.MessageFormat; 15 | 16 | import org.junit.jupiter.api.DisplayNameGenerator; 17 | 18 | class TestNGVersionAppendingDisplayNameGenerator extends DisplayNameGenerator.Standard { 19 | 20 | @Override 21 | public String generateDisplayNameForMethod(Class testClass, Method testMethod) { 22 | var regularDisplayName = super.generateDisplayNameForMethod(testClass, testMethod); 23 | return MessageFormat.format("{0} [{1}]", regularDisplayName, TestContext.testNGVersion()); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | junit.jupiter.displayname.generator.default=org.junit.support.testng.engine.TestNGVersionAppendingDisplayNameGenerator 2 | -------------------------------------------------------------------------------- /src/test/resources/log4j2-test.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/AnonymousClassTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class AnonymousClassTestCase { 16 | 17 | @Test 18 | public void test() { 19 | new AnonymousClassTestCase() { 20 | @SuppressWarnings("unused") 21 | @Test 22 | public void method() { 23 | } 24 | }; 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/ClassLevelOnlyAnnotationTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | @Test 16 | public class ClassLevelOnlyAnnotationTestCase { 17 | 18 | public void test() { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/CustomAttributeTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.CustomAttribute; 14 | import org.testng.annotations.Test; 15 | 16 | public class CustomAttributeTestCase { 17 | 18 | @Test(attributes = @CustomAttribute(name = "foo", values = { "bar", "baz" })) 19 | public void test() { 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/DefaultVisibilityBaseTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | abstract class DefaultVisibilityBaseTestCase { 16 | 17 | @Test 18 | void baseMethod() { 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/DefaultVisibilityWithDeclaredMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | class DefaultVisibilityWithDeclaredMethodTestCase extends DefaultVisibilityBaseTestCase { 16 | 17 | @Test 18 | void declaredMethod() { 19 | } 20 | 21 | } 22 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/DefaultVisibilityWithoutDeclaredMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | class DefaultVisibilityWithoutDeclaredMethodTestCase extends DefaultVisibilityBaseTestCase { 14 | } 15 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/DryRunTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class DryRunTestCase { 16 | 17 | public static int INVOCATIONS; 18 | 19 | @Test 20 | public void test() { 21 | INVOCATIONS++; 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/ExpectedExceptionsTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class ExpectedExceptionsTestCase { 16 | 17 | @Test(expectedExceptions = RuntimeException.class) 18 | public void test() { 19 | throw new RuntimeException("expected"); 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/IgnoredTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Ignore; 14 | import org.testng.annotations.Test; 15 | 16 | public class IgnoredTestCase { 17 | 18 | @Test 19 | public void test() { 20 | } 21 | 22 | @Ignore 23 | @Test 24 | public void ignored() { 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/InheritedClassLevelOnlyAnnotationTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | public class InheritedClassLevelOnlyAnnotationTestCase extends ClassLevelOnlyAnnotationTestCase { 14 | } 15 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/InheritingSubClassTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | public class InheritingSubClassTestCase extends SimpleTestCase { 14 | } 15 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/JUnitTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.junit.Test; 14 | 15 | public class JUnitTestCase { 16 | 17 | @Test 18 | public void test() { 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/NestedTestClass.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class NestedTestClass { 16 | 17 | public static class A { 18 | @Test 19 | public void test() { 20 | } 21 | } 22 | 23 | public static class B { 24 | @Test 25 | public void test() { 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/ParallelExecutionTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class ParallelExecutionTestCase { 16 | 17 | @Test(threadPoolSize = 10, invocationCount = 10) 18 | public void test() throws InterruptedException { 19 | Thread.sleep(100); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/RetriedTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import static org.testng.Assert.fail; 14 | 15 | import example.dataproviders.DataProviders; 16 | 17 | import org.testng.IRetryAnalyzer; 18 | import org.testng.ITestResult; 19 | import org.testng.annotations.Test; 20 | 21 | @Test(retryAnalyzer = RetriedTestCase.MyRetryAnalyzer.class) 22 | public class RetriedTestCase { 23 | 24 | int runs; 25 | 26 | public void test() { 27 | if (runs++ == 0) { 28 | fail("retry"); 29 | } 30 | } 31 | 32 | @Test(retryAnalyzer = MyRetryAnalyzer.class, dataProvider = "ints", dataProviderClass = DataProviders.class) 33 | public void dataProviderTest(int value) { 34 | if (runs++ == 0) { 35 | fail("retry @ " + value); 36 | } 37 | } 38 | 39 | public static class MyRetryAnalyzer implements IRetryAnalyzer { 40 | @Override 41 | public boolean retry(ITestResult result) { 42 | return true; 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/SimpleTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import static org.testng.Assert.fail; 14 | 15 | import org.testng.SkipException; 16 | import org.testng.annotations.Test; 17 | 18 | @Test(groups = "foo") 19 | public class SimpleTestCase { 20 | 21 | @Test(groups = "bar", description = "a test that passes") 22 | public void successful() { 23 | } 24 | 25 | @Test 26 | public void aborted() { 27 | throw new SkipException("not today"); 28 | } 29 | 30 | @Test 31 | public void failing() { 32 | fail("boom"); 33 | } 34 | 35 | @Test(dependsOnMethods = "failing") 36 | public void skippedDueToFailingDependency() { 37 | } 38 | 39 | @Test(enabled = false) 40 | public void disabled() { 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/SuccessPercentageTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import static org.testng.Assert.fail; 14 | 15 | import org.testng.annotations.Test; 16 | 17 | public class SuccessPercentageTestCase { 18 | 19 | int testRuns; 20 | 21 | @Test(successPercentage = 75, invocationCount = 4) 22 | public void test() { 23 | testRuns++; 24 | if (testRuns < 3) { 25 | fail("boom"); 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/TimeoutTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class TimeoutTestCase { 16 | 17 | @Test(timeOut = 1) 18 | public void timeOut() throws Exception { 19 | Thread.sleep(1_000); 20 | } 21 | 22 | @Test(invocationTimeOut = 1) 23 | public void invocationTimeOut() throws Exception { 24 | Thread.sleep(1_000); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/basics/TwoMethodsTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.basics; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class TwoMethodsTestCase { 16 | 17 | @Test 18 | public void one() { 19 | } 20 | 21 | @Test 22 | public void two() { 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/AbortedBeforeClassConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.SkipException; 14 | import org.testng.annotations.BeforeClass; 15 | import org.testng.annotations.Test; 16 | 17 | public class AbortedBeforeClassConfigurationMethodTestCase { 18 | 19 | @BeforeClass 20 | public void beforeClass() { 21 | throw new SkipException("not today"); 22 | } 23 | 24 | @Test 25 | public void test() { 26 | // never called 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingAfterClassConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.AfterClass; 14 | import org.testng.annotations.Test; 15 | 16 | public class FailingAfterClassConfigurationMethodTestCase { 17 | 18 | @AfterClass 19 | public void afterClass() { 20 | throw new AssertionError("boom"); 21 | } 22 | 23 | @Test 24 | public void test() { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingAfterMethodConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.AfterMethod; 14 | import org.testng.annotations.Test; 15 | 16 | public class FailingAfterMethodConfigurationMethodTestCase { 17 | 18 | @AfterMethod 19 | public void afterMethod() { 20 | throw new AssertionError("boom"); 21 | } 22 | 23 | @Test 24 | public void test() { 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingAfterSuiteConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.AfterSuite; 14 | import org.testng.annotations.Test; 15 | 16 | public class FailingAfterSuiteConfigurationMethodTestCase { 17 | 18 | @AfterSuite 19 | public void afterSuite() { 20 | throw new AssertionError("boom"); 21 | } 22 | 23 | @Test 24 | public void test() { 25 | // never called 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingAfterTestConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.AfterTest; 14 | import org.testng.annotations.Test; 15 | 16 | public class FailingAfterTestConfigurationMethodTestCase { 17 | 18 | @AfterTest 19 | public void afterTest() { 20 | throw new AssertionError("boom"); 21 | } 22 | 23 | @Test 24 | public void test() { 25 | // never called 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingBeforeClassConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.BeforeClass; 14 | import org.testng.annotations.Test; 15 | 16 | public class FailingBeforeClassConfigurationMethodTestCase { 17 | 18 | @BeforeClass 19 | public void beforeClass() { 20 | throw new AssertionError("boom"); 21 | } 22 | 23 | @Test 24 | public void test() { 25 | // never called 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingBeforeMethodConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.BeforeMethod; 14 | import org.testng.annotations.Test; 15 | 16 | public class FailingBeforeMethodConfigurationMethodTestCase { 17 | 18 | int calls; 19 | 20 | @BeforeMethod 21 | public void beforeMethod() { 22 | calls++; 23 | if (calls > 1) { 24 | throw new AssertionError("boom"); 25 | } 26 | } 27 | 28 | @Test 29 | public void a() { 30 | // called 31 | } 32 | 33 | @Test(dependsOnMethods = "a") 34 | public void b() { 35 | // never called 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingBeforeSuiteConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.BeforeMethod; 14 | import org.testng.annotations.BeforeSuite; 15 | import org.testng.annotations.Test; 16 | 17 | public class FailingBeforeSuiteConfigurationMethodTestCase { 18 | 19 | @BeforeSuite 20 | public void beforeSuite() { 21 | throw new AssertionError("boom"); 22 | } 23 | 24 | @BeforeMethod 25 | public void beforeMethod() { 26 | // never called 27 | } 28 | 29 | @Test 30 | public void test() { 31 | // never called 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/FailingBeforeTestConfigurationMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import org.testng.annotations.BeforeMethod; 14 | import org.testng.annotations.BeforeTest; 15 | import org.testng.annotations.Test; 16 | 17 | public class FailingBeforeTestConfigurationMethodTestCase { 18 | 19 | @BeforeTest 20 | public void beforeTest() { 21 | throw new AssertionError("boom"); 22 | } 23 | 24 | @BeforeMethod 25 | public void beforeMethod() { 26 | // never called 27 | } 28 | 29 | @Test 30 | public void test() { 31 | // never called 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/methods/GroupsConfigurationMethodsTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.methods; 12 | 13 | import java.util.ArrayList; 14 | import java.util.List; 15 | 16 | import org.testng.annotations.AfterGroups; 17 | import org.testng.annotations.BeforeGroups; 18 | import org.testng.annotations.Test; 19 | 20 | public class GroupsConfigurationMethodsTestCase { 21 | 22 | public static List EVENTS = new ArrayList<>(); 23 | 24 | @BeforeGroups("group1") 25 | public void beforeGroup1() { 26 | EVENTS.add("beforeGroup1"); 27 | } 28 | 29 | @AfterGroups("group1") 30 | public void afterGroup1() { 31 | EVENTS.add("afterGroup1"); 32 | } 33 | 34 | @BeforeGroups("group2") 35 | public void beforeGroup2() { 36 | EVENTS.add("beforeGroup2"); 37 | } 38 | 39 | @AfterGroups("group2") 40 | public void afterGroup2() { 41 | EVENTS.add("afterGroup2"); 42 | } 43 | 44 | @Test(groups = "group1") 45 | public void testGroup1() { 46 | EVENTS.add("testGroup1"); 47 | } 48 | 49 | @Test(groups = { "group1", "group2" }) 50 | public void testGroup1And2() { 51 | EVENTS.add("testGroup1And2"); 52 | } 53 | 54 | @Test(groups = "group2") 55 | public void testGroup2() { 56 | EVENTS.add("testGroup2"); 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/DataProviderThreadCountTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import static java.util.concurrent.TimeUnit.SECONDS; 14 | import static org.testng.Assert.assertTrue; 15 | import static org.testng.xml.XmlSuite.DEFAULT_DATA_PROVIDER_THREAD_COUNT; 16 | 17 | import java.util.Iterator; 18 | import java.util.concurrent.CountDownLatch; 19 | import java.util.stream.IntStream; 20 | 21 | import org.testng.annotations.DataProvider; 22 | import org.testng.annotations.Test; 23 | 24 | public class DataProviderThreadCountTestCase { 25 | 26 | public static final int NUM_INVOCATIONS = DEFAULT_DATA_PROVIDER_THREAD_COUNT + 1; 27 | 28 | final CountDownLatch latch = new CountDownLatch(NUM_INVOCATIONS); 29 | 30 | @DataProvider(name = "numbers", parallel = true) 31 | public static Iterator numbers() { 32 | return IntStream.range(0, NUM_INVOCATIONS) // 33 | .mapToObj(i -> new Object[] { i }) // 34 | .iterator(); 35 | } 36 | 37 | @Test(dataProvider = "numbers") 38 | public void test(Integer number) throws Exception { 39 | System.out.println(Thread.currentThread().getName() + ": " + number); 40 | latch.countDown(); 41 | assertTrue(latch.await(1, SECONDS)); 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/InvocationTrackingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import org.testng.IClassListener; 14 | import org.testng.ITestClass; 15 | 16 | public class InvocationTrackingListener implements IClassListener { 17 | 18 | public static boolean invoked; 19 | 20 | @Override 21 | public void onBeforeClass(ITestClass testClass) { 22 | invoked = true; 23 | } 24 | 25 | @Override 26 | public void onAfterClass(ITestClass testClass) { 27 | // do nothing 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/ParallelMethodsTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import static java.util.concurrent.TimeUnit.SECONDS; 14 | import static org.testng.Assert.assertTrue; 15 | 16 | import java.util.concurrent.CountDownLatch; 17 | 18 | import org.testng.annotations.Test; 19 | 20 | public class ParallelMethodsTestCase { 21 | 22 | final CountDownLatch latch = new CountDownLatch(2); 23 | 24 | @Test 25 | public void a() throws Exception { 26 | countDownAndAwait(); 27 | } 28 | 29 | @Test 30 | public void b() throws Exception { 31 | countDownAndAwait(); 32 | } 33 | 34 | private void countDownAndAwait() throws InterruptedException { 35 | latch.countDown(); 36 | assertTrue(latch.await(1, SECONDS)); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/PreserveOrderTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import static org.testng.Assert.assertTrue; 14 | 15 | import org.testng.ITestContext; 16 | import org.testng.annotations.Test; 17 | 18 | public class PreserveOrderTestCase { 19 | 20 | @Test 21 | public void test(ITestContext context) { 22 | assertTrue(context.getSuite().getXmlSuite().getPreserveOrder()); 23 | assertTrue(context.getCurrentXmlTest().getPreserveOrder()); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/ReturnValuesTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class ReturnValuesTestCase { 16 | 17 | @Test 18 | public String test() { 19 | return "some bogus return value"; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/SystemPropertyProvidingListener.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import org.testng.IClassListener; 14 | import org.testng.ITestClass; 15 | 16 | public class SystemPropertyProvidingListener implements IClassListener { 17 | 18 | public static final String SYSTEM_PROPERTY_KEY = "test.class"; 19 | 20 | @Override 21 | public void onBeforeClass(ITestClass testClass) { 22 | System.setProperty(SYSTEM_PROPERTY_KEY, testClass.getName()); 23 | } 24 | 25 | @Override 26 | public void onAfterClass(ITestClass testClass) { 27 | System.clearProperty(SYSTEM_PROPERTY_KEY); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/configuration/parameters/SystemPropertyReadingTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.configuration.parameters; 12 | 13 | import static org.testng.Assert.assertEquals; 14 | 15 | import org.testng.annotations.Test; 16 | 17 | public class SystemPropertyReadingTestCase { 18 | 19 | @Test 20 | public void test() { 21 | assertEquals(System.getProperty(SystemPropertyProvidingListener.SYSTEM_PROPERTY_KEY), 22 | SystemPropertyReadingTestCase.class.getName()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/DataProviderMethodEmptyListTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class DataProviderMethodEmptyListTestCase { 16 | 17 | @Test(dataProvider = "empty", dataProviderClass = DataProviders.class) 18 | public void test(int value) { 19 | // never called 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/DataProviderMethodErrorHandlingTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import org.testng.annotations.Test; 14 | 15 | public class DataProviderMethodErrorHandlingTestCase { 16 | 17 | @Test(dataProvider = "exception", dataProviderClass = DataProviders.class) 18 | public void test(int value) { 19 | // never called 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/DataProviderMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import static org.testng.Assert.fail; 14 | 15 | import org.testng.annotations.Test; 16 | 17 | public class DataProviderMethodTestCase { 18 | 19 | @Test(dataProvider = "strings", dataProviderClass = DataProviders.class) 20 | public void test(String value) { 21 | fail(value); 22 | } 23 | 24 | @Test(dataProvider = "ints", dataProviderClass = DataProviders.class) 25 | public void test(int value) { 26 | fail(String.valueOf(value)); 27 | } 28 | 29 | @Test 30 | public void test() { 31 | fail("parameterless"); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/DataProviders.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import static java.util.Collections.singletonList; 14 | 15 | import java.util.Iterator; 16 | import java.util.List; 17 | import java.util.stream.Stream; 18 | 19 | import org.testng.annotations.DataProvider; 20 | 21 | public class DataProviders { 22 | 23 | @DataProvider 24 | public static Iterator strings() { 25 | return Stream.of(singletonList("a"), singletonList("b")).map(List::toArray).iterator(); 26 | } 27 | 28 | @DataProvider 29 | public static Iterator ints() { 30 | return Stream.of(singletonList(1), singletonList(2)).map(List::toArray).iterator(); 31 | } 32 | 33 | @DataProvider 34 | public static Iterator exception() { 35 | throw new RuntimeException("exception in data provider"); 36 | } 37 | 38 | @DataProvider 39 | public static Object[][] empty() { 40 | return new Object[0][]; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/FactoryMethodTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import static org.testng.Assert.assertNotEquals; 14 | 15 | import org.testng.annotations.Factory; 16 | import org.testng.annotations.Test; 17 | 18 | public class FactoryMethodTestCase { 19 | 20 | private final String a; 21 | private final String b; 22 | 23 | @Factory 24 | public static Object[] factoryData() { 25 | return new Object[] { new FactoryMethodTestCase("a", "b"), new FactoryMethodTestCase("c", "d") }; 26 | } 27 | 28 | public FactoryMethodTestCase(String a, String b) { 29 | this.a = a; 30 | this.b = b; 31 | } 32 | 33 | @Test 34 | public void test() { 35 | assertNotEquals(a, b); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/FactoryWithDataProviderTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import static org.testng.Assert.fail; 14 | 15 | import org.testng.annotations.Factory; 16 | import org.testng.annotations.Test; 17 | 18 | public class FactoryWithDataProviderTestCase { 19 | 20 | private final String param; 21 | 22 | @Factory(dataProvider = "strings", dataProviderClass = DataProviders.class) 23 | public FactoryWithDataProviderTestCase(String param) { 24 | this.param = param; 25 | } 26 | 27 | @Test 28 | public void a() { 29 | fail(param); 30 | } 31 | 32 | @Test 33 | public void b() { 34 | fail(param); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/testFixtures/java/example/dataproviders/ParallelDataProviderTestCase.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021-2025 the original author or authors. 3 | * 4 | * All rights reserved. This program and the accompanying materials are 5 | * made available under the terms of the Eclipse Public License v2.0 which 6 | * accompanies this distribution and is available at 7 | * 8 | * https://www.eclipse.org/legal/epl-v20.html 9 | */ 10 | 11 | package example.dataproviders; 12 | 13 | import java.util.Arrays; 14 | import java.util.Collection; 15 | import java.util.Iterator; 16 | import java.util.stream.IntStream; 17 | 18 | import org.testng.annotations.DataProvider; 19 | import org.testng.annotations.Test; 20 | 21 | public class ParallelDataProviderTestCase { 22 | 23 | @DataProvider(name = "numbers", parallel = true) 24 | public static Iterator numbers() { 25 | return IntStream.of(1, 3, 5, 7, 9, 11, 13, -5, -3, 15, Integer.MAX_VALUE) // 26 | .mapToObj(Arrays::asList) // 27 | .map(Collection::toArray) // 28 | .iterator(); 29 | } 30 | 31 | @Test(dataProvider = "numbers") 32 | public void test(Integer number) { 33 | } 34 | 35 | } 36 | --------------------------------------------------------------------------------