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));
+ }
+ }
+ }
+}