This is an automated email from the ASF dual-hosted git repository.

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 37ed28d6141e CAMEL-26103: camel-test-dst - load the instrumentation 
agent with -javaagent instead of a runtime attach (#28782)
37ed28d6141e is described below

commit 37ed28d6141e13f0436b1dd59d6e67f3ae8ab59d
Author: allthingssecurity <[email protected]>
AuthorDate: Sun Nov 1 16:37:27 2026 +0530

    CAMEL-26103: camel-test-dst - load the instrumentation agent with 
-javaagent instead of a runtime attach (#28782)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/docs/test-dst.adoc    | 80 ++++++++++++++++++
 components/camel-aws/camel-aws2-sqs/pom.xml        | 20 +++++
 .../camel-azure/camel-azure-eventhubs/pom.xml      | 20 +++++
 components/camel-kafka-share/pom.xml               | 22 +++++
 components/camel-pulsar/pom.xml                    | 21 ++++-
 components/camel-slack/pom.xml                     | 23 ++++++
 components/camel-test/camel-test-dst/pom.xml       | 17 +++-
 .../camel-test-dst/src/main/docs/test-dst.adoc     | 80 ++++++++++++++++++
 .../apache/camel/test/dst/DstInstrumentation.java  | 39 +++++++--
 .../camel/test/dst/DstInstrumentationTest.java     | 12 +++
 .../apache/camel/test/dst/DstMissingAgentTest.java | 95 ++++++++++++++++++++++
 11 files changed, 419 insertions(+), 10 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
index 20ad229f32b7..8c5136a44328 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/test-dst.adoc
@@ -226,6 +226,9 @@ Netty, an HTTP client pool), fake it above them.
 
 === 2. Run the escape scan before writing anything
 
+Add the agent to the module's pom first, see <<_setup,Setup>>: without it 
every test class with `@DstInstrument`
+fails with "The byte code agent of camel-test-dst is not loaded".
+
 Write the smallest test with `@DstInstrument("<component package>")` and one 
seed, run it, and read
 `target/dst/<Test>.escapes.txt`. Every line is a place where the component 
leaves the harness. For each one, decide:
 
@@ -449,6 +452,7 @@ Later worlds (IMAP, JMS, NATS JetStream, Azure Service Bus) 
showed more:
 
 === Checklist
 
+* [ ] The module's pom loads the Byte Buddy agent with `-javaagent` (see 
<<_setup,Setup>>).
 * [ ] The client is swapped through an endpoint option or a static mock, at a 
synchronous level.
 * [ ] The escape scan was read; every escape is fixed, avoided or documented; 
the test runs in strict mode.
 * [ ] Every behaviour of the fake quotes the documentation; assumptions are 
marked; what is not modelled is listed.
@@ -567,6 +571,8 @@ Reports in `target/dst/`: `<Test>.escapes.txt` (the scan), 
`<Test>.summary.txt`
 left alive, runtime escapes) and `<Test>.<method>/seed-<n>.trace` (the trace 
of each failing or replayed seed, with
 its SHA-256 on the second line).
 
+== Setup
+
 Add the module with test scope:
 
 [source,xml]
@@ -577,3 +583,77 @@ Add the module with test scope:
     <scope>test</scope>
 </dependency>
 ----
+
+The rewriting needs the Byte Buddy agent, loaded with `-javaagent` when the 
JVM of the tests starts. The harness does
+not attach it to the running JVM: the JDK warns about an agent loaded that 
way, and is to refuse it by default
+(JEP 451). Let the `properties` goal of the `maven-dependency-plugin` set the 
path of the agent jar (the property
+`net.bytebuddy:byte-buddy-agent:jar`; `byte-buddy-agent` comes with 
`camel-test-dst`), and add the agent to the
+surefire `argLine`. In a project of your own:
+
+[source,xml,subs="-attributes"]
+----
+<properties>
+    <!-- empty by default: a plugin such as JaCoCo sets it, and surefire adds 
it through @{argLine} -->
+    <argLine/>
+</properties>
+
+<build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-dependency-plugin</artifactId>
+            <executions>
+                <execution>
+                    <id>dst-agent-path</id>
+                    <goals>
+                        <goal>properties</goal>
+                    </goals>
+                </execution>
+            </executions>
+        </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+                <argLine>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}" 
@{argLine}</argLine>
+            </configuration>
+        </plugin>
+    </plugins>
+</build>
+----
+
+The `properties` goal runs in the `initialize` phase, so `mvn test` sets the 
property before surefire starts. Keep
+`+@{argLine}+` in the `argLine`, so that an agent another plugin sets (the 
JaCoCo agent of `prepare-agent`) is still
+loaded; the empty `<argLine/>` property keeps surefire from passing 
`+@{argLine}+` as it is when no plugin sets it. The
+quotes keep a path with a space in one argument (surefire splits the `argLine` 
at the spaces outside quotes).
+
+In the Camel build, a module with DST tests adds the agent to 
`camel.surefire.fork.additional-vmargs`, which the
+`argLine` of the parent includes (with `+@{argLine}+`):
+
+[source,xml]
+----
+<properties>
+    
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
+</properties>
+
+<build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-dependency-plugin</artifactId>
+            <executions>
+                <execution>
+                    <id>dst-agent-path</id>
+                    <goals>
+                        <goal>properties</goal>
+                    </goals>
+                </execution>
+            </executions>
+        </plugin>
+    </plugins>
+</build>
+----
+
+Without the agent, every test class with `@DstInstrument` fails with "The byte 
code agent of camel-test-dst is not
+loaded". To run a DST test from an IDE, add `-javaagent:<path of 
byte-buddy-agent.jar>` to the JVM options of the run
+configuration.
diff --git a/components/camel-aws/camel-aws2-sqs/pom.xml 
b/components/camel-aws/camel-aws2-sqs/pom.xml
index 45a2c56a2b99..0a626e161a55 100644
--- a/components/camel-aws/camel-aws2-sqs/pom.xml
+++ b/components/camel-aws/camel-aws2-sqs/pom.xml
@@ -33,6 +33,8 @@
     <description>Consume and produce messages through AWS SQS 
Service</description>
 
     <properties>
+        <!-- the DST tests load the byte code agent of camel-test-dst when the 
JVM starts (see test-dst.adoc) -->
+        
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
     </properties>
 
     <dependencies>
@@ -101,4 +103,22 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- sets the property net.bytebuddy:byte-buddy-agent:jar to 
the path of the agent of the DST tests -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>dst-agent-path</id>
+                        <goals>
+                            <goal>properties</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/components/camel-azure/camel-azure-eventhubs/pom.xml 
b/components/camel-azure/camel-azure-eventhubs/pom.xml
index 82d068264238..ce7e502be543 100644
--- a/components/camel-azure/camel-azure-eventhubs/pom.xml
+++ b/components/camel-azure/camel-azure-eventhubs/pom.xml
@@ -33,6 +33,8 @@
     <description>Camel Azure Event Hubs Component</description>
 
     <properties>
+        <!-- the DST tests load the byte code agent of camel-test-dst when the 
JVM starts (see test-dst.adoc) -->
+        
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
     </properties>
 
     <dependencies>
@@ -88,4 +90,22 @@
             <scope>test</scope>
         </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- sets the property net.bytebuddy:byte-buddy-agent:jar to 
the path of the agent of the DST tests -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>dst-agent-path</id>
+                        <goals>
+                            <goal>properties</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/components/camel-kafka-share/pom.xml 
b/components/camel-kafka-share/pom.xml
index 4e854da76914..0653fb31d0e0 100644
--- a/components/camel-kafka-share/pom.xml
+++ b/components/camel-kafka-share/pom.xml
@@ -31,6 +31,11 @@
     <name>Camel :: Kafka :: Share</name>
     <description>Camel Kafka share group (queue) consumer</description>
 
+    <properties>
+        <!-- the DST tests load the byte code agent of camel-test-dst when the 
JVM starts (see test-dst.adoc) -->
+        
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
+    </properties>
+
     <dependencies>
 
         <!-- camel -->
@@ -77,4 +82,21 @@
         </dependency>
     </dependencies>
 
+    <build>
+        <plugins>
+            <plugin>
+                <!-- sets the property net.bytebuddy:byte-buddy-agent:jar to 
the path of the agent of the DST tests -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>dst-agent-path</id>
+                        <goals>
+                            <goal>properties</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/components/camel-pulsar/pom.xml b/components/camel-pulsar/pom.xml
index 1e8d1347384d..59425589e594 100644
--- a/components/camel-pulsar/pom.xml
+++ b/components/camel-pulsar/pom.xml
@@ -36,7 +36,8 @@
         <!-- Pulsar container is only available on x86 -->
         <skipITs.s390x>true</skipITs.s390x>
         <skipITs.aarch64>true</skipITs.aarch64>
-        <camel.surefire.fork.additional-vmargs>--add-opens 
java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED 
--add-opens java.base/java.lang=ALL-UNNAMED 
--add-exports=java.base/sun.net=ALL-UNNAMED</camel.surefire.fork.additional-vmargs>
+        <!-- the DST tests load the byte code agent of camel-test-dst when the 
JVM starts (see test-dst.adoc) -->
+        <camel.surefire.fork.additional-vmargs>--add-opens 
java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED 
--add-opens java.base/java.lang=ALL-UNNAMED 
--add-exports=java.base/sun.net=ALL-UNNAMED 
"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
         <!-- Make use of native access during IT test execution only -->
         
<camel.failsafe.fork.additional-vmargs>--enable-native-access=ALL-UNNAMED</camel.failsafe.fork.additional-vmargs>
     </properties>
@@ -96,4 +97,22 @@
             <scope>test</scope>
        </dependency>
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- sets the property net.bytebuddy:byte-buddy-agent:jar to 
the path of the agent of the DST tests -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>dst-agent-path</id>
+                        <goals>
+                            <goal>properties</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/components/camel-slack/pom.xml b/components/camel-slack/pom.xml
index e9f887fb9fa5..846d5caa5739 100644
--- a/components/camel-slack/pom.xml
+++ b/components/camel-slack/pom.xml
@@ -32,6 +32,11 @@
     <name>Camel :: Slack</name>
     <description>Camel Slack Support</description>
 
+    <properties>
+        <!-- the DST tests load the byte code agent of camel-test-dst when the 
JVM starts (see test-dst.adoc) -->
+        
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
+    </properties>
+
     <dependencies>
 
         <dependency>
@@ -74,4 +79,22 @@
         </dependency>
 
     </dependencies>
+
+    <build>
+        <plugins>
+            <plugin>
+                <!-- sets the property net.bytebuddy:byte-buddy-agent:jar to 
the path of the agent of the DST tests -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>dst-agent-path</id>
+                        <goals>
+                            <goal>properties</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
+        </plugins>
+    </build>
 </project>
diff --git a/components/camel-test/camel-test-dst/pom.xml 
b/components/camel-test/camel-test-dst/pom.xml
index be35ba91639d..620a9c7f0662 100644
--- a/components/camel-test/camel-test-dst/pom.xml
+++ b/components/camel-test/camel-test-dst/pom.xml
@@ -101,12 +101,25 @@
 
     <build>
         <plugins>
+            <plugin>
+                <!-- sets the property net.bytebuddy:byte-buddy-agent:jar to 
the path of the agent -->
+                <groupId>org.apache.maven.plugins</groupId>
+                <artifactId>maven-dependency-plugin</artifactId>
+                <executions>
+                    <execution>
+                        <id>dst-agent-path</id>
+                        <goals>
+                            <goal>properties</goal>
+                        </goals>
+                    </execution>
+                </executions>
+            </plugin>
             <plugin>
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-surefire-plugin</artifactId>
                 <configuration>
-                    <!-- the agent that rewrites the time calls is attached at 
runtime (as Mockito does) -->
-                    <argLine>${camel.surefire.fork.vmargs} -Xmx1g 
@{argLine}</argLine>
+                    <!-- the agent that rewrites the time calls is loaded when 
the JVM starts -->
+                    <argLine>${camel.surefire.fork.vmargs} 
"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}" -Xmx1g @{argLine}</argLine>
                     <systemPropertyVariables>
                         
<dst.traceDir>${project.build.directory}/dst</dst.traceDir>
                     </systemPropertyVariables>
diff --git a/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc 
b/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc
index 20ad229f32b7..8c5136a44328 100644
--- a/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc
+++ b/components/camel-test/camel-test-dst/src/main/docs/test-dst.adoc
@@ -226,6 +226,9 @@ Netty, an HTTP client pool), fake it above them.
 
 === 2. Run the escape scan before writing anything
 
+Add the agent to the module's pom first, see <<_setup,Setup>>: without it 
every test class with `@DstInstrument`
+fails with "The byte code agent of camel-test-dst is not loaded".
+
 Write the smallest test with `@DstInstrument("<component package>")` and one 
seed, run it, and read
 `target/dst/<Test>.escapes.txt`. Every line is a place where the component 
leaves the harness. For each one, decide:
 
@@ -449,6 +452,7 @@ Later worlds (IMAP, JMS, NATS JetStream, Azure Service Bus) 
showed more:
 
 === Checklist
 
+* [ ] The module's pom loads the Byte Buddy agent with `-javaagent` (see 
<<_setup,Setup>>).
 * [ ] The client is swapped through an endpoint option or a static mock, at a 
synchronous level.
 * [ ] The escape scan was read; every escape is fixed, avoided or documented; 
the test runs in strict mode.
 * [ ] Every behaviour of the fake quotes the documentation; assumptions are 
marked; what is not modelled is listed.
@@ -567,6 +571,8 @@ Reports in `target/dst/`: `<Test>.escapes.txt` (the scan), 
`<Test>.summary.txt`
 left alive, runtime escapes) and `<Test>.<method>/seed-<n>.trace` (the trace 
of each failing or replayed seed, with
 its SHA-256 on the second line).
 
+== Setup
+
 Add the module with test scope:
 
 [source,xml]
@@ -577,3 +583,77 @@ Add the module with test scope:
     <scope>test</scope>
 </dependency>
 ----
+
+The rewriting needs the Byte Buddy agent, loaded with `-javaagent` when the 
JVM of the tests starts. The harness does
+not attach it to the running JVM: the JDK warns about an agent loaded that 
way, and is to refuse it by default
+(JEP 451). Let the `properties` goal of the `maven-dependency-plugin` set the 
path of the agent jar (the property
+`net.bytebuddy:byte-buddy-agent:jar`; `byte-buddy-agent` comes with 
`camel-test-dst`), and add the agent to the
+surefire `argLine`. In a project of your own:
+
+[source,xml,subs="-attributes"]
+----
+<properties>
+    <!-- empty by default: a plugin such as JaCoCo sets it, and surefire adds 
it through @{argLine} -->
+    <argLine/>
+</properties>
+
+<build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-dependency-plugin</artifactId>
+            <executions>
+                <execution>
+                    <id>dst-agent-path</id>
+                    <goals>
+                        <goal>properties</goal>
+                    </goals>
+                </execution>
+            </executions>
+        </plugin>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-surefire-plugin</artifactId>
+            <configuration>
+                <argLine>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}" 
@{argLine}</argLine>
+            </configuration>
+        </plugin>
+    </plugins>
+</build>
+----
+
+The `properties` goal runs in the `initialize` phase, so `mvn test` sets the 
property before surefire starts. Keep
+`+@{argLine}+` in the `argLine`, so that an agent another plugin sets (the 
JaCoCo agent of `prepare-agent`) is still
+loaded; the empty `<argLine/>` property keeps surefire from passing 
`+@{argLine}+` as it is when no plugin sets it. The
+quotes keep a path with a space in one argument (surefire splits the `argLine` 
at the spaces outside quotes).
+
+In the Camel build, a module with DST tests adds the agent to 
`camel.surefire.fork.additional-vmargs`, which the
+`argLine` of the parent includes (with `+@{argLine}+`):
+
+[source,xml]
+----
+<properties>
+    
<camel.surefire.fork.additional-vmargs>"-javaagent:${net.bytebuddy:byte-buddy-agent:jar}"</camel.surefire.fork.additional-vmargs>
+</properties>
+
+<build>
+    <plugins>
+        <plugin>
+            <groupId>org.apache.maven.plugins</groupId>
+            <artifactId>maven-dependency-plugin</artifactId>
+            <executions>
+                <execution>
+                    <id>dst-agent-path</id>
+                    <goals>
+                        <goal>properties</goal>
+                    </goals>
+                </execution>
+            </executions>
+        </plugin>
+    </plugins>
+</build>
+----
+
+Without the agent, every test class with `@DstInstrument` fails with "The byte 
code agent of camel-test-dst is not
+loaded". To run a DST test from an IDE, add `-javaagent:<path of 
byte-buddy-agent.jar>` to the JVM options of the run
+configuration.
diff --git 
a/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstInstrumentation.java
 
b/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstInstrumentation.java
index e0d172978882..07dbf6776fa0 100644
--- 
a/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstInstrumentation.java
+++ 
b/components/camel-test/camel-test-dst/src/main/java/org/apache/camel/test/dst/DstInstrumentation.java
@@ -74,7 +74,9 @@ import net.bytebuddy.jar.asm.Opcodes;
  * virtual clock while a run is open (see {@link Dst}); only its blocking 
waits, such as the permit queue of
  * {@code org.apache.camel.processor.TotalRequestsThrottler}, still need the 
rewriting.
  * <p>
- * Uses a runtime-attached agent (as Mockito's inline mock maker does) and the 
ASM copy shipped in Byte Buddy.
+ * Uses the Byte Buddy agent, loaded when the JVM starts with {@code 
-javaagent:<path of byte-buddy-agent.jar>} (the
+ * surefire {@code argLine} of the modules with DST tests), and the ASM copy 
shipped in Byte Buddy. The agent is not
+ * attached at runtime: the JDK warns about agents loaded into a running JVM 
and is to disallow it by default (JEP 451).
  */
 public final class DstInstrumentation {
 
@@ -113,9 +115,10 @@ public final class DstInstrumentation {
 
     /**
      * Installs the time rewriting for the classes of these packages (and 
their sub packages): the code under test.
-     * Classes already loaded are retransformed.
+     * Classes already loaded are retransformed. Fails when the agent is not 
loaded, on every call.
      */
     public static synchronized void install(String... packages) {
+        ensureAgent();
         List<String> added = new ArrayList<>();
         for (String p : packages) {
             String internal = p.replace('.', '/') + "/";
@@ -145,9 +148,11 @@ public final class DstInstrumentation {
      * the code under test that reads the clock and does not use {@code 
org.apache.camel.util.TimeSource}, such as
      * {@code org.apache.camel.support.ResetableClock}. An entry whose last 
segment starts with an upper case letter is
      * a class (with its nested classes), else a package (with its sub 
packages). The rewriting stays for the rest of
-     * the JVM; outside a run the rewritten calls read the real clock.
+     * the JVM; outside a run the rewritten calls read the real clock. Fails 
when the agent is not loaded, on every
+     * call.
      */
     public static synchronized void alsoRewrite(String... classesOrPackages) {
+        ensureAgent();
         List<String> added = new ArrayList<>();
         for (String name : classesOrPackages) {
             if (name == null || name.isBlank()) {
@@ -169,10 +174,6 @@ public final class DstInstrumentation {
         if (added.isEmpty()) {
             return;
         }
-        if (instrumentation == null) {
-            instrumentation = ByteBuddyAgent.install();
-            instrumentation.addTransformer(new TimeTransformer(), true);
-        }
         List<Class<?>> loaded = new ArrayList<>();
         for (Class<?> c : instrumentation.getAllLoadedClasses()) {
             String name = c.getName().replace('.', '/');
@@ -189,6 +190,30 @@ public final class DstInstrumentation {
         }
     }
 
+    /**
+     * Looks up the agent before {@link #install(String...)} or {@link 
#alsoRewrite(String...)} registers anything: a
+     * package registered while the agent is missing would make the next class 
of that package skip the lookup and run
+     * without the rewriting, with no error.
+     */
+    private static void ensureAgent() {
+        if (instrumentation == null) {
+            instrumentation = agent();
+            instrumentation.addTransformer(new TimeTransformer(), true);
+        }
+    }
+
+    private static Instrumentation agent() {
+        try {
+            return ByteBuddyAgent.getInstrumentation();
+        } catch (IllegalStateException e) {
+            throw new IllegalStateException(
+                    "The byte code agent of camel-test-dst is not loaded: 
start the JVM of the tests with"
+                                            + " -javaagent:<path of 
byte-buddy-agent.jar>, for example in the surefire argLine"
+                                            + " (see \"Setup\" in 
test-dst.adoc)",
+                    e);
+        }
+    }
+
     private static boolean matches(String prefix, String internalName) {
         if (prefix.endsWith("/")) {
             return internalName.startsWith(prefix);
diff --git 
a/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstInstrumentationTest.java
 
b/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstInstrumentationTest.java
index 5d100ef3f405..0e11c03e0c86 100644
--- 
a/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstInstrumentationTest.java
+++ 
b/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstInstrumentationTest.java
@@ -16,22 +16,34 @@
  */
 package org.apache.camel.test.dst;
 
+import java.lang.management.ManagementFactory;
 import java.time.Instant;
 import java.time.LocalDate;
 import java.time.ZoneOffset;
 import java.util.ArrayList;
 import java.util.List;
 
+import net.bytebuddy.agent.ByteBuddyAgent;
 import org.apache.camel.test.dst.sample.SampleClock;
 import org.apache.camel.test.dst.sample.SampleStreams;
 import org.junit.jupiter.api.Test;
 
 import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
 import static org.junit.jupiter.api.Assertions.assertTrue;
 
 @DstInstrument("org.apache.camel.test.dst.sample")
 class DstInstrumentationTest {
 
+    @Test
+    void theAgentIsLoadedWhenTheJvmStarts() {
+        // -javaagent in the surefire argLine of the module, not an agent 
attached to the running JVM
+        List<String> arguments = 
ManagementFactory.getRuntimeMXBean().getInputArguments();
+        assertTrue(arguments.stream().anyMatch(a -> 
a.startsWith("-javaagent:") && a.contains("byte-buddy-agent")),
+                arguments.toString());
+        assertNotNull(ByteBuddyAgent.getInstrumentation());
+    }
+
     @Test
     void codeUnderTestReadsVirtualTime() {
         Dst dst = new Dst(1);
diff --git 
a/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstMissingAgentTest.java
 
b/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstMissingAgentTest.java
new file mode 100644
index 000000000000..32603862662f
--- /dev/null
+++ 
b/components/camel-test/camel-test-dst/src/test/java/org/apache/camel/test/dst/DstMissingAgentTest.java
@@ -0,0 +1,95 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements.  See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You under the Apache License, Version 2.0
+ * (the "License"); you may not use this file except in compliance with
+ * the License.  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.apache.camel.test.dst;
+
+import java.io.File;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.io.TempDir;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertTrue;
+
+/**
+ * Without the agent every {@code @DstInstrument} class fails with the message 
that names the setup, not only the first
+ * class of a package (two classes of a module often instrument the same 
package and run in one fork). Runs a JVM
+ * started without {@code -javaagent}; the JVM of this test has the agent.
+ */
+class DstMissingAgentTest {
+
+    private static final String MESSAGE = "The byte code agent of 
camel-test-dst is not loaded";
+
+    @Test
+    void everyClassFailsWhileTheAgentIsMissing(@TempDir Path dir) throws 
Exception {
+        File out = dir.resolve("probe.out").toFile();
+        File err = dir.resolve("probe.err").toFile();
+        ProcessBuilder builder = new ProcessBuilder(
+                Path.of(System.getProperty("java.home"), "bin", 
"java").toString(), "-Xmx64m",
+                "-cp", System.getProperty("java.class.path"), 
Probe.class.getName())
+                .redirectOutput(out).redirectError(err);
+        // the JVM options of the environment could load an agent, and the JVM 
prints that it picked them up
+        builder.environment().keySet().removeAll(List.of("JAVA_TOOL_OPTIONS", 
"JDK_JAVA_OPTIONS", "_JAVA_OPTIONS"));
+        Process probe = builder.start();
+        boolean ended = probe.waitFor(60, TimeUnit.SECONDS);
+        if (!ended) {
+            probe.destroyForcibly().waitFor();
+        }
+        String output = Files.readString(out.toPath(), StandardCharsets.UTF_8) 
+ "\nstderr:\n"
+                        + Files.readString(err.toPath(), 
StandardCharsets.UTF_8);
+        assertTrue(ended, output);
+        assertEquals(0, probe.exitValue(), output);
+        assertEquals(List.of(
+                "install sample (first class): " + MESSAGE,
+                "install sample (second class): " + MESSAGE,
+                "alsoRewrite ResetableClock (first class): " + MESSAGE,
+                "alsoRewrite ResetableClock (second class): " + MESSAGE,
+                "packages: []",
+                "alsoRewritten: []"),
+                Files.readAllLines(out.toPath(), StandardCharsets.UTF_8), 
output);
+    }
+
+    /** What the extension does for two {@code @DstInstrument} classes of the 
same package, in a JVM without agent. */
+    static final class Probe {
+
+        public static void main(String[] args) {
+            for (String c : new String[] { "first class", "second class" }) {
+                call("install sample (" + c + ")", () -> 
DstInstrumentation.install("org.apache.camel.test.dst.sample"));
+            }
+            for (String c : new String[] { "first class", "second class" }) {
+                call("alsoRewrite ResetableClock (" + c + ")",
+                        () -> 
DstInstrumentation.alsoRewrite("org.apache.camel.support.ResetableClock"));
+            }
+            System.out.println("packages: " + DstInstrumentation.packages());
+            System.out.println("alsoRewritten: " + 
DstInstrumentation.alsoRewritten());
+        }
+
+        private static void call(String what, Runnable call) {
+            try {
+                call.run();
+                System.out.println(what + ": no error");
+            } catch (IllegalStateException e) {
+                String message = e.getMessage();
+                System.out.println(what + ": " + (message.startsWith(MESSAGE) 
? MESSAGE : message));
+            }
+        }
+    }
+}

Reply via email to