Convert integration tests to java (#13244)

This commit is contained in:
Lauri Tulmin 2025-02-11 16:03:38 +02:00 committed by GitHub
parent bc7e81bfda
commit 85a52ca804
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 476 additions and 464 deletions

View File

@ -11,6 +11,8 @@ dependencies {
testCompileOnly(project(":javaagent-bootstrap"))
testCompileOnly(project(":javaagent-extension-api"))
testCompileOnly(project(":muzzle"))
testCompileOnly("com.google.auto.service:auto-service-annotations")
testCompileOnly("com.google.code.findbugs:annotations")
testImplementation("net.bytebuddy:byte-buddy")
testImplementation("net.bytebuddy:byte-buddy-agent")
@ -47,25 +49,10 @@ tasks {
jvmArgs("-XX:+IgnoreUnrecognizedVMOptions")
}
val testIndyModuleOldBytecodeInstrumentation by registering(Test::class) {
filter {
includeTestsMatching("InstrumentOldBytecode")
}
include("**/InstrumentOldBytecode.*")
}
val testInlineModuleOldBytecodeInstrumentation by registering(Test::class) {
filter {
includeTestsMatching("InstrumentOldBytecode")
}
include("**/InstrumentOldBytecode.*")
}
test {
filter {
excludeTestsMatching("context.FieldInjectionDisabledTest")
excludeTestsMatching("context.FieldBackedImplementationTest")
excludeTestsMatching("InstrumentOldBytecode")
}
// this is needed for AgentInstrumentationSpecificationTest
jvmArgs("-Dotel.javaagent.exclude-classes=config.exclude.packagename.*,config.exclude.SomeClass,config.exclude.SomeClass\$NestedClass")

View File

@ -1,154 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import com.google.common.reflect.ClassPath
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.instrumentation.test.utils.ClasspathUtils
import io.opentelemetry.javaagent.bootstrap.BootstrapPackagePrefixesHolder
import org.slf4j.LoggerFactory
import java.util.concurrent.TimeoutException
// this test is run using
// -Dotel.javaagent.exclude-classes=config.exclude.packagename.*,config.exclude.SomeClass,config.exclude.SomeClass$NestedClass
// (see integration-tests.gradle)
class AgentInstrumentationSpecificationTest extends AgentInstrumentationSpecification {
private static final ClassLoader BOOTSTRAP_CLASSLOADER = null
public static final List<String> BOOTSTRAP_PACKAGE_PREFIXES = BootstrapPackagePrefixesHolder.getBoostrapPackagePrefixes()
def "classpath setup"() {
setup:
final List<String> bootstrapClassesIncorrectlyLoaded = []
for (ClassPath.ClassInfo info : getTestClasspath().getAllClasses()) {
for (int i = 0; i < BOOTSTRAP_PACKAGE_PREFIXES.size(); ++i) {
if (info.getName().startsWith(BOOTSTRAP_PACKAGE_PREFIXES[i])) {
Class<?> bootstrapClass = Class.forName(info.getName())
def loader
try {
loader = bootstrapClass.getClassLoader()
} catch (NoClassDefFoundError e) {
// some classes in com.google.errorprone.annotations cause groovy to throw
// java.lang.NoClassDefFoundError: [Ljavax/lang/model/element/Modifier;
break
}
if (loader != BOOTSTRAP_CLASSLOADER) {
bootstrapClassesIncorrectlyLoaded.add(bootstrapClass)
}
break
}
}
}
expect:
bootstrapClassesIncorrectlyLoaded == []
}
def "waiting for child spans times out"() {
when:
runWithSpan("parent") {
waitForTraces(1)
}
then:
thrown(TimeoutException)
}
def "logging works"() {
when:
LoggerFactory.getLogger(AgentInstrumentationSpecificationTest).debug("hello")
then:
noExceptionThrown()
}
def "excluded classes are not instrumented"() {
when:
runWithSpan("parent") {
subject.getConstructor().newInstance().run()
}
then:
assertTraces(1) {
trace(0, spanName ? 2 : 1) {
span(0) {
name "parent"
}
if (spanName) {
span(1) {
name spanName
childOf span(0)
}
}
}
}
where:
subject | spanName
config.SomeClass | "SomeClass.run"
config.SomeClass.NestedClass | "NestedClass.run"
config.exclude.SomeClass | null
config.exclude.SomeClass.NestedClass | null
config.exclude.packagename.SomeClass | null
config.exclude.packagename.SomeClass.NestedClass | null
}
def "test unblocked by completed span"() {
setup:
runWithSpan("parent") {
runWithSpan("child") {}
}
expect:
assertTraces(1) {
trace(0, 2) {
span(0) {
name "parent"
hasNoParent()
}
span(1) {
name "child"
childOf span(0)
}
}
}
}
private static ClassPath getTestClasspath() {
ClassLoader testClassLoader = ClasspathUtils.getClassLoader()
if (!(testClassLoader instanceof URLClassLoader)) {
// java9's system loader does not extend URLClassLoader
// which breaks Guava ClassPath lookup
testClassLoader = buildJavaClassPathClassLoader()
}
try {
return ClassPath.from(testClassLoader)
} catch (IOException e) {
throw new IllegalStateException(e)
}
}
/**
* Parse JVM classpath and return ClassLoader containing all classpath entries. Inspired by Guava.
*/
private static ClassLoader buildJavaClassPathClassLoader() {
List<URL> urls = new ArrayList<>()
for (String entry : getClasspath()) {
try {
try {
urls.add(new File(entry).toURI().toURL())
} catch (SecurityException e) { // File.toURI checks to see if the file is a directory
urls.add(new URL("file", null, new File(entry).getAbsolutePath()))
}
} catch (MalformedURLException e) {
throw new IllegalStateException(e)
}
}
return new URLClassLoader(urls.toArray(new URL[0]), (ClassLoader) null)
}
private static String[] getClasspath() {
return System.getProperty("java.class.path").split(System.getProperty("path.separator"))
}
}

View File

@ -1,14 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
import com.ibm.as400.resource.ResourceLevel
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
class InstrumentOldBytecode extends AgentInstrumentationSpecification {
def "can instrument old bytecode"() {
expect:
new ResourceLevel().toString() == "instrumented"
}
}

View File

@ -1,227 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package context
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.instrumentation.test.utils.ClasspathUtils
import io.opentelemetry.instrumentation.test.utils.GcUtils
import io.opentelemetry.javaagent.testing.common.TestAgentListenerAccess
import library.KeyClass
import library.UntransformableKeyClass
import net.bytebuddy.agent.ByteBuddyAgent
import net.sf.cglib.proxy.Enhancer
import net.sf.cglib.proxy.MethodInterceptor
import net.sf.cglib.proxy.MethodProxy
import spock.lang.Unroll
import java.lang.instrument.ClassDefinition
import java.lang.ref.WeakReference
import java.lang.reflect.Field
import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.time.Duration
import java.util.concurrent.atomic.AtomicReference
// this test is run using
// -Dotel.instrumentation.context-test-instrumentation.enabled=true
// (see integration-tests.gradle)
class FieldBackedImplementationTest extends AgentInstrumentationSpecification {
def setupSpec() {
TestAgentListenerAccess.addSkipErrorCondition({ typeName, throwable ->
return typeName.startsWith('library.Incorrect') &&
throwable.getMessage().startsWith("Incorrect Context Api Usage detected.")
})
TestAgentListenerAccess.addSkipTransformationCondition({ typeName ->
return typeName != null && typeName.endsWith("UntransformableKeyClass")
})
}
@Unroll
def "#keyClassName structure modified = #shouldModifyStructure"() {
setup:
boolean hasField = false
boolean isPrivate = false
boolean isTransient = false
boolean isSynthetic = false
for (Field field : keyClass.getDeclaredFields()) {
if (field.getName().startsWith("__opentelemetry")) {
isPrivate = Modifier.isPrivate(field.getModifiers())
isTransient = Modifier.isTransient(field.getModifiers())
isSynthetic = field.isSynthetic()
hasField = true
break
}
}
boolean hasMarkerInterface = false
boolean hasAccessorInterface = false
boolean accessorInterfaceIsSynthetic = false
for (Class inter : keyClass.getInterfaces()) {
if (inter.getName() == 'io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker') {
hasMarkerInterface = true
}
if (inter.getName().startsWith('io.opentelemetry.javaagent.bootstrap.field.VirtualFieldAccessor$')) {
hasAccessorInterface = true
accessorInterfaceIsSynthetic = inter.isSynthetic()
}
}
expect:
hasField == shouldModifyStructure
isPrivate == shouldModifyStructure
isTransient == shouldModifyStructure
isSynthetic == shouldModifyStructure
hasMarkerInterface == shouldModifyStructure
hasAccessorInterface == shouldModifyStructure
accessorInterfaceIsSynthetic == shouldModifyStructure
keyClass.newInstance().isInstrumented() == shouldModifyStructure
where:
keyClass | keyClassName | shouldModifyStructure
KeyClass | keyClass.getSimpleName() | true
UntransformableKeyClass | keyClass.getSimpleName() | false
}
def "multiple fields are injected"() {
setup:
List<Field> fields = []
for (Field field : KeyClass.getDeclaredFields()) {
if (field.getName().startsWith("__opentelemetry")) {
fields.add(field)
}
}
List<Class<?>> interfaces = []
for (Class iface : KeyClass.getInterfaces()) {
if (iface.name.startsWith('io.opentelemetry.javaagent.bootstrap.field.VirtualFieldAccessor$')) {
interfaces.add(iface)
}
}
expect:
fields.size() == 3
fields.forEach { field ->
assert Modifier.isPrivate(field.modifiers)
assert Modifier.isTransient(field.modifiers)
assert field.synthetic
}
interfaces.size() == 3
interfaces.forEach { iface ->
assert iface.synthetic
}
}
def "correct api usage stores state in map"() {
when:
instance1.incrementContextCount()
then:
instance1.incrementContextCount() == 2
instance2.incrementContextCount() == 1
where:
instance1 | instance2
new KeyClass() | new KeyClass()
new UntransformableKeyClass() | new UntransformableKeyClass()
}
def "get/put test"() {
when:
instance1.putContextCount(10)
then:
instance1.getContextCount() == 10
where:
instance1 | _
new KeyClass() | _
new UntransformableKeyClass() | _
}
def "remove test"() {
given:
instance1.putContextCount(10)
when:
instance1.removeContextCount()
then:
instance1.getContextCount() == 0
where:
instance1 | _
new KeyClass() | _
new UntransformableKeyClass() | _
}
def "works with cglib enhanced instances which duplicates context getter and setter methods"() {
setup:
Enhancer enhancer = new Enhancer()
enhancer.setSuperclass(KeyClass)
enhancer.setCallback(new MethodInterceptor() {
@Override
Object intercept(Object arg0, Method arg1, Object[] arg2,
MethodProxy arg3) throws Throwable {
return arg3.invokeSuper(arg0, arg2)
}
})
when:
(KeyClass) enhancer.create()
then:
noExceptionThrown()
}
def "backing map should not create strong refs to key class instances #keyValue.get().getClass().getName()"() {
when:
int count = keyValue.get().incrementContextCount()
WeakReference<KeyClass> instanceRef = new WeakReference(keyValue.get())
keyValue.set(null)
GcUtils.awaitGc(instanceRef, Duration.ofSeconds(10))
then:
instanceRef.get() == null
count == 1
where:
keyValue | _
new AtomicReference(new KeyClass()) | _
new AtomicReference(new UntransformableKeyClass()) | _
}
def "context classes are retransform safe"() {
when:
ByteBuddyAgent.install()
ByteBuddyAgent.getInstrumentation().retransformClasses(KeyClass)
ByteBuddyAgent.getInstrumentation().retransformClasses(UntransformableKeyClass)
then:
new KeyClass().isInstrumented()
!new UntransformableKeyClass().isInstrumented()
new KeyClass().incrementContextCount() == 1
new UntransformableKeyClass().incrementContextCount() == 1
}
// NB: This test will fail if some other agent is also running that modifies the class structure
// in a way that is incompatible with redefining the class back to its original bytecode.
// A likely culprit is jacoco if you start seeing failure here due to a change make sure jacoco
// exclusion is working.
def "context classes are redefine safe"() {
when:
ByteBuddyAgent.install()
ByteBuddyAgent.getInstrumentation().redefineClasses(new ClassDefinition(KeyClass, ClasspathUtils.convertToByteArray(KeyClass)))
ByteBuddyAgent.getInstrumentation().redefineClasses(new ClassDefinition(UntransformableKeyClass, ClasspathUtils.convertToByteArray(UntransformableKeyClass)))
then:
new KeyClass().isInstrumented()
!new UntransformableKeyClass().isInstrumented()
new KeyClass().incrementContextCount() == 1
new UntransformableKeyClass().incrementContextCount() == 1
}
}

View File

@ -1,54 +0,0 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package context
import io.opentelemetry.instrumentation.test.AgentInstrumentationSpecification
import io.opentelemetry.javaagent.testing.common.TestAgentListenerAccess
import library.DisabledKeyClass
import java.lang.reflect.Field
// this test is run using:
// -Dotel.javaagent.experimental.field-injection.enabled=false
// -Dotel.instrumentation.context-test-instrumentation.enabled=true
// (see integration-tests.gradle)
class FieldInjectionDisabledTest extends AgentInstrumentationSpecification {
def setupSpec() {
TestAgentListenerAccess.addSkipErrorCondition({ typeName, throwable ->
return typeName.startsWith(ContextTestInstrumentationModule.getName() + '$Incorrect') && throwable.getMessage().startsWith("Incorrect Context Api Usage detected.")
})
}
def "Check that structure is not modified when structure modification is disabled"() {
setup:
def keyClass = DisabledKeyClass
boolean hasField = false
for (Field field : keyClass.getDeclaredFields()) {
if (field.getName().startsWith("__opentelemetry")) {
hasField = true
break
}
}
boolean hasMarkerInterface = false
boolean hasAccessorInterface = false
for (Class inter : keyClass.getInterfaces()) {
if (inter.getName() == 'io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker') {
hasMarkerInterface = true
}
if (inter.getName().startsWith('io.opentelemetry.javaagent.bootstrap.instrumentation.context.FieldBackedProvider$ContextAccessor')) {
hasAccessorInterface = true
}
}
expect:
hasField == false
hasMarkerInterface == false
hasAccessorInterface == false
keyClass.newInstance().isInstrumented() == true
}
}

View File

@ -0,0 +1,237 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package context;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.instrumentation.test.utils.ClasspathUtils;
import io.opentelemetry.instrumentation.test.utils.GcUtils;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.testing.common.TestAgentListenerAccess;
import java.lang.instrument.ClassDefinition;
import java.lang.ref.WeakReference;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.Stream;
import library.KeyClass;
import library.UntransformableKeyClass;
import net.bytebuddy.agent.ByteBuddyAgent;
import net.sf.cglib.proxy.Enhancer;
import net.sf.cglib.proxy.MethodInterceptor;
import net.sf.cglib.proxy.MethodProxy;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
// this test is run using
// -Dotel.instrumentation.context-test-instrumentation.enabled=true
// (see integration-tests.gradle)
class FieldBackedImplementationTest {
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@BeforeAll
static void setUp() {
TestAgentListenerAccess.addSkipErrorCondition(
(typeName, throwable) ->
typeName.startsWith("library.Incorrect")
&& throwable.getMessage().startsWith("Incorrect Context Api Usage detected."));
TestAgentListenerAccess.addSkipTransformationCondition(
typeName -> typeName != null && typeName.endsWith("UntransformableKeyClass"));
}
private static Stream<Arguments> provideKeyClassParameters() {
return Stream.of(Arguments.of(KeyClass.class), Arguments.of(UntransformableKeyClass.class));
}
@ParameterizedTest
@MethodSource("provideKeyClassParameters")
void structureModified(Class<KeyClass> keyClass) throws Exception {
boolean shouldModifyStructure = !keyClass.equals(UntransformableKeyClass.class);
boolean hasField = false;
boolean isPrivate = false;
boolean isTransient = false;
boolean isSynthetic = false;
for (Field field : keyClass.getDeclaredFields()) {
if (field.getName().startsWith("__opentelemetry")) {
isPrivate = Modifier.isPrivate(field.getModifiers());
isTransient = Modifier.isTransient(field.getModifiers());
isSynthetic = field.isSynthetic();
hasField = true;
break;
}
}
boolean hasMarkerInterface = false;
boolean hasAccessorInterface = false;
boolean accessorInterfaceIsSynthetic = false;
for (Class<?> iface : keyClass.getInterfaces()) {
if ("io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker"
.equals(iface.getName())) {
hasMarkerInterface = true;
}
if (iface
.getName()
.startsWith("io.opentelemetry.javaagent.bootstrap.field.VirtualFieldAccessor$")) {
hasAccessorInterface = true;
accessorInterfaceIsSynthetic = iface.isSynthetic();
}
}
assertThat(hasField).isEqualTo(shouldModifyStructure);
assertThat(isPrivate).isEqualTo(shouldModifyStructure);
assertThat(isTransient).isEqualTo(shouldModifyStructure);
assertThat(isSynthetic).isEqualTo(shouldModifyStructure);
assertThat(hasMarkerInterface).isEqualTo(shouldModifyStructure);
assertThat(hasAccessorInterface).isEqualTo(shouldModifyStructure);
assertThat(accessorInterfaceIsSynthetic).isEqualTo(shouldModifyStructure);
assertThat(keyClass.getConstructor().newInstance().isInstrumented())
.isEqualTo(shouldModifyStructure);
}
@Test
void multipleFieldsInjected() {
List<Field> fields = new ArrayList<>();
for (Field field : KeyClass.class.getDeclaredFields()) {
if (field.getName().startsWith("__opentelemetry")) {
fields.add(field);
}
}
List<Class<?>> interfaces = new ArrayList<>();
for (Class<?> iface : KeyClass.class.getInterfaces()) {
if (iface
.getName()
.startsWith("io.opentelemetry.javaagent.bootstrap.field.VirtualFieldAccessor$")) {
interfaces.add(iface);
}
}
assertThat(fields.size()).isEqualTo(3);
assertThat(fields)
.allSatisfy(
field -> {
assertThat(Modifier.isPrivate(field.getModifiers())).isTrue();
assertThat(Modifier.isTransient(field.getModifiers())).isTrue();
assertThat(field.isSynthetic()).isTrue();
});
assertThat(interfaces.size()).isEqualTo(3);
assertThat(interfaces).allSatisfy(iface -> assertThat(iface.isSynthetic()).isTrue());
}
@ParameterizedTest
@MethodSource("provideKeyClassParameters")
void instanceState(Class<KeyClass> keyClass) throws Exception {
KeyClass instance1 = keyClass.getConstructor().newInstance();
KeyClass instance2 = keyClass.getConstructor().newInstance();
// correct api usage stores state in map
instance1.incrementContextCount();
assertThat(instance1.incrementContextCount()).isEqualTo(2);
assertThat(instance2.incrementContextCount()).isEqualTo(1);
}
@ParameterizedTest
@MethodSource("provideKeyClassParameters")
void modifyInstanceState(Class<KeyClass> keyClass) throws Exception {
KeyClass instance1 = keyClass.getConstructor().newInstance();
instance1.putContextCount(10);
assertThat(instance1.getContextCount()).isEqualTo(10);
}
@ParameterizedTest
@MethodSource("provideKeyClassParameters")
void removeInstanceState(Class<KeyClass> keyClass) throws Exception {
KeyClass instance1 = keyClass.getConstructor().newInstance();
instance1.putContextCount(10);
instance1.removeContextCount();
assertThat(instance1.getContextCount()).isEqualTo(0);
}
@Test
void cglibProxy() {
// works with cglib enhanced instances which duplicates context getter and setter methods
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(KeyClass.class);
enhancer.setCallback(
new MethodInterceptor() {
@Override
public Object intercept(
Object instance, Method method, Object[] arguments, MethodProxy methodProxy)
throws Throwable {
return methodProxy.invokeSuper(instance, arguments);
}
});
assertThat(enhancer.create()).isInstanceOf(KeyClass.class);
}
@ParameterizedTest
@MethodSource("provideKeyClassParameters")
@SuppressWarnings("UnnecessaryAsync")
void instanceStateGc(Class<KeyClass> keyClass) throws Exception {
// backing map should not create strong refs to key class instances
AtomicReference<KeyClass> keyValue =
new AtomicReference<>(keyClass.getConstructor().newInstance());
int count = keyValue.get().incrementContextCount();
WeakReference<KeyClass> instanceRef = new WeakReference<>(keyValue.get());
keyValue.set(null);
GcUtils.awaitGc(instanceRef, Duration.ofSeconds(10));
assertThat(instanceRef.get()).isNull();
assertThat(count).isEqualTo(1);
}
@Test
void retransform() throws Exception {
// context classes are retransform safe
ByteBuddyAgent.install();
ByteBuddyAgent.getInstrumentation().retransformClasses(KeyClass.class);
ByteBuddyAgent.getInstrumentation().retransformClasses(UntransformableKeyClass.class);
assertThat(new KeyClass().isInstrumented()).isTrue();
assertThat(new UntransformableKeyClass().isInstrumented()).isFalse();
assertThat(new KeyClass().incrementContextCount()).isEqualTo(1);
assertThat(new UntransformableKeyClass().incrementContextCount()).isEqualTo(1);
}
// NB: This test will fail if some other agent is also running that modifies the class structure
// in a way that is incompatible with redefining the class back to its original bytecode.
// A likely culprit is jacoco if you start seeing failure here due to a change make sure jacoco
// exclusion is working.
@Test
void redefine() throws Exception {
// context classes are redefine safe
ByteBuddyAgent.install();
ByteBuddyAgent.getInstrumentation()
.redefineClasses(
new ClassDefinition(KeyClass.class, ClasspathUtils.convertToByteArray(KeyClass.class)));
ByteBuddyAgent.getInstrumentation()
.redefineClasses(
new ClassDefinition(
UntransformableKeyClass.class,
ClasspathUtils.convertToByteArray(UntransformableKeyClass.class)));
assertThat(new KeyClass().isInstrumented()).isTrue();
assertThat(new UntransformableKeyClass().isInstrumented()).isFalse();
assertThat(new KeyClass().incrementContextCount()).isEqualTo(1);
assertThat(new UntransformableKeyClass().incrementContextCount()).isEqualTo(1);
}
}

View File

@ -0,0 +1,67 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package context;
import static org.assertj.core.api.Assertions.assertThat;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.testing.common.TestAgentListenerAccess;
import java.lang.reflect.Field;
import library.DisabledKeyClass;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
// this test is run using:
// -Dotel.javaagent.experimental.field-injection.enabled=false
// -Dotel.instrumentation.context-test-instrumentation.enabled=true
// (see integration-tests.gradle)
class FieldInjectionDisabledTest {
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@BeforeAll
static void setUp() {
TestAgentListenerAccess.addSkipErrorCondition(
(typeName, throwable) ->
typeName.startsWith(ContextTestInstrumentationModule.class.getName() + "$Incorrect")
&& throwable.getMessage().startsWith("Incorrect Context Api Usage detected."));
}
@Test
void structuralModificationDisabled() {
Class<?> keyClass = DisabledKeyClass.class;
boolean hasField = false;
for (Field field : keyClass.getDeclaredFields()) {
if (field.getName().startsWith("__opentelemetry")) {
hasField = true;
break;
}
}
boolean hasMarkerInterface = false;
boolean hasAccessorInterface = false;
for (Class<?> inter : keyClass.getInterfaces()) {
if ("io.opentelemetry.javaagent.bootstrap.VirtualFieldInstalledMarker"
.equals(inter.getName())) {
hasMarkerInterface = true;
}
if (inter
.getName()
.startsWith(
"io.opentelemetry.javaagent.bootstrap.instrumentation.context.FieldBackedProvider$ContextAccessor")) {
hasAccessorInterface = true;
}
}
assertThat(hasField).isFalse();
assertThat(hasMarkerInterface).isFalse();
assertThat(hasAccessorInterface).isFalse();
assertThat(new DisabledKeyClass().isInstrumented()).isTrue();
}
}

View File

@ -0,0 +1,151 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package instrumentation;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatNoException;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import com.google.common.reflect.ClassPath;
import io.opentelemetry.instrumentation.test.utils.ClasspathUtils;
import io.opentelemetry.instrumentation.testing.junit.AgentInstrumentationExtension;
import io.opentelemetry.instrumentation.testing.junit.InstrumentationExtension;
import io.opentelemetry.javaagent.bootstrap.BootstrapPackagePrefixesHolder;
import io.opentelemetry.sdk.testing.assertj.SpanDataAssert;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Stream;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.Arguments;
import org.junit.jupiter.params.provider.MethodSource;
import org.slf4j.LoggerFactory;
// this test is run using
//
// -Dotel.javaagent.exclude-classes=config.exclude.packagename.*,config.exclude.SomeClass,config.exclude.SomeClass$NestedClass
// (see integration-tests.gradle)
class AgentInstrumentationTest {
private static final ClassLoader BOOTSTRAP_CLASSLOADER = null;
private static final List<String> BOOTSTRAP_PACKAGE_PREFIXES =
BootstrapPackagePrefixesHolder.getBoostrapPackagePrefixes();
@RegisterExtension
static final InstrumentationExtension testing = AgentInstrumentationExtension.create();
@Test
void classPathSetUp() throws ClassNotFoundException {
List<Class<?>> bootstrapClassesIncorrectlyLoaded = new ArrayList<>();
for (ClassPath.ClassInfo info : getTestClasspath().getAllClasses()) {
for (String bootstrapPrefix : BOOTSTRAP_PACKAGE_PREFIXES) {
if (info.getName().startsWith(bootstrapPrefix)) {
Class<?> bootstrapClass = Class.forName(info.getName());
ClassLoader loader = bootstrapClass.getClassLoader();
if (loader != BOOTSTRAP_CLASSLOADER) {
bootstrapClassesIncorrectlyLoaded.add(bootstrapClass);
}
}
}
}
assertThat(bootstrapClassesIncorrectlyLoaded).isEmpty();
}
@Test
void waitingForChildSpansTimesOut() {
assertThatThrownBy(() -> testing.runWithSpan("parent", () -> testing.waitForTraces(1)))
.isInstanceOf(AssertionError.class)
.hasMessage("Error waiting for 1 traces");
}
@Test
void loggingWorks() {
assertThatNoException()
.isThrownBy(() -> LoggerFactory.getLogger(AgentInstrumentationTest.class).debug("hello"));
}
private static Stream<Arguments> provideExcludedClassTestParameters() {
return Stream.of(
Arguments.of(config.SomeClass.class, "SomeClass.run"),
Arguments.of(config.SomeClass.NestedClass.class, "NestedClass.run"),
Arguments.of(config.exclude.SomeClass.class, null),
Arguments.of(config.exclude.SomeClass.NestedClass.class, null),
Arguments.of(config.exclude.packagename.SomeClass.class, null),
Arguments.of(config.exclude.packagename.SomeClass.NestedClass.class, null));
}
@ParameterizedTest
@MethodSource("provideExcludedClassTestParameters")
void excludedClassesAreNotInstrumented(Class<Runnable> subject, String spanName)
throws Exception {
testing.runWithSpan("parent", () -> subject.getConstructor().newInstance().run());
testing.waitAndAssertTraces(
trace -> {
List<Consumer<SpanDataAssert>> assertions = new ArrayList<>();
assertions.add(span -> span.hasName("parent").hasNoParent());
if (spanName != null) {
assertions.add(span -> span.hasName(spanName).hasParent(trace.getSpan(0)));
}
trace.hasSpansSatisfyingExactly(assertions);
});
}
@Test
void testUnblockedByCompletedSpan() {
testing.runWithSpan("parent", () -> testing.runWithSpan("child", () -> {}));
testing.waitAndAssertTraces(
trace ->
trace.hasSpansSatisfyingExactly(
span -> span.hasName("parent").hasNoParent(),
span -> span.hasName("child").hasParent(trace.getSpan(0))));
}
private static ClassPath getTestClasspath() {
ClassLoader testClassLoader = ClasspathUtils.class.getClassLoader();
if (!(testClassLoader instanceof URLClassLoader)) {
// java9's system loader does not extend URLClassLoader
// which breaks Guava ClassPath lookup
testClassLoader = buildJavaClassPathClassLoader();
}
try {
return ClassPath.from(testClassLoader);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
/**
* Parse JVM classpath and return ClassLoader containing all classpath entries. Inspired by Guava.
*/
private static ClassLoader buildJavaClassPathClassLoader() {
List<URL> urls = new ArrayList<>();
for (String entry : getClasspath()) {
try {
try {
urls.add(new File(entry).toURI().toURL());
} catch (SecurityException e) { // File.toURI checks to see if the file is a directory
urls.add(new URL("file", null, new File(entry).getAbsolutePath()));
}
} catch (MalformedURLException e) {
throw new IllegalStateException(e);
}
}
return new URLClassLoader(urls.toArray(new URL[0]), null);
}
private static String[] getClasspath() {
return System.getProperty("java.class.path").split(System.getProperty("path.separator"));
}
}

View File

@ -0,0 +1,19 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/
package instrumentation;
import static org.assertj.core.api.Assertions.assertThat;
import org.junit.jupiter.api.Test;
class InstrumentOldBytecodeTest {
@Test
@SuppressWarnings("deprecation") // com.ibm.as400.resource.ResourceLevel is deprecated
void canInstrumentOldBytecode() {
assertThat(new com.ibm.as400.resource.ResourceLevel().toString()).isEqualTo("instrumented");
}
}