Skip to content

Commit 85fa75f

Browse files
authored
extension smoke test to avoid regression with indy (#15497)
1 parent 2bc92d2 commit 85fa75f

File tree

9 files changed

+379
-2
lines changed

9 files changed

+379
-2
lines changed

settings.gradle.kts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ include(":smoke-tests:images:servlet")
151151
include(":smoke-tests:images:servlet:servlet-3.0")
152152
include(":smoke-tests:images:servlet:servlet-5.0")
153153
include(":smoke-tests:images:spring-boot")
154+
include(":smoke-tests:extensions:testapp")
155+
include(":smoke-tests:extensions:extension")
154156

155157
include(":smoke-tests-otel-starter:spring-smoke-testing")
156158
include(":smoke-tests-otel-starter:spring-boot-2")

smoke-tests/README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,8 @@
22

33
Assert that various applications will start up with the JavaAgent without any obvious ill effects.
44

5-
Each subproject underneath `smoke-tests` produces one or more docker images containing some application
5+
Each subproject underneath `smoke-tests/images` produces one or more docker images containing some application
66
under the test. Various tests in the main module then use them to run the appropriate tests.
7+
8+
The `smoke-tests/extensions` folder contains a test application and packaged instrumentation(s) extension to
9+
test compatibility with existing user-built extensions.

smoke-tests/build.gradle.kts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,20 @@ tasks {
7777
.withPropertyName("javaagent")
7878
.withNormalizer(ClasspathNormalizer::class)
7979

80+
val extensionTask = project(":smoke-tests:extensions:extension").tasks.named<Jar>("jar")
81+
val extensionJarPath = extensionTask.flatMap { it.archiveFile }
82+
83+
val extensionTestAppTask = project(":smoke-tests:extensions:testapp").tasks.named<Jar>("jar")
84+
val extensionTestAppJarPath = extensionTestAppTask.flatMap { it.archiveFile }
85+
86+
dependsOn(shadowTask, extensionTestAppTask, extensionTask)
87+
8088
doFirst {
81-
jvmArgs("-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}")
89+
jvmArgs(
90+
"-Dio.opentelemetry.smoketest.agent.shadowJar.path=${agentJarPath.get()}",
91+
"-Dio.opentelemetry.smoketest.extension.path=${extensionJarPath.get()}",
92+
"-Dio.opentelemetry.smoketest.extension.testapp.path=${extensionTestAppJarPath.get()}"
93+
)
8294
}
8395
}
8496
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
id("io.opentelemetry.instrumentation.javaagent-instrumentation")
4+
}
5+
6+
dependencies {
7+
compileOnly("io.opentelemetry:opentelemetry-sdk-extension-autoconfigure-spi")
8+
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api")
9+
compileOnly("io.opentelemetry.instrumentation:opentelemetry-instrumentation-api-incubator")
10+
compileOnly("io.opentelemetry.javaagent:opentelemetry-javaagent-extension-api")
11+
12+
compileOnly("com.google.auto.service:auto-service")
13+
compileOnly("com.google.auto.service:auto-service-annotations")
14+
15+
annotationProcessor("com.google.auto.service:auto-service")
16+
17+
// Used by byte-buddy but not brought in as a transitive dependency
18+
compileOnly("com.google.code.findbugs:annotations")
19+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.smoketest.extensions.inlined;
7+
8+
import static net.bytebuddy.matcher.ElementMatchers.named;
9+
import static net.bytebuddy.matcher.ElementMatchers.returns;
10+
import static net.bytebuddy.matcher.ElementMatchers.takesArgument;
11+
12+
import io.opentelemetry.instrumentation.api.util.VirtualField;
13+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
14+
import io.opentelemetry.javaagent.extension.instrumentation.TypeTransformer;
15+
import java.util.Arrays;
16+
import net.bytebuddy.asm.Advice;
17+
import net.bytebuddy.description.type.TypeDescription;
18+
import net.bytebuddy.matcher.ElementMatcher;
19+
20+
public class SmokeInlinedInstrumentation implements TypeInstrumentation {
21+
22+
@Override
23+
public ElementMatcher<TypeDescription> typeMatcher() {
24+
return named("io.opentelemetry.smoketest.extensions.app.AppMain");
25+
}
26+
27+
@Override
28+
public void transform(TypeTransformer transformer) {
29+
transformer.applyAdviceToMethod(
30+
named("returnValue").and(takesArgument(0, int.class)),
31+
this.getClass().getName() + "$ModifyReturnValueAdvice");
32+
transformer.applyAdviceToMethod(
33+
named("methodArguments").and(takesArgument(0, int.class)),
34+
this.getClass().getName() + "$ModifyArgumentsAdvice");
35+
transformer.applyAdviceToMethod(
36+
named("setVirtualFieldValue")
37+
.and(takesArgument(0, Object.class))
38+
.and(takesArgument(1, Integer.class)),
39+
this.getClass().getName() + "$VirtualFieldSetAdvice");
40+
transformer.applyAdviceToMethod(
41+
named("getVirtualFieldValue")
42+
.and(takesArgument(0, Object.class))
43+
.and(returns(Integer.class)),
44+
this.getClass().getName() + "$VirtualFieldGetAdvice");
45+
transformer.applyAdviceToMethod(
46+
named("localValue").and(takesArgument(0, int[].class)).and(returns(int[].class)),
47+
this.getClass().getName() + "$LocalVariableAdvice");
48+
}
49+
50+
@SuppressWarnings("unused")
51+
public static class ModifyReturnValueAdvice {
52+
53+
@Advice.OnMethodExit(suppress = Throwable.class)
54+
public static void onExit(@Advice.Return(readOnly = false) int returnValue) {
55+
returnValue = returnValue + 1;
56+
}
57+
}
58+
59+
@SuppressWarnings("unused")
60+
public static class ModifyArgumentsAdvice {
61+
@Advice.OnMethodEnter(suppress = Throwable.class)
62+
public static void onEnter(@Advice.Argument(value = 0, readOnly = false) int argument) {
63+
argument = argument - 1;
64+
}
65+
}
66+
67+
public static class VirtualFieldSetAdvice {
68+
@Advice.OnMethodEnter(suppress = Throwable.class)
69+
public static void onEnter(
70+
@Advice.Argument(0) Object target, @Advice.Argument(1) Integer value) {
71+
VirtualField<Object, Integer> field = VirtualField.find(Object.class, Integer.class);
72+
field.set(target, value);
73+
}
74+
}
75+
76+
@SuppressWarnings("unused")
77+
public static class VirtualFieldGetAdvice {
78+
@SuppressWarnings("UnusedVariable")
79+
@Advice.OnMethodExit(suppress = Throwable.class)
80+
public static void onExit(
81+
@Advice.Argument(0) Object target, @Advice.Return(readOnly = false) Integer returnValue) {
82+
VirtualField<Object, Integer> field = VirtualField.find(Object.class, Integer.class);
83+
returnValue = field.get(target);
84+
}
85+
}
86+
87+
@SuppressWarnings("unused")
88+
public static class LocalVariableAdvice {
89+
90+
@SuppressWarnings("UnusedVariable")
91+
@Advice.OnMethodEnter(suppress = Throwable.class)
92+
public static void onEnter(
93+
@Advice.Argument(0) int[] array, @Advice.Local("backup") int[] backupArray) {
94+
backupArray = Arrays.copyOf(array, array.length);
95+
}
96+
97+
@SuppressWarnings("UnusedVariable")
98+
@Advice.OnMethodExit(suppress = Throwable.class)
99+
public static void onExit(
100+
@Advice.Return(readOnly = false) int[] array, @Advice.Local("backup") int[] backupArray) {
101+
array = Arrays.copyOf(backupArray, backupArray.length);
102+
}
103+
}
104+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.smoketest.extensions.inlined;
7+
8+
import static java.util.Collections.singletonList;
9+
10+
import com.google.auto.service.AutoService;
11+
import io.opentelemetry.javaagent.extension.instrumentation.InstrumentationModule;
12+
import io.opentelemetry.javaagent.extension.instrumentation.TypeInstrumentation;
13+
import java.util.List;
14+
15+
@AutoService(InstrumentationModule.class)
16+
public class SmokeInlinedInstrumentationModule extends InstrumentationModule {
17+
18+
public SmokeInlinedInstrumentationModule() {
19+
super("smoke-test-extension-inlined");
20+
}
21+
22+
@Override
23+
public List<TypeInstrumentation> typeInstrumentations() {
24+
return singletonList(new SmokeInlinedInstrumentation());
25+
}
26+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
plugins {
2+
id("otel.java-conventions")
3+
}
4+
5+
dependencies {
6+
}
7+
8+
java {
9+
sourceCompatibility = JavaVersion.VERSION_1_8
10+
targetCompatibility = JavaVersion.VERSION_1_8
11+
}
12+
13+
tasks {
14+
jar {
15+
manifest {
16+
attributes("Main-Class" to "io.opentelemetry.smoketest.extensions.app.AppMain")
17+
}
18+
}
19+
}
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
/*
2+
* Copyright The OpenTelemetry Authors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*/
5+
6+
package io.opentelemetry.smoketest.extensions.app;
7+
8+
import com.google.errorprone.annotations.CanIgnoreReturnValue;
9+
import java.util.Arrays;
10+
11+
public class AppMain {
12+
13+
private AppMain() {}
14+
15+
public static void main(String[] args) {
16+
testReturnValue();
17+
testMethodArguments();
18+
testVirtualFields();
19+
testLocalValue();
20+
}
21+
22+
@SuppressWarnings("SystemOut") // prevent checkstyle complaining about using System.out
23+
private static void msg(String msg) {
24+
System.out.println(msg);
25+
}
26+
27+
private static void testReturnValue() {
28+
int returnValue = returnValue(42);
29+
if (returnValue != 42) {
30+
msg("return value has been modified");
31+
} else {
32+
msg("return value not modified");
33+
}
34+
}
35+
36+
private static int returnValue(int value) {
37+
// method return value should be modified by instrumentation
38+
return value;
39+
}
40+
41+
private static void testMethodArguments() {
42+
methodArguments(42, 42);
43+
}
44+
45+
private static void methodArguments(int argument, int originalArgument) {
46+
// method first argument should be modified by instrumentation
47+
if (argument != originalArgument) {
48+
msg("argument has been modified");
49+
} else {
50+
msg("argument not modified");
51+
}
52+
}
53+
54+
private static void testVirtualFields() {
55+
Object target = new Object();
56+
setVirtualFieldValue(target, 42);
57+
Integer fieldValue = getVirtualFieldValue(target);
58+
if (fieldValue == null || fieldValue != 42) {
59+
msg("virtual field not supported");
60+
} else {
61+
msg("virtual field supported");
62+
}
63+
}
64+
65+
public static void setVirtualFieldValue(Object target, Integer value) {
66+
// implementation should be provided by instrumentation
67+
}
68+
69+
public static Integer getVirtualFieldValue(Object target) {
70+
// implementation should be provided by instrumentation
71+
return null;
72+
}
73+
74+
private static void testLocalValue() {
75+
int[] input = new int[] {1, 2, 3};
76+
int[] result = localValue(input);
77+
if (result.length != 3) {
78+
throw new IllegalStateException();
79+
}
80+
// assumption on the instrumentation implementation to use a local value to preserve original
81+
// array
82+
boolean preserved = result[0] == 1 && result[1] == 2 && result[2] == 3;
83+
if (!preserved) {
84+
msg("local advice variable not supported");
85+
} else {
86+
msg("local advice variable supported");
87+
}
88+
}
89+
90+
@CanIgnoreReturnValue
91+
private static int[] localValue(int[] array) {
92+
Arrays.fill(array, 0);
93+
return array;
94+
}
95+
}

0 commit comments

Comments
 (0)