Skip to content

Commit

Permalink
Merge pull request quarkusio#35992 from cescoffier/loom-unit-integration
Browse files Browse the repository at this point in the history
Integrate @ShouldPin/@ShouldNotPin into QuarkusTests
  • Loading branch information
cescoffier authored Sep 21, 2023
2 parents 268dda7 + 8d80397 commit 5ac4f50
Show file tree
Hide file tree
Showing 12 changed files with 986 additions and 0 deletions.
5 changes: 5 additions & 0 deletions bom/application/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -3076,6 +3076,11 @@
<artifactId>quarkus-project-core-extension-codestarts</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>io.quarkus.junit5</groupId>
<artifactId>junit5-virtual-threads</artifactId>
<version>${project.version}</version>
</dependency>

<!-- External dependencies -->

Expand Down
67 changes: 67 additions & 0 deletions docs/src/main/asciidoc/virtual-threads.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -462,6 +462,73 @@ quarkus.virtual-threads.name-prefix=
----

== Testing virtual thread applications

As mentioned above, virtual threads have a few limitations that can drastically affect your application performance and memory usage.
The _junit5-virtual-threads_ extension provides a way to detect pinned carrier threads while running your tests.
Thus, you can eliminate one of the most prominent limitations or be aware of the problem.

To enable this detection:

* 1) Add the `junit5-virtual-threads` dependency to your project:
[source, xml]
----
<dependency>
<groupId>io.quarkus.junit5</groupId>
<artifactId>junit5-virtual-threads</artifactId>
<scope>test</scope>
</dependency>
----

* 2) In your test case, add the `io.quarkus.test.junit5.virtual.VirtualThreadUnit` and `io.quarkus.test.junit.virtual.ShouldNotPin` annotations:
[source, java]
----
@QuarkusTest
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
@VirtualThreadUnit // Use the extension
@ShouldNotPin // Detect pinned carrier thread
class TodoResourceTest {
// ...
}
----

When you run your test (remember to use Java 21+), Quarkus detects pinned carrier threads.
When it happens, the test fails.

The `@ShouldNotPin` can also be used on methods directly.

The _junit5-virtual-threads_ also provides a `@ShouldPin` annotation for cases where pinning is unavoidable.
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.

[source, java]
----
@VirtualThreadUnit // Use the extension
public class LoomUnitExampleTest {
CodeUnderTest codeUnderTest = new CodeUnderTest();
@Test
@ShouldNotPin
public void testThatShouldNotPin() {
// ...
}
@Test
@ShouldPin(atMost = 1)
public void testThatShouldPinAtMostOnce() {
codeUnderTest.pin();
}
@Test
public void testThatShouldNotPin(ThreadPinnedEvents events) { // Inject an object to check the pin events
Assertions.assertTrue(events.getEvents().isEmpty());
codeUnderTest.pin();
await().until(() -> events.getEvents().size() > 0);
Assertions.assertEquals(events.getEvents().size(), 1);
}
}
----

== Additional references

Expand Down
298 changes: 298 additions & 0 deletions independent-projects/junit5-virtual-threads/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,298 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-parent</artifactId>
<version>999-SNAPSHOT</version>
<relativePath>../parent/pom.xml</relativePath>
</parent>

<groupId>io.quarkus.junit5</groupId>
<artifactId>junit5-virtual-threads</artifactId>

<name>Quarkus - JUnit 5 Extension - Virtual Threads</name>
<description>Module that allows detecting virtual threads pinning</description>
<url>https://github.com/quarkusio/quarkus</url>

<licenses>
<license>
<name>Apache License, Version 2.0</name>
<distribution>repo</distribution>
<url>https://www.apache.org/licenses/LICENSE-2.0.html</url>
</license>
</licenses>

<scm child.scm.connection.inherit.append.path="false"
child.scm.developerConnection.inherit.append.path="false"
child.scm.url.inherit.append.path="false">
<url>https://github.com/quarkusio/quarkus</url>
<connection>scm:git:[email protected]:quarkusio/quarkus.git</connection>
<developerConnection>scm:git:[email protected]:quarkusio/quarkus.git</developerConnection>
<tag>HEAD</tag>
</scm>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.target>11</maven.compiler.target>
<maven.compiler.source>11</maven.compiler.source>
<maven.compiler.release>11</maven.compiler.release>

<compiler.plugin.version>3.11.0</compiler.plugin.version>
<enforcer.plugin.version>3.2.1</enforcer.plugin.version>
<surefire.plugin.version>3.1.2</surefire.plugin.version>
<jandex.version>3.1.3</jandex.version>
<formatter-maven-plugin.version>2.23.0</formatter-maven-plugin.version>
<impsort-maven-plugin.version>1.9.0</impsort-maven-plugin.version>

<junit.jupiter.version>5.9.3</junit.jupiter.version>
</properties>


<dependencies>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>compile</scope>
<version>${junit.jupiter.version}</version>
</dependency>
</dependencies>

<build>
<pluginManagement>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>${compiler.plugin.version}</version>
</plugin>
<plugin>
<groupId>io.smallrye</groupId>
<artifactId>jandex-maven-plugin</artifactId>
<version>${jandex.version}</version>
</plugin>
<plugin>
<artifactId>maven-javadoc-plugin</artifactId>
<configuration>
<quiet>true</quiet>
<doclint>none</doclint>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<dependencies>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-enforcer-rules</artifactId>
<version>${project.version}</version>
</dependency>
</dependencies>
<executions>
<execution>
<id>enforce</id>
<configuration>
<rules>
<dependencyConvergence/>
<externalRules>
<location>classpath:enforcer-rules/quarkus-require-java-version.xml</location>
</externalRules>
<externalRules>
<location>classpath:enforcer-rules/quarkus-require-maven-version.xml</location>
</externalRules>
<externalRules>
<location>classpath:enforcer-rules/quarkus-banned-dependencies.xml</location>
</externalRules>
<bannedDependencies>
<excludes>
<!-- findbugs is not required at runtime -->
<exclude>com.google.code.findbugs:jsr305</exclude>
<!-- com.google.guava:listenablefuture is empty and the ListenableFuture class is available in Guava -->
<exclude>com.google.guava:listenablefuture</exclude>
</excludes>
</bannedDependencies>
</rules>
</configuration>
<goals>
<goal>enforce</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>${surefire.plugin.version}</version>
<configuration>
<!-- combine.self suppresses warnings about java.io.tmpdir being defined twice -->
<systemPropertyVariables combine.self="override"/>
<!-- set tmpdir as early as possible because failsafe sets it too late for JDK16 -->
<argLine>-Djava.io.tmpdir="${project.build.directory}"</argLine>
<excludedEnvironmentVariables>MAVEN_OPTS</excludedEnvironmentVariables>
</configuration>
</plugin>
<!-- Replicate what's in parent, since this pom doesn't inherit parent but IDE settings will be common -->
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<version>${formatter-maven-plugin.version}</version>
<dependencies>
<dependency>
<artifactId>quarkus-ide-config</artifactId>
<groupId>io.quarkus</groupId>
<version>${project.version}</version>
</dependency>
</dependencies>
<configuration>
<!-- store outside of target to speed up formatting when mvn clean is used -->
<cachedir>.cache/formatter-maven-plugin-${formatter-maven-plugin.version}</cachedir>
<configFile>eclipse-format.xml</configFile>
<lineEnding>LF</lineEnding>
<skip>${format.skip}</skip>
</configuration>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>impsort-maven-plugin</artifactId>
<version>${impsort-maven-plugin.version}</version>
<configuration>
<!-- store outside of target to speed up formatting when mvn clean is used -->
<cachedir>.cache/impsort-maven-plugin-${impsort-maven-plugin.version}</cachedir>
<groups>java.,javax.,jakarta.,org.,com.</groups>
<staticGroups>*</staticGroups>
<skip>${format.skip}</skip>
<removeUnused>true</removeUnused>
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>

<distributionManagement>
<snapshotRepository>
<id>sonatype-nexus-snapshots</id>
<url>https://s01.oss.sonatype.org/content/repositories/snapshots</url>
</snapshotRepository>
<repository>
<id>sonatype-nexus-release</id>
<url>https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
</distributionManagement>

<profiles>
<profile>
<id>quick-build</id>
<activation>
<property>
<name>quickly</name>
</property>
</activation>
<properties>
<skipTests>true</skipTests>
<skipITs>true</skipITs>
<enforcer.skip>true</enforcer.skip>
</properties>
<build>
<defaultGoal>clean install</defaultGoal>
</build>
</profile>

<profile>
<!-- separate "quickly" profile for CI to keep local "quickly" demands separated from CI demands -->
<id>quick-build-ci</id>
<activation>
<property>
<name>quickly-ci</name>
</property>
</activation>
<properties>
<skipTests>true</skipTests>
<skipITs>true</skipITs>
<enforcer.skip>true</enforcer.skip>
<format.skip>true</format.skip>
</properties>
</profile>
<profile>
<id>format</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>!no-format</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>format</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>impsort-maven-plugin</artifactId>
<executions>
<execution>
<id>sort-imports</id>
<goals>
<goal>sort</goal>
</goals>
</execution>
</executions>
<configuration>
<removeUnused>true</removeUnused>
</configuration>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>validate</id>
<activation>
<activeByDefault>true</activeByDefault>
<property>
<name>no-format</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>net.revelc.code.formatter</groupId>
<artifactId>formatter-maven-plugin</artifactId>
<executions>
<execution>
<phase>process-sources</phase>
<goals>
<goal>validate</goal>
</goals>
</execution>
</executions>
</plugin>
<plugin>
<groupId>net.revelc.code</groupId>
<artifactId>impsort-maven-plugin</artifactId>
<configuration>
<removeUnused>true</removeUnused>
</configuration>
<executions>
<execution>
<id>check-imports</id>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>

</project>
Loading

0 comments on commit 5ac4f50

Please sign in to comment.