|
@@ -0,0 +1,173 @@
|
|
|
+/*
|
|
|
+ * Copyright The OpenTelemetry Authors
|
|
|
+ * SPDX-License-Identifier: Apache-2.0
|
|
|
+ */
|
|
|
+
|
|
|
+package io.opentelemetry.javaagent.tooling.instrumentation.indy;
|
|
|
+
|
|
|
+import static net.bytebuddy.matcher.ElementMatchers.isMethod;
|
|
|
+import static net.bytebuddy.matcher.ElementMatchers.nameStartsWith;
|
|
|
+import static net.bytebuddy.matcher.ElementMatchers.named;
|
|
|
+import static org.assertj.core.api.Assertions.assertThat;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.nio.file.Files;
|
|
|
+import java.nio.file.Path;
|
|
|
+import java.util.Arrays;
|
|
|
+import java.util.List;
|
|
|
+import java.util.stream.Stream;
|
|
|
+import net.bytebuddy.ClassFileVersion;
|
|
|
+import net.bytebuddy.agent.ByteBuddyAgent;
|
|
|
+import net.bytebuddy.agent.builder.AgentBuilder;
|
|
|
+import net.bytebuddy.agent.builder.ResettableClassFileTransformer;
|
|
|
+import net.bytebuddy.description.method.MethodDescription;
|
|
|
+import net.bytebuddy.dynamic.ClassFileLocator;
|
|
|
+import net.bytebuddy.matcher.ElementMatcher;
|
|
|
+import net.bytebuddy.utility.JavaModule;
|
|
|
+import org.junit.jupiter.api.AfterAll;
|
|
|
+import org.junit.jupiter.api.BeforeAll;
|
|
|
+import org.junit.jupiter.api.io.TempDir;
|
|
|
+import org.junit.jupiter.params.ParameterizedTest;
|
|
|
+import org.junit.jupiter.params.provider.MethodSource;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+
|
|
|
+class PatchBytecodeVersionTest {
|
|
|
+
|
|
|
+ private static final String BYTEBUDDY_DUMP = "net.bytebuddy.dump";
|
|
|
+ public static final String ORIGINAL_SUFFIX = "-original";
|
|
|
+ public static final String CLASSNAME_PREFIX = "oldbytecode_";
|
|
|
+ private static ResettableClassFileTransformer transformer;
|
|
|
+
|
|
|
+ private static final Logger logger = LoggerFactory.getLogger(PatchBytecodeVersionTest.class);
|
|
|
+
|
|
|
+ private static Path tempDir;
|
|
|
+
|
|
|
+ @BeforeAll
|
|
|
+ static void setUp(@TempDir Path temp) {
|
|
|
+
|
|
|
+ assertThat(temp).isEmptyDirectory();
|
|
|
+ tempDir = temp;
|
|
|
+ System.setProperty(BYTEBUDDY_DUMP, temp.toString());
|
|
|
+
|
|
|
+ AgentBuilder builder =
|
|
|
+ new AgentBuilder.Default()
|
|
|
+ .disableClassFormatChanges()
|
|
|
+ .with(AgentBuilder.RedefinitionStrategy.RETRANSFORMATION)
|
|
|
+ .with(
|
|
|
+ new AgentBuilder.Listener.Adapter() {
|
|
|
+ @Override
|
|
|
+ public void onError(
|
|
|
+ String typeName,
|
|
|
+ ClassLoader classLoader,
|
|
|
+ JavaModule module,
|
|
|
+ boolean loaded,
|
|
|
+ Throwable throwable) {
|
|
|
+ logger.error("Transformation error", throwable);
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .type(nameStartsWith(CLASSNAME_PREFIX))
|
|
|
+ .transform(new PatchByteCodeVersionTransformer())
|
|
|
+ .transform(transformerFor(isMethod().and(named("toString"))));
|
|
|
+
|
|
|
+ ByteBuddyAgent.install();
|
|
|
+ transformer = builder.installOn(ByteBuddyAgent.getInstrumentation());
|
|
|
+ }
|
|
|
+
|
|
|
+ @AfterAll
|
|
|
+ static void tearDown() {
|
|
|
+ transformer.reset(
|
|
|
+ ByteBuddyAgent.getInstrumentation(), AgentBuilder.RedefinitionStrategy.RETRANSFORMATION);
|
|
|
+
|
|
|
+ System.clearProperty(BYTEBUDDY_DUMP);
|
|
|
+ }
|
|
|
+
|
|
|
+ @ParameterizedTest
|
|
|
+ @MethodSource("bytecodeVersions")
|
|
|
+ void upgradeBytecode(ClassFileVersion version) {
|
|
|
+
|
|
|
+ String className = CLASSNAME_PREFIX + version.getMinorMajorVersion();
|
|
|
+
|
|
|
+ int startCount = PatchTestAdvice.invocationCount.get();
|
|
|
+ assertThat(PatchTestAdvice.invocationCount.get()).isEqualTo(startCount);
|
|
|
+
|
|
|
+ assertThat(OldBytecode.generateAndRun(className, version)).isEqualTo("toString");
|
|
|
+
|
|
|
+ assertThat(PatchTestAdvice.invocationCount.get()).isEqualTo(startCount + 1);
|
|
|
+
|
|
|
+ Path instrumentedClass;
|
|
|
+ Path instrumentedClassOriginal;
|
|
|
+
|
|
|
+ try (Stream<Path> files =
|
|
|
+ Files.find(
|
|
|
+ tempDir,
|
|
|
+ 1,
|
|
|
+ (path, attr) -> {
|
|
|
+ String fileName = path.getFileName().toString();
|
|
|
+ return Files.isRegularFile(path)
|
|
|
+ && fileName.startsWith(className)
|
|
|
+ && fileName.contains(ORIGINAL_SUFFIX);
|
|
|
+ })) {
|
|
|
+
|
|
|
+ instrumentedClassOriginal = files.findFirst().orElseThrow(IllegalStateException::new);
|
|
|
+ String upgradedClassFileName =
|
|
|
+ instrumentedClassOriginal.getFileName().toString().replace(ORIGINAL_SUFFIX, "");
|
|
|
+ instrumentedClass = instrumentedClassOriginal.resolveSibling(upgradedClassFileName);
|
|
|
+
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new IllegalStateException(e);
|
|
|
+ }
|
|
|
+
|
|
|
+ assertThat(instrumentedClass).exists();
|
|
|
+ assertThat(instrumentedClassOriginal).exists();
|
|
|
+
|
|
|
+ assertThat(getBytecodeVersion(instrumentedClassOriginal))
|
|
|
+ .describedAs(
|
|
|
+ "expected original bytecode for class '%s' in '%s' should have been compiled for %s",
|
|
|
+ className, instrumentedClassOriginal.toAbsolutePath(), version)
|
|
|
+ .isEqualTo(version);
|
|
|
+
|
|
|
+ if (version.isLessThan(ClassFileVersion.JAVA_V7)) {
|
|
|
+ assertThat(getBytecodeVersion(instrumentedClass))
|
|
|
+ .describedAs(
|
|
|
+ "expected instrumented bytecode for class '%s' in '%s' should have been upgraded to Java 7",
|
|
|
+ className, instrumentedClass.toAbsolutePath())
|
|
|
+ .isEqualTo(ClassFileVersion.JAVA_V7);
|
|
|
+ } else {
|
|
|
+ assertThat(getBytecodeVersion(instrumentedClass))
|
|
|
+ .describedAs("original bytecode version shouldn't be altered")
|
|
|
+ .isEqualTo(version);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ static List<ClassFileVersion> bytecodeVersions() {
|
|
|
+ return Arrays.asList(
|
|
|
+ ClassFileVersion.JAVA_V1,
|
|
|
+ ClassFileVersion.JAVA_V2,
|
|
|
+ ClassFileVersion.JAVA_V3,
|
|
|
+ ClassFileVersion.JAVA_V4,
|
|
|
+ ClassFileVersion.JAVA_V5,
|
|
|
+ ClassFileVersion.JAVA_V6,
|
|
|
+ // Java 7 and later should not be upgraded
|
|
|
+ ClassFileVersion.JAVA_V7,
|
|
|
+ ClassFileVersion.JAVA_V8);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static ClassFileVersion getBytecodeVersion(Path file) {
|
|
|
+ try {
|
|
|
+ byte[] bytecode = Files.readAllBytes(file);
|
|
|
+ return ClassFileVersion.ofMinorMajor((bytecode[5] << 16) | (bytecode[7] & 0xFF));
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new IllegalStateException(e);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static AgentBuilder.Transformer.ForAdvice transformerFor(
|
|
|
+ ElementMatcher.Junction<MethodDescription> methodMatcher) {
|
|
|
+ return new AgentBuilder.Transformer.ForAdvice()
|
|
|
+ .with(
|
|
|
+ new AgentBuilder.LocationStrategy.Simple(
|
|
|
+ ClassFileLocator.ForClassLoader.of(PatchTestAdvice.class.getClassLoader())))
|
|
|
+ .advice(methodMatcher, PatchTestAdvice.class.getName());
|
|
|
+ }
|
|
|
+}
|