async-test-lib
Used in:
components
- OverviewOverview
- VersionsVersions
- DependentsDependents
- DependenciesDependencies
<dependency>
<groupId>se.deversity.async-test-lib</groupId>
<artifactId>async-test-lib</artifactId>
<version>1.12.2</version>
</dependency><?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>se.deversity.async-test-lib</groupId>
<artifactId>async-test-parent</artifactId>
<version>1.12.2</version>
</parent>
<!-- Deliberately unchanged: this is the coordinate every existing consumer depends on. -->
<artifactId>async-test-lib</artifactId>
<packaging>jar</packaging>
<name>Async Test Library</name>
<description>Enterprise-grade JUnit 5 concurrency testing library with 146 problem detectors for detecting deadlocks, visibility issues, false sharing, livelocks, and other subtle concurrency bugs.</description>
<licenses>
<license>
<name>PolyForm Noncommercial License 1.0.0</name>
<url>https://polyformproject.org/licenses/noncommercial/1.0.0/</url>
<distribution>repo</distribution>
</license>
</licenses>
<dependencies>
<dependency>
<groupId>org.apiguardian</groupId>
<artifactId>apiguardian-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-engine</artifactId>
</dependency>
<dependency>
<groupId>se.deversity.common</groupId>
<artifactId>common-license-lib</artifactId>
</dependency>
<!-- SLF4J API: logging facade; consumers supply their own backend (logback, log4j, etc.) -->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>
<dependency>
<groupId>org.junit.platform</groupId>
<artifactId>junit-platform-testkit</artifactId>
<scope>test</scope>
</dependency>
<!-- Jazzer API for fuzz targets (standalone, no JUnit runner dependency) -->
<dependency>
<groupId>com.code-intelligence</groupId>
<artifactId>jazzer-api</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.tngtech.archunit</groupId>
<artifactId>archunit-junit5</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<!-- Jazzer fuzz targets compile as test classes but must not live under
src/test/java. OpenSSF Scorecard's fuzzing check discards every path
containing "/src/test/" before it scans for the Jazzer import, so a
target kept there is invisible to it and the repo scores 0 on Fuzzing
while genuinely being fuzzed. Keeping them in src/fuzz/java and adding
the root here leaves the Jazzer classpath (target/test-classes) unchanged.
See docs/quality-gates/mutation-fuzzing-benchmarks.md#fuzzing. -->
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>${build-helper-maven-plugin.version}</version>
<executions>
<execution>
<id>add-fuzz-test-source</id>
<phase>generate-test-sources</phase>
<goals>
<goal>add-test-source</goal>
</goals>
<configuration>
<sources>
<source>src/fuzz/java</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
<!-- Stable JPMS name. Without it the module system derives one from the file name,
so a modular consumer's requires clause breaks the first time the artifact is
renamed. A full module-info would be stronger, but the baseline is JDK 21 and
this artifact is test-scoped, so the automatic name is the useful floor. -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>${maven-jar-plugin.version}</version>
<configuration>
<archive>
<!-- Writes Implementation-Version, the one place the reports read the library
version from (ReportListeners.libraryVersion). Without it the JSON report
carried a literal that stayed at 1.6.0 and SARIF said "unknown" (#703). -->
<manifest>
<addDefaultImplementationEntries>true</addDefaultImplementationEntries>
</manifest>
<manifestEntries>
<Automatic-Module-Name>se.deversity.asynctest</Automatic-Module-Name>
</manifestEntries>
</archive>
</configuration>
</plugin>
<!-- The binary-compatibility gate applies to this module only: it is the artifact
with a published 1.6.0 predecessor. -->
<plugin>
<groupId>com.github.siom79.japicmp</groupId>
<artifactId>japicmp-maven-plugin</artifactId>
<version>0.26.2</version>
<configuration>
<!-- Baseline: the most recent release.
This was pinned to 1.6.0 while six releases shipped past it (1.7.0
through 1.9.1), so every API added since 1.6.0 - the offline-licensing
surface, DetectorFailurePolicy, the lock-awareness API, the listener and
telemetry additions - did not exist in the baseline and could have been
broken or deleted without the gate noticing. docs/CHANGELOG.md for 1.9.1
claimed "japicmp green against 1.9.0"; the gate was comparing 1.6.0, so
that published compatibility statement had never been machine-checked.
Re-pin this on every release; docs/RELEASE.md carries the step. It was
missed at four consecutive releases while the only thing asking for it
was this comment, so JapicmpBaselineFreshnessTest now checks it: the
version below must be the newest release in docs/CHANGELOG.md that is
older than the version being built. Editing it by hand is still fine -
the test says what it expected and why. -->
<oldVersion>
<dependency>
<groupId>se.deversity.async-test-lib</groupId>
<artifactId>async-test-lib</artifactId>
<version>1.12.1</version>
<type>jar</type>
</dependency>
</oldVersion>
<newVersion>
<file>
<path>${project.build.directory}/${project.build.finalName}.${project.packaging}</path>
</file>
</newVersion>
<parameter>
<onlyBinaryIncompatible>true</onlyBinaryIncompatible>
<!-- Semantic versioning, not "never break".
breakBuildOnBinaryIncompatibleModifications stood here and failed
the build on any binary-incompatible change whatever the version
number said, which made 2.0.0 unbuildable: the release exists to
remove what 1.x deprecated, and the gate would have refused the
first removal. Measured on 2026-08-27 by setting the version to
2.0.0 and deleting one deprecated accessor, which is exactly what
the roadmap plans - BUILD FAILURE, "There is at least one
incompatibility: AsyncTestContext.semaphoreMonitor():METHOD_REMOVED".
breakBuildBasedOnSemanticVersioning permits what semver permits and
nothing else. The same removal, measured the same way: 2.0.0 passes,
1.10.0 fails with "indicate a minor change but binary incompatible
changes found", 1.9.9 fails with the patch-change wording. Adding
public API in a patch release still passes, because
onlyBinaryIncompatible scopes this to breaks and an addition is not
one - which matters here, where new detectors have shipped as patch
releases four times. -->
<breakBuildBasedOnSemanticVersioning>true</breakBuildBasedOnSemanticVersioning>
<ignoreMissingClasses>true</ignoreMissingClasses>
<!-- No excludes.
The previous list waived two whole packages (se.deversity.asynctest.agent
and .analysis, which left this artifact when the reactor was split) and
the UNCOMMITTED_CHANGES elements removed after 1.7.2. Every one of those
breaks predates 1.9.1, so against this baseline they are not breaks at
all and the waivers only widened the hole. The pom's own comment said to
remove them "once <oldVersion> is re-pinned to a release that already had
the split" - this is that. -->
</parameter>
</configuration>
<executions>
<execution>
<phase>verify</phase>
<goals>
<goal>cmp</goal>
</goals>
</execution>
</executions>
</plugin>
<!-- Coverage gate. Declared here rather than in the parent so the two thin wrapper
modules are not held to ratios measured against this test suite. -->
<plugin>
<groupId>org.jacoco</groupId>
<artifactId>jacoco-maven-plugin</artifactId>
<executions>
<execution>
<id>jacoco-check</id>
<phase>verify</phase>
<goals>
<goal>check</goal>
</goals>
<configuration>
<!-- True unless the e2e profile runs: the excluded engine tier
carries most runner/extension coverage, so gating a partial
run would fail over code that is covered in CI. -->
<skip>${jacoco.check.skip}</skip>
<rules>
<rule>
<element>BUNDLE</element>
<limits>
<limit>
<counter>LINE</counter>
<value>COVEREDRATIO</value>
<minimum>0.70</minimum>
</limit>
<limit>
<counter>BRANCH</counter>
<value>COVEREDRATIO</value>
<minimum>0.65</minimum>
</limit>
</limits>
</rule>
</rules>
</configuration>
</execution>
</executions>
</plugin>
<!-- PITest mutation testing — run on demand:
mvn test-compile org.pitest:pitest-maven:mutationCoverage
Full run ≈ 2h (the e2e engine tier is included in the measurement; a scoped
run via -DtargetClasses=... takes ~1-2 min). -->
<plugin>
<groupId>org.pitest</groupId>
<artifactId>pitest-maven</artifactId>
<version>${pitest-maven.version}</version>
<dependencies>
<dependency>
<groupId>org.pitest</groupId>
<artifactId>pitest-junit5-plugin</artifactId>
<version>${pitest-junit5-plugin.version}</version>
</dependency>
</dependencies>
<configuration>
<targetClasses>
<param>se.deversity.asynctest.*</param>
</targetClasses>
<targetTests>
<param>se.deversity.asynctest.*</param>
</targetTests>
<excludedTestClasses>
<!-- Jazzer fuzz harnesses and ArchUnit rules don't kill mutants
and are expensive under repeated forked execution -->
<param>se.deversity.asynctest.fuzz.*</param>
<param>se.deversity.asynctest.architecture.*</param>
<!-- These two wedge virtual threads permanently, on purpose: one parks
them inside a class initializer, the other deadlocks a pair of them,
and each is testing exactly that. Before JEP 491 (JDK 24) such a
thread pins the carrier it is mounted on, and the carrier pool is
sized to availableProcessors - four on a runner. A json thread dump
of the hung minion counted six wedged virtual threads from these two
classes alone, so the scheduler had nothing left and every later
test that started a virtual thread and joined it waited forever.
That is what kept the weekly gate from ever completing (#479).
Surefire never sees this because reuseForks=false gives each class
its own JVM and the pinned carriers die with the fork; pitest runs
all of them in one. They cannot move to platform threads either -
a virtual thread is the subject. Excluding them costs their
mutation-killing contribution, which is the price of the gate
producing any number at all. -->
<param>se.deversity.asynctest.diagnostics.StaticInitSampleSeesVirtualThreadsTest</param>
<param>se.deversity.asynctest.diagnostics.VirtualThreadLockGraphTest</param>
</excludedTestClasses>
<threads>4</threads>
<!-- Surefire's argLine contains JaCoCo's late-bound @{argLine} which
pitest cannot resolve; required flags are passed via jvmArgs instead -->
<parseSurefireArgLine>false</parseSurefireArgLine>
<!-- Do not inherit surefire settings: the local surefire default excludes
the @Tag("e2e") engine tier (surefire.excludedGroups), and inheriting
that measured mutation score against half the suite — 74% with the
33 EngineTestKit classes invisible vs 77% with them. Everything pitest
needs is declared explicitly in this block. -->
<parseSurefireConfig>false</parseSurefireConfig>
<!-- Stress tests coordinate via CyclicBarrier; give mutants generous
headroom before being flagged TIMED_OUT -->
<timeoutConstant>10000</timeoutConstant>
<jvmArgs>
<value>-Djdk.attach.allowAttachSelf=true</value>
<value>-Dlicense.mock.mode=true</value>
</jvmArgs>
<!-- Gate: the build fails below this mutation score. Measured 81%
(9019 mutations, 7272 killed) on 2026-09-06, run 34017000749 - the
first CI run of this job ever to complete, and so the first score that
came from the job rather than from somebody's laptop. Line coverage of
mutated classes 92% (16121/17442); 525 mutations had no coverage at
all; test strength 86%.
The previous figure here, 77.5% on 2026-08-31, was a local run: every
CI attempt died in the coverage phase until #479 was fixed. The two are
not like for like, because that one predates both the detector sweep and
the excludedTestClasses above.
Survivors are dominated by diagnostic output, timing-heuristic detector
mutants that cannot be killed without flaky, timing-forced tests, and
agent-fed hook shims that never execute without the bytecode agent
attached. The gate sits 5pp below the measurement because TIMED_OUT
mutant counts jitter between runs. -->
<mutationThreshold>76</mutationThreshold>
<outputFormats>
<param>HTML</param>
<param>XML</param>
</outputFormats>
<timestampedReports>false</timestampedReports>
</configuration>
</plugin>
</plugins>
</build>
</project>