Convert integration tests to java (#13244)
This commit is contained in:
parent
bc7e81bfda
commit
85a52ca804
|
@ -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")
|
||||
|
|
|
@ -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"))
|
||||
}
|
||||
}
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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"));
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue