From cabecf046f0935adfa9f264ca1a5399c7d3edc6a Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 27 Nov 2025 14:05:45 +0100 Subject: [PATCH 01/18] boostrap app in docker image --- settings.gradle.kts | 2 + .../images/extensions/build.gradle.kts | 37 ++++++++++++++ .../extensions/inlined/build.gradle.kts | 3 ++ .../smoketest/extensions/app/AppMain.java | 41 ++++++++++++++++ .../smoketest/ExtensionsSmokeTest.java | 49 +++++++++++++++++++ 5 files changed, 132 insertions(+) create mode 100644 smoke-tests/images/extensions/build.gradle.kts create mode 100644 smoke-tests/images/extensions/inlined/build.gradle.kts create mode 100644 smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java create mode 100644 smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java diff --git a/settings.gradle.kts b/settings.gradle.kts index 125371b711ea..0e7f205e35bb 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -156,6 +156,8 @@ include(":smoke-tests:images:servlet") include(":smoke-tests:images:servlet:servlet-3.0") include(":smoke-tests:images:servlet:servlet-5.0") include(":smoke-tests:images:spring-boot") +include(":smoke-tests:images:extensions") +include(":smoke-tests:images:extensions:inlined") include(":smoke-tests-otel-starter:spring-smoke-testing") include(":smoke-tests-otel-starter:spring-boot-2") diff --git a/smoke-tests/images/extensions/build.gradle.kts b/smoke-tests/images/extensions/build.gradle.kts new file mode 100644 index 000000000000..256e541b43ca --- /dev/null +++ b/smoke-tests/images/extensions/build.gradle.kts @@ -0,0 +1,37 @@ +import com.google.cloud.tools.jib.gradle.JibTask +import java.time.LocalDateTime +import java.time.format.DateTimeFormatter + +plugins { + id("otel.java-conventions") + id("com.google.cloud.tools.jib") +} + +dependencies { +} + +val targetJDK = project.findProperty("targetJDK") ?: "17" + +val tag = findProperty("tag") + ?: DateTimeFormatter.ofPattern("yyyyMMdd.HHmmSS").format(LocalDateTime.now()) + +java { + // needed by jib to detect java version used in project + // for jdk9+ jib uses an entrypoint that doesn't work with jdk8 + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +val repo = System.getenv("GITHUB_REPOSITORY") ?: "open-telemetry/opentelemetry-java-instrumentation" + +jib { + from.image = "eclipse-temurin:$targetJDK" + to.image = "ghcr.io/$repo/smoke-test-extensions:jdk$targetJDK-$tag" +} + +tasks { + withType().configureEach { + // Jib tasks access Task.project at execution time which is not compatible with configuration cache + notCompatibleWithConfigurationCache("Jib task accesses Task.project at execution time") + } +} diff --git a/smoke-tests/images/extensions/inlined/build.gradle.kts b/smoke-tests/images/extensions/inlined/build.gradle.kts new file mode 100644 index 000000000000..0a3932d24293 --- /dev/null +++ b/smoke-tests/images/extensions/inlined/build.gradle.kts @@ -0,0 +1,3 @@ +plugins { + id("otel.java-conventions") +} diff --git a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java new file mode 100644 index 000000000000..acd20c5bfca7 --- /dev/null +++ b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest.extensions.app; + +public class AppMain { + + private AppMain() {} + + public static void main(String[] args) { + testReturnValue(); + testMethodArguments(); + } + + private static void testReturnValue() { + int returnValue = returnValue(42); + if (returnValue != 42) { + System.out.println("return value has been modified"); + } else { + System.out.println("return value not modified"); + } + } + + private static int returnValue(int value) { + return value; + } + + private static void testMethodArguments() { + methodArguments(42, 42); + } + + private static void methodArguments(int argument, int originalArgument) { + if (argument != originalArgument) { + System.out.println("argument has been modified"); + } else { + System.out.println("argument not modified"); + } + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java new file mode 100644 index 000000000000..38acb52381c8 --- /dev/null +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -0,0 +1,49 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest; + +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.Slf4jLogConsumer; +import org.testcontainers.utility.DockerImageName; +import org.testcontainers.utility.MountableFile; +import java.time.Duration; + +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +public class ExtensionsSmokeTest { + + private static final Logger logger = LoggerFactory.getLogger(ExtensionsSmokeTest.class); + + private static final String TARGET_AGENT_FILENAME = "opentelemetry-javaagent.jar"; + private static final String agentPath = + System.getProperty("io.opentelemetry.smoketest.agent.shadowJar.path"); + + private static final String IMAGE_VERSION = "jdk17-20251127.135061"; + + @Test + void testApp() throws InterruptedException { + GenericContainer target = new GenericContainer<>( + DockerImageName.parse( + "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-extensions:" + + IMAGE_VERSION)) + .withStartupTimeout(Duration.ofMinutes(1)) + .withLogConsumer(new Slf4jLogConsumer(logger)) + .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/" + TARGET_AGENT_FILENAME) + .withCopyFileToContainer( + MountableFile.forHostPath(agentPath), "/" + TARGET_AGENT_FILENAME); + target.start(); + while (target.isRunning()) { + Thread.sleep(1000); + } + assertThat(target.getLogs().split("\n")) + .contains("return value not modified", "argument not modified"); + } + + +} From dda3c8574fe4ae4923e8d281a8c19383e304d5f0 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 27 Nov 2025 15:42:03 +0100 Subject: [PATCH 02/18] start creating instrumentation --- .../extensions/inlined/build.gradle.kts | 7 ++++ .../inlined/SmokeInlinedInstrumentation.java | 41 +++++++++++++++++++ 2 files changed, 48 insertions(+) create mode 100644 smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java diff --git a/smoke-tests/images/extensions/inlined/build.gradle.kts b/smoke-tests/images/extensions/inlined/build.gradle.kts index 0a3932d24293..b98185f1e684 100644 --- a/smoke-tests/images/extensions/inlined/build.gradle.kts +++ b/smoke-tests/images/extensions/inlined/build.gradle.kts @@ -1,3 +1,10 @@ plugins { id("otel.java-conventions") } + +dependencies { + compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi") + compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api") + compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") + compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api") +} diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java new file mode 100644 index 000000000000..f161c09469fc --- /dev/null +++ b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -0,0 +1,41 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest.extensions.inlined; + +import static net.bytebuddy.matcher.ElementMatchers.isPublic; +import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; +import static net.bytebuddy.matcher.ElementMatchers.takesArgument; + +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import net.bytebuddy.asm.Advice; +import net.bytebuddy.description.type.TypeDescription; +import net.bytebuddy.matcher.ElementMatcher; + +public class SmokeInlinedInstrumentation implements TypeInstrumentation { + + @Override + public ElementMatcher typeMatcher() { + return named("io.opentelemetry.smoketest.extensions.app.AppMain"); + } + + @Override + public void transform(TypeTransformer typeTransformer) { + typeTransformer.applyAdviceToMethod( + namedOneOf("testReturnValue").and(takesArgument(0, int.class)).and(isPublic()), + this.getClass().getName() + "$ModifyReturnValueAdvice"); + } + + @SuppressWarnings("unused") + public static class ModifyReturnValueAdvice { + + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Argument(value = 1, readOnly = false) int returnValue) { + returnValue = returnValue + 1; + } + } +} From 45a5d606f97a8f4e45450ef771d81b4e1572139a Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:25:12 +0100 Subject: [PATCH 03/18] wip --- smoke-tests/build.gradle.kts | 7 ++++++- .../smoketest/ExtensionsSmokeTest.java | 15 +++++++++++---- 2 files changed, 17 insertions(+), 5 deletions(-) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index f1719c82abcd..fd367f83a006 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -78,8 +78,13 @@ tasks { .withPropertyName("javaagent") .withNormalizer(ClasspathNormalizer::class) + val extensionInlineShadowTask = project(":smoke-tests:images:extensions:inlined").tasks.named("jar") + val extensionInlineJarPath = extensionInlineShadowTask.flatMap { it.archiveFile } + doFirst { - jvmArgs("-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}") + jvmArgs( + "-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}", + "-Dio.opentelemetry.smoketest.extension.inline.path=${extensionInlineJarPath.get()}") } } } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 38acb52381c8..27f48bd673e6 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -20,23 +20,30 @@ public class ExtensionsSmokeTest { private static final Logger logger = LoggerFactory.getLogger(ExtensionsSmokeTest.class); - private static final String TARGET_AGENT_FILENAME = "opentelemetry-javaagent.jar"; + private static final String TARGET_AGENT_FILENAME = "/opentelemetry-javaagent.jar"; private static final String agentPath = System.getProperty("io.opentelemetry.smoketest.agent.shadowJar.path"); + private static final String TARGET_EXTENSION_FILENAME = "/opentelemetry-extension.jar"; + private static final String extensionInlinePath = + System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); + private static final String IMAGE_VERSION = "jdk17-20251127.135061"; @Test - void testApp() throws InterruptedException { + void inlinedExtension() throws InterruptedException { GenericContainer target = new GenericContainer<>( DockerImageName.parse( "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-extensions:" + IMAGE_VERSION)) .withStartupTimeout(Duration.ofMinutes(1)) .withLogConsumer(new Slf4jLogConsumer(logger)) - .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:/" + TARGET_AGENT_FILENAME) + .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:" + TARGET_AGENT_FILENAME) + .withEnv("OTEL_JAVAAGENT_EXTENSIONS", TARGET_EXTENSION_FILENAME) + .withCopyFileToContainer( + MountableFile.forHostPath(agentPath), TARGET_AGENT_FILENAME) .withCopyFileToContainer( - MountableFile.forHostPath(agentPath), "/" + TARGET_AGENT_FILENAME); + MountableFile.forHostPath(extensionInlinePath), TARGET_EXTENSION_FILENAME); target.start(); while (target.isRunning()) { Thread.sleep(1000); From 678a6fbf2e8b0f23d1d292210b8ddef90bebef13 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Thu, 27 Nov 2025 16:50:26 +0100 Subject: [PATCH 04/18] wip --- .../extensions/inlined/build.gradle.kts | 3 +++ .../inlined/SmokeInlinedInstrumentation.java | 2 +- .../SmokeInlinedInstrumentationModule.java | 25 +++++++++++++++++++ .../smoketest/ExtensionsSmokeTest.java | 5 ++++ 4 files changed, 34 insertions(+), 1 deletion(-) create mode 100644 smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java diff --git a/smoke-tests/images/extensions/inlined/build.gradle.kts b/smoke-tests/images/extensions/inlined/build.gradle.kts index b98185f1e684..4ebd33de1217 100644 --- a/smoke-tests/images/extensions/inlined/build.gradle.kts +++ b/smoke-tests/images/extensions/inlined/build.gradle.kts @@ -7,4 +7,7 @@ dependencies { compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api") compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator") compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api") + + compileOnly("com.google.auto.service:auto-service") + compileOnly("com.google.auto.service:auto-service-annotations") } diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index f161c09469fc..037783dd45cb 100644 --- a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -26,7 +26,7 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer typeTransformer) { typeTransformer.applyAdviceToMethod( - namedOneOf("testReturnValue").and(takesArgument(0, int.class)).and(isPublic()), + namedOneOf("returnValue").and(takesArgument(0, int.class)).and(isPublic()), this.getClass().getName() + "$ModifyReturnValueAdvice"); } diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java new file mode 100644 index 000000000000..f38b1779d492 --- /dev/null +++ b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java @@ -0,0 +1,25 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.smoketest.extensions.inlined; + +import com.google.auto.service.AutoService; +import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule; +import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; +import java.util.Collections; +import java.util.List; + +@AutoService(InstrumentationModule.class) +public class SmokeInlinedInstrumentationModule extends InstrumentationModule { + + public SmokeInlinedInstrumentationModule() { + super("smoke-test-extension-inlined"); + } + + @Override + public List typeInstrumentations() { + return Collections.singletonList(new SmokeInlinedInstrumentation()); + } +} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 27f48bd673e6..cef029292abe 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -38,6 +38,11 @@ void inlinedExtension() throws InterruptedException { + IMAGE_VERSION)) .withStartupTimeout(Duration.ofMinutes(1)) .withLogConsumer(new Slf4jLogConsumer(logger)) + // disable export as we only instrument + .withEnv("OTEL_TRACES_EXPORTER", "none") + .withEnv("OTEL_METRICS_EXPORTER", "none") + .withEnv("OTEL_LOGS_EXPORTER", "none") + // .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:" + TARGET_AGENT_FILENAME) .withEnv("OTEL_JAVAAGENT_EXTENSIONS", TARGET_EXTENSION_FILENAME) .withCopyFileToContainer( From 7c907ce77cc238dec4e707c6ad28cd7057bc2b87 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:21:41 +0100 Subject: [PATCH 05/18] add missing dependencies --- smoke-tests/images/extensions/inlined/build.gradle.kts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/smoke-tests/images/extensions/inlined/build.gradle.kts b/smoke-tests/images/extensions/inlined/build.gradle.kts index 4ebd33de1217..5ea8a3ee4270 100644 --- a/smoke-tests/images/extensions/inlined/build.gradle.kts +++ b/smoke-tests/images/extensions/inlined/build.gradle.kts @@ -10,4 +10,9 @@ dependencies { compileOnly("com.google.auto.service:auto-service") compileOnly("com.google.auto.service:auto-service-annotations") + + annotationProcessor("com.google.auto.service:auto-service") + + // Used by byte-buddy but not brought in as a transitive dependency + compileOnly("com.google.code.findbugs:annotations") } From 9c9d3dee34e92dd58991cd080d3aa62e3b882376 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:37:06 +0100 Subject: [PATCH 06/18] add missing task dependency for tests --- smoke-tests/build.gradle.kts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index fd367f83a006..7b3a65fd890c 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -81,6 +81,8 @@ tasks { val extensionInlineShadowTask = project(":smoke-tests:images:extensions:inlined").tasks.named("jar") val extensionInlineJarPath = extensionInlineShadowTask.flatMap { it.archiveFile } + dependsOn(shadowTask, extensionInlineShadowTask) + doFirst { jvmArgs( "-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}", From 9766b9fd338d1923d6d51e70850ae45dacbf4dbc Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:45:41 +0100 Subject: [PATCH 07/18] fix instrumentation --- .../extensions/inlined/SmokeInlinedInstrumentation.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index 037783dd45cb..3ef791cd6b28 100644 --- a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -5,7 +5,6 @@ package io.opentelemetry.smoketest.extensions.inlined; -import static net.bytebuddy.matcher.ElementMatchers.isPublic; import static net.bytebuddy.matcher.ElementMatchers.named; import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; @@ -26,7 +25,7 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer typeTransformer) { typeTransformer.applyAdviceToMethod( - namedOneOf("returnValue").and(takesArgument(0, int.class)).and(isPublic()), + namedOneOf("returnValue").and(takesArgument(0, int.class)), this.getClass().getName() + "$ModifyReturnValueAdvice"); } @@ -34,7 +33,7 @@ public void transform(TypeTransformer typeTransformer) { public static class ModifyReturnValueAdvice { @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit(@Advice.Argument(value = 1, readOnly = false) int returnValue) { + public static void onExit(@Advice.Return(readOnly = false) int returnValue) { returnValue = returnValue + 1; } } From 352431de74e81819fdd5e234a334220b0b885a9a Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 16:54:45 +0100 Subject: [PATCH 08/18] it's working !! --- .../smoketest/extensions/app/AppMain.java | 13 ++++++++---- .../smoketest/ExtensionsSmokeTest.java | 21 ++++++++++++++----- 2 files changed, 25 insertions(+), 9 deletions(-) diff --git a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index acd20c5bfca7..6ab5f215fa1a 100644 --- a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -17,9 +17,9 @@ public static void main(String[] args) { private static void testReturnValue() { int returnValue = returnValue(42); if (returnValue != 42) { - System.out.println("return value has been modified"); + printMsg("return value has been modified"); } else { - System.out.println("return value not modified"); + printMsg("return value not modified"); } } @@ -33,9 +33,14 @@ private static void testMethodArguments() { private static void methodArguments(int argument, int originalArgument) { if (argument != originalArgument) { - System.out.println("argument has been modified"); + printMsg("argument has been modified"); } else { - System.out.println("argument not modified"); + printMsg("argument not modified"); } } + + private static void printMsg(String msg) { + // using a known prefix to allow easy filtering of expected output in test + System.out.println(">>> " + msg); + } } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index cef029292abe..3dcd24d8784e 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -9,10 +9,14 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.output.OutputFrame; import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; import java.time.Duration; +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; @@ -28,7 +32,10 @@ public class ExtensionsSmokeTest { private static final String extensionInlinePath = System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); - private static final String IMAGE_VERSION = "jdk17-20251127.135061"; + // TODO: add version constant in TestImageVersions. + private static final String IMAGE_VERSION = "jdk17-20251128.165054"; + + // TODO: test with and without indy mode @Test void inlinedExtension() throws InterruptedException { @@ -42,7 +49,6 @@ void inlinedExtension() throws InterruptedException { .withEnv("OTEL_TRACES_EXPORTER", "none") .withEnv("OTEL_METRICS_EXPORTER", "none") .withEnv("OTEL_LOGS_EXPORTER", "none") - // .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:" + TARGET_AGENT_FILENAME) .withEnv("OTEL_JAVAAGENT_EXTENSIONS", TARGET_EXTENSION_FILENAME) .withCopyFileToContainer( @@ -51,10 +57,15 @@ void inlinedExtension() throws InterruptedException { MountableFile.forHostPath(extensionInlinePath), TARGET_EXTENSION_FILENAME); target.start(); while (target.isRunning()) { - Thread.sleep(1000); + Thread.sleep(100); } - assertThat(target.getLogs().split("\n")) - .contains("return value not modified", "argument not modified"); + + List appOutput = Arrays.stream(target.getLogs(OutputFrame.OutputType.STDOUT).split("\n")) + .filter(line -> line.startsWith(">>>")) + .map(line -> line.substring(4)) + .collect(Collectors.toList()); + assertThat(appOutput) + .containsExactlyInAnyOrder("return value has been modified", "argument not modified"); } From b0a8052fefbccbf4506a2bf94a98df20d1b7d285 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Fri, 28 Nov 2025 17:06:28 +0100 Subject: [PATCH 09/18] further simplify and modify arguments --- .../inlined/SmokeInlinedInstrumentation.java | 14 ++++++++++++-- .../smoketest/extensions/app/AppMain.java | 12 ++++-------- .../smoketest/ExtensionsSmokeTest.java | 10 +++------- 3 files changed, 19 insertions(+), 17 deletions(-) diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index 3ef791cd6b28..ffaa5bd1e1f9 100644 --- a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -6,7 +6,6 @@ package io.opentelemetry.smoketest.extensions.inlined; import static net.bytebuddy.matcher.ElementMatchers.named; -import static net.bytebuddy.matcher.ElementMatchers.namedOneOf; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; @@ -25,8 +24,11 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer typeTransformer) { typeTransformer.applyAdviceToMethod( - namedOneOf("returnValue").and(takesArgument(0, int.class)), + named("returnValue").and(takesArgument(0, int.class)), this.getClass().getName() + "$ModifyReturnValueAdvice"); + typeTransformer.applyAdviceToMethod( + named("methodArguments").and(takesArgument(0, int.class)), + this.getClass().getName() + "$ModifyArgumentsAdvice"); } @SuppressWarnings("unused") @@ -37,4 +39,12 @@ public static void onExit(@Advice.Return(readOnly = false) int returnValue) { returnValue = returnValue + 1; } } + + @SuppressWarnings("unused") + public static class ModifyArgumentsAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.Argument(value = 0, readOnly = false) int argument) { + argument = argument - 1; + } + } } diff --git a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index 6ab5f215fa1a..d32ef7f5fd1d 100644 --- a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -17,9 +17,9 @@ public static void main(String[] args) { private static void testReturnValue() { int returnValue = returnValue(42); if (returnValue != 42) { - printMsg("return value has been modified"); + System.out.println("return value has been modified"); } else { - printMsg("return value not modified"); + System.out.println("return value not modified"); } } @@ -33,14 +33,10 @@ private static void testMethodArguments() { private static void methodArguments(int argument, int originalArgument) { if (argument != originalArgument) { - printMsg("argument has been modified"); + System.out.println("argument has been modified"); } else { - printMsg("argument not modified"); + System.out.println("argument not modified"); } } - private static void printMsg(String msg) { - // using a known prefix to allow easy filtering of expected output in test - System.out.println(">>> " + msg); - } } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 3dcd24d8784e..0ea231198f3e 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -16,7 +16,6 @@ import java.time.Duration; import java.util.Arrays; import java.util.List; -import java.util.stream.Collectors; import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; @@ -33,7 +32,7 @@ public class ExtensionsSmokeTest { System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); // TODO: add version constant in TestImageVersions. - private static final String IMAGE_VERSION = "jdk17-20251128.165054"; + private static final String IMAGE_VERSION = "jdk17-20251128.170123"; // TODO: test with and without indy mode @@ -60,12 +59,9 @@ void inlinedExtension() throws InterruptedException { Thread.sleep(100); } - List appOutput = Arrays.stream(target.getLogs(OutputFrame.OutputType.STDOUT).split("\n")) - .filter(line -> line.startsWith(">>>")) - .map(line -> line.substring(4)) - .collect(Collectors.toList()); + List appOutput = Arrays.asList(target.getLogs(OutputFrame.OutputType.STDOUT).split("\n")); assertThat(appOutput) - .containsExactlyInAnyOrder("return value has been modified", "argument not modified"); + .containsExactlyInAnyOrder("return value has been modified", "argument has been modified"); } From a157d643305fb109572e9e27433438829a5605ac Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:00:42 +0100 Subject: [PATCH 10/18] wip: test with virtual fields --- .../inlined/SmokeInlinedInstrumentation.java | 41 ++++++++++++++++--- .../smoketest/extensions/app/AppMain.java | 23 +++++++++++ .../smoketest/ExtensionsSmokeTest.java | 2 +- 3 files changed, 60 insertions(+), 6 deletions(-) diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index ffaa5bd1e1f9..0d70eb143599 100644 --- a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -6,8 +6,10 @@ package io.opentelemetry.smoketest.extensions.inlined; import static net.bytebuddy.matcher.ElementMatchers.named; +import static net.bytebuddy.matcher.ElementMatchers.returns; import static net.bytebuddy.matcher.ElementMatchers.takesArgument; +import io.opentelemetry.instrumentation.api.util.VirtualField; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; import net.bytebuddy.asm.Advice; @@ -22,13 +24,25 @@ public ElementMatcher typeMatcher() { } @Override - public void transform(TypeTransformer typeTransformer) { - typeTransformer.applyAdviceToMethod( - named("returnValue").and(takesArgument(0, int.class)), + public void transform(TypeTransformer transformer) { + transformer.applyAdviceToMethod( + named("returnValue") + .and(takesArgument(0, int.class)), this.getClass().getName() + "$ModifyReturnValueAdvice"); - typeTransformer.applyAdviceToMethod( - named("methodArguments").and(takesArgument(0, int.class)), + transformer.applyAdviceToMethod( + named("methodArguments") + .and(takesArgument(0, int.class)), this.getClass().getName() + "$ModifyArgumentsAdvice"); + transformer.applyAdviceToMethod( + named("setVirtualFieldValue") + .and(takesArgument(0, Runnable.class)) + .and(takesArgument(1, Integer.class)), + this.getClass().getName() + "$VirtualFieldSetAdvice"); + transformer.applyAdviceToMethod( + named("getVirtualFieldValue") + .and(takesArgument(0, Runnable.class)) + .and(returns(Integer.class)), + this.getClass().getName() + "$VirtualFieldGetAdvice"); } @SuppressWarnings("unused") @@ -47,4 +61,21 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) int arg argument = argument - 1; } } + + public static class VirtualFieldSetAdvice { + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter(@Advice.Argument(0) Runnable target,@Advice.Argument(1) Integer value) { + VirtualField field = VirtualField.find(Runnable.class, Integer.class); + field.set(target, value); + } + } + + public static class VirtualFieldGetAdvice { + @SuppressWarnings("UnusedVariable") + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit(@Advice.Argument(0) Runnable target, @Advice.Return(readOnly = false) Integer returnValue) { + VirtualField field = VirtualField.find(Runnable.class, Integer.class); + returnValue = field.get(target); + } + } } diff --git a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index d32ef7f5fd1d..bb3de3308ed9 100644 --- a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -12,6 +12,7 @@ private AppMain() {} public static void main(String[] args) { testReturnValue(); testMethodArguments(); + testVirtualFields(); } private static void testReturnValue() { @@ -24,6 +25,7 @@ private static void testReturnValue() { } private static int returnValue(int value) { + // method return value should be modified by instrumentation return value; } @@ -32,6 +34,7 @@ private static void testMethodArguments() { } private static void methodArguments(int argument, int originalArgument) { + // method first argument should be modified by instrumentation if (argument != originalArgument) { System.out.println("argument has been modified"); } else { @@ -39,4 +42,24 @@ private static void methodArguments(int argument, int originalArgument) { } } + private static void testVirtualFields() { + Runnable target = () -> {}; + setVirtualFieldValue(target, 42); + Integer fieldValue = getVirtualFieldValue(target); + if(fieldValue == null || fieldValue != 42){ + System.out.println("virtual field not supported"); + } else { + System.out.println("virtual field supported"); + } + } + + public static void setVirtualFieldValue(Runnable target, Integer value) { + // implementation should be provided by instrumentation + } + + public static Integer getVirtualFieldValue(Runnable target) { + // implementation should be provided by instrumentation + return null; + } + } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 0ea231198f3e..27b59c419b9a 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -32,7 +32,7 @@ public class ExtensionsSmokeTest { System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); // TODO: add version constant in TestImageVersions. - private static final String IMAGE_VERSION = "jdk17-20251128.170123"; + private static final String IMAGE_VERSION = "jdk17-20251201.122011"; // TODO: test with and without indy mode From 00e8b5989433fb001e7fa6350a64c20ad97376c7 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 13:45:38 +0100 Subject: [PATCH 11/18] add wip changes --- .../smoketest/ExtensionsSmokeTest.java | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 27b59c419b9a..dc0b4ad9a8b0 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -31,10 +31,20 @@ public class ExtensionsSmokeTest { private static final String extensionInlinePath = System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); - // TODO: add version constant in TestImageVersions. - private static final String IMAGE_VERSION = "jdk17-20251201.122011"; + // TODO: use reusable docker image when available + private static final String IMAGE_VERSION = "jdk17-latest"; + + // TODO: test with virtual fields directly into advice method + // - VirtualField.find method call directly in advice method + // - implement test in the application that allows to detect it: for example state has been attached to an immutable object + + // TODO: create an "indy compliant extension" + + // TODO: test with and without "indy mode" + // inlined extension should work as usual + // indy extension is only expected to work in "indy mode" + - // TODO: test with and without indy mode @Test void inlinedExtension() throws InterruptedException { @@ -50,6 +60,7 @@ void inlinedExtension() throws InterruptedException { .withEnv("OTEL_LOGS_EXPORTER", "none") .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:" + TARGET_AGENT_FILENAME) .withEnv("OTEL_JAVAAGENT_EXTENSIONS", TARGET_EXTENSION_FILENAME) + .withEnv("OTEL_JAVAAGENT_DEBUG", "true") .withCopyFileToContainer( MountableFile.forHostPath(agentPath), TARGET_AGENT_FILENAME) .withCopyFileToContainer( From 04d5252266e4e3827cc22e67fdc45125448f16fa Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 15:32:59 +0100 Subject: [PATCH 12/18] simplify things without custom docker image --- settings.gradle.kts | 4 +- smoke-tests/build.gradle.kts | 13 ++- .../extensions/inlined/build.gradle.kts | 0 .../inlined/SmokeInlinedInstrumentation.java | 12 +-- .../SmokeInlinedInstrumentationModule.java | 0 .../extensions/testapp/build.gradle.kts | 19 ++++ .../smoketest/extensions/app/AppMain.java | 7 +- .../images/extensions/build.gradle.kts | 37 ------- .../smoketest/ExtensionsSmokeTest.java | 102 ++++++++++-------- 9 files changed, 98 insertions(+), 96 deletions(-) rename smoke-tests/{images => }/extensions/inlined/build.gradle.kts (100%) rename smoke-tests/{images => }/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java (87%) rename smoke-tests/{images => }/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java (100%) create mode 100644 smoke-tests/extensions/testapp/build.gradle.kts rename smoke-tests/{images/extensions => extensions/testapp}/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java (91%) delete mode 100644 smoke-tests/images/extensions/build.gradle.kts diff --git a/settings.gradle.kts b/settings.gradle.kts index 0e7f205e35bb..75005d5b13bd 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -156,8 +156,8 @@ include(":smoke-tests:images:servlet") include(":smoke-tests:images:servlet:servlet-3.0") include(":smoke-tests:images:servlet:servlet-5.0") include(":smoke-tests:images:spring-boot") -include(":smoke-tests:images:extensions") -include(":smoke-tests:images:extensions:inlined") +include(":smoke-tests:extensions:testapp") +include(":smoke-tests:extensions:inlined") include(":smoke-tests-otel-starter:spring-smoke-testing") include(":smoke-tests-otel-starter:spring-boot-2") diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 7b3a65fd890c..1e986ef5f0a5 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -78,15 +78,20 @@ tasks { .withPropertyName("javaagent") .withNormalizer(ClasspathNormalizer::class) - val extensionInlineShadowTask = project(":smoke-tests:images:extensions:inlined").tasks.named("jar") - val extensionInlineJarPath = extensionInlineShadowTask.flatMap { it.archiveFile } + val extensionInlineTask = project(":smoke-tests:extensions:inlined").tasks.named("jar") + val extensionInlineJarPath = extensionInlineTask.flatMap { it.archiveFile } - dependsOn(shadowTask, extensionInlineShadowTask) + val extensionTestAppTask = project(":smoke-tests:extensions:testapp").tasks.named("jar") + val extensionTestAppJarPath = extensionTestAppTask.flatMap { it.archiveFile } + + dependsOn(shadowTask, extensionTestAppTask) doFirst { jvmArgs( "-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}", - "-Dio.opentelemetry.smoketest.extension.inline.path=${extensionInlineJarPath.get()}") + "-Dio.opentelemetry.smoketest.extension.inline.path=${extensionInlineJarPath.get()}", + "-Dio.opentelemetry.smoketest.extension.testapp.path=${extensionTestAppJarPath.get()}" + ) } } } diff --git a/smoke-tests/images/extensions/inlined/build.gradle.kts b/smoke-tests/extensions/inlined/build.gradle.kts similarity index 100% rename from smoke-tests/images/extensions/inlined/build.gradle.kts rename to smoke-tests/extensions/inlined/build.gradle.kts diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java similarity index 87% rename from smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java rename to smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index 0d70eb143599..4a349733c5f0 100644 --- a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -26,12 +26,10 @@ public ElementMatcher typeMatcher() { @Override public void transform(TypeTransformer transformer) { transformer.applyAdviceToMethod( - named("returnValue") - .and(takesArgument(0, int.class)), + named("returnValue").and(takesArgument(0, int.class)), this.getClass().getName() + "$ModifyReturnValueAdvice"); transformer.applyAdviceToMethod( - named("methodArguments") - .and(takesArgument(0, int.class)), + named("methodArguments").and(takesArgument(0, int.class)), this.getClass().getName() + "$ModifyArgumentsAdvice"); transformer.applyAdviceToMethod( named("setVirtualFieldValue") @@ -64,7 +62,8 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) int arg public static class VirtualFieldSetAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) - public static void onEnter(@Advice.Argument(0) Runnable target,@Advice.Argument(1) Integer value) { + public static void onEnter( + @Advice.Argument(0) Runnable target, @Advice.Argument(1) Integer value) { VirtualField field = VirtualField.find(Runnable.class, Integer.class); field.set(target, value); } @@ -73,7 +72,8 @@ public static void onEnter(@Advice.Argument(0) Runnable target,@Advice.Argument( public static class VirtualFieldGetAdvice { @SuppressWarnings("UnusedVariable") @Advice.OnMethodExit(suppress = Throwable.class) - public static void onExit(@Advice.Argument(0) Runnable target, @Advice.Return(readOnly = false) Integer returnValue) { + public static void onExit( + @Advice.Argument(0) Runnable target, @Advice.Return(readOnly = false) Integer returnValue) { VirtualField field = VirtualField.find(Runnable.class, Integer.class); returnValue = field.get(target); } diff --git a/smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java similarity index 100% rename from smoke-tests/images/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java rename to smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java diff --git a/smoke-tests/extensions/testapp/build.gradle.kts b/smoke-tests/extensions/testapp/build.gradle.kts new file mode 100644 index 000000000000..8494723dea4e --- /dev/null +++ b/smoke-tests/extensions/testapp/build.gradle.kts @@ -0,0 +1,19 @@ +plugins { + id("otel.java-conventions") +} + +dependencies { +} + +java { + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 +} + +tasks { + jar { + manifest { + attributes("Main-Class" to "io.opentelemetry.smoketest.extensions.app.AppMain") + } + } +} diff --git a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java similarity index 91% rename from smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java rename to smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index bb3de3308ed9..c714fa0f2e46 100644 --- a/smoke-tests/images/extensions/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -12,7 +12,8 @@ private AppMain() {} public static void main(String[] args) { testReturnValue(); testMethodArguments(); - testVirtualFields(); + // TODO: wip, does not work yet + // testVirtualFields(); } private static void testReturnValue() { @@ -42,11 +43,12 @@ private static void methodArguments(int argument, int originalArgument) { } } + @SuppressWarnings("unused") private static void testVirtualFields() { Runnable target = () -> {}; setVirtualFieldValue(target, 42); Integer fieldValue = getVirtualFieldValue(target); - if(fieldValue == null || fieldValue != 42){ + if (fieldValue == null || fieldValue != 42) { System.out.println("virtual field not supported"); } else { System.out.println("virtual field supported"); @@ -61,5 +63,4 @@ public static Integer getVirtualFieldValue(Runnable target) { // implementation should be provided by instrumentation return null; } - } diff --git a/smoke-tests/images/extensions/build.gradle.kts b/smoke-tests/images/extensions/build.gradle.kts deleted file mode 100644 index 256e541b43ca..000000000000 --- a/smoke-tests/images/extensions/build.gradle.kts +++ /dev/null @@ -1,37 +0,0 @@ -import com.google.cloud.tools.jib.gradle.JibTask -import java.time.LocalDateTime -import java.time.format.DateTimeFormatter - -plugins { - id("otel.java-conventions") - id("com.google.cloud.tools.jib") -} - -dependencies { -} - -val targetJDK = project.findProperty("targetJDK") ?: "17" - -val tag = findProperty("tag") - ?: DateTimeFormatter.ofPattern("yyyyMMdd.HHmmSS").format(LocalDateTime.now()) - -java { - // needed by jib to detect java version used in project - // for jdk9+ jib uses an entrypoint that doesn't work with jdk8 - sourceCompatibility = JavaVersion.VERSION_1_8 - targetCompatibility = JavaVersion.VERSION_1_8 -} - -val repo = System.getenv("GITHUB_REPOSITORY") ?: "open-telemetry/opentelemetry-java-instrumentation" - -jib { - from.image = "eclipse-temurin:$targetJDK" - to.image = "ghcr.io/$repo/smoke-test-extensions:jdk$targetJDK-$tag" -} - -tasks { - withType().configureEach { - // Jib tasks access Task.project at execution time which is not compatible with configuration cache - notCompatibleWithConfigurationCache("Jib task accesses Task.project at execution time") - } -} diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index dc0b4ad9a8b0..1005577b2908 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -5,7 +5,16 @@ package io.opentelemetry.smoketest; -import org.junit.jupiter.api.Test; +import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; + +import java.time.Duration; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.testcontainers.containers.GenericContainer; @@ -13,11 +22,6 @@ import org.testcontainers.containers.output.Slf4jLogConsumer; import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; -import java.time.Duration; -import java.util.Arrays; -import java.util.List; - -import static io.opentelemetry.sdk.testing.assertj.OpenTelemetryAssertions.assertThat; public class ExtensionsSmokeTest { @@ -31,49 +35,59 @@ public class ExtensionsSmokeTest { private static final String extensionInlinePath = System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); - // TODO: use reusable docker image when available - private static final String IMAGE_VERSION = "jdk17-latest"; - - // TODO: test with virtual fields directly into advice method - // - VirtualField.find method call directly in advice method - // - implement test in the application that allows to detect it: for example state has been attached to an immutable object - - // TODO: create an "indy compliant extension" - - // TODO: test with and without "indy mode" - // inlined extension should work as usual - // indy extension is only expected to work in "indy mode" - - - - @Test - void inlinedExtension() throws InterruptedException { - GenericContainer target = new GenericContainer<>( - DockerImageName.parse( - "ghcr.io/open-telemetry/opentelemetry-java-instrumentation/smoke-test-extensions:" - + IMAGE_VERSION)) - .withStartupTimeout(Duration.ofMinutes(1)) - .withLogConsumer(new Slf4jLogConsumer(logger)) - // disable export as we only instrument - .withEnv("OTEL_TRACES_EXPORTER", "none") - .withEnv("OTEL_METRICS_EXPORTER", "none") - .withEnv("OTEL_LOGS_EXPORTER", "none") - .withEnv("JAVA_TOOL_OPTIONS", "-javaagent:" + TARGET_AGENT_FILENAME) - .withEnv("OTEL_JAVAAGENT_EXTENSIONS", TARGET_EXTENSION_FILENAME) - .withEnv("OTEL_JAVAAGENT_DEBUG", "true") - .withCopyFileToContainer( - MountableFile.forHostPath(agentPath), TARGET_AGENT_FILENAME) - .withCopyFileToContainer( - MountableFile.forHostPath(extensionInlinePath), TARGET_EXTENSION_FILENAME); + private static final String TARGET_APP_FILENAME = "/app.jar"; + private static final String appPath = + System.getProperty("io.opentelemetry.smoketest.extension.testapp.path"); + + private static final String IMAGE = "eclipse-temurin:21"; + + @ParameterizedTest + @ValueSource(booleans = {true, false}) + void inlinedExtension(boolean indy) throws InterruptedException { + + List cmd = new ArrayList<>(); + cmd.add("java"); + cmd.add("-javaagent:" + TARGET_AGENT_FILENAME); + + Map config = new HashMap<>(); + // disable export as we only instrument + config.put("otel.logs.exporter", "none"); + config.put("otel.metrics.exporter", "none"); + config.put("otel.traces.exporter", "none"); + // add extension + config.put("otel.javaagent.extensions", TARGET_EXTENSION_FILENAME); + // toggle indy on/off + config.put("otel.javaagent.experimental.indy", Boolean.toString(indy)); + // toggle debug if needed + config.put("otel.javaagent.debug", "false"); + config.forEach((k, v) -> cmd.add(String.format("-D%s=%s", k, v))); + + cmd.add("-jar"); + cmd.add(TARGET_APP_FILENAME); + + GenericContainer target = + new GenericContainer<>(DockerImageName.parse(IMAGE)) + .withStartupTimeout(Duration.ofMinutes(1)) + .withLogConsumer(new Slf4jLogConsumer(logger)) + .withCopyFileToContainer(MountableFile.forHostPath(agentPath), TARGET_AGENT_FILENAME) + .withCopyFileToContainer( + MountableFile.forHostPath(extensionInlinePath), TARGET_EXTENSION_FILENAME) + .withCopyFileToContainer(MountableFile.forHostPath(appPath), TARGET_APP_FILENAME) + .withCommand(String.join(" ", cmd)); + + logger.info("starting JVM with command: " + String.join(" ", cmd)); target.start(); while (target.isRunning()) { Thread.sleep(100); } - List appOutput = Arrays.asList(target.getLogs(OutputFrame.OutputType.STDOUT).split("\n")); + List appOutput = + Arrays.asList(target.getLogs(OutputFrame.OutputType.STDOUT).split("\n")); assertThat(appOutput) - .containsExactlyInAnyOrder("return value has been modified", "argument has been modified"); + .describedAs("return value instrumentation") + .contains("return value has been modified") + .describedAs("argument value instrumentation") + .contains("argument has been modified"); + // TODO add assertion for virtual fields } - - } From 27c951ce86d30bf7905c02d1ea76e064b97ed830 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 16:11:42 +0100 Subject: [PATCH 13/18] fix virtual fields and make it work again --- smoke-tests/build.gradle.kts | 2 +- smoke-tests/extensions/inlined/build.gradle.kts | 1 + .../inlined/SmokeInlinedInstrumentation.java | 12 ++++++------ .../smoketest/extensions/app/AppMain.java | 10 ++++------ 4 files changed, 12 insertions(+), 13 deletions(-) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 1e986ef5f0a5..1da4d061aa45 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -84,7 +84,7 @@ tasks { val extensionTestAppTask = project(":smoke-tests:extensions:testapp").tasks.named("jar") val extensionTestAppJarPath = extensionTestAppTask.flatMap { it.archiveFile } - dependsOn(shadowTask, extensionTestAppTask) + dependsOn(shadowTask, extensionTestAppTask, extensionInlineTask ) doFirst { jvmArgs( diff --git a/smoke-tests/extensions/inlined/build.gradle.kts b/smoke-tests/extensions/inlined/build.gradle.kts index 5ea8a3ee4270..6d9067e532fc 100644 --- a/smoke-tests/extensions/inlined/build.gradle.kts +++ b/smoke-tests/extensions/inlined/build.gradle.kts @@ -1,5 +1,6 @@ plugins { id("otel.java-conventions") + id("io.opentelemetry.instrumentation.javaagent-instrumentation") } dependencies { diff --git a/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index 4a349733c5f0..b730eb092e58 100644 --- a/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -33,12 +33,12 @@ public void transform(TypeTransformer transformer) { this.getClass().getName() + "$ModifyArgumentsAdvice"); transformer.applyAdviceToMethod( named("setVirtualFieldValue") - .and(takesArgument(0, Runnable.class)) + .and(takesArgument(0, Object.class)) .and(takesArgument(1, Integer.class)), this.getClass().getName() + "$VirtualFieldSetAdvice"); transformer.applyAdviceToMethod( named("getVirtualFieldValue") - .and(takesArgument(0, Runnable.class)) + .and(takesArgument(0, Object.class)) .and(returns(Integer.class)), this.getClass().getName() + "$VirtualFieldGetAdvice"); } @@ -63,8 +63,8 @@ public static void onEnter(@Advice.Argument(value = 0, readOnly = false) int arg public static class VirtualFieldSetAdvice { @Advice.OnMethodEnter(suppress = Throwable.class) public static void onEnter( - @Advice.Argument(0) Runnable target, @Advice.Argument(1) Integer value) { - VirtualField field = VirtualField.find(Runnable.class, Integer.class); + @Advice.Argument(0) Object target, @Advice.Argument(1) Integer value) { + VirtualField field = VirtualField.find(Object.class, Integer.class); field.set(target, value); } } @@ -73,8 +73,8 @@ public static class VirtualFieldGetAdvice { @SuppressWarnings("UnusedVariable") @Advice.OnMethodExit(suppress = Throwable.class) public static void onExit( - @Advice.Argument(0) Runnable target, @Advice.Return(readOnly = false) Integer returnValue) { - VirtualField field = VirtualField.find(Runnable.class, Integer.class); + @Advice.Argument(0) Object target, @Advice.Return(readOnly = false) Integer returnValue) { + VirtualField field = VirtualField.find(Object.class, Integer.class); returnValue = field.get(target); } } diff --git a/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index c714fa0f2e46..517f6a7fd641 100644 --- a/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -12,8 +12,7 @@ private AppMain() {} public static void main(String[] args) { testReturnValue(); testMethodArguments(); - // TODO: wip, does not work yet - // testVirtualFields(); + testVirtualFields(); } private static void testReturnValue() { @@ -43,9 +42,8 @@ private static void methodArguments(int argument, int originalArgument) { } } - @SuppressWarnings("unused") private static void testVirtualFields() { - Runnable target = () -> {}; + Object target = new Object(); setVirtualFieldValue(target, 42); Integer fieldValue = getVirtualFieldValue(target); if (fieldValue == null || fieldValue != 42) { @@ -55,11 +53,11 @@ private static void testVirtualFields() { } } - public static void setVirtualFieldValue(Runnable target, Integer value) { + public static void setVirtualFieldValue(Object target, Integer value) { // implementation should be provided by instrumentation } - public static Integer getVirtualFieldValue(Runnable target) { + public static Integer getVirtualFieldValue(Object target) { // implementation should be provided by instrumentation return null; } From 31ecfc250043f845550becb4030b63739cad32b1 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:14:17 +0100 Subject: [PATCH 14/18] fix test assertions + impl local variable --- .../smoketest/extensions/app/AppMain.java | 26 +++++++++++++++++++ .../smoketest/ExtensionsSmokeTest.java | 7 +++-- 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index 517f6a7fd641..d360b83331e4 100644 --- a/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -13,6 +13,7 @@ public static void main(String[] args) { testReturnValue(); testMethodArguments(); testVirtualFields(); + testLocalValue(); } private static void testReturnValue() { @@ -61,4 +62,29 @@ public static Integer getVirtualFieldValue(Object target) { // implementation should be provided by instrumentation return null; } + + private static void testLocalValue() { + int[] input = new int[] {1, 2, 3}; + int result = localValue(input); + if (result != 6) { + throw new IllegalStateException(); + } + // assumption on the instrumentation implementation to use a local value to preserve original array + boolean preserved = input[0] == 1 && input[1] == 2 && input[2] == 3; + if(!preserved) { + System.out.println("local advice variable not supported"); + } else { + System.out.println("local advice variable supported"); + } + + } + + private static int localValue(int[] array) { + int sum = 0; + for (int i = 0; i < array.length; i++) { + sum += array[i]; + array[i] = 0; + } + return sum; + } } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 1005577b2908..503a48031a3a 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -85,9 +85,12 @@ void inlinedExtension(boolean indy) throws InterruptedException { Arrays.asList(target.getLogs(OutputFrame.OutputType.STDOUT).split("\n")); assertThat(appOutput) .describedAs("return value instrumentation") - .contains("return value has been modified") + .contains("return value has been modified"); + assertThat(appOutput) .describedAs("argument value instrumentation") .contains("argument has been modified"); - // TODO add assertion for virtual fields + assertThat(appOutput) + .describedAs("virtual field support") + .contains("virtual field supported"); } } From 4fe6a4bd3d0eeb86de5ee7e7a2a016a74da790a5 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 17:53:12 +0100 Subject: [PATCH 15/18] local value support working --- .../inlined/SmokeInlinedInstrumentation.java | 23 +++++++++ .../smoketest/extensions/app/AppMain.java | 49 +++++++++++-------- .../smoketest/ExtensionsSmokeTest.java | 3 ++ 3 files changed, 54 insertions(+), 21 deletions(-) diff --git a/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java index b730eb092e58..92fb56ffb80b 100644 --- a/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java +++ b/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java @@ -12,6 +12,7 @@ import io.opentelemetry.instrumentation.api.util.VirtualField; import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation; import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer; +import java.util.Arrays; import net.bytebuddy.asm.Advice; import net.bytebuddy.description.type.TypeDescription; import net.bytebuddy.matcher.ElementMatcher; @@ -41,6 +42,9 @@ public void transform(TypeTransformer transformer) { .and(takesArgument(0, Object.class)) .and(returns(Integer.class)), this.getClass().getName() + "$VirtualFieldGetAdvice"); + transformer.applyAdviceToMethod( + named("localValue").and(takesArgument(0, int[].class)).and(returns(int[].class)), + this.getClass().getName() + "$LocalVariableAdvice"); } @SuppressWarnings("unused") @@ -69,6 +73,7 @@ public static void onEnter( } } + @SuppressWarnings("unused") public static class VirtualFieldGetAdvice { @SuppressWarnings("UnusedVariable") @Advice.OnMethodExit(suppress = Throwable.class) @@ -78,4 +83,22 @@ public static void onExit( returnValue = field.get(target); } } + + @SuppressWarnings("unused") + public static class LocalVariableAdvice { + + @SuppressWarnings("UnusedVariable") + @Advice.OnMethodEnter(suppress = Throwable.class) + public static void onEnter( + @Advice.Argument(0) int[] array, @Advice.Local("backup") int[] backupArray) { + backupArray = Arrays.copyOf(array, array.length); + } + + @SuppressWarnings("UnusedVariable") + @Advice.OnMethodExit(suppress = Throwable.class) + public static void onExit( + @Advice.Return(readOnly = false) int[] array, @Advice.Local("backup") int[] backupArray) { + array = Arrays.copyOf(backupArray, backupArray.length); + } + } } diff --git a/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java index d360b83331e4..507d758274c6 100644 --- a/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java +++ b/smoke-tests/extensions/testapp/src/main/java/io/opentelemetry/smoketest/extensions/app/AppMain.java @@ -5,6 +5,10 @@ package io.opentelemetry.smoketest.extensions.app; +import com.google.errorprone.annotations.CanIgnoreReturnValue; +import java.io.PrintStream; +import java.util.Arrays; + public class AppMain { private AppMain() {} @@ -16,12 +20,18 @@ public static void main(String[] args) { testLocalValue(); } + private static void msg(String msg) { + // avoid checkstyle to complain + PrintStream out = System.out; + out.println(msg); + } + private static void testReturnValue() { int returnValue = returnValue(42); if (returnValue != 42) { - System.out.println("return value has been modified"); + msg("return value has been modified"); } else { - System.out.println("return value not modified"); + msg("return value not modified"); } } @@ -37,9 +47,9 @@ private static void testMethodArguments() { private static void methodArguments(int argument, int originalArgument) { // method first argument should be modified by instrumentation if (argument != originalArgument) { - System.out.println("argument has been modified"); + msg("argument has been modified"); } else { - System.out.println("argument not modified"); + msg("argument not modified"); } } @@ -48,9 +58,9 @@ private static void testVirtualFields() { setVirtualFieldValue(target, 42); Integer fieldValue = getVirtualFieldValue(target); if (fieldValue == null || fieldValue != 42) { - System.out.println("virtual field not supported"); + msg("virtual field not supported"); } else { - System.out.println("virtual field supported"); + msg("virtual field supported"); } } @@ -65,26 +75,23 @@ public static Integer getVirtualFieldValue(Object target) { private static void testLocalValue() { int[] input = new int[] {1, 2, 3}; - int result = localValue(input); - if (result != 6) { + int[] result = localValue(input); + if (result.length != 3) { throw new IllegalStateException(); } - // assumption on the instrumentation implementation to use a local value to preserve original array - boolean preserved = input[0] == 1 && input[1] == 2 && input[2] == 3; - if(!preserved) { - System.out.println("local advice variable not supported"); + // assumption on the instrumentation implementation to use a local value to preserve original + // array + boolean preserved = result[0] == 1 && result[1] == 2 && result[2] == 3; + if (!preserved) { + msg("local advice variable not supported"); } else { - System.out.println("local advice variable supported"); + msg("local advice variable supported"); } - } - private static int localValue(int[] array) { - int sum = 0; - for (int i = 0; i < array.length; i++) { - sum += array[i]; - array[i] = 0; - } - return sum; + @CanIgnoreReturnValue + private static int[] localValue(int[] array) { + Arrays.fill(array, 0); + return array; } } diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 503a48031a3a..9d451b34a3fa 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -92,5 +92,8 @@ void inlinedExtension(boolean indy) throws InterruptedException { assertThat(appOutput) .describedAs("virtual field support") .contains("virtual field supported"); + assertThat(appOutput) + .describedAs("local advice variable support") + .contains("local advice variable supported"); } } From 6d3508f674f321d8e2d46e6dfebc7c3f5a6540af Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Mon, 1 Dec 2025 18:03:17 +0100 Subject: [PATCH 16/18] spotless --- smoke-tests/build.gradle.kts | 2 +- .../java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java | 4 +--- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 1da4d061aa45..243f85240cf3 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -84,7 +84,7 @@ tasks { val extensionTestAppTask = project(":smoke-tests:extensions:testapp").tasks.named("jar") val extensionTestAppJarPath = extensionTestAppTask.flatMap { it.archiveFile } - dependsOn(shadowTask, extensionTestAppTask, extensionInlineTask ) + dependsOn(shadowTask, extensionTestAppTask, extensionInlineTask) doFirst { jvmArgs( diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 9d451b34a3fa..946ebbf45702 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -89,9 +89,7 @@ void inlinedExtension(boolean indy) throws InterruptedException { assertThat(appOutput) .describedAs("argument value instrumentation") .contains("argument has been modified"); - assertThat(appOutput) - .describedAs("virtual field support") - .contains("virtual field supported"); + assertThat(appOutput).describedAs("virtual field support").contains("virtual field supported"); assertThat(appOutput) .describedAs("local advice variable support") .contains("local advice variable supported"); From 936f40c8d02438d6fee27110a2e4e8a78bec6137 Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Tue, 2 Dec 2025 10:07:46 +0100 Subject: [PATCH 17/18] update readme --- smoke-tests/README.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/smoke-tests/README.md b/smoke-tests/README.md index 9d4c2426efb1..de1c44f2ac4e 100644 --- a/smoke-tests/README.md +++ b/smoke-tests/README.md @@ -2,5 +2,8 @@ Assert that various applications will start up with the JavaAgent without any obvious ill effects. -Each subproject underneath `smoke-tests` produces one or more docker images containing some application +Each subproject underneath `smoke-tests/images` produces one or more docker images containing some application under the test. Various tests in the main module then use them to run the appropriate tests. + +The `smoke-tests/extensions` folder contains a test application and packaged instrumentation(s) extension to +test compatibility with existing user-built extensions. From 4f01f708709957cbf2c05d7bf12f4b4e1a3df02e Mon Sep 17 00:00:00 2001 From: Sylvain Juge <763082+SylvainJuge@users.noreply.github.com> Date: Wed, 3 Dec 2025 16:12:49 +0100 Subject: [PATCH 18/18] rename and simplify --- settings.gradle.kts | 2 +- smoke-tests/build.gradle.kts | 8 ++++---- .../extensions/{inlined => extension}/build.gradle.kts | 0 .../extensions/inlined/SmokeInlinedInstrumentation.java | 0 .../inlined/SmokeInlinedInstrumentationModule.java | 0 .../io/opentelemetry/smoketest/ExtensionsSmokeTest.java | 4 ++-- 6 files changed, 7 insertions(+), 7 deletions(-) rename smoke-tests/extensions/{inlined => extension}/build.gradle.kts (100%) rename smoke-tests/extensions/{inlined => extension}/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java (100%) rename smoke-tests/extensions/{inlined => extension}/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java (100%) diff --git a/settings.gradle.kts b/settings.gradle.kts index 75005d5b13bd..8ef58554ef73 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -157,7 +157,7 @@ include(":smoke-tests:images:servlet:servlet-3.0") include(":smoke-tests:images:servlet:servlet-5.0") include(":smoke-tests:images:spring-boot") include(":smoke-tests:extensions:testapp") -include(":smoke-tests:extensions:inlined") +include(":smoke-tests:extensions:extension") include(":smoke-tests-otel-starter:spring-smoke-testing") include(":smoke-tests-otel-starter:spring-boot-2") diff --git a/smoke-tests/build.gradle.kts b/smoke-tests/build.gradle.kts index 243f85240cf3..3dfc1b3ce0e7 100644 --- a/smoke-tests/build.gradle.kts +++ b/smoke-tests/build.gradle.kts @@ -78,18 +78,18 @@ tasks { .withPropertyName("javaagent") .withNormalizer(ClasspathNormalizer::class) - val extensionInlineTask = project(":smoke-tests:extensions:inlined").tasks.named("jar") - val extensionInlineJarPath = extensionInlineTask.flatMap { it.archiveFile } + val extensionTask = project(":smoke-tests:extensions:extension").tasks.named("jar") + val extensionJarPath = extensionTask.flatMap { it.archiveFile } val extensionTestAppTask = project(":smoke-tests:extensions:testapp").tasks.named("jar") val extensionTestAppJarPath = extensionTestAppTask.flatMap { it.archiveFile } - dependsOn(shadowTask, extensionTestAppTask, extensionInlineTask) + dependsOn(shadowTask, extensionTestAppTask, extensionTask) doFirst { jvmArgs( "-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}", - "-Dio.opentelemetry.smoketest.extension.inline.path=${extensionInlineJarPath.get()}", + "-Dio.opentelemetry.smoketest.extension.path=${extensionJarPath.get()}", "-Dio.opentelemetry.smoketest.extension.testapp.path=${extensionTestAppJarPath.get()}" ) } diff --git a/smoke-tests/extensions/inlined/build.gradle.kts b/smoke-tests/extensions/extension/build.gradle.kts similarity index 100% rename from smoke-tests/extensions/inlined/build.gradle.kts rename to smoke-tests/extensions/extension/build.gradle.kts diff --git a/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java b/smoke-tests/extensions/extension/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java similarity index 100% rename from smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java rename to smoke-tests/extensions/extension/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentation.java diff --git a/smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java b/smoke-tests/extensions/extension/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java similarity index 100% rename from smoke-tests/extensions/inlined/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java rename to smoke-tests/extensions/extension/src/main/java/io/opentelemetry/smoketest/extensions/inlined/SmokeInlinedInstrumentationModule.java diff --git a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java index 946ebbf45702..94746b0dd013 100644 --- a/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java +++ b/smoke-tests/src/test/java/io/opentelemetry/smoketest/ExtensionsSmokeTest.java @@ -23,7 +23,7 @@ import org.testcontainers.utility.DockerImageName; import org.testcontainers.utility.MountableFile; -public class ExtensionsSmokeTest { +class ExtensionsSmokeTest { private static final Logger logger = LoggerFactory.getLogger(ExtensionsSmokeTest.class); @@ -33,7 +33,7 @@ public class ExtensionsSmokeTest { private static final String TARGET_EXTENSION_FILENAME = "/opentelemetry-extension.jar"; private static final String extensionInlinePath = - System.getProperty("io.opentelemetry.smoketest.extension.inline.path"); + System.getProperty("io.opentelemetry.smoketest.extension.path"); private static final String TARGET_APP_FILENAME = "/app.jar"; private static final String appPath =