Skip to content

Commit 790e15b

Browse files
authored
Add new Blackbird test module (#349)
1 parent 5f33352 commit 790e15b

10 files changed

Lines changed: 769 additions & 0 deletions

File tree

blackbird-tests/README.md

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# jackson-module-blackbird-tests
2+
3+
Classpath-mode integration tests for `jackson-module-blackbird`. **This module is
4+
test-only and is never published.**
5+
6+
## Why this module exists
7+
8+
This module is the Blackbird counterpart to
9+
[`afterburner-tests`](../afterburner-tests/README.md). The coverage gap it
10+
addresses is narrower than Afterburner's, because Blackbird's optimizer
11+
mechanism is structurally different — and better aligned with JPMS — than
12+
Afterburner's.
13+
14+
**Afterburner:** uses ByteBuddy + `ClassLoader.defineClass` + reflective
15+
access to `java.lang.ClassLoader` methods. On Java 9+, reflective access to
16+
`ClassLoader` is gated by JPMS, and in the JPMS-named test module every test
17+
POJO ends up in a sealed package. That combination silently disables the
18+
optimizer in the in-tree afterburner test suite. See the `afterburner-tests`
19+
README for the full story.
20+
21+
**Blackbird:** uses `MethodHandles.Lookup.defineClass` (a JDK 9+ supported
22+
API) + `MethodHandles.privateLookupIn`. Neither needs reflective access to
23+
`ClassLoader` methods, neither cares whether the target package is "sealed by
24+
module", and `privateLookupIn` works across the JPMS module boundary whenever
25+
the target module opens its package to the caller (or lives in the unnamed
26+
module). As a result, Blackbird's in-tree tests **do** exercise the optimizer
27+
for setter-based POJOs.
28+
29+
What's NOT exercised by Blackbird's in-tree tests, and is covered here:
30+
31+
- **Classpath / unnamed-module POJOs.** A real-world POJO loaded by the
32+
system class loader rather than as part of the Blackbird JPMS module.
33+
This module's test POJOs live in the unnamed module (no `module-info.java`
34+
anywhere), so the optimizer has to cross the module boundary.
35+
- **The documented limitations of Blackbird's optimizer** — specifically
36+
that direct public-field access is **not** optimized on either the
37+
deserializer or serializer side. Blackbird's
38+
`BBDeserializerModifier.nextProperty` and
39+
`BBSerializerModifier.createProperty` both skip non-method members.
40+
- **The `CrossLoaderAccess` fast-path behavior.** Blackbird's
41+
`CrossLoaderAccess` contains a slow-path that defines a
42+
`$$JacksonBlackbirdAccess` companion class via `Lookup.defineClass(byte[])`
43+
to upgrade a partial-privilege lookup. `CrossLoaderAccessTest` pins the
44+
current behavior that on JDK 9+ with an unnamed-module bean, the fast
45+
path always wins and the companion class is never defined.
46+
47+
## Do not add a `module-info.java` here
48+
49+
Same reason as `afterburner-tests`: adding a module descriptor would put the
50+
test POJOs back into a named module and change how `privateLookupIn`
51+
resolves them, silently undermining the classpath-coverage purpose.
52+
53+
## Do not publish this module
54+
55+
Several parent-POM plugin bindings are unbound or skipped in `pom.xml` so
56+
that `mvn install` on this module produces no jar, no SBOM, no Gradle module
57+
metadata, and no OSGi bundle. See `afterburner-tests/pom.xml` for the same
58+
pattern with extended commentary; this module's `pom.xml` shares the same
59+
shape.
60+
61+
## What is covered
62+
63+
- `BBDeserializerModifier` — setter specializations (int / long / boolean /
64+
String / Object) in `SetterOptimizationTest`.
65+
- `BBSerializerModifier` — getter-based writer specializations in
66+
`SerializerInjectionTest`.
67+
- Field-access negative cases (known design limitation) on both sides, in
68+
`FieldAccessNotOptimizedTest` and `SerializerInjectionTest`.
69+
- `CrossLoaderAccess` fast-path short-circuit for unnamed-module beans, in
70+
`CrossLoaderAccessTest`.
71+
72+
## What is *not* covered
73+
74+
- Private-class guard (`Modifier.isPrivate(beanClass)` in both modifiers).
75+
- The slow-path `CrossLoaderAccess.accessClassIn` companion-class definition
76+
— that code is effectively dead for classpath POJOs on JDK 9+; see
77+
`CrossLoaderAccessTest` javadoc for the explanation.
78+
- `CreatorOptimizer` — Blackbird has one; a follow-up could mirror
79+
`afterburner-tests/CreatorOptimizerTest`.
80+
- Concurrent deserializer construction.
81+
- Bean classes in a JPMS module other than Blackbird's own. Testing that
82+
would require a third submodule with its own `module-info.java` that
83+
`opens` its package to Blackbird, which adds complexity for unclear
84+
additional value.
85+
- Lambda-classloader growth as a function of mapper count. Blackbird's
86+
fundamental design creates a new anonymous lambda class per
87+
`LambdaMetafactory.metafactory` invocation; this isn't a bug and isn't
88+
caching-related, so there's nothing to assert.
89+
90+
## References
91+
92+
- PR that introduced this module — see git history
93+
- [`afterburner-tests/README.md`](../afterburner-tests/README.md) for the
94+
sibling module and the broader rationale
95+
- Issue #348 — analogous (but different) afterburner classloader caching
96+
issue. Does not apply to Blackbird: Blackbird uses `Lookup.defineClass`,
97+
not reflective `ClassLoader.defineClass`, so it is structurally immune.

blackbird-tests/pom.xml

Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
<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">
2+
<modelVersion>4.0.0</modelVersion>
3+
<parent>
4+
<groupId>tools.jackson.module</groupId>
5+
<artifactId>jackson-modules-base</artifactId>
6+
<version>3.2.0-SNAPSHOT</version>
7+
</parent>
8+
9+
<artifactId>jackson-module-blackbird-tests</artifactId>
10+
<name>Jackson module: Blackbird integration tests</name>
11+
<packaging>jar</packaging>
12+
13+
<description>Classpath-mode integration tests for jackson-module-blackbird. Unlike
14+
jackson-module-afterburner-tests, Blackbird's in-tree tests already exercise most of
15+
the optimizer because Blackbird uses the JDK 9+ supported MethodHandles.Lookup API
16+
rather than reflective ClassLoader access. What this module adds is coverage of the
17+
classpath/unnamed-module path: when a POJO lives outside Blackbird's own JPMS module,
18+
Blackbird's CrossLoaderAccess has to define a $$JacksonBlackbirdAccess companion class
19+
via Lookup.defineClass to upgrade the caller lookup to one with full privilege access
20+
over the target package. That path is not exercised by in-tree tests (caller and bean
21+
share a module, so privateLookupIn already has full privilege). This module is test-only
22+
and is never published.</description>
23+
24+
<dependencies>
25+
<dependency>
26+
<groupId>tools.jackson.module</groupId>
27+
<artifactId>jackson-module-blackbird</artifactId>
28+
<version>${project.version}</version>
29+
</dependency>
30+
</dependencies>
31+
32+
<build>
33+
<plugins>
34+
<!-- This module is test-only and must never produce a publishable artifact.
35+
Packaging stays `jar` so the compile + test lifecycle runs normally,
36+
but every artifact-producing / artifact-publishing plugin inherited
37+
from the parent chain is unbound here.
38+
39+
Verified via `mvn help:effective-pom -pl blackbird-tests`: every
40+
execution listed below appears in the merged model at phase=none or
41+
with skip=true. If you rename an execution here, re-verify with that
42+
command — a typo turns into silent dead config and stale artifacts
43+
in target/ rather than a build error. See afterburner-tests/pom.xml
44+
for the same pattern with more detailed comments. -->
45+
<plugin>
46+
<groupId>org.apache.maven.plugins</groupId>
47+
<artifactId>maven-jar-plugin</artifactId>
48+
<executions>
49+
<execution>
50+
<id>default-jar</id>
51+
<phase>none</phase>
52+
</execution>
53+
</executions>
54+
</plugin>
55+
<plugin>
56+
<groupId>org.apache.maven.plugins</groupId>
57+
<artifactId>maven-install-plugin</artifactId>
58+
<configuration>
59+
<skip>true</skip>
60+
</configuration>
61+
</plugin>
62+
<plugin>
63+
<groupId>org.apache.maven.plugins</groupId>
64+
<artifactId>maven-deploy-plugin</artifactId>
65+
<configuration>
66+
<skip>true</skip>
67+
</configuration>
68+
</plugin>
69+
<plugin>
70+
<groupId>org.apache.felix</groupId>
71+
<artifactId>maven-bundle-plugin</artifactId>
72+
<executions>
73+
<execution>
74+
<id>bundle-manifest</id>
75+
<phase>none</phase>
76+
</execution>
77+
<execution>
78+
<id>default-install</id>
79+
<phase>none</phase>
80+
</execution>
81+
</executions>
82+
</plugin>
83+
<plugin>
84+
<groupId>org.cyclonedx</groupId>
85+
<artifactId>cyclonedx-maven-plugin</artifactId>
86+
<configuration>
87+
<skip>true</skip>
88+
</configuration>
89+
</plugin>
90+
<plugin>
91+
<groupId>org.gradlex</groupId>
92+
<artifactId>gradle-module-metadata-maven-plugin</artifactId>
93+
<executions>
94+
<execution>
95+
<id>default</id>
96+
<phase>none</phase>
97+
</execution>
98+
</executions>
99+
</plugin>
100+
<plugin>
101+
<groupId>org.jacoco</groupId>
102+
<artifactId>jacoco-maven-plugin</artifactId>
103+
<executions>
104+
<execution>
105+
<id>report</id>
106+
<phase>none</phase>
107+
</execution>
108+
</executions>
109+
</plugin>
110+
<!-- Force surefire to classpath mode: this module has no module-info.java,
111+
so tests must run on the classpath (unnamed module). That's the whole
112+
point — Blackbird's in-tree tests run as a named module and take the
113+
easy privateLookupIn path that doesn't exercise CrossLoaderAccess. -->
114+
<plugin>
115+
<groupId>org.apache.maven.plugins</groupId>
116+
<artifactId>maven-surefire-plugin</artifactId>
117+
<configuration>
118+
<useModulePath>false</useModulePath>
119+
</configuration>
120+
</plugin>
121+
</plugins>
122+
</build>
123+
</project>

0 commit comments

Comments
 (0)