├── .github └── workflows │ ├── ci-build.yml │ └── release.yml ├── .gitignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── build.gradle.kts ├── gradle.properties ├── gradle ├── checkstyle │ ├── checkstyle.xml │ └── header.txt └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── renovate.json ├── samples ├── use-all-java-module-plugins │ ├── app │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integtest │ │ │ ├── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── integtest │ │ │ │ │ └── AppIntegTest.java │ │ │ └── resources │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── integtest │ │ │ │ └── AppTestData.txt │ │ │ ├── main │ │ │ ├── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── App.java │ │ │ └── resources │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── AppData.txt │ │ │ └── test │ │ │ ├── java │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── AppWhiteboxTest.java │ │ │ └── resources │ │ │ └── org │ │ │ └── my │ │ │ └── app │ │ │ └── AppTestData.txt │ ├── build-logic │ │ ├── build.gradle.kts │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ ├── org.my.gradle.base.gradle.kts │ │ │ ├── org.my.gradle.java-module.gradle.kts │ │ │ └── org.my.gradle.platform.gradle.kts │ ├── build.out │ ├── build.sample.conf │ ├── lib │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integtest │ │ │ └── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── lib │ │ │ │ └── integtest │ │ │ │ └── LibIntegTest.java │ │ │ ├── main │ │ │ └── java │ │ │ │ └── module-info.java │ │ │ └── test │ │ │ └── java │ │ │ └── org │ │ │ └── my │ │ │ └── lib │ │ │ └── LibWhiteboxTest.java │ ├── platform │ │ └── build.gradle.kts │ └── settings.gradle.kts ├── use-only-java-module-testing-plugin │ ├── app │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integtest │ │ │ ├── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── integtest │ │ │ │ │ └── AppIntegTest.java │ │ │ └── resources │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── integtest │ │ │ │ └── AppTestData.txt │ │ │ ├── main │ │ │ ├── java │ │ │ │ ├── module-info.java │ │ │ │ └── org │ │ │ │ │ └── my │ │ │ │ │ └── app │ │ │ │ │ └── App.java │ │ │ └── resources │ │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── AppData.txt │ │ │ └── test │ │ │ ├── java │ │ │ └── org │ │ │ │ └── my │ │ │ │ └── app │ │ │ │ └── AppWhiteboxTest.java │ │ │ └── resources │ │ │ └── org │ │ │ └── my │ │ │ └── app │ │ │ └── AppTestData.txt │ ├── build-logic │ │ ├── build.gradle.kts │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── main │ │ │ └── kotlin │ │ │ ├── org.my.gradle.base.gradle.kts │ │ │ ├── org.my.gradle.java-module.gradle.kts │ │ │ └── org.my.gradle.platform.gradle.kts │ ├── build.out │ ├── build.sample.conf │ ├── lib │ │ ├── build.gradle.kts │ │ └── src │ │ │ ├── integtest │ │ │ └── java │ │ │ │ └── module-info.java │ │ │ └── main │ │ │ └── java │ │ │ └── module-info.java │ ├── platform │ │ └── build.gradle.kts │ └── settings.gradle.kts └── use-with-test-fixtures │ ├── build-logic │ ├── build.gradle.kts │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── org.my.gradle.base.gradle.kts │ │ └── org.my.gradle.java-module.gradle.kts │ ├── build.out │ ├── build.sample.conf │ ├── lib │ ├── build.gradle.kts │ └── src │ │ ├── integtest │ │ └── java │ │ │ ├── module-info.java │ │ │ └── org │ │ │ └── my │ │ │ └── lib │ │ │ └── integtest │ │ │ └── LibBlackboxTest.java │ │ ├── main │ │ └── java │ │ │ ├── module-info.java │ │ │ └── org │ │ │ └── my │ │ │ └── lib │ │ │ └── Lib.java │ │ ├── test │ │ └── java │ │ │ └── org │ │ │ └── my │ │ │ └── lib │ │ │ └── LibWhiteboxTest.java │ │ └── testFixtures │ │ └── java │ │ ├── module-info.java │ │ └── org │ │ └── my │ │ └── lib │ │ └── test │ │ └── fixtures │ │ └── LibFixture.java │ └── settings.gradle.kts ├── settings.gradle.kts └── src ├── main └── java │ └── org │ └── gradlex │ └── javamodule │ └── testing │ ├── JavaModuleTestingExtension.java │ ├── JavaModuleTestingPlugin.java │ ├── TaskLockService.java │ ├── WhiteboxJvmTestSuite.java │ └── internal │ ├── ModuleInfoParser.java │ ├── ModuleInfoRequiresParser.java │ ├── actions │ └── JavaCompileSetModulePathAction.java │ ├── bridges │ └── JavaModuleDependenciesBridge.java │ └── provider │ ├── WhiteboxTestCompileArgumentProvider.java │ └── WhiteboxTestRuntimeArgumentProvider.java └── test ├── groovy └── org │ └── gradlex │ └── javamodule │ └── testing │ ├── internal │ └── ModuleInfoParseTest.groovy │ └── test │ ├── ClasspathSuiteTest.groovy │ ├── CoreFunctionailtyTest.groovy │ ├── CustomizationTest.groovy │ ├── JavaModuleDependenciesBridgeTest.groovy │ └── fixture │ └── GradleBuild.groovy └── java └── org └── gradlex └── javamodule └── testing └── test └── samples ├── PluginBuildLocationSampleModifier.java └── SamplesTest.java /.github/workflows/ci-build.yml: -------------------------------------------------------------------------------- 1 | name: Build Plugin 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | pull_request: 7 | branches: [ main ] 8 | 9 | jobs: 10 | gradle-build: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - name: git clone 14 | uses: actions/checkout@v4 15 | - name: Set up JDK 16 | uses: actions/setup-java@v4 17 | with: 18 | distribution: temurin 19 | java-version: 17 20 | - name: Set up Gradle 21 | uses: gradle/actions/setup-gradle@v4 22 | - run: "./gradlew build" -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Publish Release 2 | on: 3 | push: 4 | tags: 5 | - 'v*' 6 | jobs: 7 | release-build: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: git clone 11 | uses: actions/checkout@v4 12 | - name: Set up JDK 13 | uses: actions/setup-java@v4 14 | with: 15 | distribution: temurin 16 | java-version: 17 17 | - name: Set up Gradle 18 | uses: gradle/actions/setup-gradle@v4 19 | - run: "./gradlew :publishPlugin --no-configuration-cache" 20 | env: 21 | SIGNING_KEY: ${{ secrets.SIGNING_KEY }} 22 | SIGNING_PASSPHRASE: ${{ secrets.SIGNING_PASSPHRASE }} 23 | GRADLE_PUBLISH_KEY: ${{ secrets.GRADLE_PUBLISH_KEY }} 24 | GRADLE_PUBLISH_SECRET: ${{ secrets.GRADLE_PUBLISH_SECRET }} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .gradle 2 | .idea 3 | build 4 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Java Module Testing Gradle Plugin - Changelog 2 | 3 | ## Version 1.7 4 | * [#100](https://github.com/gradlex-org/java-module-testing/issues/100) Use `exportsTo` and `opensTo` statements from Module Info DSL 5 | 6 | ## Version 1.6.1 7 | * Use all test src folders in compilation (if there are more than one) 8 | * Only add class folders that exist to the '--patch-module' argument 9 | * Only perform 'extendsFrom(compileOnly)' if not already done 10 | 11 | ## Version 1.6 12 | * [#85](https://github.com/gradlex-org/java-module-testing/issues/85) Further improve interoperability with `java-module-dependencies` plugin 13 | 14 | ## Version 1.5 15 | * [#47](https://github.com/gradlex-org/java-module-testing/issues/47) Add support for Classpath Test Suites 16 | * [#51](https://github.com/gradlex-org/java-module-testing/issues/51) testCompileOnly extends compileOnly for Whitebox Test Suites 17 | * [#67](https://github.com/gradlex-org/java-module-testing/issues/67) Whitebox Test Suites: add `exportsTo` configuration option 18 | 19 | ## Version 1.4 20 | * [#2](https://github.com/gradlex-org/java-module-testing/issues/2) New approach to split Module Path and Classpath for whitebox testing 21 | * [#40](https://github.com/gradlex-org/java-module-testing/issues/40) `useJUnitJupiter("")` without version does not fail for empty test directories 22 | * [#39](https://github.com/gradlex-org/java-module-testing/issues/39) Add `TaskLockService` for conveniently running test tasks in isolation 23 | 24 | ## Version 1.3.1 25 | * Improve interoperability with `java-module-dependencies` plugin 26 | 27 | ## Version 1.3 28 | * [#18](https://github.com/gradlex-org/java-module-testing/issues/18) Allow whitebox tests to define requires in `java9/module-info.java` (Thanks [brianoliver](https://github.com/brianoliver) for suggesting!) 29 | 30 | ## Version 1.2.2 31 | * No duplicated '--add-opens' runtime args 32 | 33 | ## Version 1.2.1 34 | * Fix 'module-info.java' parsing bug 35 | 36 | ## Version 1.2 37 | * [#8](https://github.com/gradlex-org/java-module-testing/issues/8) Automatically configure test suites based on the existence of a `module-info.java` file 38 | * [#5](https://github.com/gradlex-org/java-module-testing/issues/5) Improve module-info parsing 39 | 40 | ## Version 1.1 41 | * Integrate with https://github.com/gradlex-org/java-module-dependencies/issues/19 42 | 43 | ## Version 1.0 44 | * Moved project to [GradleX](https://gradlex.org) - new plugin ID: `org.gradlex.java-module-testing` 45 | 46 | ## Versions 0.1 47 | * Initial features added 48 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | 179 | APPENDIX: How to apply the Apache License to your work. 180 | 181 | To apply the Apache License to your work, attach the following 182 | boilerplate notice, with the fields enclosed by brackets "[]" 183 | replaced with your own identifying information. (Don't include 184 | the brackets!) The text should be enclosed in the appropriate 185 | comment syntax for the file format. We also recommend that a 186 | file or class name and description of purpose be included on the 187 | same "printed page" as the copyright notice for easier 188 | identification within third-party archives. 189 | 190 | Copyright 2019 Louis Jacomet 191 | 192 | Licensed under the Apache License, Version 2.0 (the "License"); 193 | you may not use this file except in compliance with the License. 194 | You may obtain a copy of the License at 195 | 196 | http://www.apache.org/licenses/LICENSE-2.0 197 | 198 | Unless required by applicable law or agreed to in writing, software 199 | distributed under the License is distributed on an "AS IS" BASIS, 200 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 201 | See the License for the specific language governing permissions and 202 | limitations under the License. 203 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Java Module Testing Gradle plugin 2 | 3 | [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fgradlex-org%2Fjava-module-testing%2Fbadge%3Fref%3Dmain&style=flat)](https://actions-badge.atrox.dev/gradlex-org/java-module-testing/goto?ref=main) 4 | [![Gradle Plugin Portal](https://img.shields.io/maven-metadata/v?label=Plugin%20Portal&metadataUrl=https%3A%2F%2Fplugins.gradle.org%2Fm2%2Forg%2Fgradlex%2Fjava-module-testing%2Forg.gradlex.java-module-testing.gradle.plugin%2Fmaven-metadata.xml)](https://plugins.gradle.org/plugin/org.gradlex.java-module-testing) 5 | 6 | A Gradle 7.4+ plugin to turn a [JVM Test Suite](https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html#sec:jvm_test_suite_configuration) 7 | into a **Blackbox** or **Whitebox** Test Suite for Java Modules. 8 | 9 | This plugin is maintained by me, [Jendrik Johannes](https://github.com/jjohannes). 10 | I offer consulting and training for Gradle and/or the Java Module System - please [reach out](mailto:jendrik.johannes@gmail.com) if you are interested. 11 | There is also my [YouTube channel](https://www.youtube.com/playlist?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) on Gradle topics. 12 | 13 | If you have a suggestion or a question, please [open an issue](https://github.com/gradlex-org/java-module-testing/issues/new). 14 | 15 | # Java Modules with Gradle 16 | 17 | If you plan to build Java Modules with Gradle, you should consider using these plugins on top of Gradle core: 18 | 19 | - [`id("org.gradlex.java-module-dependencies")`](https://github.com/gradlex-org/java-module-dependencies) 20 | Avoid duplicated dependency definitions and get your Module Path under control 21 | - [`id("org.gradlex.java-module-testing")`](https://github.com/gradlex-org/java-module-testing) 22 | Proper test setup for Java Modules 23 | - [`id("org.gradlex.extra-java-module-info")`](https://github.com/gradlex-org/extra-java-module-info) 24 | Only if your (existing) project cannot avoid using non-module legacy Jars 25 | 26 | [Here is a sample](https://github.com/gradlex-org/java-module-testing/tree/main/samples/use-all-java-module-plugins) 27 | that shows all plugins in combination. 28 | 29 | [In episodes 31, 32, 33 of Understanding Gradle](https://github.com/jjohannes/understanding-gradle) I explain what these plugins do and why they are needed. 30 | [](https://www.youtube.com/watch?v=X9u1taDwLSA&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 31 | [](https://www.youtube.com/watch?v=T9U0BOlVc-c&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 32 | [](https://www.youtube.com/watch?v=6rFEDcP8Noc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 33 | 34 | [Full Java Module System Project Setup](https://github.com/jjohannes/gradle-project-setup-howto/tree/java_module_system) is a full-fledged Java Module System project setup using these plugins. 35 | [](https://www.youtube.com/watch?v=uRieSnovlVc&list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE) 36 | 37 | # How to use? 38 | 39 | For a quick start, you can find some samples here: 40 | * [samples/use-all-java-module-plugins](samples/use-all-java-module-plugins) 41 | * [samples/use-only-java-module-testing-plugin](samples/use-only-java-module-testing-plugin) 42 | * [samples/use-with-test-fixtures](samples/use-with-test-fixtures) 43 | 44 | For general information about how to structure Gradle builds and apply community plugins like this one to all subprojects 45 | you can check out my [Understanding Gradle video series](https://www.youtube.com/playlist?list=PLWQK2ZdV4Yl2k2OmC_gsjDpdIBTN0qqkE). 46 | 47 | ## Plugin dependency 48 | 49 | Add this to the build file of your convention plugin's build 50 | (e.g. `build-logic/build.gradle(.kts)` or `buildSrc/build.gradle(.kts)`). 51 | 52 | ``` 53 | dependencies { 54 | implementation("org.gradlex:java-module-testing:1.7") 55 | } 56 | ``` 57 | 58 | ## Apply the plugin 59 | 60 | In your convention plugin, apply the plugin. 61 | 62 | ``` 63 | plugins { 64 | id("org.gradlex.java-module-testing") 65 | } 66 | ``` 67 | 68 | ## Blackbox Test Suites 69 | 70 | The plugin automatically turns [JVM Test Suites](https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html) into _Blackbox Test Suites_ if the `src//module-info.java` file exists. 71 | A blackbox test suite is a separate module itself. 72 | See documentation on [JVM Test Suites](https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html#sec:jvm_test_suite_configuration) for more details on creating and configuring test suites. 73 | 74 | ## Whitebox Test Suites 75 | 76 | The plugin automatically turns [JVM Test Suites](https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html) **without** `module-info.java` file into _Whitebox Test Suites_. 77 | Whitebox Test Suites might require additional configuration, which can be done like this: 78 | 79 | ``` 80 | javaModuleTesting.whitebox(testing.suites["test"]) { 81 | requires.add("org.junit.jupiter.api") 82 | // opensTo.add("org.junit.platform.commons") <-- opensTo 'org.junit.platform.commons' is done by default 83 | // exportsTo.add("...") 84 | } 85 | ``` 86 | 87 | See documentation on [JVM Test Suites](https://docs.gradle.org/current/userguide/jvm_test_suite_plugin.html#sec:jvm_test_suite_configuration) for more details on creating and configuring test suites. 88 | 89 | Alternatively, you can put the `requires` into a `module-info.java` file using the same notation that you would use for blackbox tests. 90 | For this, you need to create the file in `/java9/module-info.java`. For example: 91 | 92 | ``` 93 | src 94 | ├── main 95 | │ └── java 96 | │ ├── module-info.java 97 | │ └── ... 98 | └── test 99 | ├── java 100 | │ └── ... 101 | └── java9 102 | └── module-info.java 103 | | module org.example.app.test { 104 | | requires org.example.app; // 'main' module into which the tests are patched 105 | | requires org.junit.jupiter.api; 106 | | } 107 | ``` 108 | 109 | A whitebox _test source set_ does **not** have a `module-info.java`. 110 | Instead, the _main_ and _test_ classes will be patched together and the test will run in the _main_ module which now includes the test classes as well. 111 | Additional `requires` for the test are defined as shown above. 112 | If the _sources under test_ are located in a different source set (not `main`), this can be configured via `sourcesUnderTest.set("source-set-name")`. 113 | 114 | ## Classpath Test Suites 115 | 116 | An alternative variant of "whitebox" testing is to run testing on the classpath and ignore **all** module information. 117 | This is what [Gradle does without this plugin](https://docs.gradle.org/current/userguide/java_testing.html#whitebox_unit_test_execution_on_the_classpath). 118 | By default, this plugin replaces this with the [Whitebox Test Suite setup](#whitebox-test-suites), which should be the preferred testing approach. 119 | If you still need to use the classpath-based setup for a Test Suite, you may configure it as follows: 120 | 121 | ``` 122 | javaModuleTesting.classpath(testing.suites["test"]) 123 | ``` 124 | 125 | A reason to do testing like this is if you need to utilise testing libraries (e.g. for mocking) that do not work with the Module System at all. 126 | 127 | # What does the plugin do? 128 | 129 | The plugin rewires the inputs of test compilation (`:compileTestJava`) and test runtime (`:test`). 130 | This includes configuring the Module Path and adding patch parameters in the case of whitebox testing. 131 | 132 | ## Blackbox Test 133 | 134 | Changes for test runtime (`:test`): 135 | - Normally, the test classes are loaded from a `classes` folder 136 | - Now, the test classes are packaged into a module `jar` together with the test resources. Otherwise, test resources would not be visible to the test module at runtime. 137 | 138 | ## Whitebox Test 139 | 140 | Changes for test compilation (`:compileTestJava`): 141 | - Normally, Gradle would not use the Module Path, as there is no `module-info.java` in the source set 142 | - Now, a Module Path is computed for the compilation. 143 | Using `--patch-module`, the test classes are compiled as an addition to the main module. 144 | 145 | Changes for test runtime (`:test`): 146 | - Normally, Gradle would not run its test runner as Module, as there is no `module-info.class` as part of the compiled tests. 147 | - Now, the main and test classes are both used as locations for test discovery. 148 | By this, Gradle will find the `module-info.class` of the main module for the tests. 149 | Using `--patch-module`, _main classes_, _main resources_, _test classes_, and _test resources_ folders are all merged to be treated as one module during test runtime. 150 | 151 | # Disclaimer 152 | 153 | Gradle and the Gradle logo are trademarks of Gradle, Inc. 154 | The GradleX project is not endorsed by, affiliated with, or associated with Gradle or Gradle, Inc. in any way. 155 | -------------------------------------------------------------------------------- /build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("groovy") 3 | id("org.gradlex.internal.plugin-publish-conventions") version "0.6" 4 | } 5 | 6 | group = "org.gradlex" 7 | version = "1.7" 8 | 9 | java { 10 | sourceCompatibility = JavaVersion.VERSION_1_8 11 | targetCompatibility = JavaVersion.VERSION_1_8 12 | } 13 | 14 | dependencies { 15 | testImplementation("org.spockframework:spock-core:2.1-groovy-3.0") 16 | testImplementation("org.gradle.exemplar:samples-check:1.0.3") 17 | testRuntimeOnly("org.junit.vintage:junit-vintage-engine") 18 | } 19 | 20 | pluginPublishConventions { 21 | id("${project.group}.${project.name}") 22 | implementationClass("org.gradlex.javamodule.testing.JavaModuleTestingPlugin") 23 | displayName("Java Module Testing Gradle Plugin") 24 | description("A plugin to test Java Modules (whitebox and blackbox) without the hassle.") 25 | tags("gradlex", "java", "modularity", "jigsaw", "jpms", "testing") 26 | gitHub("https://github.com/gradlex-org/java-module-testing") 27 | developer { 28 | id.set("jjohannes") 29 | name.set("Jendrik Johannes") 30 | email.set("jendrik@gradlex.org") 31 | } 32 | } 33 | 34 | testing.suites.named("test") { 35 | useJUnitJupiter() 36 | listOf("7.4", "7.6.5", "8.0.2").forEach { gradleVersionUnderTest -> 37 | targets.register("test${gradleVersionUnderTest}") { 38 | testTask { 39 | group = LifecycleBasePlugin.VERIFICATION_GROUP 40 | description = "Runs tests against Gradle $gradleVersionUnderTest" 41 | systemProperty("gradleVersionUnderTest", gradleVersionUnderTest) 42 | exclude("**/*SamplesTest.class") // Not yet cross-version ready 43 | } 44 | } 45 | } 46 | targets.all { 47 | testTask { 48 | maxParallelForks = 4 49 | inputs.dir(layout.projectDirectory.dir("samples")) 50 | inputs.dir("samples") 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | org.gradle.caching=true 2 | org.gradle.configuration-cache=true -------------------------------------------------------------------------------- /gradle/checkstyle/checkstyle.xml: -------------------------------------------------------------------------------- 1 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /gradle/checkstyle/header.txt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gradlex-org/java-module-testing/efb3fa50c5d4897b6f00b1074cf3281b3e2340b2/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 | -------------------------------------------------------------------------------- /renovate.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://docs.renovatebot.com/renovate-schema.json", 3 | "extends": [ 4 | "config:recommended" 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("application") 4 | } 5 | 6 | application { 7 | applicationDefaultJvmArgs = listOf("-ea") 8 | mainClass.set("org.my.app.App") 9 | mainModule.set("org.my.app") 10 | } 11 | 12 | dependencies { 13 | javaModuleDependencies { 14 | runtimeOnly(gav("org.slf4j.simple")) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/integtest/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.app.integtest { 2 | requires org.my.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/integtest/java/org/my/app/integtest/AppIntegTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app.integtest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.my.app.App; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | class AppIntegTest { 14 | 15 | @Test 16 | void appDoesNotExplode() throws IOException { 17 | assertTrue(App.doWork()); 18 | 19 | try (InputStream is = AppIntegTest.class.getResourceAsStream("AppTestData.txt")) { 20 | System.out.println(new BufferedReader(new InputStreamReader(is)).readLine()); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/integtest/resources/org/my/app/integtest/AppTestData.txt: -------------------------------------------------------------------------------- 1 | This is balackbox test data -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.app { 2 | requires org.slf4j; 3 | requires org.my.lib; 4 | requires org.apache.xmlbeans; 5 | 6 | exports org.my.app; 7 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/main/java/org/my/app/App.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.spi.LoggerFactoryBinder; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | 11 | public class App { 12 | public static void main(String[] args) throws IOException { 13 | doWork(); 14 | } 15 | 16 | public static boolean doWork() throws IOException { 17 | ObjectMapper om = new ObjectMapper(); 18 | if (!om.canSerialize(LoggerFactoryBinder.class)) { 19 | throw new RuntimeException("Boom!"); 20 | } 21 | System.out.println(App.class.getModule().getName()); 22 | 23 | printData(); 24 | 25 | return true; 26 | } 27 | 28 | protected static void printData() throws IOException { 29 | try (InputStream is = App.class.getResourceAsStream("AppData.txt")) { 30 | System.out.println(new BufferedReader(new InputStreamReader(is)).readLine()); 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/main/resources/org/my/app/AppData.txt: -------------------------------------------------------------------------------- 1 | This is data -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/test/java/org/my/app/AppWhiteboxTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | class AppWhiteboxTest { 13 | 14 | @Test 15 | void printDataTest() throws IOException { 16 | App.printData(); 17 | assertEquals("org.my.app", AppWhiteboxTest.class.getModule().getName()); 18 | 19 | try (InputStream is = AppWhiteboxTest.class.getResourceAsStream("AppTestData.txt")) { 20 | System.out.println(new BufferedReader(new InputStreamReader(is)).readLine()); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/app/src/test/resources/org/my/app/AppTestData.txt: -------------------------------------------------------------------------------- 1 | This is whitebox test data -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:extra-java-module-info:1.12") 7 | implementation("org.gradlex:java-module-dependencies:1.9") 8 | implementation("org.gradlex:java-module-testing:1.7") 9 | } 10 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build-logic/src/main/kotlin/org.my.gradle.base.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.gradlex.extra-java-module-info") 3 | id("org.gradlex.java-module-dependencies") 4 | } 5 | 6 | group = "org.my" 7 | 8 | extraJavaModuleInfo { 9 | automaticModule("org.apache.commons:commons-math3", "commons.math3") 10 | } 11 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build-logic/src/main/kotlin/org.my.gradle.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.my.gradle.base") 4 | id("org.gradlex.java-module-testing") 5 | } 6 | 7 | javaModuleTesting.whitebox( 8 | testing.suites.getByName("test") { 9 | useJUnitJupiter("") 10 | targets.all { 11 | testTask { jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") } 12 | } 13 | } 14 | ) { 15 | requires.add("org.junit.jupiter.api") 16 | } 17 | 18 | testing.suites.create("integtest") { 19 | useJUnitJupiter("") 20 | dependencies { 21 | implementation(project.dependencies.platform(project(":platform"))) 22 | } 23 | targets.all { 24 | testTask { jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") } 25 | } 26 | tasks.check { dependsOn(targets) } 27 | } 28 | 29 | dependencies { 30 | implementation(platform(project(":platform"))) 31 | } 32 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build-logic/src/main/kotlin/org.my.gradle.platform.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-platform") 3 | id("org.my.gradle.base") 4 | } 5 | 6 | javaPlatform.allowDependencies() // Use existing Platforms/BOMs 7 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build.out: -------------------------------------------------------------------------------- 1 | > Task :app:run 2 | org.my.app 3 | This is data 4 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | args: run 3 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("java-library") 4 | } 5 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/lib/src/integtest/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.integtest { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/lib/src/integtest/java/org/my/lib/integtest/LibIntegTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.integtest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class LibIntegTest { 6 | 7 | @Test 8 | void testLib() { 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | // Patched to be a module 5 | requires commons.math3; 6 | 7 | // JDK modules 8 | requires java.logging; 9 | requires jdk.charsets; 10 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/lib/src/test/java/org/my/lib/LibWhiteboxTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | class LibWhiteboxTest { 6 | 7 | @Test 8 | void testLib() { 9 | } 10 | 11 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/platform/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.platform") 3 | } 4 | 5 | 6 | dependencies { 7 | api(platform("com.fasterxml.jackson:jackson-bom:2.19.0")) 8 | api(platform("org.junit:junit-bom:5.13.1")) 9 | } 10 | 11 | dependencies.constraints { 12 | javaModuleDependencies { 13 | api(gav("commons.math3", "3.6.1")) 14 | api(gav("org.apache.xmlbeans", "5.0.1")) 15 | api(gav("org.slf4j", "1.7.28")) 16 | api(gav("org.slf4j.simple", "1.7.28")) 17 | } 18 | } -------------------------------------------------------------------------------- /samples/use-all-java-module-plugins/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | include("platform") 10 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("application") 4 | } 5 | 6 | application { 7 | applicationDefaultJvmArgs = listOf("-ea") 8 | mainClass.set("org.my.app.App") 9 | mainModule.set("org.my.app") 10 | } 11 | 12 | dependencies { 13 | implementation(project(":lib")) 14 | implementation("org.apache.xmlbeans:xmlbeans") 15 | implementation("org.slf4j:slf4j-api") 16 | 17 | integtestImplementation(project(path)) 18 | 19 | runtimeOnly("org.slf4j:slf4j-simple") 20 | } 21 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/integtest/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.app.integtest { 2 | requires org.my.app; 3 | requires org.junit.jupiter.api; 4 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/integtest/java/org/my/app/integtest/AppIntegTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app.integtest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.my.app.App; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | 11 | import static org.junit.jupiter.api.Assertions.assertTrue; 12 | 13 | public class AppIntegTest { 14 | 15 | @Test 16 | public void appDoesNotExplode() throws IOException { 17 | assertTrue(App.doWork()); 18 | 19 | try (InputStream is = AppIntegTest.class.getResourceAsStream("AppTestData.txt")) { 20 | System.out.println(new BufferedReader(new InputStreamReader(is)).readLine()); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/integtest/resources/org/my/app/integtest/AppTestData.txt: -------------------------------------------------------------------------------- 1 | This is balackbox test data -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.app { 2 | requires org.slf4j; 3 | requires org.my.lib; 4 | requires org.apache.xmlbeans; 5 | 6 | exports org.my.app; 7 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/main/java/org/my/app/App.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import com.fasterxml.jackson.databind.ObjectMapper; 4 | import org.slf4j.spi.LoggerFactoryBinder; 5 | 6 | import java.io.BufferedReader; 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | 11 | public class App { 12 | public static void main(String[] args) throws IOException { 13 | doWork(); 14 | } 15 | 16 | public static boolean doWork() throws IOException { 17 | ObjectMapper om = new ObjectMapper(); 18 | if (!om.canSerialize(LoggerFactoryBinder.class)) { 19 | throw new RuntimeException("Boom!"); 20 | } 21 | System.out.println(App.class.getModule().getName()); 22 | printData(); 23 | 24 | return true; 25 | } 26 | 27 | protected static void printData() throws IOException { 28 | try (InputStream is = App.class.getResourceAsStream("AppData.txt")) { 29 | System.out.println(new BufferedReader(new InputStreamReader(is)).readLine()); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/main/resources/org/my/app/AppData.txt: -------------------------------------------------------------------------------- 1 | This is data -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/test/java/org/my/app/AppWhiteboxTest.java: -------------------------------------------------------------------------------- 1 | package org.my.app; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.io.BufferedReader; 6 | import java.io.IOException; 7 | import java.io.InputStream; 8 | import java.io.InputStreamReader; 9 | 10 | import static org.junit.jupiter.api.Assertions.assertEquals; 11 | 12 | public class AppWhiteboxTest { 13 | 14 | @Test 15 | public void printDataTest() throws IOException { 16 | App.printData(); 17 | assertEquals("org.my.app", AppWhiteboxTest.class.getModule().getName()); 18 | 19 | try (InputStream is = AppWhiteboxTest.class.getResourceAsStream("AppTestData.txt")) { 20 | System.out.println(new BufferedReader(new InputStreamReader(is)).readLine()); 21 | } 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/app/src/test/resources/org/my/app/AppTestData.txt: -------------------------------------------------------------------------------- 1 | This is whitebox test data -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:java-module-testing:1.7") 7 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build-logic/src/main/kotlin/org.my.gradle.base.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "org.my" 2 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build-logic/src/main/kotlin/org.my.gradle.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("org.my.gradle.base") 4 | id("org.gradlex.java-module-testing") 5 | } 6 | 7 | javaModuleTesting.whitebox( 8 | testing.suites.getByName("test") { 9 | useJUnitJupiter("") 10 | targets.all { 11 | testTask { jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") } 12 | } 13 | } 14 | ) { 15 | requires.add("org.junit.jupiter.api") 16 | } 17 | 18 | testing.suites.create("integtest") { 19 | useJUnitJupiter("") 20 | dependencies { 21 | implementation(project.dependencies.platform(project(":platform"))) 22 | } 23 | targets.all { 24 | testTask { jvmArgs("-Dorg.slf4j.simpleLogger.defaultLogLevel=error") } 25 | } 26 | tasks.check { dependsOn(targets) } 27 | } 28 | 29 | 30 | dependencies { 31 | implementation(platform(project(":platform"))) 32 | } 33 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build-logic/src/main/kotlin/org.my.gradle.platform.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java-platform") 3 | id("org.my.gradle.base") 4 | } 5 | 6 | javaPlatform.allowDependencies() // Use existing Platforms/BOMs 7 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build.out: -------------------------------------------------------------------------------- 1 | > Task :app:run 2 | org.my.app 3 | This is data 4 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | args: run 3 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("java-library") 4 | } 5 | 6 | dependencies { 7 | api("com.fasterxml.jackson.core:jackson-databind") 8 | 9 | integtestImplementation(project(path)) 10 | } 11 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/lib/src/integtest/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.integtest { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | } 5 | -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires transitive com.fasterxml.jackson.databind; 3 | 4 | // JDK modules 5 | requires java.logging; 6 | requires jdk.charsets; 7 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/platform/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.platform") 3 | } 4 | 5 | 6 | dependencies { 7 | api(platform("com.fasterxml.jackson:jackson-bom:2.19.0")) 8 | api(platform("org.junit:junit-bom:5.13.1")) 9 | } 10 | 11 | dependencies.constraints { 12 | api("org.apache.xmlbeans:xmlbeans:5.3.0") 13 | api("org.slf4j:slf4j-api:2.0.17") 14 | api("org.slf4j:slf4j-simple:2.0.17") 15 | } -------------------------------------------------------------------------------- /samples/use-only-java-module-testing-plugin/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("app", "lib") 9 | include("platform") 10 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/build-logic/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `kotlin-dsl` 3 | } 4 | 5 | dependencies { 6 | implementation("org.gradlex:java-module-testing:1.7") 7 | implementation("org.gradlex:java-module-dependencies:1.9") 8 | } -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/build-logic/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | dependencyResolutionManagement { 2 | repositories.gradlePluginPortal() 3 | } 4 | 5 | // This is for testing against the latest version of the plugin, remove if you copied this for a real project 6 | includeBuild(extra.properties["pluginLocation"] ?: rootDir.parentFile.parentFile.parent) 7 | 8 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/build-logic/src/main/kotlin/org.my.gradle.base.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "org.my" 2 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/build-logic/src/main/kotlin/org.my.gradle.java-module.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("java") 3 | id("java-test-fixtures") 4 | id("org.my.gradle.base") 5 | id("org.gradlex.java-module-dependencies") 6 | id("org.gradlex.java-module-testing") 7 | } 8 | 9 | javaModuleTesting.whitebox( 10 | testing.suites.getByName("test") { 11 | targets.all { testTask { testLogging.showStandardStreams = true } } 12 | } 13 | ) { 14 | requires.add("org.junit.jupiter.api") 15 | requires.add("org.my.lib.test.fixtures") 16 | } 17 | 18 | testing.suites.create("integtest") { 19 | targets.all { testTask { testLogging.showStandardStreams = true } } 20 | tasks.check { dependsOn(targets) } 21 | } 22 | 23 | dependencies { 24 | implementation(platform("com.fasterxml.jackson:jackson-bom:2.19.0")) 25 | } 26 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/build.out: -------------------------------------------------------------------------------- 1 | > Task :lib:compileJava 2 | > Task :lib:processResources NO-SOURCE 3 | > Task :lib:classes 4 | > Task :lib:jar 5 | > Task :lib:compileTestFixturesJava 6 | > Task :lib:processTestFixturesResources NO-SOURCE 7 | > Task :lib:testFixturesClasses 8 | > Task :lib:testFixturesJar 9 | > Task :lib:compileTestJava 10 | > Task :lib:processTestResources NO-SOURCE 11 | > Task :lib:testClasses 12 | 13 | > Task :lib:test 14 | 15 | LibWhiteboxTest > testModule() STANDARD_OUT 16 | Unit Test Module: org.my.lib 17 | 18 | > Task :lib:assemble 19 | > Task :lib:compileIntegtestJava 20 | > Task :lib:processIntegtestResources NO-SOURCE 21 | > Task :lib:integtestClasses 22 | > Task :lib:integtestJar 23 | 24 | > Task :lib:integtest 25 | 26 | LibBlackboxTest > testModule() STANDARD_OUT 27 | Integration Test Module: org.my.lib.integtest 28 | 29 | > Task :lib:check 30 | > Task :lib:build 31 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/build.sample.conf: -------------------------------------------------------------------------------- 1 | executable: gradlew 2 | args: test 3 | expected-output-file: build.out -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.my.gradle.java-module") 3 | id("java-library") 4 | } 5 | 6 | dependencies { 7 | implementation("com.fasterxml.jackson.core:jackson-databind") 8 | 9 | integtestImplementation(testFixtures(project(path))) 10 | } 11 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/integtest/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.integtest { 2 | requires org.my.lib; 3 | requires org.junit.jupiter.api; 4 | requires org.my.lib.test.fixtures; 5 | } 6 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/integtest/java/org/my/lib/integtest/LibBlackboxTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.integtest; 2 | 3 | import org.junit.jupiter.api.Test; 4 | import org.my.lib.Lib; 5 | import org.my.lib.test.fixtures.LibFixture; 6 | 7 | public class LibBlackboxTest { 8 | 9 | @Test 10 | void testModule() { 11 | new LibFixture(); 12 | new Lib().use(); 13 | System.out.println("Integration Test Module: " + LibBlackboxTest.class.getModule().getName()); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/main/java/module-info.java: -------------------------------------------------------------------------------- 1 | module org.my.lib { 2 | requires com.fasterxml.jackson.databind; 3 | 4 | exports org.my.lib; 5 | } -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/main/java/org/my/lib/Lib.java: -------------------------------------------------------------------------------- 1 | package org.my.lib; 2 | 3 | public class Lib { 4 | 5 | void doStuff() { 6 | 7 | } 8 | 9 | public void use() { 10 | doStuff(); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/test/java/org/my/lib/LibWhiteboxTest.java: -------------------------------------------------------------------------------- 1 | package org.my.lib; 2 | 3 | import org.my.lib.test.fixtures.LibFixture; 4 | import org.junit.jupiter.api.Test; 5 | 6 | public class LibWhiteboxTest { 7 | 8 | @Test 9 | void testModule() { 10 | new LibFixture(); 11 | new Lib().doStuff(); 12 | System.out.println("Unit Test Module: " + LibWhiteboxTest.class.getModule().getName()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/testFixtures/java/module-info.java: -------------------------------------------------------------------------------- 1 | open module org.my.lib.test.fixtures { 2 | exports org.my.lib.test.fixtures; 3 | 4 | requires org.my.lib; 5 | } 6 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/lib/src/testFixtures/java/org/my/lib/test/fixtures/LibFixture.java: -------------------------------------------------------------------------------- 1 | package org.my.lib.test.fixtures; 2 | 3 | public class LibFixture { 4 | } 5 | -------------------------------------------------------------------------------- /samples/use-with-test-fixtures/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | pluginManagement { 2 | includeBuild("build-logic") 3 | } 4 | dependencyResolutionManagement { 5 | repositories.mavenCentral() 6 | } 7 | 8 | include("lib") 9 | -------------------------------------------------------------------------------- /settings.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.gradle.develocity") version "4.0.2" 3 | } 4 | 5 | dependencyResolutionManagement { 6 | repositories.mavenCentral() 7 | } 8 | 9 | rootProject.name = "java-module-testing" 10 | 11 | develocity { 12 | buildScan { 13 | val isCi = providers.environmentVariable("CI").getOrElse("false").toBoolean() 14 | if (isCi) { 15 | termsOfUseUrl = "https://gradle.com/help/legal-terms-of-use" 16 | termsOfUseAgree = "yes" 17 | } else { 18 | publishing.onlyIf { false } 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingExtension.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing; 18 | 19 | import org.gradle.api.Action; 20 | import org.gradle.api.Describable; 21 | import org.gradle.api.Project; 22 | import org.gradle.api.artifacts.Configuration; 23 | import org.gradle.api.artifacts.ConfigurationContainer; 24 | import org.gradle.api.artifacts.dsl.DependencyHandler; 25 | import org.gradle.api.file.RegularFile; 26 | import org.gradle.api.plugins.jvm.JvmTestSuite; 27 | import org.gradle.api.provider.Provider; 28 | import org.gradle.api.tasks.SourceSet; 29 | import org.gradle.api.tasks.SourceSetContainer; 30 | import org.gradle.api.tasks.TaskContainer; 31 | import org.gradle.api.tasks.TaskProvider; 32 | import org.gradle.api.tasks.compile.JavaCompile; 33 | import org.gradle.api.tasks.testing.Test; 34 | import org.gradle.jvm.tasks.Jar; 35 | import org.gradle.testing.base.TestSuite; 36 | import org.gradle.testing.base.TestingExtension; 37 | import org.gradlex.javamodule.testing.internal.ModuleInfoParser; 38 | import org.gradlex.javamodule.testing.internal.ModuleInfoRequiresParser; 39 | import org.gradlex.javamodule.testing.internal.actions.JavaCompileSetModulePathAction; 40 | import org.gradlex.javamodule.testing.internal.bridges.JavaModuleDependenciesBridge; 41 | import org.gradlex.javamodule.testing.internal.provider.WhiteboxTestCompileArgumentProvider; 42 | import org.gradlex.javamodule.testing.internal.provider.WhiteboxTestRuntimeArgumentProvider; 43 | 44 | import javax.inject.Inject; 45 | import java.io.File; 46 | import java.util.Collections; 47 | import java.util.List; 48 | import java.util.stream.Collectors; 49 | 50 | @SuppressWarnings("UnstableApiUsage") 51 | public abstract class JavaModuleTestingExtension { 52 | private static final Action NO_OP_ACTION = c -> {}; 53 | 54 | private final Project project; 55 | 56 | @Inject 57 | public JavaModuleTestingExtension(Project project) { 58 | this.project = project; 59 | 60 | TestingExtension testing = project.getExtensions().getByType(TestingExtension.class); 61 | testing.getSuites().withType(JvmTestSuite.class).configureEach(jvmTestSuite -> { 62 | boolean isTestModule = jvmTestSuite.getSources().getJava().getSrcDirs().stream().anyMatch(src -> new File(src, "module-info.java").exists()); 63 | if ("test".equals(jvmTestSuite.getName())) { 64 | jvmTestSuite.useJUnitJupiter(); // override old Gradle convention to default to JUnit5 for all suites 65 | } 66 | 67 | if (isTestModule) { 68 | blackbox(jvmTestSuite); 69 | } else { 70 | whitebox(jvmTestSuite, conf -> conf.getOpensTo().add("org.junit.platform.commons")); 71 | } 72 | }); 73 | } 74 | 75 | /** 76 | * Turn the given JVM Test Suite into a Blackbox Test Suite. 77 | * For example: 78 | *

79 | * javaModuleTesting.blackbox(testing.suites["integtest"]) 80 | * 81 | * @param jvmTestSuite the JVM Test Suite to configure 82 | */ 83 | public void blackbox(TestSuite jvmTestSuite) { 84 | if (jvmTestSuite instanceof JvmTestSuite) { 85 | configureJvmTestSuiteForBlackbox((JvmTestSuite) jvmTestSuite); 86 | } 87 | } 88 | 89 | /** 90 | * Turn the given JVM Test Suite into a Whitebox Test Suite. 91 | * For example: 92 | *

93 | * javaModuleTesting.whitebox(testing.suites["test"]) 94 | * 95 | * @param jvmTestSuite the JVM Test Suite to configure 96 | */ 97 | @SuppressWarnings("unused") 98 | public void whitebox(TestSuite jvmTestSuite) { 99 | whitebox(jvmTestSuite, NO_OP_ACTION); 100 | } 101 | 102 | /** 103 | * Turn the given JVM Test Suite into a Classpath Test Suite. 104 | * For example: 105 | *

106 | * javaModuleTesting.classpath(testing.suites["test"]) 107 | *

108 | * This restores the default behavior of Gradle to run tests on the Classpath if 109 | * no 'module-info.java' is present in the source folder of the given test suite. 110 | * 111 | * @param jvmTestSuite the JVM Test Suite to configure 112 | */ 113 | @SuppressWarnings("unused") 114 | public void classpath(TestSuite jvmTestSuite) { 115 | if (jvmTestSuite instanceof JvmTestSuite) { 116 | revertJvmTestSuiteForWhitebox((JvmTestSuite) jvmTestSuite); 117 | } 118 | } 119 | 120 | /** 121 | * Turn the given JVM Test Suite into a Whitebox Test Suite. 122 | * If needed, configure additional 'requires' and open the 123 | * test packages for reflection. 124 | *

125 | * For example, for JUnit 5, you need at least: 126 | *

127 | * javaModuleTesting.whitebox(testing.suites["test"]) { 128 | * requires.add("org.junit.jupiter.api") 129 | * opensTo.add("org.junit.platform.commons") 130 | * } 131 | * 132 | * @param jvmTestSuite the JVM Test Suite to configure 133 | * @param conf configuration details for the whitebox test setup 134 | */ 135 | public void whitebox(TestSuite jvmTestSuite, Action conf) { 136 | if (jvmTestSuite instanceof JvmTestSuite) { 137 | SourceSet suiteSourceSet = ((JvmTestSuite) jvmTestSuite).getSources(); 138 | boolean testFolderExists = suiteSourceSet.getJava().getSrcDirs().stream().anyMatch(File::exists); 139 | if (!testFolderExists) { 140 | // Remove the dependencies added by Gradle in case the test directory is missing. Then stop. This allows the use of 'useJUnitJupiter("")' without hassle. 141 | project.getConfigurations().getByName(suiteSourceSet.getImplementationConfigurationName(), implementation -> 142 | implementation.withDependencies(dependencySet -> dependencySet.removeIf(d -> "org.junit.jupiter".equals(d.getGroup()) && "junit-jupiter".equals(d.getName())))); 143 | project.getConfigurations().getByName(suiteSourceSet.getRuntimeOnlyConfigurationName(), runtimeOnly -> 144 | runtimeOnly.withDependencies(dependencySet -> dependencySet.removeIf(d -> "org.junit.platform".equals(d.getGroup()) && "junit-platform-launcher".equals(d.getName())))); 145 | return; 146 | } 147 | 148 | WhiteboxJvmTestSuite whiteboxJvmTestSuite = project.getObjects().newInstance(WhiteboxJvmTestSuite.class); 149 | whiteboxJvmTestSuite.getSourcesUnderTest().convention(project.getExtensions().getByType(SourceSetContainer.class).getByName(SourceSet.MAIN_SOURCE_SET_NAME)); 150 | whiteboxJvmTestSuite.getRequires().addAll(requiresFromModuleInfo((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite.getSourcesUnderTest(), false)); 151 | whiteboxJvmTestSuite.getRequiresRuntime().addAll(requiresFromModuleInfo((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite.getSourcesUnderTest(), true)); 152 | conf.execute(whiteboxJvmTestSuite); 153 | configureJvmTestSuiteForWhitebox((JvmTestSuite) jvmTestSuite, whiteboxJvmTestSuite); 154 | } 155 | } 156 | 157 | private Provider> requiresFromModuleInfo(JvmTestSuite jvmTestSuite, Provider sourcesUnderTest, boolean runtimeOnly) { 158 | RegularFile moduleInfoFile = project.getLayout().getProjectDirectory().file(whiteboxModuleInfo(jvmTestSuite).getAbsolutePath()); 159 | Provider moduleInfoContent = project.getProviders().fileContents(moduleInfoFile).getAsText(); 160 | return moduleInfoContent.map(c -> { 161 | ModuleInfoParser moduleInfoParser = new ModuleInfoParser(project.getLayout(), project.getProviders()); 162 | String mainModuleName = moduleInfoParser.moduleName(sourcesUnderTest.get().getAllJava().getSrcDirs()); 163 | List requires = ModuleInfoRequiresParser.parse(moduleInfoContent.get(), runtimeOnly); 164 | if (requires.stream().anyMatch(r -> r.equals(mainModuleName)) || runtimeOnly) { 165 | return requires.stream().filter(r -> !r.equals(mainModuleName)).collect(Collectors.toList()); 166 | } 167 | return Collections.emptyList(); 168 | }).orElse(Collections.emptyList()); 169 | } 170 | 171 | private File whiteboxModuleInfo(JvmTestSuite jvmTestSuite) { 172 | File sourceSetDir = jvmTestSuite.getSources().getJava().getSrcDirs().iterator().next().getParentFile(); 173 | return new File(sourceSetDir, "java9/module-info.java"); 174 | } 175 | 176 | private void configureJvmTestSuiteForBlackbox(JvmTestSuite jvmTestSuite) { 177 | ConfigurationContainer configurations = project.getConfigurations(); 178 | TaskContainer tasks = project.getTasks(); 179 | 180 | TaskProvider jarTask; 181 | SourceSet sourceSet = jvmTestSuite.getSources(); 182 | if (!tasks.getNames().contains(sourceSet.getJarTaskName())) { 183 | jarTask = tasks.register(sourceSet.getJarTaskName(), Jar.class, t -> { 184 | t.getArchiveClassifier().set(sourceSet.getName()); 185 | t.from(sourceSet.getOutput()); 186 | }); 187 | } else { 188 | jarTask = tasks.named(sourceSet.getJarTaskName(), Jar.class); 189 | } 190 | 191 | tasks.named(sourceSet.getName(), Test.class, t -> { 192 | // Classpath consists only of Jars to include classes+resources in one place 193 | t.setClasspath(configurations.getByName(sourceSet.getRuntimeClasspathConfigurationName()).plus(project.files(jarTask))); 194 | // Reset test classes dir 195 | t.setTestClassesDirs(sourceSet.getOutput().getClassesDirs()); 196 | }); 197 | } 198 | 199 | private void configureJvmTestSuiteForWhitebox(JvmTestSuite jvmTestSuite, WhiteboxJvmTestSuite whiteboxJvmTestSuite) { 200 | ConfigurationContainer configurations = project.getConfigurations(); 201 | DependencyHandler dependencies = project.getDependencies(); 202 | TaskContainer tasks = project.getTasks(); 203 | ModuleInfoParser moduleInfoParser = new ModuleInfoParser(project.getLayout(), project.getProviders()); 204 | 205 | SourceSet testSources = jvmTestSuite.getSources(); 206 | JavaModuleDependenciesBridge.addRequiresRuntimeSupport(project, whiteboxJvmTestSuite.getSourcesUnderTest().get(), jvmTestSuite.getSources()); 207 | 208 | tasks.named(testSources.getCompileJavaTaskName(), JavaCompile.class, compileJava -> { 209 | SourceSet sourcesUnderTest = whiteboxJvmTestSuite.getSourcesUnderTest().get(); 210 | 211 | Configuration compileOnly = configurations.getByName(sourcesUnderTest.getCompileOnlyConfigurationName()); 212 | Configuration testCompileOnly = configurations.getByName(testSources.getCompileOnlyConfigurationName()); 213 | if (!testCompileOnly.getExtendsFrom().contains(compileOnly)) { 214 | testCompileOnly.extendsFrom(compileOnly); 215 | } 216 | 217 | compileJava.setClasspath(sourcesUnderTest.getOutput().plus(configurations.getByName(testSources.getCompileClasspathConfigurationName()))); 218 | 219 | WhiteboxTestCompileArgumentProvider argumentProvider = (WhiteboxTestCompileArgumentProvider) compileJava.getOptions().getCompilerArgumentProviders().stream() 220 | .filter(p -> p instanceof WhiteboxTestCompileArgumentProvider).findFirst().orElseGet(() -> { 221 | WhiteboxTestCompileArgumentProvider newProvider = new WhiteboxTestCompileArgumentProvider( 222 | sourcesUnderTest.getJava().getSrcDirs(), 223 | testSources.getJava().getSrcDirs(), 224 | moduleInfoParser, 225 | project.getObjects()); 226 | compileJava.getOptions().getCompilerArgumentProviders().add(newProvider); 227 | compileJava.doFirst(project.getObjects().newInstance(JavaCompileSetModulePathAction.class)); 228 | return newProvider; 229 | }); 230 | argumentProvider.testRequires(JavaModuleDependenciesBridge.getCompileClasspathModules(project, testSources)); 231 | argumentProvider.testRequires(whiteboxJvmTestSuite.getRequires()); 232 | }); 233 | 234 | tasks.named(testSources.getName(), Test.class, test -> { 235 | SourceSet sourcesUnderTest = whiteboxJvmTestSuite.getSourcesUnderTest().get(); 236 | test.setClasspath(configurations.getByName(testSources.getRuntimeClasspathConfigurationName()).plus(sourcesUnderTest.getOutput()).plus(testSources.getOutput())); 237 | 238 | // Add main classes here so that Gradle finds module-info.class and treats this as a test with module path 239 | test.setTestClassesDirs(sourcesUnderTest.getOutput().getClassesDirs().plus(testSources.getOutput().getClassesDirs())); 240 | 241 | WhiteboxTestRuntimeArgumentProvider argumentProvider = (WhiteboxTestRuntimeArgumentProvider) test.getJvmArgumentProviders().stream() 242 | .filter(p -> p instanceof WhiteboxTestRuntimeArgumentProvider).findFirst().orElseGet(() -> { 243 | WhiteboxTestRuntimeArgumentProvider newProvider = new WhiteboxTestRuntimeArgumentProvider( 244 | sourcesUnderTest.getJava().getSrcDirs(), 245 | testSources.getJava().getClassesDirectory(), 246 | sourcesUnderTest.getOutput().getResourcesDir(), 247 | testSources.getOutput().getResourcesDir(), 248 | moduleInfoParser, 249 | project.getObjects()); 250 | test.getJvmArgumentProviders().add(newProvider); 251 | return newProvider; 252 | }); 253 | argumentProvider.testRequires(JavaModuleDependenciesBridge.getRuntimeClasspathModules(project, testSources)); 254 | argumentProvider.testRequires(whiteboxJvmTestSuite.getRequires()); 255 | argumentProvider.testOpensTo(JavaModuleDependenciesBridge.getOpensToModules(project, testSources)); 256 | argumentProvider.testOpensTo(whiteboxJvmTestSuite.getOpensTo()); 257 | argumentProvider.testExportsTo(JavaModuleDependenciesBridge.getExportsToModules(project, testSources)); 258 | argumentProvider.testExportsTo(whiteboxJvmTestSuite.getExportsTo()); 259 | }); 260 | 261 | Configuration implementation = configurations.getByName(testSources.getImplementationConfigurationName()); 262 | implementation.withDependencies(d -> { 263 | for (String requiresModuleName : whiteboxJvmTestSuite.getRequires().get()) { 264 | Provider dependency = JavaModuleDependenciesBridge.create(project, requiresModuleName, whiteboxJvmTestSuite.getSourcesUnderTest().get()); 265 | if (dependency != null) { 266 | dependencies.addProvider(implementation.getName(), dependency); 267 | } 268 | } 269 | }); 270 | Configuration runtimeOnly = configurations.getByName(testSources.getRuntimeOnlyConfigurationName()); 271 | runtimeOnly.withDependencies(d -> { 272 | for (String requiresModuleName : whiteboxJvmTestSuite.getRequiresRuntime().get()) { 273 | Provider dependency = JavaModuleDependenciesBridge.create(project, requiresModuleName, whiteboxJvmTestSuite.getSourcesUnderTest().get()); 274 | if (dependency != null) { 275 | dependencies.addProvider(runtimeOnly.getName(), dependency); 276 | } 277 | } 278 | }); 279 | } 280 | 281 | /** 282 | * Resets changes performed in 'configureJvmTestSuiteForWhitebox' to Gradle defaults. 283 | */ 284 | private void revertJvmTestSuiteForWhitebox(JvmTestSuite jvmTestSuite) { 285 | TaskContainer tasks = project.getTasks(); 286 | SourceSet testSources = jvmTestSuite.getSources(); 287 | 288 | tasks.named(testSources.getCompileJavaTaskName(), JavaCompile.class, compileJava -> { 289 | compileJava.setClasspath(testSources.getCompileClasspath()); 290 | compileJava.getOptions().getCompilerArgumentProviders().removeIf(p -> p instanceof WhiteboxTestCompileArgumentProvider); 291 | compileJava.getActions().removeIf(a -> a instanceof Describable 292 | && JavaCompileSetModulePathAction.class.getName().equals(((Describable) a).getDisplayName())); 293 | }); 294 | 295 | tasks.named(testSources.getName(), Test.class, test -> { 296 | test.setClasspath(testSources.getRuntimeClasspath()); 297 | test.setTestClassesDirs(testSources.getOutput().getClassesDirs()); 298 | test.getJvmArgumentProviders().removeIf(p -> p instanceof WhiteboxTestRuntimeArgumentProvider); 299 | }); 300 | } 301 | } 302 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/JavaModuleTestingPlugin.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing; 18 | 19 | import org.gradle.api.NonNullApi; 20 | import org.gradle.api.Plugin; 21 | import org.gradle.api.Project; 22 | import org.gradle.api.plugins.JvmTestSuitePlugin; 23 | import org.gradle.util.GradleVersion; 24 | 25 | @SuppressWarnings("unused") 26 | @NonNullApi 27 | public abstract class JavaModuleTestingPlugin implements Plugin { 28 | 29 | @Override 30 | public void apply(Project project) { 31 | if (GradleVersion.current().compareTo(GradleVersion.version("7.4")) < 0) { 32 | throw new RuntimeException("This plugin requires Gradle 7.4+"); 33 | } 34 | project.getPlugins().withType(JvmTestSuitePlugin.class, p-> 35 | project.getExtensions().create("javaModuleTesting", JavaModuleTestingExtension.class)); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/TaskLockService.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing; 18 | 19 | import org.gradle.api.services.BuildService; 20 | import org.gradle.api.services.BuildServiceParameters; 21 | 22 | /** 23 | * No-op service that can serve as 'lock' to prevent multiple test tasks from running in parallel: 24 | * usesService(gradle.sharedServices.registerIfAbsent(TaskLockService.NAME, TaskLockService::class) { maxParallelUsages = 1 }) 25 | */ 26 | public abstract class TaskLockService implements BuildService { 27 | public static String NAME = "taskLock"; 28 | } 29 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/WhiteboxJvmTestSuite.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing; 18 | 19 | import org.gradle.api.provider.ListProperty; 20 | import org.gradle.api.provider.Property; 21 | import org.gradle.api.tasks.SourceSet; 22 | 23 | public interface WhiteboxJvmTestSuite { 24 | 25 | /** 26 | * Configure which source set contains the 'sources under test' for 27 | * this Whitebox Test Suite - defaults to 'main'. 28 | * 29 | * @return the source set under test 30 | */ 31 | Property getSourcesUnderTest(); 32 | 33 | /** 34 | * Add additional 'requires' directives for the test code. 35 | * For example, 'requires.add("org.junit.jupiter.api")'. 36 | * 37 | * @return modifiable list of addition 'requires' (--add-reads) 38 | */ 39 | ListProperty getRequires(); 40 | 41 | /** 42 | * Add a runtime-only dependency via Module Name when combined with 43 | * 'java-module-dependencies' plugin. 44 | * 45 | * @return modifiable list of addition 'runtimeOnly' dependencies 46 | */ 47 | ListProperty getRequiresRuntime(); 48 | 49 | /** 50 | * Open all packages of this Whitebox Test Suite to a given Module 51 | * for reflection at runtime. 52 | * For example, 'opensTo.add("org.junit.platform.commons")'. 53 | * 54 | * @return modifiable list of addition '--add-opens' 55 | */ 56 | ListProperty getOpensTo(); 57 | 58 | /** 59 | * Export all packages of this Whitebox Test Suite to a given Module 60 | * for access to public methods at runtime. 61 | * 62 | * @return modifiable list of addition '--add-exports' 63 | */ 64 | ListProperty getExportsTo(); 65 | } 66 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/internal/ModuleInfoParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.internal; 18 | 19 | import org.gradle.api.file.ProjectLayout; 20 | import org.gradle.api.file.RegularFile; 21 | import org.gradle.api.provider.Provider; 22 | import org.gradle.api.provider.ProviderFactory; 23 | 24 | import java.io.File; 25 | import java.util.Arrays; 26 | import java.util.List; 27 | import java.util.Set; 28 | 29 | public class ModuleInfoParser { 30 | 31 | private final ProjectLayout layout; 32 | private final ProviderFactory providers; 33 | 34 | public ModuleInfoParser(ProjectLayout layout, ProviderFactory providers) { 35 | this.layout = layout; 36 | this.providers = providers; 37 | } 38 | 39 | public String moduleName(Set sourceFolders) { 40 | for (File folder : sourceFolders) { 41 | Provider moduleInfoFile = layout.file(providers.provider(() -> new File(folder, "module-info.java"))); 42 | Provider moduleInfoContent = providers.fileContents(moduleInfoFile).getAsText(); 43 | if (moduleInfoContent.isPresent()) { 44 | return moduleName(moduleInfoContent.get()); 45 | } 46 | } 47 | return null; 48 | } 49 | 50 | static String moduleName(String moduleInfoFileContent) { 51 | boolean inComment = false; 52 | boolean moduleKeywordFound = false; 53 | 54 | for(String line: moduleInfoFileContent.split("\n")) { 55 | String cleanedLine = line 56 | .replaceAll("/\\*.*\\*/", "") // open & close in this line 57 | .replaceAll("//.*", ""); // line comment 58 | inComment = inComment || cleanedLine.contains("/*"); 59 | cleanedLine = cleanedLine.replaceAll("/\\*.*", ""); // open in this line 60 | inComment = inComment && !line.contains("*/"); 61 | cleanedLine = cleanedLine.replaceAll(".*\\*/", "").trim(); // closing part of comment 62 | 63 | if (inComment) { 64 | continue; 65 | } 66 | 67 | List tokens = Arrays.asList(cleanedLine.split("\\s+")); 68 | if (moduleKeywordFound && !tokens.isEmpty()) { 69 | return tokens.get(0); 70 | } 71 | 72 | int moduleKeywordIndex = tokens.indexOf("module"); 73 | if (moduleKeywordIndex == 0 || moduleKeywordIndex == 1) { 74 | if (tokens.size() > moduleKeywordIndex) { 75 | return tokens.get(moduleKeywordIndex + 1); 76 | } 77 | moduleKeywordFound = true; 78 | } 79 | } 80 | return null; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/internal/ModuleInfoRequiresParser.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.internal; 18 | 19 | import java.util.ArrayList; 20 | import java.util.Arrays; 21 | import java.util.List; 22 | 23 | public class ModuleInfoRequiresParser { 24 | private static final String RUNTIME_KEYWORD = "/*runtime*/"; 25 | 26 | public static List parse(String moduleInfoFileContent, boolean runtimeOnly) { 27 | List requires = new ArrayList<>(); 28 | boolean insideComment = false; 29 | for(String line: moduleInfoFileContent.split("\n")) { 30 | insideComment = parseLine(line, insideComment, requires, runtimeOnly); 31 | } 32 | return requires; 33 | } 34 | 35 | /** 36 | * @return true, if we are inside a multi-line comment after this line 37 | */ 38 | private static boolean parseLine(String moduleLine, boolean insideComment, List requires, boolean runtimeOnly) { 39 | if (insideComment) { 40 | return !moduleLine.contains("*/"); 41 | } 42 | 43 | List tokens = Arrays.asList(moduleLine 44 | .replace(";", "") 45 | .replace("{", "") 46 | .replace(RUNTIME_KEYWORD, "runtime") 47 | .replaceAll("/\\*.*?\\*/", " ") 48 | .trim().split("\\s+")); 49 | int singleLineCommentStartIndex = tokens.indexOf("//"); 50 | if (singleLineCommentStartIndex >= 0) { 51 | tokens = tokens.subList(0, singleLineCommentStartIndex); 52 | } 53 | 54 | if (tokens.size() > 1 && tokens.get(0).equals("requires")) { 55 | if (runtimeOnly) { 56 | if (tokens.size() > 2 && tokens.contains("runtime")) { 57 | requires.add(tokens.get(2)); 58 | } 59 | } else { 60 | if (tokens.size() > 3 && tokens.contains("static") && tokens.contains("transitive")) { 61 | requires.add(tokens.get(3)); 62 | } else if (tokens.size() > 2 && tokens.contains("transitive")) { 63 | requires.add(tokens.get(2)); 64 | } else if (tokens.size() > 2 && tokens.contains("static")) { 65 | requires.add(tokens.get(2)); 66 | } else if (!tokens.contains("runtime")) { 67 | requires.add(tokens.get(1)); 68 | } 69 | } 70 | } 71 | return moduleLine.lastIndexOf("/*") > moduleLine.lastIndexOf("*/"); 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/internal/actions/JavaCompileSetModulePathAction.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.internal.actions; 18 | 19 | import org.gradle.api.Action; 20 | import org.gradle.api.Describable; 21 | import org.gradle.api.NonNullApi; 22 | import org.gradle.api.Task; 23 | import org.gradle.api.file.FileCollection; 24 | import org.gradle.api.tasks.compile.JavaCompile; 25 | import org.gradle.internal.jvm.JavaModuleDetector; 26 | 27 | import javax.inject.Inject; 28 | import java.util.ArrayList; 29 | import java.util.List; 30 | 31 | @NonNullApi 32 | public abstract class JavaCompileSetModulePathAction implements Action, Describable { 33 | 34 | @Inject 35 | protected abstract JavaModuleDetector getJavaModuleDetector(); 36 | 37 | @Override 38 | public String getDisplayName() { 39 | return JavaCompileSetModulePathAction.class.getName(); 40 | } 41 | 42 | @Override 43 | public void execute(Task task) { 44 | JavaCompile javaCompile = (JavaCompile) task; 45 | FileCollection classpathAndModulePath = javaCompile.getClasspath(); 46 | List compilerArgs = new ArrayList<>(javaCompile.getOptions().getCompilerArgs()); 47 | 48 | // Since for Gradle this sources set does not look like a module, we have to define the module path ourselves 49 | compilerArgs.add("--module-path"); 50 | compilerArgs.add(getJavaModuleDetector().inferModulePath(true, classpathAndModulePath).getAsPath()); 51 | javaCompile.setClasspath(getJavaModuleDetector().inferClasspath(true, classpathAndModulePath)); 52 | javaCompile.getOptions().setCompilerArgs(compilerArgs); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/internal/bridges/JavaModuleDependenciesBridge.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.internal.bridges; 18 | 19 | import org.gradle.api.Project; 20 | import org.gradle.api.provider.Provider; 21 | import org.gradle.api.tasks.SourceSet; 22 | 23 | import java.lang.reflect.Method; 24 | import java.util.Collections; 25 | import java.util.List; 26 | 27 | public class JavaModuleDependenciesBridge { 28 | 29 | public static Provider create(Project project, String moduleName, SourceSet sourceSetWithModuleInfo) { 30 | Object javaModuleDependencies = project.getExtensions().findByName("javaModuleDependencies"); 31 | if (javaModuleDependencies == null) { 32 | return null; 33 | } 34 | try { 35 | Method gav = javaModuleDependencies.getClass().getMethod("create", String.class, SourceSet.class); 36 | return (Provider) gav.invoke(javaModuleDependencies, moduleName, sourceSetWithModuleInfo); 37 | } catch (ReflectiveOperationException e) { 38 | throw new RuntimeException(e); 39 | } 40 | } 41 | 42 | public static void addRequiresRuntimeSupport(Project project, SourceSet sourceSetForModuleInfo, SourceSet sourceSetForClasspath) { 43 | Object javaModuleDependencies = project.getExtensions().findByName("javaModuleDependencies"); 44 | if (javaModuleDependencies == null) { 45 | return; 46 | } 47 | try { 48 | Method addRequiresRuntimeSupport = javaModuleDependencies.getClass().getMethod("addRequiresRuntimeSupport", SourceSet.class, SourceSet.class); 49 | addRequiresRuntimeSupport.invoke(javaModuleDependencies, sourceSetForModuleInfo, sourceSetForClasspath); 50 | } catch (NoSuchMethodException e) { 51 | //noinspection UnnecessaryReturnStatement 52 | return; 53 | } catch (ReflectiveOperationException e) { 54 | throw new RuntimeException(e); 55 | } 56 | } 57 | 58 | public static List getRuntimeClasspathModules(Project project, SourceSet sourceSet) { 59 | return getDeclaredModules("getRuntimeClasspathModules", project, sourceSet); 60 | } 61 | 62 | public static List getCompileClasspathModules(Project project, SourceSet sourceSet) { 63 | return getDeclaredModules("getCompileClasspathModules", project, sourceSet); 64 | } 65 | 66 | public static List getExportsToModules(Project project, SourceSet sourceSet) { 67 | return getDeclaredModules("getExportsToModules", project, sourceSet); 68 | } 69 | 70 | public static List getOpensToModules(Project project, SourceSet sourceSet) { 71 | return getDeclaredModules("getOpensToModules", project, sourceSet); 72 | } 73 | 74 | private static List getDeclaredModules(String getter, Project project, SourceSet sourceSet) { 75 | Object moduleInfoDslExtension = project.getExtensions().findByName(sourceSet.getName() + "ModuleInfo"); 76 | if (moduleInfoDslExtension == null) { 77 | return Collections.emptyList(); 78 | } 79 | try { 80 | Method gav = moduleInfoDslExtension.getClass().getMethod(getter); 81 | @SuppressWarnings("unchecked") 82 | List modules = (List) gav.invoke(moduleInfoDslExtension); 83 | return modules; 84 | } catch (NoSuchMethodException e) { 85 | return Collections.emptyList(); 86 | } catch (ReflectiveOperationException e) { 87 | throw new RuntimeException(e); 88 | } 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestCompileArgumentProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.internal.provider; 18 | 19 | import org.gradle.api.file.FileCollection; 20 | import org.gradle.api.model.ObjectFactory; 21 | import org.gradle.api.provider.ListProperty; 22 | import org.gradle.api.provider.Provider; 23 | import org.gradlex.javamodule.testing.internal.ModuleInfoParser; 24 | import org.gradle.api.tasks.compile.JavaCompile; 25 | import org.gradle.internal.jvm.JavaModuleDetector; 26 | import org.gradle.process.CommandLineArgumentProvider; 27 | 28 | import java.io.File; 29 | import java.util.ArrayList; 30 | import java.util.List; 31 | import java.util.Set; 32 | import java.util.stream.Collectors; 33 | 34 | public class WhiteboxTestCompileArgumentProvider implements CommandLineArgumentProvider { 35 | private final Set mainSourceFolders; 36 | private final Set testSourceFolders; 37 | private final ModuleInfoParser moduleInfoParser; 38 | 39 | private final ListProperty allTestRequires; 40 | 41 | public WhiteboxTestCompileArgumentProvider( 42 | Set mainSourceFolders, Set testSourceFolders, ModuleInfoParser moduleInfoParser, ObjectFactory objects) { 43 | this.mainSourceFolders = mainSourceFolders; 44 | this.testSourceFolders = testSourceFolders; 45 | this.moduleInfoParser = moduleInfoParser; 46 | this.allTestRequires = objects.listProperty(String.class); 47 | } 48 | 49 | public void testRequires(Provider> testRequires) { 50 | allTestRequires.addAll(testRequires); 51 | } 52 | 53 | public void testRequires(List testRequires) { 54 | allTestRequires.addAll(testRequires); 55 | } 56 | 57 | @Override 58 | public Iterable asArguments() { 59 | String moduleName = moduleInfoParser.moduleName(mainSourceFolders); 60 | String testSources = testSourceFolders.stream().map(File::getPath) 61 | .collect(Collectors.joining(File.pathSeparator)); 62 | 63 | List args = new ArrayList<>(); 64 | 65 | for (String testRequires : allTestRequires.get()) { 66 | args.add("--add-modules"); 67 | args.add(testRequires); 68 | args.add("--add-reads"); 69 | args.add(moduleName + "=" + testRequires); 70 | } 71 | 72 | // Patch 'main' and 'test' sources together 73 | args.add("--patch-module"); 74 | args.add(moduleName + "=" + testSources); 75 | 76 | return args; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/main/java/org/gradlex/javamodule/testing/internal/provider/WhiteboxTestRuntimeArgumentProvider.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.internal.provider; 18 | 19 | import org.gradle.api.file.Directory; 20 | import org.gradle.api.model.ObjectFactory; 21 | import org.gradle.api.provider.ListProperty; 22 | import org.gradle.api.provider.Provider; 23 | import org.gradle.process.CommandLineArgumentProvider; 24 | import org.gradlex.javamodule.testing.internal.ModuleInfoParser; 25 | 26 | import java.io.File; 27 | import java.util.ArrayList; 28 | import java.util.List; 29 | import java.util.Set; 30 | import java.util.TreeSet; 31 | 32 | public class WhiteboxTestRuntimeArgumentProvider implements CommandLineArgumentProvider { 33 | private final Set mainSourceFolders; 34 | private final Provider testClassesFolders; 35 | private final File resourcesUnderTest; 36 | private final File testResources; 37 | private final ModuleInfoParser moduleInfoParser; 38 | 39 | private final ListProperty allTestRequires; 40 | private final ListProperty allTestOpensTo; 41 | private final ListProperty allTestExportsTo; 42 | 43 | public WhiteboxTestRuntimeArgumentProvider(Set mainSourceFolders, 44 | Provider testClassesFolders, File resourcesUnderTest, File testResources, 45 | ModuleInfoParser moduleInfoParser, ObjectFactory objects) { 46 | 47 | this.mainSourceFolders = mainSourceFolders; 48 | this.testClassesFolders = testClassesFolders; 49 | this.resourcesUnderTest = resourcesUnderTest; 50 | this.testResources = testResources; 51 | this.moduleInfoParser = moduleInfoParser; 52 | this.allTestRequires = objects.listProperty(String.class); 53 | this.allTestOpensTo = objects.listProperty(String.class); 54 | this.allTestExportsTo = objects.listProperty(String.class); 55 | } 56 | 57 | public void testRequires(Provider> testRequires) { 58 | allTestRequires.addAll(testRequires); 59 | } 60 | 61 | public void testRequires(List testRequires) { 62 | allTestRequires.addAll(testRequires); 63 | } 64 | 65 | public void testOpensTo(Provider> testOpensTo) { 66 | allTestOpensTo.addAll(testOpensTo); 67 | } 68 | 69 | public void testOpensTo(List testOpensTo) { 70 | allTestOpensTo.addAll(testOpensTo); 71 | } 72 | 73 | public void testExportsTo(Provider> testExportsTo) { 74 | allTestExportsTo.addAll(testExportsTo); 75 | } 76 | 77 | public void testExportsTo(List testExportsTo) { 78 | allTestExportsTo.addAll(testExportsTo); 79 | } 80 | 81 | @Override 82 | public Iterable asArguments() { 83 | String moduleName = moduleInfoParser.moduleName(mainSourceFolders); 84 | 85 | Set allTestClassPackages = new TreeSet<>(); 86 | testClassesFolders.get().getAsFileTree().visit(file -> { 87 | String path = file.getPath(); 88 | if (path.endsWith(".class") && path.contains("/")) { 89 | allTestClassPackages.add(path.substring(0, path.lastIndexOf("/")).replace('/', '.')); 90 | } 91 | }); 92 | 93 | List args = new ArrayList<>(); 94 | 95 | for (String testRequires : allTestRequires.get()) { 96 | args.add("--add-modules"); 97 | args.add(testRequires); 98 | args.add("--add-reads"); 99 | args.add(moduleName + "=" + testRequires); 100 | } 101 | 102 | for (String packageName : allTestClassPackages) { 103 | for (String opensTo : allTestOpensTo.get()) { 104 | args.add("--add-opens"); 105 | args.add(moduleName + "/" + packageName + "=" + opensTo); 106 | } 107 | } 108 | 109 | for (String packageName : allTestClassPackages) { 110 | for (String opensTo : allTestExportsTo.get()) { 111 | args.add("--add-exports"); 112 | args.add(moduleName + "/" + packageName + "=" + opensTo); 113 | } 114 | } 115 | 116 | String testClassesPath = testClassesFolders.get().getAsFile().getPath(); 117 | String resourcesUnderTestPath = toAppendablePathEntry(resourcesUnderTest); 118 | String testResourcesPath = toAppendablePathEntry(testResources); 119 | 120 | // Patch into Module located in the 'main' classes folder: test classes, resources, test resources 121 | args.add("--patch-module"); 122 | args.add(moduleName + "=" + testClassesPath + resourcesUnderTestPath + testResourcesPath); 123 | 124 | return args; 125 | } 126 | 127 | private String toAppendablePathEntry(File folder) { 128 | if (folder.exists()) { 129 | return File.pathSeparator + folder.getPath(); 130 | } else { 131 | return ""; 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /src/test/groovy/org/gradlex/javamodule/testing/internal/ModuleInfoParseTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradlex.javamodule.testing.internal 2 | 3 | import spock.lang.Specification 4 | 5 | class ModuleInfoParseTest extends Specification { 6 | 7 | def "ignores single line comments"() { 8 | given: 9 | def nameFromFile = ModuleInfoParser.moduleName(''' 10 | // module some.thing.else 11 | module some.thing { 12 | requires transitive foo.bar.la; 13 | } 14 | ''') 15 | 16 | expect: 17 | nameFromFile == 'some.thing' 18 | } 19 | 20 | def "ignores single line comments late in line"() { 21 | given: 22 | def nameFromFile = ModuleInfoParser.moduleName(''' 23 | module some.thing { // module some.thing.else 24 | requires transitive foo.bar.la; 25 | } 26 | ''') 27 | 28 | expect: 29 | nameFromFile == 'some.thing' 30 | } 31 | 32 | def "ignores multi line comments"() { 33 | given: 34 | def nameFromFile = ModuleInfoParser.moduleName(''' 35 | /* 36 | module some.thing.else; 37 | */ 38 | module some.thing { 39 | requires static foo.bar.la; 40 | } 41 | ''') 42 | 43 | expect: 44 | nameFromFile == 'some.thing' 45 | } 46 | 47 | def "ignores multi line comments between keywords"() { 48 | given: 49 | def nameFromFile = ModuleInfoParser.moduleName(''' 50 | module /*module some.other*/ some.thing { /* module 51 | odd comment*/ requires transitive foo.bar.la; 52 | requires/* weird comment*/ static foo.bar.lo; 53 | requires /*something to say*/foo.bar.li; /* 54 | requires only.a.comment 55 | */ 56 | } 57 | ''') 58 | 59 | expect: 60 | nameFromFile == 'some.thing' 61 | } 62 | 63 | def "finds module name when open keyword is used"() { 64 | given: 65 | def nameFromFile = ModuleInfoParser.moduleName(''' 66 | open module /*module some.other*/ some.thing { /* module 67 | odd comment*/ requires transitive foo.bar.la; 68 | requires/* weird comment*/ static foo.bar.lo; 69 | requires /*something to say*/foo.bar.li; /* 70 | requires only.a.comment 71 | */ 72 | } 73 | ''') 74 | 75 | expect: 76 | nameFromFile == 'some.thing' 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /src/test/groovy/org/gradlex/javamodule/testing/test/ClasspathSuiteTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradlex.javamodule.testing.test 2 | 3 | import org.gradle.testkit.runner.TaskOutcome 4 | import org.gradlex.javamodule.testing.test.fixture.GradleBuild 5 | import spock.lang.Specification 6 | 7 | class ClasspathSuiteTest extends Specification { 8 | 9 | @Delegate 10 | GradleBuild build = new GradleBuild() 11 | 12 | def "can configure classpath test suite"() { 13 | given: 14 | appBuildFile << ''' 15 | javaModuleTesting.classpath(testing.suites["test"]) 16 | ''' 17 | appModuleInfoFile << ''' 18 | module org.example.app { 19 | } 20 | ''' 21 | 22 | when: 23 | def result = runTests() 24 | 25 | then: 26 | result.output.contains('Main Module: null') 27 | result.output.contains('Test Module: null') 28 | result.task(':app:test').outcome == TaskOutcome.SUCCESS 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/test/groovy/org/gradlex/javamodule/testing/test/CoreFunctionailtyTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradlex.javamodule.testing.test 2 | 3 | import org.gradle.testkit.runner.TaskOutcome 4 | import org.gradlex.javamodule.testing.test.fixture.GradleBuild 5 | import spock.lang.Specification 6 | 7 | class CoreFunctionailtyTest extends Specification { 8 | 9 | @Delegate 10 | GradleBuild build = new GradleBuild() 11 | 12 | def "testCompileOnly extends compileOnly for whitebox test suites"() { 13 | given: 14 | appBuildFile << ''' 15 | javaModuleTesting.classpath(testing.suites["test"]) 16 | dependencies { 17 | compileOnly("jakarta.servlet:jakarta.servlet-api:6.1.0") 18 | } 19 | ''' 20 | file("app/src/main/java/org/example/app/ServletImpl.java") << ''' 21 | package org.example.app; 22 | public abstract class ServletImpl implements jakarta.servlet.Servlet { } 23 | ''' 24 | file("app/src/test/java/org/example/app/test/ServletMock.java") << ''' 25 | package org.example.app.test; 26 | public abstract class ServletMock extends org.example.app.ServletImpl { } 27 | ''' 28 | appModuleInfoFile << ''' 29 | module org.example.app { 30 | requires static jakarta.servlet; 31 | } 32 | ''' 33 | 34 | when: 35 | def result = runner('compileTestJava').build() 36 | 37 | then: 38 | result.task(':app:compileTestJava').outcome == TaskOutcome.SUCCESS 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/test/groovy/org/gradlex/javamodule/testing/test/CustomizationTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradlex.javamodule.testing.test 2 | 3 | import org.gradle.testkit.runner.TaskOutcome 4 | import org.gradlex.javamodule.testing.test.fixture.GradleBuild 5 | import spock.lang.Specification 6 | 7 | class CustomizationTest extends Specification { 8 | 9 | @Delegate 10 | GradleBuild build = new GradleBuild() 11 | 12 | def "can customize whitebox test suites in multiple steps"() { 13 | given: 14 | appBuildFile << ''' 15 | javaModuleTesting.whitebox(testing.suites["test"]) { 16 | requires.add("org.junit.jupiter.api") 17 | } 18 | javaModuleTesting.whitebox(testing.suites["test"]) { 19 | opensTo.add("org.junit.platform.commons") 20 | } 21 | ''' 22 | appModuleInfoFile << ''' 23 | module org.example.app { 24 | } 25 | ''' 26 | 27 | when: 28 | def result = runTests() 29 | 30 | then: 31 | result.output.contains('Main Module: org.example.app') 32 | result.output.contains('Test Module: org.example.app') 33 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 34 | } 35 | 36 | def "can define whitebox test suite requires in module-info file"() { 37 | given: 38 | appModuleInfoFile << ''' 39 | module org.example.app { 40 | } 41 | ''' 42 | appWhiteboxTestModuleInfoFile << ''' 43 | module org.example.app.test { 44 | requires org.example.app; 45 | requires org.junit.jupiter.api; 46 | } 47 | ''' 48 | 49 | when: 50 | def result = runTests() 51 | 52 | then: 53 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 54 | } 55 | 56 | def "can customize whitebox test suites with exportsTo"() { 57 | given: 58 | def mainTest = file("app/src/test/java/org/example/app/test/MainTest.java") 59 | // make test public, so that 'exportsTo org.junit.platform.commons' is sufficient 60 | mainTest.text = mainTest.text.replace('void testApp()' , 'public void testApp()') 61 | 62 | appBuildFile << ''' 63 | javaModuleTesting.classpath(testing.suites["test"]) // reset default setup 64 | javaModuleTesting.whitebox(testing.suites["test"]) { 65 | requires.add("org.junit.jupiter.api") 66 | exportsTo.add("org.junit.platform.commons") 67 | } 68 | ''' 69 | appModuleInfoFile << ''' 70 | module org.example.app { 71 | } 72 | ''' 73 | 74 | when: 75 | def result = runTests() 76 | 77 | then: 78 | result.output.contains('Main Module: org.example.app') 79 | result.output.contains('Test Module: org.example.app') 80 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 81 | } 82 | 83 | 84 | def "repetitive blackbox calls on the same test suite have no effect"() { 85 | given: 86 | appBuildFile << ''' 87 | javaModuleTesting.blackbox(testing.suites["test"]) 88 | javaModuleTesting.blackbox(testing.suites["test"]) 89 | dependencies { testImplementation(project(path)) } 90 | ''' 91 | appModuleInfoFile << ''' 92 | module org.example.app { 93 | exports org.example.app; 94 | } 95 | ''' 96 | appTestModuleInfoFile << ''' 97 | open module org.example.app.test { 98 | requires org.example.app; 99 | requires org.junit.jupiter.api; 100 | } 101 | ''' 102 | 103 | when: 104 | def result = runTests() 105 | 106 | then: 107 | result.output.contains('Main Module: org.example.app') 108 | result.output.contains('Test Module: org.example.app.test') 109 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 110 | } 111 | 112 | def "can use task lock service"() { 113 | given: 114 | appBuildFile.text = 'import org.gradlex.javamodule.testing.TaskLockService\n\n' + appBuildFile.text 115 | appBuildFile << ''' 116 | javaModuleTesting.whitebox(testing.suites.getByName("test") { 117 | targets.all { 118 | testTask { 119 | usesService(gradle.sharedServices.registerIfAbsent(TaskLockService.NAME, TaskLockService::class) { maxParallelUsages.set(1) }) 120 | } 121 | } 122 | }) { 123 | requires.add("org.junit.jupiter.api") 124 | } 125 | ''' 126 | appModuleInfoFile << ''' 127 | module org.example.app { 128 | } 129 | ''' 130 | 131 | when: 132 | def result = runTests() 133 | 134 | then: 135 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 136 | } 137 | 138 | def "build does not fail when JUnit has no version and the test folder is empty"() { 139 | given: 140 | appTestModuleInfoFile.parentFile.deleteDir() 141 | appBuildFile << ''' 142 | testing.suites.withType().all { 143 | useJUnitJupiter("") // <- no version, we want to manage that ourselves 144 | } 145 | ''' 146 | 147 | when: 148 | def result = runTests() 149 | 150 | then: 151 | result.task(":app:test").outcome == TaskOutcome.NO_SOURCE 152 | } 153 | 154 | def "build does not fail when JUnit has no version, the test folder is empty and whitebox was manually configured"() { 155 | given: 156 | appTestModuleInfoFile.parentFile.deleteDir() 157 | appBuildFile << ''' 158 | testing.suites.withType().all { 159 | useJUnitJupiter("") // <- no version, we want to manage that ourselves 160 | } 161 | javaModuleTesting.whitebox(testing.suites["test"]) { 162 | requires.add("org.junit.jupiter.api") 163 | } 164 | ''' 165 | 166 | when: 167 | def result = runTests() 168 | 169 | then: 170 | result.task(":app:test").outcome == TaskOutcome.NO_SOURCE 171 | } 172 | } 173 | -------------------------------------------------------------------------------- /src/test/groovy/org/gradlex/javamodule/testing/test/JavaModuleDependenciesBridgeTest.groovy: -------------------------------------------------------------------------------- 1 | package org.gradlex.javamodule.testing.test 2 | 3 | import org.gradle.testkit.runner.TaskOutcome 4 | import org.gradlex.javamodule.testing.test.fixture.GradleBuild 5 | import spock.lang.Specification 6 | 7 | class JavaModuleDependenciesBridgeTest extends Specification { 8 | 9 | @Delegate 10 | GradleBuild build = new GradleBuild() 11 | 12 | def setup() { 13 | useJavaModuleDependenciesPlugin() 14 | } 15 | 16 | def "respects moduleNameToGA mappings"() { 17 | given: 18 | appBuildFile << ''' 19 | javaModuleDependencies { 20 | moduleNameToGA.put("org.example.lib", "org.example:lib") 21 | } 22 | javaModuleTesting.whitebox(testing.suites["test"]) { 23 | requires.add("org.junit.jupiter.api") 24 | requires.add("org.example.lib") 25 | opensTo.add("org.junit.platform.commons") 26 | } 27 | ''' 28 | appModuleInfoFile << ''' 29 | module org.example.app { 30 | } 31 | ''' 32 | libModuleInfoFile << ''' 33 | module org.example.lib { 34 | } 35 | ''' 36 | 37 | when: 38 | def result = runTests() 39 | 40 | then: 41 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 42 | } 43 | 44 | def "respects moduleNamePrefixToGroup mappings"() { 45 | given: 46 | appBuildFile << ''' 47 | javaModuleDependencies { 48 | moduleNamePrefixToGroup.put("org.example.", "org.example") 49 | } 50 | javaModuleTesting.whitebox(testing.suites["test"]) { 51 | requires.add("org.junit.jupiter.api") 52 | requires.add("org.example.lib") 53 | opensTo.add("org.junit.platform.commons") 54 | } 55 | ''' 56 | appModuleInfoFile << ''' 57 | module org.example.app { 58 | } 59 | ''' 60 | libModuleInfoFile << ''' 61 | module org.example.lib { 62 | } 63 | ''' 64 | 65 | when: 66 | def result = runTests() 67 | 68 | then: 69 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 70 | } 71 | 72 | def "compiles with provides runtime directives"() { 73 | given: 74 | appBuildFile << ''' 75 | dependencies.constraints { 76 | javaModuleDependencies { 77 | implementation(gav("org.slf4j", "2.0.3")) 78 | implementation(gav("org.slf4j.simple", "2.0.3")) 79 | } 80 | } 81 | javaModuleDependencies { 82 | moduleNameToGA.put("org.example.lib", "org.example:lib") 83 | } 84 | javaModuleTesting.whitebox(testing.suites["test"]) { 85 | requires.add("org.junit.jupiter.api") 86 | requires.add("org.example.lib") 87 | opensTo.add("org.junit.platform.commons") 88 | } 89 | ''' 90 | appModuleInfoFile << ''' 91 | module org.example.app { 92 | requires org.slf4j; 93 | requires /*runtime*/ org.slf4j.simple; 94 | } 95 | ''' 96 | libModuleInfoFile << ''' 97 | module org.example.lib { 98 | } 99 | ''' 100 | 101 | when: 102 | def result = runTests() 103 | 104 | then: 105 | result.task(":app:compileTestJava").outcome == TaskOutcome.SUCCESS 106 | } 107 | 108 | def "can be combined with test-fixtures plugins"() { 109 | given: 110 | useTestFixturesPlugin() 111 | appModuleInfoFile << ''' 112 | module org.example.app { 113 | } 114 | ''' 115 | file("app/src/testFixtures/java/module-info.java") << ''' 116 | open module org.example.app.test.fixtures { 117 | requires org.example.app; 118 | } 119 | ''' 120 | appBuildFile << ''' 121 | javaModuleTesting.whitebox(testing.suites["test"]) { 122 | requires.add("org.junit.jupiter.api") 123 | requires.add("org.example.app.test.fixtures") 124 | } 125 | javaModuleDependencies { 126 | } 127 | ''' 128 | 129 | when: 130 | def result = runTests() 131 | 132 | then: 133 | result.task(":app:test").outcome == TaskOutcome.SUCCESS 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/test/groovy/org/gradlex/javamodule/testing/test/fixture/GradleBuild.groovy: -------------------------------------------------------------------------------- 1 | package org.gradlex.javamodule.testing.test.fixture 2 | 3 | import org.gradle.testkit.runner.BuildResult 4 | import org.gradle.testkit.runner.GradleRunner 5 | 6 | import java.lang.management.ManagementFactory 7 | import java.nio.file.Files 8 | 9 | class GradleBuild { 10 | 11 | final File projectDir 12 | final File settingsFile 13 | final File appBuildFile 14 | final File appModuleInfoFile 15 | final File appTestModuleInfoFile 16 | final File appWhiteboxTestModuleInfoFile 17 | final File libBuildFile 18 | final File libModuleInfoFile 19 | 20 | final String gradleVersionUnderTest = System.getProperty("gradleVersionUnderTest") 21 | boolean canUseProjectIsolation = gradleVersionUnderTest == null 22 | 23 | GradleBuild(File projectDir = Files.createTempDirectory("gradle-build").toFile()) { 24 | this.projectDir = projectDir 25 | this.settingsFile = file("settings.gradle.kts") 26 | this.appBuildFile = file("app/build.gradle.kts") 27 | this.appModuleInfoFile = file("app/src/main/java/module-info.java") 28 | this.appTestModuleInfoFile = file("app/src/test/java/module-info.java") 29 | this.appWhiteboxTestModuleInfoFile = file("app/src/test/java9/module-info.java") 30 | this.libBuildFile = file("lib/build.gradle.kts") 31 | this.libModuleInfoFile = file("lib/src/main/java/module-info.java") 32 | 33 | def launcherDependency = gradleVersionUnderTest == '7.4' ? 34 | 'testRuntimeOnly("org.junit.platform:junit-platform-launcher")' : '' 35 | 36 | settingsFile << ''' 37 | pluginManagement { 38 | plugins { id("org.gradlex.java-module-dependencies") version "1.8" } 39 | } 40 | dependencyResolutionManagement { repositories.mavenCentral() } 41 | includeBuild(".") 42 | rootProject.name = "test-project" 43 | include("app", "lib") 44 | ''' 45 | appBuildFile << """ 46 | plugins { 47 | id("org.gradlex.java-module-testing") 48 | id("application") 49 | } 50 | group = "org.example" 51 | dependencies { 52 | testImplementation(platform("org.junit:junit-bom:5.9.0")) 53 | $launcherDependency 54 | } 55 | application { 56 | mainModule.set("org.example.app") 57 | mainClass.set("org.example.app.Main") 58 | } 59 | tasks.test { 60 | testLogging.showStandardStreams = true 61 | } 62 | """ 63 | file("app/src/main/java/org/example/app/Main.java") << ''' 64 | package org.example.app; 65 | 66 | public class Main { 67 | public void main(String... args) { 68 | } 69 | } 70 | ''' 71 | file("app/src/test/java/org/example/app/test/MainTest.java") << ''' 72 | package org.example.app.test; 73 | 74 | import org.junit.jupiter.api.Test; 75 | import org.example.app.Main; 76 | 77 | public class MainTest { 78 | 79 | @Test 80 | void testApp() { 81 | new Main(); 82 | System.out.println("Main Module: " + Main.class.getModule().getName()); 83 | System.out.println("Test Module: " + MainTest.class.getModule().getName()); 84 | } 85 | } 86 | ''' 87 | 88 | libBuildFile << ''' 89 | plugins { 90 | id("org.gradlex.java-module-testing") 91 | id("java-library") 92 | } 93 | group = "org.example" 94 | ''' 95 | } 96 | 97 | void useJavaModuleDependenciesPlugin() { 98 | canUseProjectIsolation = false // 'java-module-dependencies' not yet fully compatible 99 | appBuildFile.text = appBuildFile.text.replace('plugins {', 'plugins { id("org.gradlex.java-module-dependencies")') 100 | libBuildFile.text = libBuildFile.text.replace('plugins {', 'plugins { id("org.gradlex.java-module-dependencies")') 101 | } 102 | 103 | def useTestFixturesPlugin() { 104 | appBuildFile.text = appBuildFile.text.replace('plugins {', 'plugins { id("java-test-fixtures");') 105 | libBuildFile.text = libBuildFile.text.replace('plugins {', 'plugins { id("java-test-fixtures");') 106 | } 107 | 108 | File file(String path) { 109 | new File(projectDir, path).tap { 110 | it.getParentFile().mkdirs() 111 | } 112 | } 113 | 114 | BuildResult build() { 115 | runner('build').build() 116 | } 117 | 118 | BuildResult run() { 119 | runner('run').build() 120 | } 121 | 122 | BuildResult runTests() { 123 | runner(':app:test').build() 124 | } 125 | 126 | BuildResult fail() { 127 | runner('build').buildAndFail() 128 | } 129 | 130 | GradleRunner runner(String... args) { 131 | List latestFeaturesArgs = canUseProjectIsolation ? [ 132 | '-Dorg.gradle.unsafe.isolated-projects=true' 133 | ] : [] 134 | GradleRunner.create() 135 | .forwardOutput() 136 | .withPluginClasspath() 137 | .withProjectDir(projectDir) 138 | .withArguments(Arrays.asList(args) + latestFeaturesArgs + '-s' + '--configuration-cache') 139 | .withDebug(ManagementFactory.getRuntimeMXBean().getInputArguments().toString().contains("-agentlib:jdwp")).with { 140 | gradleVersionUnderTest ? it.withGradleVersion(gradleVersionUnderTest) : it 141 | } 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/testing/test/samples/PluginBuildLocationSampleModifier.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.test.samples; 18 | 19 | import org.gradle.exemplar.model.Command; 20 | import org.gradle.exemplar.model.Sample; 21 | import org.gradle.exemplar.test.runner.SampleModifier; 22 | 23 | import java.io.File; 24 | import java.util.stream.Collectors; 25 | import java.util.stream.Stream; 26 | 27 | public class PluginBuildLocationSampleModifier implements SampleModifier { 28 | @Override 29 | public Sample modify(Sample sampleIn) { 30 | Command cmd = sampleIn.getCommands().remove(0); 31 | File pluginProjectDir = new File("."); 32 | sampleIn.getCommands().add( 33 | new Command(new File(pluginProjectDir, "gradlew").getAbsolutePath(), 34 | cmd.getExecutionSubdirectory(), 35 | Stream.concat(cmd.getArgs().stream(), Stream.of("build", "--warning-mode=all","-PpluginLocation=" + pluginProjectDir.getAbsolutePath())).collect(Collectors.toList()), 36 | cmd.getFlags(), 37 | cmd.getExpectedOutput(), 38 | cmd.isExpectFailure(), 39 | true, 40 | cmd.isAllowDisorderedOutput(), 41 | cmd.getUserInputs())); 42 | return sampleIn; 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/test/java/org/gradlex/javamodule/testing/test/samples/SamplesTest.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright the GradleX team. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package org.gradlex.javamodule.testing.test.samples; 18 | 19 | import org.gradle.exemplar.test.runner.SampleModifiers; 20 | import org.gradle.exemplar.test.runner.SamplesRoot; 21 | import org.gradle.exemplar.test.runner.SamplesRunner; 22 | import org.junit.runner.RunWith; 23 | 24 | @RunWith(SamplesRunner.class) 25 | @SamplesRoot("samples") 26 | @SampleModifiers(PluginBuildLocationSampleModifier.class) 27 | public class SamplesTest { 28 | 29 | } 30 | --------------------------------------------------------------------------------