├── .github └── workflows │ └── main.yml ├── .gitignore ├── .idea ├── .gitignore ├── com.github.sormuras.junit.looming.iml ├── compiler.xml ├── google-java-format.xml ├── junit5-looming.iml ├── libraries │ ├── junit_jupiter.xml │ └── junit_platform_console.xml ├── misc.xml ├── modules.xml ├── runConfigurations │ └── All_in_test_integration.xml ├── test.integration.iml └── vcs.xml ├── LICENSE ├── README.md ├── STORY.md ├── com.github.sormuras.junit.looming ├── com │ └── github │ │ └── sormuras │ │ └── junit │ │ └── looming │ │ ├── LoomTestEngine.java │ │ └── Test.java └── module-info.java ├── lib ├── apiguardian-api-1.1.2.jar ├── junit-jupiter-5.9.2.jar ├── junit-jupiter-api-5.9.2.jar ├── junit-jupiter-engine-5.9.2.jar ├── junit-jupiter-params-5.9.2.jar ├── junit-platform-commons-1.9.2.jar ├── junit-platform-console-1.9.2.jar ├── junit-platform-engine-1.9.2.jar ├── junit-platform-launcher-1.9.2.jar ├── junit-platform-reporting-1.9.2.jar └── opentest4j-1.2.0.jar └── test.integration └── test ├── java ├── module-info.java └── test │ └── integration │ ├── IntegrationTests.java │ └── PrependModuleName.java └── resources └── junit-platform.properties /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | workflow_dispatch: 4 | 5 | jobs: 6 | build: 7 | name: "JUnit 5 Looming (${{ matrix.os }})" 8 | strategy: 9 | fail-fast: false 10 | matrix: 11 | os: [ ubuntu-latest ] 12 | runs-on: ${{ matrix.os }} 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-java@v3 16 | with: 17 | distribution: oracle 18 | java-version: 21 19 | - name: 'Build' 20 | run: | 21 | javac --module-path lib --module-source-path . --module com.github.sormuras.junit.looming -d classes 22 | jar --create --file lib/com.github.sormuras.junit.looming.jar -C classes/com.github.sormuras.junit.looming . 23 | - name: 'Prepare JUnit Platform Runs' 24 | run: echo "JUNIT=${JAVA_HOME}/bin/java -Xmx2G --module-path lib --module org.junit.platform.console --details SUMMARY --scan-modules" >> $GITHUB_ENV 25 | - name: 'Normal Threads running 10.000 tests' 26 | run: $JUNIT --config virtual=false --config tests=10000 27 | - name: 'Normal Threads running 100.000 tests' 28 | run: $JUNIT --config virtual=false --config tests=100000 29 | - name: 'Virtual Threads running 10.000 tests' 30 | run: $JUNIT --config virtual=true --config tests=10000 31 | - name: 'Virtual Threads running 100.000 tests' 32 | run: $JUNIT --config virtual=true --config tests=100000 33 | - name: 'Virtual Threads running 1.000.000 tests' 34 | run: $JUNIT --config virtual=true --config tests=1000000 35 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | classes/ 2 | 3 | *.jar 4 | *.log 5 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | out/ 2 | 3 | uiDesigner.xml 4 | workspace.xml -------------------------------------------------------------------------------- /.idea/com.github.sormuras.junit.looming.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /.idea/compiler.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/google-java-format.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /.idea/junit5-looming.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.idea/libraries/junit_jupiter.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /.idea/libraries/junit_platform_console.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /.idea/runConfigurations/All_in_test_integration.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 15 | -------------------------------------------------------------------------------- /.idea/test.integration.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # junit5-looming 2 | 3 | JUnit 5 Test Engine show-casing Java's Virtual Threads 4 | 5 | - https://jdk.java.net/loom **Project Loom Early-Access Builds** 6 | - https://wiki.openjdk.java.net/display/loom/Getting+started **Getting started with Loom** 7 | - https://mail.openjdk.java.net/pipermail/loom-dev **The loom-dev Archives** 8 | - https://cr.openjdk.java.net/~rpressler/loom/loom/sol1_part1.html **State of Loom** Ron Pressler, May 2020 9 | - https://www.youtube.com/watch?v=NV46KFV1m-4 **Project Loom Update** Alan Bateman, Rickard Bäckman, July 2019 10 | 11 | ## timings 12 | 13 | | Processor | Threads 10.000 | Threads 100.000 | Virtual 10.000 | Virtual 100.000 | Virtual 1.000.000 | 14 | |----------------------|----------------|-----------------|----------------|-----------------|-------------------| 15 | | Ryzen 3700X (Win 10) | 5.6 | 51 | 1.1 | 1.2 | 3.9 :rocket: | 16 | | i7-3770K (Win 10) | 6 | 51 | 1.2 | 1.5 | 5.2 :rocket: | 17 | | i7-7920HQ (Mac OS) | 6 | 51 | 1.2 | 1.7 | 7.1 :rocket: | 18 | | GitHub/Azure (Linux) | 6 | n.a. | 3 | 20 | 223 | 19 | | GH 2019-12-19 | 7 | 53 | 3 | 18 | 227 | 20 | | GH 2020-01-25 | 6 | 53 | 2 | 14 | 162 | 21 | | GH 2020-01-28 | 7 | 53 | 3 | 16 | 150 | 22 | | GH 2020-02-27 | 7 | 52 | 3 | 17 | 150 | 23 | | GH 2020-05-15 | 6 | 53 | 3 | 18 | 183 | 24 | | GH 2020-06-29 | 6 | 51 | 2 | 12 | 120 | 25 | | GH 2020-11-12 | 8 | 54 | 3 | 19 | 159 | 26 | | GH 2020-12-01 | 6 | 50 | 2 | 13 | 129 | 27 | | GH 2021-01-16 | 6 | 51 | 2 | 12 | 114 | 28 | | GH 2021-03-13 | 6 | 54 | 3 | 16 | 134 | 29 | | GH 2021-09-08 | 6 | 51 | 3 | 11 | 141 | 30 | | GH 2021-11-16 | 6 | 51 | 3 | 17 | 193 | 31 | | GH 2022-02-05 | 6 | 51 | 2 | 12 | 149 | 32 | | GH 2022-05-11 | 6 | 53 | 3 | 12 | 146 | 33 | | GH 2023-02-15 | 6 | 51 | 2 | 11 | 149 | 34 | | GH 2023-10-12 | 6 | 51 | 3 | 18 | 274 | 35 | 36 | ## projects, demos, blogs, and spikes using loom 37 | 38 | - https://github.com/forax/loom-fiber (Rémi Forax) 39 | - https://i-rant.arnaudbos.com/loom-part-0-rationale/ (@arnaud_bos) 40 | - https://horstmann.com/unblog/2019-12-05/ (@cayhorstmann) 41 | - https://blog.softwaremill.com/will-project-loom-obliterate-java-futures-fb1a28508232 (Adam Warski) 42 | - https://developers.redhat.com/blog/2019/06/19/project-loom-lightweight-java-threads (Faisal Masood) 43 | -------------------------------------------------------------------------------- /STORY.md: -------------------------------------------------------------------------------- 1 | # JUnit 5 & Project Loom 2 | 3 | - JUnit 5 4 | - Project Loom 5 | - TestEngine: `junit5-looming` 6 | - TestEngine: `Jupiter` + Loom 7 | - Conclusion 8 | 9 | ## JUnit 5 10 | 11 | Explain concept of `TestEngine` 12 | Platform + Jupiter + Vintage + ... 13 | 14 | ## Project Loom 15 | 16 | A Users' View on Project Loom 17 | 18 | Let's read the API documentation of class `java.base/java.lang.Thread` together: 19 | https://download.java.net/java/early_access/loom/docs/api/java.base/java/lang/Thread.html 20 | 21 | > A thread is a thread of execution in a program. 22 | > The Java virtual machine allows an application to have multiple threads of execution running concurrently. 23 | > 24 | > Thread supports the creation of threads that are scheduled by the operating system. 25 | > These threads are sometimes known as kernel threads or heavyweight threads and will usually have a large stack and other resources that are maintained by the operating system. 26 | > Kernel threads are suitable for executing all types of tasks but they are a limited resource. 27 | > 28 | > Thread also supports the creation of virtual threads that are scheduled by the Java virtual machine using a small set of kernel threads. 29 | > Virtual threads will typically require few resources and a single Java virtual machine may support millions of virtual threads. 30 | > Virtual threads are suitable for executing tasks that spend most of the time blocked, often waiting for synchronous blocking I/O operations to complete. 31 | > Locking and I/O operations are the scheduling points where a kernel thread is re-scheduled from one virtual thread to another. 32 | > Code executing in virtual threads will usually not be aware of the underlying kernel thread, and in particular, the currentThread() method, to obtain a reference to the current thread, will return the Thread object for the virtual thread. 33 | > 34 | > Thread defines factory methods, and a Thread.Builder API, for creating kernel or virtual threads. 35 | > It also defines (for compatibility and customization reasons) constructors for creating kernel threads. 36 | > Newer code is encouraged to use the factory methods or the builder rather than the constructors. 37 | 38 | With this documentation (noted the `@since 1` tag?) in mind, let's also read Ron's mail: 39 | https://mail.openjdk.java.net/pipermail/loom-dev/2019-December/000931.html 40 | 41 | > API 42 | > --- 43 | > 44 | > This prototype has the new API we introduced in October, that represents Loom's 45 | > lightweight user-mode threads as instances of java.lang.Thread [1]; the 46 | > rationale for that decision is explained in [2]. We are now calling Loom's 47 | > lightweight user-mode threads "virtual threads" [3]. 48 | > 49 | > Virtual threads can be created with the newly-introduced Thread.Builder class. 50 | > It can be used to directly build Thread instances, or to create a ThreadFactory 51 | > instance. For example: 52 | > 53 | > Thread thread = Thread.builder().virtual().task(() -> { ... }).start(); 54 | > 55 | > Thread.Builder exposes other settings we're experimenting with, like optionally 56 | > disallowing the use of ThreadLocal. 57 | > 58 | > The previous EA build introduced structured concurrency [4]. In the updated 59 | > prototype, a more limited form of a structured concurrency can be achieved with 60 | > ExecutorService; for example: 61 | > 62 | > ThreadFactory factory = Thread.builder().virtual().factory(); 63 | > try (ExecutorService executor = Executors.newUnboundedExecutor(factory)) { 64 | > executor.submit(task1); 65 | > executor.submit(task2); 66 | > } 67 | > 68 | > The new Executors.newUnboundedExecutor method creates an ExecutorService that 69 | > spawns a new thread for each submitted task -- in this case, a virtual thread 70 | > constructed by the provided factory. 71 | > 72 | > [...] 73 | > 74 | > [1]: https://mail.openjdk.java.net/pipermail/loom-dev/2019-October/000825.html 75 | > [2]: https://mail.openjdk.java.net/pipermail/loom-dev/2019-October/000825.html 76 | > [3]: https://mail.openjdk.java.net/pipermail/loom-dev/2019-November/000864.html 77 | > [4]: https://vorpus.org/blog/notes-on-structured-concurrency-or-go-statement-considered-harmful/ 78 | 79 | This talk focuses on the usage of Loom. 80 | Therefore, the sections on "Performance", "Debugging", "Profiling and Monitoring", "Continuations", and "Stability" are left out on purpose. 81 | 82 | Enough of theory and documentation. 83 | Let's "copy and paste" some of the code snippets presented in order to get a JUnit Platform TestEngine implementation running on Loom. 84 | 85 | ## TestEngine: `junit5-looming` 86 | 87 | https://github.com/sormuras/junit5-looming 88 | 89 | JUnit Platform + "A Virtual Thread is a Thread" = junit5-looming 90 | Starting with a simplistic "test" that sleeps some milliseconds: 91 | 92 | Thread.sleep((long) (Math.random() * 1000)) 93 | 94 | Generate a bunch of those "tests" and execute them all at once. 95 | Make the amount of generated test configurable. 96 | Raise the bar and see what happens. 97 | 98 | if (virtual) { 99 | var factory = Thread.builder().virtual().factory(); 100 | return Executors.newUnboundedExecutor(factory); 101 | } 102 | return Executors.newFixedThreadPool(1000); 103 | 104 | TODO: Live Demo 105 | 106 | Round up with table of not-benchmarks: 107 | 108 | | Processor | Threads 10.000 | Threads 100.000 | Virtual 10.000 | Virtual 100.000 | Virtual 1.000.000| 109 | |----------------------- |----------------|-----------------|----------------|---------------- |------------------| 110 | | Ryzen 3700X (Win 10) | 5.6 | 51 | 1.1 | 1.2 | 3.9 :rocket: | 111 | | i7-3770K (Win 10) | 6 | 51 | 1.2 | 1.5 | 5.2 :rocket: | 112 | | i7-7920HQ (Mac OS) | 6 | 51 | 1.2 | 1.7 | 7.1 :rocket: | 113 | | GitHub/Azure (Linux) | 6 | n.a. | 3 | 20 | 223 | 114 | | [GH 2019-12-19] (Linux)| 7 | 53 | 3 | 18 | 227 | 115 | | [GH 2020-01-25] (Linux)| 6 | 53 | 2 | 14 | 162 | 116 | | [GH 2020-01-28] (Linux)| 7 | 53 | 3 | 16 | 150 | 117 | | [GH 2020-02-27] (Linux)| 7 | 52 | 3 | 17 | 150 | 118 | 119 | ## TestEngine: `Jupiter` + Loom 120 | 121 | https://github.com/junit-team/junit5/tree/loom 122 | 123 | Jupiter utilizes Fork-Join-Pool for parallel test execution 124 | Replace system threads with virtual threads in Jupiter - what else? 125 | Initial prototype of parallel execution using virtual threads looked promising: 126 | https://github.com/junit-team/junit5/commit/dfb8ab64b073d3e26af56e86038edea1b924727b#diff-37325a23fcbc3b777c6e974e8761f85c 127 | 128 | ThreadFactory threadFactory = Thread.builder().virtual().name("junit-executor", 1).factory(); 129 | executorService = Executors.newUnboundedExecutor(threadFactory); 130 | 131 | Execute a single test task: 132 | 133 | CompletableFuture.runAsync(testTask::execute, executorService); 134 | 135 | Execute multiple test tasks: 136 | 137 | CompletableFuture.allOf(testTasks.stream().map(this::submit).toArray(CompletableFuture[]::new)).join(); 138 | 139 | Too easy and we got more "green lights" than expected. 140 | 141 | Finished the spike some hours later: 142 | https://github.com/junit-team/junit5/commit/119345734f18792fe8831f9e5feaa0f70242e8e7#diff-37325a23fcbc3b777c6e974e8761f85c 143 | 144 | After that, all parallel execution related tests went "green". 145 | 146 | TODO: Create "big test suite" using Jupiter's normal, dynamic, and parameterized test features. 147 | TODO: Explain and demo JitPack-ed distribution of the junit5/loom branch. 148 | 149 | ## Conclusion Project Loom 150 | 151 | OpenJDK Early Access builds are fun to play with 152 | Feedback is welcome on the related mailing list 153 | For Project Loom that is: https://mail.openjdk.java.net/pipermail/loom-dev 154 | 155 | ## Conclusion JUnit 5 + Loom 156 | 157 | Modular design of JUnit 5 works out 158 | MR-JAR technology enables virtual threads on JDK 15-loom by default 159 | Blocking tests (IO-intensive) will profit out of the box 160 | Seeing 100.000+ (sleepy fake) tests executed in IDE is ... WOAH! (= 161 | "Will you backport Loom support to JUnit 4?!" 162 | -------------------------------------------------------------------------------- /com.github.sormuras.junit.looming/com/github/sormuras/junit/looming/LoomTestEngine.java: -------------------------------------------------------------------------------- 1 | package com.github.sormuras.junit.looming; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.ExecutorService; 5 | import java.util.concurrent.Executors; 6 | import java.util.function.BiConsumer; 7 | import org.junit.platform.engine.EngineDiscoveryRequest; 8 | import org.junit.platform.engine.EngineExecutionListener; 9 | import org.junit.platform.engine.ExecutionRequest; 10 | import org.junit.platform.engine.TestDescriptor; 11 | import org.junit.platform.engine.TestEngine; 12 | import org.junit.platform.engine.TestExecutionResult; 13 | import org.junit.platform.engine.UniqueId; 14 | import org.junit.platform.engine.support.descriptor.EngineDescriptor; 15 | 16 | /** Test engine implementation generating and executing "sleepy tests". */ 17 | public class LoomTestEngine implements TestEngine { 18 | 19 | /** 20 | * Number of tests to generate. 21 | * 22 | *

{@code -Dtests=12345} 23 | * 24 | * @see JUnit 26 | * Configuration Parameters 27 | */ 28 | public static final int TESTS = 20; 29 | 30 | @Override 31 | public String getId() { 32 | return "looming"; 33 | } 34 | 35 | @Override 36 | public TestDescriptor discover(EngineDiscoveryRequest request, UniqueId uniqueId) { 37 | var virtual = request.getConfigurationParameters().getBoolean("virtual").orElse(false); 38 | var caption = "Looming on " + Runtime.version() + " [" + (virtual ? "virtual" : "system") + "]"; 39 | System.out.println(caption); 40 | var engine = new EngineDescriptor(uniqueId, caption); 41 | int tests = request.getConfigurationParameters().get("tests", Integer::parseInt).orElse(TESTS); 42 | System.out.println("Creating " + tests + " tests"); 43 | for (int i = 0; i < tests; i++) { 44 | engine.addChild(new Test(engine.getUniqueId(), i)); 45 | } 46 | return engine; 47 | } 48 | 49 | @Override 50 | public void execute(ExecutionRequest request) { 51 | var engine = request.getRootTestDescriptor(); 52 | var listener = request.getEngineExecutionListener(); 53 | var virtual = request.getConfigurationParameters().getBoolean("virtual").orElse(false); 54 | var tests = engine.getChildren(); 55 | System.out.println("Scheduling " + tests.size() + " tests"); 56 | listener.executionStarted(engine); 57 | try (var executor = newExecutorService(virtual)) { 58 | for (var test : tests) { 59 | listener.executionStarted(test); 60 | var future = CompletableFuture.runAsync((Runnable) test, executor); 61 | future.whenCompleteAsync(markSuccessfullyFinished(listener, test)); 62 | } 63 | System.out.printf("Awaiting all %s threads to complete...%n", virtual ? "virtual" : "system"); 64 | } finally { 65 | listener.executionFinished(engine, TestExecutionResult.successful()); 66 | } 67 | } 68 | 69 | private static ExecutorService newExecutorService(boolean virtual) { 70 | if (virtual) return Executors.newVirtualThreadPerTaskExecutor(); 71 | return Executors.newFixedThreadPool(1000); 72 | } 73 | 74 | private static BiConsumer markSuccessfullyFinished( 75 | EngineExecutionListener listener, TestDescriptor test) { 76 | 77 | return (r, t) -> listener.executionFinished(test, TestExecutionResult.successful()); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /com.github.sormuras.junit.looming/com/github/sormuras/junit/looming/Test.java: -------------------------------------------------------------------------------- 1 | package com.github.sormuras.junit.looming; 2 | 3 | import org.junit.platform.engine.UniqueId; 4 | import org.junit.platform.engine.support.descriptor.AbstractTestDescriptor; 5 | 6 | class Test extends AbstractTestDescriptor implements Runnable { 7 | 8 | Test(UniqueId uniqueId, int index) { 9 | super(uniqueId.append("test", "#" + index), "LoomingTest #" + index); 10 | } 11 | 12 | @Override 13 | public Type getType() { 14 | return Type.TEST; 15 | } 16 | 17 | @Override 18 | public void run() { 19 | try { 20 | Thread.sleep((long) (Math.random() * 1000)); 21 | } catch (InterruptedException e) { 22 | // ignore 23 | } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /com.github.sormuras.junit.looming/module-info.java: -------------------------------------------------------------------------------- 1 | module com.github.sormuras.junit.looming { 2 | requires org.junit.platform.engine; 3 | provides org.junit.platform.engine.TestEngine with 4 | com.github.sormuras.junit.looming.LoomTestEngine; 5 | } 6 | -------------------------------------------------------------------------------- /lib/apiguardian-api-1.1.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/apiguardian-api-1.1.2.jar -------------------------------------------------------------------------------- /lib/junit-jupiter-5.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-jupiter-5.9.2.jar -------------------------------------------------------------------------------- /lib/junit-jupiter-api-5.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-jupiter-api-5.9.2.jar -------------------------------------------------------------------------------- /lib/junit-jupiter-engine-5.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-jupiter-engine-5.9.2.jar -------------------------------------------------------------------------------- /lib/junit-jupiter-params-5.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-jupiter-params-5.9.2.jar -------------------------------------------------------------------------------- /lib/junit-platform-commons-1.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-platform-commons-1.9.2.jar -------------------------------------------------------------------------------- /lib/junit-platform-console-1.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-platform-console-1.9.2.jar -------------------------------------------------------------------------------- /lib/junit-platform-engine-1.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-platform-engine-1.9.2.jar -------------------------------------------------------------------------------- /lib/junit-platform-launcher-1.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-platform-launcher-1.9.2.jar -------------------------------------------------------------------------------- /lib/junit-platform-reporting-1.9.2.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/junit-platform-reporting-1.9.2.jar -------------------------------------------------------------------------------- /lib/opentest4j-1.2.0.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sormuras/junit5-looming/0fe2aebb1dbab9f151a83955bdde9007d8ca1a2c/lib/opentest4j-1.2.0.jar -------------------------------------------------------------------------------- /test.integration/test/java/module-info.java: -------------------------------------------------------------------------------- 1 | open /*test*/ module test.integration { 2 | // module under test 3 | requires com.github.sormuras.junit.looming; 4 | // modules we're testing with 5 | requires org.junit.jupiter; 6 | requires org.junit.platform.console; 7 | requires org.junit.platform.engine; 8 | 9 | uses org.junit.platform.engine.TestEngine; 10 | } 11 | -------------------------------------------------------------------------------- /test.integration/test/java/test/integration/IntegrationTests.java: -------------------------------------------------------------------------------- 1 | package test.integration; 2 | 3 | import static org.junit.jupiter.api.Assertions.assertEquals; 4 | 5 | import java.util.ServiceLoader; 6 | import java.util.Set; 7 | import org.junit.jupiter.api.DisplayNameGeneration; 8 | import org.junit.jupiter.api.Test; 9 | import org.junit.platform.engine.TestEngine; 10 | 11 | @DisplayNameGeneration(PrependModuleName.class) 12 | class IntegrationTests { 13 | 14 | @Test 15 | void loadLoomingEngineAndCheckItsModuleAPI() { 16 | var module = 17 | ServiceLoader.load(TestEngine.class).stream() 18 | .map(ServiceLoader.Provider::get) 19 | .filter(engine -> engine.getId().equals("looming")) 20 | .findFirst() 21 | .orElseThrow() 22 | .getClass() 23 | .getModule(); 24 | assertEquals("com.github.sormuras.junit.looming", module.getName()); 25 | assertEquals(Set.of(), module.getDescriptor().exports()); 26 | assertEquals( 27 | "[org.junit.platform.engine.TestEngine with [com.github.sormuras.junit.looming.LoomTestEngine]]", 28 | module.getDescriptor().provides().toString()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /test.integration/test/java/test/integration/PrependModuleName.java: -------------------------------------------------------------------------------- 1 | package test.integration; 2 | 3 | import org.junit.jupiter.api.DisplayNameGenerator; 4 | 5 | public class PrependModuleName extends DisplayNameGenerator.Standard { 6 | 7 | @Override 8 | public String generateDisplayNameForClass(Class testClass) { 9 | return testClass.getModule().getName() + "/" + super.generateDisplayNameForClass(testClass); 10 | } 11 | 12 | @Override 13 | public String generateDisplayNameForNestedClass(Class nestedClass) { 14 | return nestedClass.getModule().getName() + "/" + super.generateDisplayNameForNestedClass(nestedClass); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /test.integration/test/resources/junit-platform.properties: -------------------------------------------------------------------------------- 1 | tests=9000 2 | virtual=true 3 | --------------------------------------------------------------------------------