From 67f7b26ab6d5755ff4a87b34d5462cc38a78954c Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:30:01 +0200 Subject: [PATCH 1/9] convert test --- .../muzzle/MuzzleBytecodeTransformTest.groovy | 58 ---------------- .../muzzle/MuzzleBytecodeTransformTest.java | 67 +++++++++++++++++++ 2 files changed, 67 insertions(+), 58 deletions(-) delete mode 100644 javaagent/src/test/groovy/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.groovy create mode 100644 javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java diff --git a/javaagent/src/test/groovy/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.groovy b/javaagent/src/test/groovy/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.groovy deleted file mode 100644 index b998f663dc16..000000000000 --- a/javaagent/src/test/groovy/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.groovy +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.muzzle - -import io.opentelemetry.javaagent.IntegrationTestUtils -import spock.lang.Specification - -import java.lang.reflect.Field -import java.lang.reflect.Method - -class MuzzleBytecodeTransformTest extends Specification { - - def "muzzle fields added to all instrumentation"() { - setup: - List unMuzzledClasses = [] - List nonLazyFields = [] - List unInitFields = [] - def instrumentationModuleClass = IntegrationTestUtils.getAgentClassLoader().loadClass("io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule") - for (Object instrumenter : ServiceLoader.load(instrumentationModuleClass)) { - if (!instrumentationModuleClass.isAssignableFrom(instrumenter.getClass())) { - // muzzle only applies to default instrumenters - continue - } - Field f - Method m - try { - f = instrumenter.getClass().getDeclaredField("muzzleReferences") - f.setAccessible(true) - if (f.get(instrumenter) != null) { - nonLazyFields.add(instrumenter.getClass()) - } - m = instrumenter.getClass().getDeclaredMethod("getMuzzleReferences") - m.setAccessible(true) - m.invoke(instrumenter) - if (f.get(instrumenter) == null) { - unInitFields.add(instrumenter.getClass()) - } - } catch (NoSuchFieldException | NoSuchMethodException e) { - unMuzzledClasses.add(instrumenter.getClass()) - } finally { - if (null != f) { - f.setAccessible(false) - } - if (null != m) { - m.setAccessible(false) - } - } - } - expect: - unMuzzledClasses == [] - nonLazyFields == [] - unInitFields == [] - } - -} diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java new file mode 100644 index 000000000000..4f33cf8c6f92 --- /dev/null +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java @@ -0,0 +1,67 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.muzzle; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.javaagent.IntegrationTestUtils; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; +import java.util.ServiceLoader; +import org.junit.jupiter.api.Test; + +class MuzzleBytecodeTransformTest { + + @Test + void muzzleFieldsAddedToAllInstrumentation() throws Exception { + List> unMuzzledClasses = new ArrayList<>(); + List> nonLazyFields = new ArrayList<>(); + List> unInitFields = new ArrayList<>(); + + Class instrumentationModuleClass = IntegrationTestUtils.getAgentClassLoader() + .loadClass("io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule"); + + for (Object instrumenter : ServiceLoader.load(instrumentationModuleClass)) { + if (!instrumentationModuleClass.isAssignableFrom(instrumenter.getClass())) { + // muzzle only applies to default instrumenters + continue; + } + + Field f = null; + Method m = null; + try { + f = instrumenter.getClass().getDeclaredField("muzzleReferences"); + f.setAccessible(true); + if (f.get(instrumenter) != null) { + nonLazyFields.add(instrumenter.getClass()); + } + + m = instrumenter.getClass().getDeclaredMethod("getMuzzleReferences"); + m.setAccessible(true); + m.invoke(instrumenter); + + if (f.get(instrumenter) == null) { + unInitFields.add(instrumenter.getClass()); + } + } catch (NoSuchFieldException | NoSuchMethodException e) { + unMuzzledClasses.add(instrumenter.getClass()); + } finally { + if (f != null) { + f.setAccessible(false); + } + if (m != null) { + m.setAccessible(false); + } + } + } + + assertThat(unMuzzledClasses).isEmpty(); + assertThat(nonLazyFields).isEmpty(); + assertThat(unInitFields).isEmpty(); + } +} From 22eeaf58689c5e58da2b57eed77538673d906826 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:31:40 +0200 Subject: [PATCH 2/9] convert test --- .../AgentLoadedIntoBootstrapTest.groovy | 51 --------------- .../AgentLoadedIntoBootstrapTest.java | 62 +++++++++++++++++++ 2 files changed, 62 insertions(+), 51 deletions(-) delete mode 100644 javaagent/src/test/groovy/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.groovy create mode 100644 javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java diff --git a/javaagent/src/test/groovy/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.groovy b/javaagent/src/test/groovy/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.groovy deleted file mode 100644 index 0ae4e2327a5f..000000000000 --- a/javaagent/src/test/groovy/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.groovy +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent - -import jvmbootstraptest.AgentLoadedChecker -import jvmbootstraptest.MyClassLoaderIsNotBootstrap -import spock.lang.Specification - -class AgentLoadedIntoBootstrapTest extends Specification { - - def "Agent loads in when separate jvm is launched"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(AgentLoadedChecker.getName() - , "" as String[] - , "" as String[] - , [:] - , true) == 0 - } - - // this tests the case where someone adds the contents of opentelemetry-javaagent.jar by mistake - // to their application's "uber.jar" - // - // the reason this can cause issues is because we locate the agent jar based on the CodeSource of - // the OpenTelemetryAgent class, and then we add that jar file to the bootstrap class path - // - // but if we find the OpenTelemetryAgent class in an uber jar file, and we add that (whole) uber - // jar file to the bootstrap class loader, that can cause some applications to break, as there's a - // lot of application and library code that doesn't handle getClassLoader() returning null - // (e.g. https://github.com/qos-ch/logback/pull/291) - def "application uber jar should not be added to the bootstrap class loader"() { - setup: - def mainClassName = MyClassLoaderIsNotBootstrap.getName() - def pathToJar = IntegrationTestUtils.createJarWithClasses(mainClassName, - MyClassLoaderIsNotBootstrap, - OpenTelemetryAgent).getPath() - - expect: - IntegrationTestUtils.runOnSeparateJvm(mainClassName - , "" as String[] - , "" as String[] - , [:] - , pathToJar as String - , true) == 0 - - cleanup: - new File(pathToJar).delete() - } -} diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java new file mode 100644 index 000000000000..c6f0a20b7e14 --- /dev/null +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java @@ -0,0 +1,62 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.io.File; +import java.util.Collections; +import jvmbootstraptest.AgentLoadedChecker; +import jvmbootstraptest.MyClassLoaderIsNotBootstrap; +import org.junit.jupiter.api.Test; + +class AgentLoadedIntoBootstrapTest { + + @Test + void agentLoadsInWhenSeparateJvmIsLaunched() throws Exception { + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + AgentLoadedChecker.class.getName(), + new String[0], + new String[0], + Collections.emptyMap(), + true); + + assertThat(exitCode).isEqualTo(0); + } + + // this tests the case where someone adds the contents of opentelemetry-javaagent.jar by mistake + // to their application's "uber.jar" + // + // the reason this can cause issues is because we locate the agent jar based on the CodeSource of + // the OpenTelemetryAgent class, and then we add that jar file to the bootstrap class path + // + // but if we find the OpenTelemetryAgent class in an uber jar file, and we add that (whole) uber + // jar file to the bootstrap class loader, that can cause some applications to break, as there's a + // lot of application and library code that doesn't handle getClassLoader() returning null + // (e.g. https://github.com/qos-ch/logback/pull/291) + @Test + void applicationUberJarShouldNotBeAddedToTheBootstrapClassLoader() throws Exception { + String mainClassName = MyClassLoaderIsNotBootstrap.class.getName(); + String pathToJar = IntegrationTestUtils.createJarWithClasses( + mainClassName, + MyClassLoaderIsNotBootstrap.class, + OpenTelemetryAgent.class).getPath(); + + try { + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + mainClassName, + new String[0], + new String[0], + Collections.emptyMap(), + pathToJar, + true); + + assertThat(exitCode).isEqualTo(0); + } finally { + new File(pathToJar).delete(); + } + } +} From ff3e664f9030f0d8bec0b39bce1feeeb2b87f7f4 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:34:19 +0200 Subject: [PATCH 3/9] convert test --- .../javaagent/AgentLoadedIntoBootstrapTest.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java index c6f0a20b7e14..2c5e9a575ff4 100644 --- a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java @@ -24,7 +24,7 @@ void agentLoadsInWhenSeparateJvmIsLaunched() throws Exception { Collections.emptyMap(), true); - assertThat(exitCode).isEqualTo(0); + assertThat(exitCode).isZero(); } // this tests the case where someone adds the contents of opentelemetry-javaagent.jar by mistake @@ -54,9 +54,12 @@ void applicationUberJarShouldNotBeAddedToTheBootstrapClassLoader() throws Except pathToJar, true); - assertThat(exitCode).isEqualTo(0); + assertThat(exitCode).isZero(); } finally { - new File(pathToJar).delete(); + boolean deleted = new File(pathToJar).delete(); + if (!deleted) { + System.err.println("Failed to delete temporary jar file: " + pathToJar); + } } } } From 0161aa942be7385279207e888693309edc3b07c7 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:35:46 +0200 Subject: [PATCH 4/9] convert test --- .../classloading/ClassLoadingTest.groovy | 98 ----------------- .../classloading/ClassLoadingTest.java | 100 ++++++++++++++++++ 2 files changed, 100 insertions(+), 98 deletions(-) delete mode 100644 javaagent/src/test/groovy/io/opentelemetry/javaagent/classloading/ClassLoadingTest.groovy create mode 100644 javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java diff --git a/javaagent/src/test/groovy/io/opentelemetry/javaagent/classloading/ClassLoadingTest.groovy b/javaagent/src/test/groovy/io/opentelemetry/javaagent/classloading/ClassLoadingTest.groovy deleted file mode 100644 index b0fdaa91a7c4..000000000000 --- a/javaagent/src/test/groovy/io/opentelemetry/javaagent/classloading/ClassLoadingTest.groovy +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.classloading - -import io.opentelemetry.instrumentation.test.utils.GcUtils -import io.opentelemetry.javaagent.ClassToInstrument -import io.opentelemetry.javaagent.ClassToInstrumentChild -import spock.lang.Specification - -import java.lang.ref.WeakReference -import java.time.Duration - -import static io.opentelemetry.javaagent.IntegrationTestUtils.createJarWithClasses - -class ClassLoadingTest extends Specification { - - final URL[] classpath = [createJarWithClasses(ClassToInstrument, ClassToInstrumentChild)] - - /** Assert that we can instrument classloaders which cannot resolve agent advice classes. */ - def "instrument classloader without agent classes"() { - setup: - URLClassLoader loader = new URLClassLoader(classpath, (ClassLoader) null) - - when: - loader.loadClass("io.opentelemetry.javaagent.instrumentation.trace_annotation.TraceAdvice") - then: - thrown ClassNotFoundException - - when: - Class instrumentedClass = loader.loadClass(ClassToInstrument.getName()) - then: - instrumentedClass.getClassLoader() == loader - } - - def "make sure ByteBuddy does not hold strong references to ClassLoader"() { - setup: - URLClassLoader loader = new URLClassLoader(classpath, (ClassLoader) null) - WeakReference ref = new WeakReference<>(loader) - - when: - loader.loadClass(ClassToInstrument.getName()) - loader = null - - GcUtils.awaitGc(ref, Duration.ofSeconds(10)) - - then: - null == ref.get() - } - - // We are doing this because Groovy cannot properly resolve constructor argument types in anonymous classes - static class CountingClassLoader extends URLClassLoader { - public int count = 0 - - CountingClassLoader(URL[] urls) { - super(urls, (ClassLoader) null) - } - - @Override - URL getResource(String name) { - count++ - return super.getResource(name) - } - } - - def "make sure that ByteBuddy reads the class bytes only once"() { - setup: - CountingClassLoader loader = new CountingClassLoader(classpath) - - when: - //loader.loadClass("aaa") - loader.loadClass(ClassToInstrument.getName()) - int countAfterFirstLoad = loader.count - loader.loadClass(ClassToInstrumentChild.getName()) - - then: - // ClassToInstrumentChild won't cause an additional getResource() because its TypeDescription is created from transformation bytes. - loader.count > 0 - loader.count == countAfterFirstLoad - } - - def "make sure that ByteBuddy doesn't reuse cached type descriptions between different classloaders"() { - setup: - CountingClassLoader loader1 = new CountingClassLoader(classpath) - CountingClassLoader loader2 = new CountingClassLoader(classpath) - - when: - loader1.loadClass(ClassToInstrument.getName()) - loader2.loadClass(ClassToInstrument.getName()) - - then: - loader1.count > 0 - loader2.count > 0 - loader1.count == loader2.count - } -} diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java new file mode 100644 index 000000000000..e3197445bc46 --- /dev/null +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java @@ -0,0 +1,100 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.classloading; + +import static io.opentelemetry.javaagent.IntegrationTestUtils.createJarWithClasses; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import io.opentelemetry.instrumentation.test.utils.GcUtils; +import io.opentelemetry.javaagent.ClassToInstrument; +import io.opentelemetry.javaagent.ClassToInstrumentChild; +import java.lang.ref.WeakReference; +import java.net.URL; +import java.net.URLClassLoader; +import java.time.Duration; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +class ClassLoadingTest { + + private URL[] classpath; + + @BeforeEach + void setUp() throws Exception { + classpath = new URL[] {createJarWithClasses(ClassToInstrument.class, ClassToInstrumentChild.class)}; + } + + /** Assert that we can instrument classloaders which cannot resolve agent advice classes. */ + @Test + void instrumentClassloaderWithoutAgentClasses() throws Exception { + URLClassLoader loader = new URLClassLoader(classpath, null); + + assertThatThrownBy(() -> + loader.loadClass("io.opentelemetry.javaagent.instrumentation.trace_annotation.TraceAdvice")) + .isInstanceOf(ClassNotFoundException.class); + + Class instrumentedClass = loader.loadClass(ClassToInstrument.class.getName()); + assertThat(instrumentedClass.getClassLoader()).isEqualTo(loader); + } + + @Test + void makeSureByteBuddyDoesNotHoldStrongReferencesToClassLoader() throws Exception { + URLClassLoader loader = new URLClassLoader(classpath, null); + WeakReference ref = new WeakReference<>(loader); + + loader.loadClass(ClassToInstrument.class.getName()); + //noinspection UnusedAssignment + loader = null; + + GcUtils.awaitGc(ref, Duration.ofSeconds(10)); + + assertThat(ref.get()).isNull(); + } + + // We are doing this because Groovy cannot properly resolve constructor argument types in anonymous classes + static class CountingClassLoader extends URLClassLoader { + public int count = 0; + + CountingClassLoader(URL[] urls) { + super(urls, null); + } + + @Override + public URL getResource(String name) { + count++; + return super.getResource(name); + } + } + + @Test + void makeSureThatByteBuddyReadsTheClassBytesOnlyOnce() throws Exception { + try (CountingClassLoader loader = new CountingClassLoader(classpath)) { + loader.loadClass(ClassToInstrument.class.getName()); + int countAfterFirstLoad = loader.count; + loader.loadClass(ClassToInstrumentChild.class.getName()); + + // ClassToInstrumentChild won't cause an additional getResource() because its TypeDescription is created from transformation bytes. + assertThat(loader.count) + .isPositive() + .isEqualTo(countAfterFirstLoad); + } + } + + @Test + void makeSureThatByteBuddyDoesntReuseCachedTypeDescriptionsBetweenDifferentClassloaders() throws Exception { + try (CountingClassLoader loader1 = new CountingClassLoader(classpath); + CountingClassLoader loader2 = new CountingClassLoader(classpath)) { + + loader1.loadClass(ClassToInstrument.class.getName()); + loader2.loadClass(ClassToInstrument.class.getName()); + + assertThat(loader1.count).isPositive(); + assertThat(loader2.count).isPositive(); + assertThat(loader1.count).isEqualTo(loader2.count); + } + } +} From f8f046f30b4b21f63bcfb95b4a755572d94501e2 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:37:58 +0200 Subject: [PATCH 5/9] convert test --- .../javaagent/LogLevelTest.groovy | 93 ------------ .../opentelemetry/javaagent/LogLevelTest.java | 143 ++++++++++++++++++ 2 files changed, 143 insertions(+), 93 deletions(-) delete mode 100644 javaagent/src/test/groovy/io/opentelemetry/javaagent/LogLevelTest.groovy create mode 100644 javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java diff --git a/javaagent/src/test/groovy/io/opentelemetry/javaagent/LogLevelTest.groovy b/javaagent/src/test/groovy/io/opentelemetry/javaagent/LogLevelTest.groovy deleted file mode 100644 index 87fffce554a6..000000000000 --- a/javaagent/src/test/groovy/io/opentelemetry/javaagent/LogLevelTest.groovy +++ /dev/null @@ -1,93 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent - -import jvmbootstraptest.LogLevelChecker -import spock.lang.Specification - -class LogLevelTest extends Specification { - - - /* Priority: io.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel > opentelemetry.javaagent.debug > OTEL_JAVAAGENT_DEBUG - 1: INFO LOGS - 0: DEBUG Logs - */ - - def "otel.javaagent.debug false"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , [:] - , true) == 1 - } - - def "SLF4J DEBUG && otel.javaagent.debug is false"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dotel.javaagent.debug=false", "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , [:] - , true) == 0 - } - - def "otel.javaagent.debug is false && OTEL_JAVAAGENT_DEBUG is true"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , ["OTEL_JAVAAGENT_DEBUG": "true"] - , true) == 1 - } - - def "otel.javaagent.debug is true"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , [:] - , true) == 0 - } - - - def "OTEL_JAVAAGENT_DEBUG is true"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , ["OTEL_JAVAAGENT_DEBUG": "true"] - , true) == 0 - } - - def "otel.javaagent.debug is true && OTEL_JAVAAGENT_DEBUG is false"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , ["OTEL_JAVAAGENT_DEBUG": "false"] - , true) == 0 - } - - - def "SLF4J DEBUG && OTEL_JAVAAGENT_DEBUG is false"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , ["OTEL_JAVAAGENT_DEBUG": "false"] - , true) == 0 - } - - def "SLF4J INFO && OTEL_JAVAAGENT_DEBUG is true"() { - expect: - IntegrationTestUtils.runOnSeparateJvm(LogLevelChecker.getName() - , ["-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=info", "-Dotel.javaagent.enabled=false"] as String[] - , "" as String[] - , ["OTEL_JAVAAGENT_DEBUG": "true"] - , true) == 1 - } - -} diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java new file mode 100644 index 000000000000..90303bad0a88 --- /dev/null +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java @@ -0,0 +1,143 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; +import jvmbootstraptest.LogLevelChecker; +import org.junit.jupiter.api.Test; + +class LogLevelTest { + + /* Priority: io.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel > opentelemetry.javaagent.debug > OTEL_JAVAAGENT_DEBUG + 1: INFO LOGS + 0: DEBUG Logs + */ + + @Test + void otelJavaagentDebugFalse() throws Exception { + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{"-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"}, + new String[0], + Collections.emptyMap(), + true); + + assertThat(exitCode).isOne(); + } + + @Test + void slf4jDebugAndOtelJavaagentDebugIsFalse() throws Exception { + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{ + "-Dotel.javaagent.debug=false", + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", + "-Dotel.javaagent.enabled=false" + }, + new String[0], + Collections.emptyMap(), + true); + + assertThat(exitCode).isZero(); + } + + @Test + void otelJavaagentDebugIsFalseAndOtelJavaagentDebugIsTrue() throws Exception { + Map env = new HashMap<>(); + env.put("OTEL_JAVAAGENT_DEBUG", "true"); + + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{"-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"}, + new String[0], + env, + true); + + assertThat(exitCode).isOne(); + } + + @Test + void otelJavaagentDebugIsTrue() throws Exception { + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{"-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"}, + new String[0], + Collections.emptyMap(), + true); + + assertThat(exitCode).isZero(); + } + + @Test + void otelJavaagentDebugIsTrue2() throws Exception { + Map env = new HashMap<>(); + env.put("OTEL_JAVAAGENT_DEBUG", "true"); + + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{"-Dotel.javaagent.enabled=false"}, + new String[0], + env, + true); + + assertThat(exitCode).isZero(); + } + + @Test + void otelJavaagentDebugIsTrueAndOtelJavaagentDebugIsFalse() throws Exception { + Map env = new HashMap<>(); + env.put("OTEL_JAVAAGENT_DEBUG", "false"); + + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{"-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"}, + new String[0], + env, + true); + + assertThat(exitCode).isZero(); + } + + @Test + void slf4jDebugAndOtelJavaagentDebugIsFalse2() throws Exception { + Map env = new HashMap<>(); + env.put("OTEL_JAVAAGENT_DEBUG", "false"); + + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{ + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", + "-Dotel.javaagent.enabled=false" + }, + new String[0], + env, + true); + + assertThat(exitCode).isZero(); + } + + @Test + void slf4jInfoAndOtelJavaagentDebugIsTrue() throws Exception { + Map env = new HashMap<>(); + env.put("OTEL_JAVAAGENT_DEBUG", "true"); + + int exitCode = IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[]{ + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=info", + "-Dotel.javaagent.enabled=false" + }, + new String[0], + env, + true); + + assertThat(exitCode).isOne(); + } +} From ace892739c5eb380c7d8b10fa816f3c42acd3365 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:38:19 +0200 Subject: [PATCH 6/9] convert test --- .../AgentLoadedIntoBootstrapTest.java | 31 ++--- .../opentelemetry/javaagent/LogLevelTest.java | 124 ++++++++++-------- .../classloading/ClassLoadingTest.java | 24 ++-- .../muzzle/MuzzleBytecodeTransformTest.java | 6 +- 4 files changed, 98 insertions(+), 87 deletions(-) diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java index 2c5e9a575ff4..d2fc15c6858b 100644 --- a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java @@ -17,12 +17,13 @@ class AgentLoadedIntoBootstrapTest { @Test void agentLoadsInWhenSeparateJvmIsLaunched() throws Exception { - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - AgentLoadedChecker.class.getName(), - new String[0], - new String[0], - Collections.emptyMap(), - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + AgentLoadedChecker.class.getName(), + new String[0], + new String[0], + Collections.emptyMap(), + true); assertThat(exitCode).isZero(); } @@ -40,19 +41,15 @@ void agentLoadsInWhenSeparateJvmIsLaunched() throws Exception { @Test void applicationUberJarShouldNotBeAddedToTheBootstrapClassLoader() throws Exception { String mainClassName = MyClassLoaderIsNotBootstrap.class.getName(); - String pathToJar = IntegrationTestUtils.createJarWithClasses( - mainClassName, - MyClassLoaderIsNotBootstrap.class, - OpenTelemetryAgent.class).getPath(); + String pathToJar = + IntegrationTestUtils.createJarWithClasses( + mainClassName, MyClassLoaderIsNotBootstrap.class, OpenTelemetryAgent.class) + .getPath(); try { - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - mainClassName, - new String[0], - new String[0], - Collections.emptyMap(), - pathToJar, - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + mainClassName, new String[0], new String[0], Collections.emptyMap(), pathToJar, true); assertThat(exitCode).isZero(); } finally { diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java index 90303bad0a88..1e759564b0c8 100644 --- a/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/LogLevelTest.java @@ -22,28 +22,30 @@ class LogLevelTest { @Test void otelJavaagentDebugFalse() throws Exception { - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{"-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"}, - new String[0], - Collections.emptyMap(), - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] {"-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"}, + new String[0], + Collections.emptyMap(), + true); assertThat(exitCode).isOne(); } @Test void slf4jDebugAndOtelJavaagentDebugIsFalse() throws Exception { - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{ - "-Dotel.javaagent.debug=false", - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", - "-Dotel.javaagent.enabled=false" - }, - new String[0], - Collections.emptyMap(), - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] { + "-Dotel.javaagent.debug=false", + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", + "-Dotel.javaagent.enabled=false" + }, + new String[0], + Collections.emptyMap(), + true); assertThat(exitCode).isZero(); } @@ -53,24 +55,26 @@ void otelJavaagentDebugIsFalseAndOtelJavaagentDebugIsTrue() throws Exception { Map env = new HashMap<>(); env.put("OTEL_JAVAAGENT_DEBUG", "true"); - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{"-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"}, - new String[0], - env, - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] {"-Dotel.javaagent.debug=false", "-Dotel.javaagent.enabled=false"}, + new String[0], + env, + true); assertThat(exitCode).isOne(); } @Test void otelJavaagentDebugIsTrue() throws Exception { - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{"-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"}, - new String[0], - Collections.emptyMap(), - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] {"-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"}, + new String[0], + Collections.emptyMap(), + true); assertThat(exitCode).isZero(); } @@ -80,12 +84,13 @@ void otelJavaagentDebugIsTrue2() throws Exception { Map env = new HashMap<>(); env.put("OTEL_JAVAAGENT_DEBUG", "true"); - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{"-Dotel.javaagent.enabled=false"}, - new String[0], - env, - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] {"-Dotel.javaagent.enabled=false"}, + new String[0], + env, + true); assertThat(exitCode).isZero(); } @@ -95,12 +100,13 @@ void otelJavaagentDebugIsTrueAndOtelJavaagentDebugIsFalse() throws Exception { Map env = new HashMap<>(); env.put("OTEL_JAVAAGENT_DEBUG", "false"); - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{"-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"}, - new String[0], - env, - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] {"-Dotel.javaagent.debug=true", "-Dotel.javaagent.enabled=false"}, + new String[0], + env, + true); assertThat(exitCode).isZero(); } @@ -110,15 +116,16 @@ void slf4jDebugAndOtelJavaagentDebugIsFalse2() throws Exception { Map env = new HashMap<>(); env.put("OTEL_JAVAAGENT_DEBUG", "false"); - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{ - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", - "-Dotel.javaagent.enabled=false" - }, - new String[0], - env, - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] { + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=debug", + "-Dotel.javaagent.enabled=false" + }, + new String[0], + env, + true); assertThat(exitCode).isZero(); } @@ -128,15 +135,16 @@ void slf4jInfoAndOtelJavaagentDebugIsTrue() throws Exception { Map env = new HashMap<>(); env.put("OTEL_JAVAAGENT_DEBUG", "true"); - int exitCode = IntegrationTestUtils.runOnSeparateJvm( - LogLevelChecker.class.getName(), - new String[]{ - "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=info", - "-Dotel.javaagent.enabled=false" - }, - new String[0], - env, - true); + int exitCode = + IntegrationTestUtils.runOnSeparateJvm( + LogLevelChecker.class.getName(), + new String[] { + "-Dio.opentelemetry.javaagent.slf4j.simpleLogger.defaultLogLevel=info", + "-Dotel.javaagent.enabled=false" + }, + new String[0], + env, + true); assertThat(exitCode).isOne(); } diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java index e3197445bc46..e6bbae57a5cf 100644 --- a/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/classloading/ClassLoadingTest.java @@ -25,7 +25,8 @@ class ClassLoadingTest { @BeforeEach void setUp() throws Exception { - classpath = new URL[] {createJarWithClasses(ClassToInstrument.class, ClassToInstrumentChild.class)}; + classpath = + new URL[] {createJarWithClasses(ClassToInstrument.class, ClassToInstrumentChild.class)}; } /** Assert that we can instrument classloaders which cannot resolve agent advice classes. */ @@ -33,8 +34,10 @@ void setUp() throws Exception { void instrumentClassloaderWithoutAgentClasses() throws Exception { URLClassLoader loader = new URLClassLoader(classpath, null); - assertThatThrownBy(() -> - loader.loadClass("io.opentelemetry.javaagent.instrumentation.trace_annotation.TraceAdvice")) + assertThatThrownBy( + () -> + loader.loadClass( + "io.opentelemetry.javaagent.instrumentation.trace_annotation.TraceAdvice")) .isInstanceOf(ClassNotFoundException.class); Class instrumentedClass = loader.loadClass(ClassToInstrument.class.getName()); @@ -55,7 +58,8 @@ void makeSureByteBuddyDoesNotHoldStrongReferencesToClassLoader() throws Exceptio assertThat(ref.get()).isNull(); } - // We are doing this because Groovy cannot properly resolve constructor argument types in anonymous classes + // We are doing this because Groovy cannot properly resolve constructor argument types in + // anonymous classes static class CountingClassLoader extends URLClassLoader { public int count = 0; @@ -77,17 +81,17 @@ void makeSureThatByteBuddyReadsTheClassBytesOnlyOnce() throws Exception { int countAfterFirstLoad = loader.count; loader.loadClass(ClassToInstrumentChild.class.getName()); - // ClassToInstrumentChild won't cause an additional getResource() because its TypeDescription is created from transformation bytes. - assertThat(loader.count) - .isPositive() - .isEqualTo(countAfterFirstLoad); + // ClassToInstrumentChild won't cause an additional getResource() because its TypeDescription + // is created from transformation bytes. + assertThat(loader.count).isPositive().isEqualTo(countAfterFirstLoad); } } @Test - void makeSureThatByteBuddyDoesntReuseCachedTypeDescriptionsBetweenDifferentClassloaders() throws Exception { + void makeSureThatByteBuddyDoesntReuseCachedTypeDescriptionsBetweenDifferentClassloaders() + throws Exception { try (CountingClassLoader loader1 = new CountingClassLoader(classpath); - CountingClassLoader loader2 = new CountingClassLoader(classpath)) { + CountingClassLoader loader2 = new CountingClassLoader(classpath)) { loader1.loadClass(ClassToInstrument.class.getName()); loader2.loadClass(ClassToInstrument.class.getName()); diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java index 4f33cf8c6f92..853e1254dd30 100644 --- a/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/muzzle/MuzzleBytecodeTransformTest.java @@ -23,8 +23,10 @@ void muzzleFieldsAddedToAllInstrumentation() throws Exception { List> nonLazyFields = new ArrayList<>(); List> unInitFields = new ArrayList<>(); - Class instrumentationModuleClass = IntegrationTestUtils.getAgentClassLoader() - .loadClass("io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule"); + Class instrumentationModuleClass = + IntegrationTestUtils.getAgentClassLoader() + .loadClass( + "io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule"); for (Object instrumenter : ServiceLoader.load(instrumentationModuleClass)) { if (!instrumentationModuleClass.isAssignableFrom(instrumenter.getClass())) { From cbcb20d30cce5e518a66fb6279747b60124be386 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:40:20 +0200 Subject: [PATCH 7/9] convert test --- .../bootstrap/AgentClassLoaderTest.groovy | 87 ----------------- .../bootstrap/AgentClassLoaderTest.java | 97 +++++++++++++++++++ 2 files changed, 97 insertions(+), 87 deletions(-) delete mode 100644 javaagent-bootstrap/src/test/groovy/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.groovy create mode 100644 javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java diff --git a/javaagent-bootstrap/src/test/groovy/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.groovy b/javaagent-bootstrap/src/test/groovy/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.groovy deleted file mode 100644 index 27300475fd6e..000000000000 --- a/javaagent-bootstrap/src/test/groovy/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.groovy +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright The OpenTelemetry Authors - * SPDX-License-Identifier: Apache-2.0 - */ - -package io.opentelemetry.javaagent.bootstrap - -import io.opentelemetry.sdk.OpenTelemetrySdk -import spock.lang.Specification - -import java.lang.reflect.Method -import java.util.concurrent.Phaser - -class AgentClassLoaderTest extends Specification { - - def "agent classloader does not lock classloading around instance"() { - setup: - def className1 = 'some/class/Name1' - def className2 = 'some/class/Name2' - // any jar would do, use opentelemety sdk - URL testJarLocation = OpenTelemetrySdk.getProtectionDomain().getCodeSource().getLocation() - AgentClassLoader loader = new AgentClassLoader(new File(testJarLocation.toURI())) - Phaser threadHoldLockPhase = new Phaser(2) - Phaser acquireLockFromMainThreadPhase = new Phaser(2) - - when: - Thread thread1 = new Thread() { - @Override - void run() { - synchronized (loader.getClassLoadingLock(className1)) { - threadHoldLockPhase.arrive() - acquireLockFromMainThreadPhase.arriveAndAwaitAdvance() - } - } - } - thread1.start() - - Thread thread2 = new Thread() { - @Override - void run() { - threadHoldLockPhase.arriveAndAwaitAdvance() - synchronized (loader.getClassLoadingLock(className2)) { - acquireLockFromMainThreadPhase.arrive() - } - } - } - thread2.start() - thread1.join() - thread2.join() - boolean applicationDidNotDeadlock = true - - then: - applicationDidNotDeadlock - } - - def "multi release jar"() { - setup: - boolean jdk8 = "1.8" == System.getProperty("java.specification.version") - def mrJarClass = Class.forName("io.opentelemetry.instrumentation.resources.internal.ProcessArguments") - // sdk is a multi release jar - URL multiReleaseJar = mrJarClass.getProtectionDomain().getCodeSource().getLocation() - AgentClassLoader loader = new AgentClassLoader(new File(multiReleaseJar.toURI())) { - @Override - protected String getClassSuffix() { - return "" - } - } - - when: - URL url = loader.findResource("io/opentelemetry/instrumentation/resources/internal/ProcessArguments.class") - - then: - url != null - // versioned resource is found when not running on jdk 8 - jdk8 != url.toString().contains("META-INF/versions/9/") - - and: - Class clazz = loader.loadClass("io.opentelemetry.instrumentation.resources.internal.ProcessArguments") - // class was loaded by agent loader used in this test - clazz.getClassLoader() == loader - Method method = clazz.getDeclaredMethod("getProcessArguments") - method.setAccessible(true) - String[] result = method.invoke(null) - // jdk8 versions returns empty array, jdk9 version does not - jdk8 != result.length > 0 - } -} diff --git a/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java b/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java new file mode 100644 index 000000000000..3afb8cd3aa6d --- /dev/null +++ b/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java @@ -0,0 +1,97 @@ +/* + * Copyright The OpenTelemetry Authors + * SPDX-License-Identifier: Apache-2.0 + */ + +package io.opentelemetry.javaagent.bootstrap; + +import static org.assertj.core.api.Assertions.assertThat; + +import io.opentelemetry.sdk.OpenTelemetrySdk; +import java.io.File; +import java.lang.reflect.Method; +import java.net.URL; +import java.util.concurrent.Phaser; +import org.junit.jupiter.api.Test; + +class AgentClassLoaderTest { + + @Test + void agentClassloaderDoesNotLockClassloadingAroundInstance() throws Exception { + String className1 = "some/class/Name1"; + String className2 = "some/class/Name2"; + // any jar would do, use opentelemety sdk + URL testJarLocation = OpenTelemetrySdk.class.getProtectionDomain().getCodeSource().getLocation(); + + try (AgentClassLoader loader = new AgentClassLoader(new File(testJarLocation.toURI()))) { + Phaser threadHoldLockPhase = new Phaser(2); + Phaser acquireLockFromMainThreadPhase = new Phaser(2); + + // Use reflection to access protected getClassLoadingLock method + Method getClassLoadingLockMethod = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class); + getClassLoadingLockMethod.setAccessible(true); + + Thread thread1 = new Thread(() -> { + try { + Object lock1 = getClassLoadingLockMethod.invoke(loader, className1); + synchronized (lock1) { + threadHoldLockPhase.arrive(); + acquireLockFromMainThreadPhase.arriveAndAwaitAdvance(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + thread1.start(); + + Thread thread2 = new Thread(() -> { + try { + threadHoldLockPhase.arriveAndAwaitAdvance(); + Object lock2 = getClassLoadingLockMethod.invoke(loader, className2); + synchronized (lock2) { + acquireLockFromMainThreadPhase.arrive(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); + thread2.start(); + + thread1.join(); + thread2.join(); + boolean applicationDidNotDeadlock = true; + + assertThat(applicationDidNotDeadlock).isTrue(); + } + } + + @Test + void multiReleaseJar() throws Exception { + boolean jdk8 = "1.8".equals(System.getProperty("java.specification.version")); + Class mrJarClass = Class.forName("io.opentelemetry.instrumentation.resources.internal.ProcessArguments"); + // sdk is a multi release jar + URL multiReleaseJar = mrJarClass.getProtectionDomain().getCodeSource().getLocation(); + + try (AgentClassLoader loader = new AgentClassLoader(new File(multiReleaseJar.toURI())) { + @Override + protected String getClassSuffix() { + return ""; + } + }) { + URL url = loader.findResource("io/opentelemetry/instrumentation/resources/internal/ProcessArguments.class"); + + assertThat(url).isNotNull(); + // versioned resource is found when not running on jdk 8 + assertThat(url.toString().contains("META-INF/versions/9/")).isNotEqualTo(jdk8); + + Class clazz = loader.loadClass("io.opentelemetry.instrumentation.resources.internal.ProcessArguments"); + // class was loaded by agent loader used in this test + assertThat(clazz.getClassLoader()).isEqualTo(loader); + Method method = clazz.getDeclaredMethod("getProcessArguments"); + method.setAccessible(true); + String[] result = (String[]) method.invoke(null); + // jdk8 versions returns empty array, jdk9 version does not + assertThat(result.length > 0).isNotEqualTo(jdk8); + } + } +} From fa5c2473e1296db80506dc23eb5847e1fcd220cf Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 14:41:23 +0200 Subject: [PATCH 8/9] convert test --- .../bootstrap/AgentClassLoaderTest.java | 77 +++++++++++-------- 1 file changed, 44 insertions(+), 33 deletions(-) diff --git a/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java b/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java index 3afb8cd3aa6d..d25e68f686af 100644 --- a/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java +++ b/javaagent-bootstrap/src/test/java/io/opentelemetry/javaagent/bootstrap/AgentClassLoaderTest.java @@ -21,40 +21,46 @@ void agentClassloaderDoesNotLockClassloadingAroundInstance() throws Exception { String className1 = "some/class/Name1"; String className2 = "some/class/Name2"; // any jar would do, use opentelemety sdk - URL testJarLocation = OpenTelemetrySdk.class.getProtectionDomain().getCodeSource().getLocation(); + URL testJarLocation = + OpenTelemetrySdk.class.getProtectionDomain().getCodeSource().getLocation(); try (AgentClassLoader loader = new AgentClassLoader(new File(testJarLocation.toURI()))) { Phaser threadHoldLockPhase = new Phaser(2); Phaser acquireLockFromMainThreadPhase = new Phaser(2); // Use reflection to access protected getClassLoadingLock method - Method getClassLoadingLockMethod = ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class); + Method getClassLoadingLockMethod = + ClassLoader.class.getDeclaredMethod("getClassLoadingLock", String.class); getClassLoadingLockMethod.setAccessible(true); - Thread thread1 = new Thread(() -> { - try { - Object lock1 = getClassLoadingLockMethod.invoke(loader, className1); - synchronized (lock1) { - threadHoldLockPhase.arrive(); - acquireLockFromMainThreadPhase.arriveAndAwaitAdvance(); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + Thread thread1 = + new Thread( + () -> { + try { + Object lock1 = getClassLoadingLockMethod.invoke(loader, className1); + synchronized (lock1) { + threadHoldLockPhase.arrive(); + acquireLockFromMainThreadPhase.arriveAndAwaitAdvance(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); thread1.start(); - Thread thread2 = new Thread(() -> { - try { - threadHoldLockPhase.arriveAndAwaitAdvance(); - Object lock2 = getClassLoadingLockMethod.invoke(loader, className2); - synchronized (lock2) { - acquireLockFromMainThreadPhase.arrive(); - } - } catch (Exception e) { - throw new RuntimeException(e); - } - }); + Thread thread2 = + new Thread( + () -> { + try { + threadHoldLockPhase.arriveAndAwaitAdvance(); + Object lock2 = getClassLoadingLockMethod.invoke(loader, className2); + synchronized (lock2) { + acquireLockFromMainThreadPhase.arrive(); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + }); thread2.start(); thread1.join(); @@ -68,23 +74,28 @@ void agentClassloaderDoesNotLockClassloadingAroundInstance() throws Exception { @Test void multiReleaseJar() throws Exception { boolean jdk8 = "1.8".equals(System.getProperty("java.specification.version")); - Class mrJarClass = Class.forName("io.opentelemetry.instrumentation.resources.internal.ProcessArguments"); + Class mrJarClass = + Class.forName("io.opentelemetry.instrumentation.resources.internal.ProcessArguments"); // sdk is a multi release jar URL multiReleaseJar = mrJarClass.getProtectionDomain().getCodeSource().getLocation(); - try (AgentClassLoader loader = new AgentClassLoader(new File(multiReleaseJar.toURI())) { - @Override - protected String getClassSuffix() { - return ""; - } - }) { - URL url = loader.findResource("io/opentelemetry/instrumentation/resources/internal/ProcessArguments.class"); + try (AgentClassLoader loader = + new AgentClassLoader(new File(multiReleaseJar.toURI())) { + @Override + protected String getClassSuffix() { + return ""; + } + }) { + URL url = + loader.findResource( + "io/opentelemetry/instrumentation/resources/internal/ProcessArguments.class"); assertThat(url).isNotNull(); // versioned resource is found when not running on jdk 8 assertThat(url.toString().contains("META-INF/versions/9/")).isNotEqualTo(jdk8); - Class clazz = loader.loadClass("io.opentelemetry.instrumentation.resources.internal.ProcessArguments"); + Class clazz = + loader.loadClass("io.opentelemetry.instrumentation.resources.internal.ProcessArguments"); // class was loaded by agent loader used in this test assertThat(clazz.getClassLoader()).isEqualTo(loader); Method method = clazz.getDeclaredMethod("getProcessArguments"); From 41bc2946053695a7334e67c8b4fc19a7601b2cb1 Mon Sep 17 00:00:00 2001 From: Gregor Zeitlinger Date: Tue, 7 Oct 2025 15:07:59 +0200 Subject: [PATCH 9/9] convert tests --- .../javaagent/AgentLoadedIntoBootstrapTest.java | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java index d2fc15c6858b..eb0bc09860b1 100644 --- a/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java +++ b/javaagent/src/test/java/io/opentelemetry/javaagent/AgentLoadedIntoBootstrapTest.java @@ -53,10 +53,7 @@ void applicationUberJarShouldNotBeAddedToTheBootstrapClassLoader() throws Except assertThat(exitCode).isZero(); } finally { - boolean deleted = new File(pathToJar).delete(); - if (!deleted) { - System.err.println("Failed to delete temporary jar file: " + pathToJar); - } + new File(pathToJar).delete(); } } }