|
| 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. |
0 commit comments