slawekjaranowski commented on issue #3460:
URL: 
https://github.com/apache/maven-surefire/issues/3460#issuecomment-5583344061

   We hit the same regression from a different direction. Two things to add: a 
minimal deterministic reproducer, and a second, much more confusing failure 
mode.
   
   ### Minimal reproducer
   
   No Jakarta Validation, no `ForkJoinPool`, no race — just a thread with a 
`null` context class loader writing one line:
   
   ```java
   import org.junit.jupiter.api.Test;
   
   class NullTcclPrintTest {
   
       @Test
       void printFromThreadWithNullContextClassLoader() throws Exception {
           Throwable[] failure = new Throwable[1];
           Thread t = new Thread(() -> {
               try {
                   System.err.println("hello from null-TCCL thread");
               } catch (Throwable e) {
                   failure[0] = e;
               }
           });
           t.setContextClassLoader(null);
           t.start();
           t.join();
           if (failure[0] != null) {
               throw new AssertionError("println failed", failure[0]);
           }
       }
   }
   ```
   
   Plain JUnit 5 project, nothing else needed. Fails on 3.6.0, passes on 3.5.6:
   
   ```
   Caused by: java.lang.ExceptionInInitializerError
        at 
org.apache.maven.surefire.api.report.StackTraceProvider.getStack(StackTraceProvider.java:107)
        at 
org.apache.maven.surefire.api.report.TestOutputReportEntry.<init>(TestOutputReportEntry.java:52)
        at 
org.apache.maven.surefire.api.report.TestOutputReportEntry.<init>(TestOutputReportEntry.java:63)
        at 
org.apache.maven.surefire.api.report.TestOutputReportEntry.stdErrln(TestOutputReportEntry.java:132)
        at 
org.apache.maven.surefire.api.report.ConsoleOutputCapture$ForwardingPrintStream.println(ConsoleOutputCapture.java:116)
        at 
NullTcclPrintTest.lambda$printFromThreadWithNullContextClassLoader$0(NullTcclPrintTest.java:10)
        at java.base/java.lang.Thread.run(Thread.java:1583)
   Caused by: java.lang.NullPointerException: Cannot invoke 
"java.lang.ClassLoader.loadClass(String)" because "classLoader" is null
        at 
org.apache.maven.surefire.api.util.ReflectionUtils.tryLoadClass(ReflectionUtils.java:152)
        at 
org.apache.maven.surefire.api.report.StackWalkerStrategy.<clinit>(StackWalkerStrategy.java:60)
   ```
   
   Reproduces with `forkCount=1` and `forkCount=0`, and with 
`useSystemClassLoader` both `true` and `false`.
   
   ### Second failure mode: dynamically attached JVM agents
   
   This one does not surface as a `StackWalkerStrategy` error at all, which 
makes it hard to trace back here.
   
   Our case is a Reactor BlockHound test (`BlockHound.install()` in 
`@BeforeAll`) on JDK 21:
   
   1. `BlockHound.install()` attaches a Byte Buddy agent to the running JVM.
   2. On JDK 9+ the JVM prints `WARNING: A Java agent has been loaded 
dynamically ...` from
      `sun.instrument.InstrumentationImpl.<init>`, running on the **`Attach 
Listener`** thread — whose
      context class loader is `null` by design.
   3. That write goes through `ConsoleOutputCapture$ForwardingPrintStream` and 
throws
      `ExceptionInInitializerError` as above.
   4. The exception escapes the `InstrumentationImpl` constructor, so the JVM 
aborts agent loading:
   
   ```
   *** java.lang.instrument ASSERTION FAILED ***: "!errorOutstanding" with 
message
       call constructor on InstrumentationImpl failed at 
src/java.instrument/share/native/libinstrument/JPLISAgent.c line: 526
   *** java.lang.instrument ASSERTION FAILED ***: "success" with message
       createInstrumentationImpl failed at 
src/java.instrument/share/native/libinstrument/InvocationAdapter.c line: 425
   ```
   
   5. What the user actually sees is Byte Buddy's downstream error, which 
points at the agent and the JDK,
      not at surefire:
   
   ```
   java.lang.IllegalStateException: Could not self-attach to current VM using 
external process
        at 
net.bytebuddy.agent.ByteBuddyAgent.installExternal(ByteBuddyAgent.java:710)
   ```
   
   So on 3.6.0 any test that attaches an agent at runtime (BlockHound, 
`ByteBuddyAgent.install()`,
   profilers, Mockito's inline mock maker in setups where it self-attaches) can 
break, with no hint that
   the report layer is involved. It took an `-Xlog:exceptions` dump to find the 
`Attach Listener` thread
   in the middle of it.
   
   ### On the fix
   
   Agreed on catching `RuntimeException` in `tryLoadClass`. Two additions worth 
considering:
   
   - `StackWalkerStrategy.<clinit>` does not need the TCCL at all — 
`java.lang.StackWalker` is a JDK class,
     so `StackWalkerStrategy.class.getClassLoader()` (or 
`ClassLoader.getPlatformClassLoader()`) removes the
     dependency on whatever the current thread happens to carry.
   - `StackTraceProvider.getStack()` and the rest of the `ConsoleOutputCapture` 
path could be made
     exception-proof independently: failing to collect diagnostic stack data 
should degrade to an empty
     stack, never propagate into user code that is only printing a line. As the 
agent case shows, an
     exception thrown out of a `println` can break things far outside 
surefire's own reporting.
   
   ### Workaround
   
   For the agent-attach case, suppressing the JDK warning is enough, since 
nothing is then printed from the
   `Attach Listener` thread:
   
   ```xml
   <argLine>-XX:+EnableDynamicAgentLoading</argLine>
   ```
   
   Otherwise pinning maven-surefire-plugin to 3.5.6 works.
   
   Environment: maven-surefire-plugin 3.6.0, Temurin 21.0.12+8-LTS, macOS 
26.6.2 (aarch64).
   


-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to