Skip to content

Commit 5ac4f50

Browse files
authored
Merge pull request quarkusio#35992 from cescoffier/loom-unit-integration
Integrate @ShouldPin/@ShouldNotPin into QuarkusTests
2 parents 268dda7 + 8d80397 commit 5ac4f50

File tree

12 files changed

+986
-0
lines changed

12 files changed

+986
-0
lines changed

bom/application/pom.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3076,6 +3076,11 @@
30763076
<artifactId>quarkus-project-core-extension-codestarts</artifactId>
30773077
<version>${project.version}</version>
30783078
</dependency>
3079+
<dependency>
3080+
<groupId>io.quarkus.junit5</groupId>
3081+
<artifactId>junit5-virtual-threads</artifactId>
3082+
<version>${project.version}</version>
3083+
</dependency>
30793084

30803085
<!-- External dependencies -->
30813086

docs/src/main/asciidoc/virtual-threads.adoc

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -462,6 +462,73 @@ quarkus.virtual-threads.name-prefix=
462462
463463
----
464464

465+
== Testing virtual thread applications
466+
467+
As mentioned above, virtual threads have a few limitations that can drastically affect your application performance and memory usage.
468+
The _junit5-virtual-threads_ extension provides a way to detect pinned carrier threads while running your tests.
469+
Thus, you can eliminate one of the most prominent limitations or be aware of the problem.
470+
471+
To enable this detection:
472+
473+
* 1) Add the `junit5-virtual-threads` dependency to your project:
474+
[source, xml]
475+
----
476+
<dependency>
477+
<groupId>io.quarkus.junit5</groupId>
478+
<artifactId>junit5-virtual-threads</artifactId>
479+
<scope>test</scope>
480+
</dependency>
481+
----
482+
483+
* 2) In your test case, add the `io.quarkus.test.junit5.virtual.VirtualThreadUnit` and `io.quarkus.test.junit.virtual.ShouldNotPin` annotations:
484+
[source, java]
485+
----
486+
@QuarkusTest
487+
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
488+
@VirtualThreadUnit // Use the extension
489+
@ShouldNotPin // Detect pinned carrier thread
490+
class TodoResourceTest {
491+
// ...
492+
}
493+
----
494+
495+
When you run your test (remember to use Java 21+), Quarkus detects pinned carrier threads.
496+
When it happens, the test fails.
497+
498+
The `@ShouldNotPin` can also be used on methods directly.
499+
500+
The _junit5-virtual-threads_ also provides a `@ShouldPin` annotation for cases where pinning is unavoidable.
501+
The following snippet demonstrates the `@ShouldPin` annotation usage and the possibility to inject a `ThreadPinnedEvents` instance in your test to verify when the carrier thread was pinned manually.
502+
503+
[source, java]
504+
----
505+
@VirtualThreadUnit // Use the extension
506+
public class LoomUnitExampleTest {
507+
508+
CodeUnderTest codeUnderTest = new CodeUnderTest();
509+
510+
@Test
511+
@ShouldNotPin
512+
public void testThatShouldNotPin() {
513+
// ...
514+
}
515+
516+
@Test
517+
@ShouldPin(atMost = 1)
518+
public void testThatShouldPinAtMostOnce() {
519+
codeUnderTest.pin();
520+
}
521+
522+
@Test
523+
public void testThatShouldNotPin(ThreadPinnedEvents events) { // Inject an object to check the pin events
524+
Assertions.assertTrue(events.getEvents().isEmpty());
525+
codeUnderTest.pin();
526+
await().until(() -> events.getEvents().size() > 0);
527+
Assertions.assertEquals(events.getEvents().size(), 1);
528+
}
529+
530+
}
531+
----
465532

466533
== Additional references
467534

Lines changed: 298 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,298 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<project xmlns="http://maven.apache.org/POM/4.0.0"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5+
<modelVersion>4.0.0</modelVersion>
6+
7+
<parent>
8+
<groupId>io.quarkus</groupId>
9+
<artifactId>quarkus-parent</artifactId>
10+
<version>999-SNAPSHOT</version>
11+
<relativePath>../parent/pom.xml</relativePath>
12+
</parent>
13+
14+
<groupId>io.quarkus.junit5</groupId>
15+
<artifactId>junit5-virtual-threads</artifactId>
16+
17+
<name>Quarkus - JUnit 5 Extension - Virtual Threads</name>
18+
<description>Module that allows detecting virtual threads pinning</description>
19+
<url>https://github.com/quarkusio/quarkus</url>
20+
21+
<licenses>
22+
<license>
23+
<name>Apache License, Version 2.0</name>
24+
<distribution>repo</distribution>
25+
<url>https://www.apache.org/licenses/LICENSE-2.0.html</url>
26+
</license>
27+
</licenses>
28+
29+
<scm child.scm.connection.inherit.append.path="false"
30+
child.scm.developerConnection.inherit.append.path="false"
31+
child.scm.url.inherit.append.path="false">
32+
<url>https://github.com/quarkusio/quarkus</url>
33+
<connection>scm:git:[email protected]:quarkusio/quarkus.git</connection>
34+
<developerConnection>scm:git:[email protected]:quarkusio/quarkus.git</developerConnection>
35+
<tag>HEAD</tag>
36+
</scm>
37+
38+
<properties>
39+
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
40+
<maven.compiler.target>11</maven.compiler.target>
41+
<maven.compiler.source>11</maven.compiler.source>
42+
<maven.compiler.release>11</maven.compiler.release>
43+
44+
<compiler.plugin.version>3.11.0</compiler.plugin.version>
45+
<enforcer.plugin.version>3.2.1</enforcer.plugin.version>
46+
<surefire.plugin.version>3.1.2</surefire.plugin.version>
47+
<jandex.version>3.1.3</jandex.version>
48+
<formatter-maven-plugin.version>2.23.0</formatter-maven-plugin.version>
49+
<impsort-maven-plugin.version>1.9.0</impsort-maven-plugin.version>
50+
51+
<junit.jupiter.version>5.9.3</junit.jupiter.version>
52+
</properties>
53+
54+
55+
<dependencies>
56+
<dependency>
57+
<groupId>org.junit.jupiter</groupId>
58+
<artifactId>junit-jupiter</artifactId>
59+
<scope>compile</scope>
60+
<version>${junit.jupiter.version}</version>
61+
</dependency>
62+
</dependencies>
63+
64+
<build>
65+
<pluginManagement>
66+
<plugins>
67+
<plugin>
68+
<artifactId>maven-compiler-plugin</artifactId>
69+
<version>${compiler.plugin.version}</version>
70+
</plugin>
71+
<plugin>
72+
<groupId>io.smallrye</groupId>
73+
<artifactId>jandex-maven-plugin</artifactId>
74+
<version>${jandex.version}</version>
75+
</plugin>
76+
<plugin>
77+
<artifactId>maven-javadoc-plugin</artifactId>
78+
<configuration>
79+
<quiet>true</quiet>
80+
<doclint>none</doclint>
81+
</configuration>
82+
</plugin>
83+
<plugin>
84+
<groupId>org.apache.maven.plugins</groupId>
85+
<artifactId>maven-enforcer-plugin</artifactId>
86+
<dependencies>
87+
<dependency>
88+
<groupId>io.quarkus</groupId>
89+
<artifactId>quarkus-enforcer-rules</artifactId>
90+
<version>${project.version}</version>
91+
</dependency>
92+
</dependencies>
93+
<executions>
94+
<execution>
95+
<id>enforce</id>
96+
<configuration>
97+
<rules>
98+
<dependencyConvergence/>
99+
<externalRules>
100+
<location>classpath:enforcer-rules/quarkus-require-java-version.xml</location>
101+
</externalRules>
102+
<externalRules>
103+
<location>classpath:enforcer-rules/quarkus-require-maven-version.xml</location>
104+
</externalRules>
105+
<externalRules>
106+
<location>classpath:enforcer-rules/quarkus-banned-dependencies.xml</location>
107+
</externalRules>
108+
<bannedDependencies>
109+
<excludes>
110+
<!-- findbugs is not required at runtime -->
111+
<exclude>com.google.code.findbugs:jsr305</exclude>
112+
<!-- com.google.guava:listenablefuture is empty and the ListenableFuture class is available in Guava -->
113+
<exclude>com.google.guava:listenablefuture</exclude>
114+
</excludes>
115+
</bannedDependencies>
116+
</rules>
117+
</configuration>
118+
<goals>
119+
<goal>enforce</goal>
120+
</goals>
121+
</execution>
122+
</executions>
123+
</plugin>
124+
<plugin>
125+
<artifactId>maven-surefire-plugin</artifactId>
126+
<version>${surefire.plugin.version}</version>
127+
<configuration>
128+
<!-- combine.self suppresses warnings about java.io.tmpdir being defined twice -->
129+
<systemPropertyVariables combine.self="override"/>
130+
<!-- set tmpdir as early as possible because failsafe sets it too late for JDK16 -->
131+
<argLine>-Djava.io.tmpdir="${project.build.directory}"</argLine>
132+
<excludedEnvironmentVariables>MAVEN_OPTS</excludedEnvironmentVariables>
133+
</configuration>
134+
</plugin>
135+
<!-- Replicate what's in parent, since this pom doesn't inherit parent but IDE settings will be common -->
136+
<plugin>
137+
<groupId>net.revelc.code.formatter</groupId>
138+
<artifactId>formatter-maven-plugin</artifactId>
139+
<version>${formatter-maven-plugin.version}</version>
140+
<dependencies>
141+
<dependency>
142+
<artifactId>quarkus-ide-config</artifactId>
143+
<groupId>io.quarkus</groupId>
144+
<version>${project.version}</version>
145+
</dependency>
146+
</dependencies>
147+
<configuration>
148+
<!-- store outside of target to speed up formatting when mvn clean is used -->
149+
<cachedir>.cache/formatter-maven-plugin-${formatter-maven-plugin.version}</cachedir>
150+
<configFile>eclipse-format.xml</configFile>
151+
<lineEnding>LF</lineEnding>
152+
<skip>${format.skip}</skip>
153+
</configuration>
154+
</plugin>
155+
<plugin>
156+
<groupId>net.revelc.code</groupId>
157+
<artifactId>impsort-maven-plugin</artifactId>
158+
<version>${impsort-maven-plugin.version}</version>
159+
<configuration>
160+
<!-- store outside of target to speed up formatting when mvn clean is used -->
161+
<cachedir>.cache/impsort-maven-plugin-${impsort-maven-plugin.version}</cachedir>
162+
<groups>java.,javax.,jakarta.,org.,com.</groups>
163+
<staticGroups>*</staticGroups>
164+
<skip>${format.skip}</skip>
165+
<removeUnused>true</removeUnused>
166+
</configuration>
167+
</plugin>
168+
</plugins>
169+
</pluginManagement>
170+
</build>
171+
172+
<distributionManagement>
173+
<snapshotRepository>
174+
<id>sonatype-nexus-snapshots</id>
175+
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
176+
</snapshotRepository>
177+
<repository>
178+
<id>sonatype-nexus-release</id>
179+
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
180+
</repository>
181+
</distributionManagement>
182+
183+
<profiles>
184+
<profile>
185+
<id>quick-build</id>
186+
<activation>
187+
<property>
188+
<name>quickly</name>
189+
</property>
190+
</activation>
191+
<properties>
192+
<skipTests>true</skipTests>
193+
<skipITs>true</skipITs>
194+
<enforcer.skip>true</enforcer.skip>
195+
</properties>
196+
<build>
197+
<defaultGoal>clean install</defaultGoal>
198+
</build>
199+
</profile>
200+
201+
<profile>
202+
<!-- separate "quickly" profile for CI to keep local "quickly" demands separated from CI demands -->
203+
<id>quick-build-ci</id>
204+
<activation>
205+
<property>
206+
<name>quickly-ci</name>
207+
</property>
208+
</activation>
209+
<properties>
210+
<skipTests>true</skipTests>
211+
<skipITs>true</skipITs>
212+
<enforcer.skip>true</enforcer.skip>
213+
<format.skip>true</format.skip>
214+
</properties>
215+
</profile>
216+
<profile>
217+
<id>format</id>
218+
<activation>
219+
<activeByDefault>true</activeByDefault>
220+
<property>
221+
<name>!no-format</name>
222+
</property>
223+
</activation>
224+
<build>
225+
<plugins>
226+
<plugin>
227+
<groupId>net.revelc.code.formatter</groupId>
228+
<artifactId>formatter-maven-plugin</artifactId>
229+
<executions>
230+
<execution>
231+
<phase>process-sources</phase>
232+
<goals>
233+
<goal>format</goal>
234+
</goals>
235+
</execution>
236+
</executions>
237+
</plugin>
238+
<plugin>
239+
<groupId>net.revelc.code</groupId>
240+
<artifactId>impsort-maven-plugin</artifactId>
241+
<executions>
242+
<execution>
243+
<id>sort-imports</id>
244+
<goals>
245+
<goal>sort</goal>
246+
</goals>
247+
</execution>
248+
</executions>
249+
<configuration>
250+
<removeUnused>true</removeUnused>
251+
</configuration>
252+
</plugin>
253+
</plugins>
254+
</build>
255+
</profile>
256+
<profile>
257+
<id>validate</id>
258+
<activation>
259+
<activeByDefault>true</activeByDefault>
260+
<property>
261+
<name>no-format</name>
262+
</property>
263+
</activation>
264+
<build>
265+
<plugins>
266+
<plugin>
267+
<groupId>net.revelc.code.formatter</groupId>
268+
<artifactId>formatter-maven-plugin</artifactId>
269+
<executions>
270+
<execution>
271+
<phase>process-sources</phase>
272+
<goals>
273+
<goal>validate</goal>
274+
</goals>
275+
</execution>
276+
</executions>
277+
</plugin>
278+
<plugin>
279+
<groupId>net.revelc.code</groupId>
280+
<artifactId>impsort-maven-plugin</artifactId>
281+
<configuration>
282+
<removeUnused>true</removeUnused>
283+
</configuration>
284+
<executions>
285+
<execution>
286+
<id>check-imports</id>
287+
<goals>
288+
<goal>check</goal>
289+
</goals>
290+
</execution>
291+
</executions>
292+
</plugin>
293+
</plugins>
294+
</build>
295+
</profile>
296+
</profiles>
297+
298+
</project>

0 commit comments

Comments
 (0)