Add RuntimeHintsAgent Java agent

With the introduction of `RuntimeHints`, we can now contribute
reflection, resources and proxies hints that describe the expected
runtime behavior of the application. While this can be verified at
runtime with smoke tests, managing such tests and compiling to native
there is not very efficient.

This commit introduces the new `RuntimeHintsAgent`, a Java agent that
instruments JDK methods related to `RuntimeHints`.
It is different from the GraalVM agent, which aims at collecting all the
required hints for the runtime behavior of an application and dump those
in the expected format.
Here, the `RuntimeHintsAgent` can collect the related invocations only
for a delimited scope (typically, a lambda within a test) and later
check those against a `RuntimeHints` instance. In the case of testing
`RuntimeHintsRegistrar` implementations, the process is reversed:
instead of manually checking for registered hints in a `RuntimeHints`
instance, tests should exercise the use cases and then check that the
recorded behavior is in line with the prepared hints.

This first commit adds the agent infrastructure that collects the
invocations for all relevant JDK methods.

See gh-27981
This commit is contained in:
Brian Clozel
2022-06-30 18:20:16 +02:00
parent 033d9d09e4
commit c86e678788
13 changed files with 2459 additions and 0 deletions

View File

@@ -0,0 +1,650 @@
/*
* Copyright 2002-2022 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.aot.agent;
import java.lang.reflect.Proxy;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.aot.hint.ExecutableMode;
import org.springframework.aot.hint.MemberCategory;
import org.springframework.aot.hint.RuntimeHints;
import org.springframework.aot.hint.TypeReference;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link InstrumentedMethod}.
*
* @author Brian Clozel
*/
class InstrumentedMethodTests {
private RuntimeHints hints = new RuntimeHints();
@Nested
class ClassReflectionInstrumentationTests {
RecordedInvocation stringGetClasses = RecordedInvocation.of(InstrumentedMethod.CLASS_GETCLASSES)
.onInstance(String.class).returnValue(String.class.getClasses()).build();
RecordedInvocation stringGetDeclaredClasses = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDCLASSES)
.onInstance(String.class).returnValue(String.class.getDeclaredClasses()).build();
@Test
void classForNameShouldMatchReflectionOnType() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_FORNAME)
.withArgument("java.lang.String").returnValue(String.class).build();
hints.reflection().registerType(String.class, typeHint -> {
});
assertThatInvocationMatches(InstrumentedMethod.CLASS_FORNAME, invocation);
}
@Test
void classGetClassesShouldNotMatchReflectionOnType() {
hints.reflection().registerType(String.class, typeHint -> {
});
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETCLASSES, this.stringGetClasses);
}
@Test
void classGetClassesShouldMatchPublicClasses() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_CLASSES));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCLASSES, this.stringGetClasses);
}
@Test
void classGetClassesShouldMatchDeclaredClasses() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_CLASSES));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCLASSES, this.stringGetClasses);
}
@Test
void classGetDeclaredClassesShouldMatchDeclaredClassesHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_CLASSES));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCLASSES, this.stringGetDeclaredClasses);
}
@Test
void classGetDeclaredClassesShouldNotMatchPublicClassesHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_CLASSES));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCLASSES, this.stringGetDeclaredClasses);
}
@Test
void classLoaderLoadClassShouldMatchReflectionHintOnType() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASSLOADER_LOADCLASS)
.onInstance(ClassLoader.getSystemClassLoader())
.withArgument(PublicField.class.getCanonicalName()).returnValue(PublicField.class).build();
hints.reflection().registerType(PublicField.class, builder -> {
});
assertThatInvocationMatches(InstrumentedMethod.CLASSLOADER_LOADCLASS, invocation);
}
}
@Nested
class ConstructorReflectionInstrumentationTests {
RecordedInvocation stringGetConstructor;
RecordedInvocation stringGetConstructors = RecordedInvocation.of(InstrumentedMethod.CLASS_GETCONSTRUCTORS)
.onInstance(String.class).returnValue(String.class.getConstructors()).build();
RecordedInvocation stringGetDeclaredConstructor;
RecordedInvocation stringGetDeclaredConstructors = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS)
.onInstance(String.class).returnValue(String.class.getDeclaredConstructors()).build();
@BeforeEach
public void setup() throws Exception {
this.stringGetConstructor = RecordedInvocation.of(InstrumentedMethod.CLASS_GETCONSTRUCTOR)
.onInstance(String.class).withArgument(new Class[0]).returnValue(String.class.getConstructor()).build();
this.stringGetDeclaredConstructor = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR)
.onInstance(String.class).withArgument(new Class[] {byte[].class, byte.class})
.returnValue(String.class.getDeclaredConstructor(byte[].class, byte.class)).build();
}
@Test
void classGetConstructorShouldMatchInstrospectPublicConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@Test
void classGetConstructorShouldMatchInvokePublicConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@Test
void classGetConstructorShouldMatchIntrospectDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@Test
void classGetConstructorShouldMatchInvokeDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@Test
void classGetConstructorShouldMatchInstrospectConstructorHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(),
constructorHint -> constructorHint.setModes(ExecutableMode.INTROSPECT)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@Test
void classGetConstructorShouldMatchInvokeConstructorHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(Collections.emptyList(),
constructorHint -> constructorHint.setModes(ExecutableMode.INVOKE)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTOR, this.stringGetConstructor);
}
@Test
void classGetConstructorsShouldMatchIntrospectPublicConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetConstructorsShouldMatchInvokePublicConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetConstructorsShouldMatchIntrospectDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetConstructorsShouldMatchInvokeDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETCONSTRUCTORS, this.stringGetConstructors);
}
@Test
void classGetDeclaredConstructorShouldMatchIntrospectDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
}
@Test
void classGetDeclaredConstructorShouldNotMatchIntrospectPublicConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
}
@Test
void classGetDeclaredConstructorShouldMatchInstrospectConstructorHint() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(byte[].class), TypeReference.of(byte.class));
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(parameterTypes, constructorHint -> constructorHint.setModes(ExecutableMode.INTROSPECT)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
}
@Test
void classGetDeclaredConstructorShouldMatchInvokeConstructorHint() {
List<TypeReference> parameterTypes = List.of(TypeReference.of(byte[].class), TypeReference.of(byte.class));
hints.reflection().registerType(String.class, typeHint -> typeHint.withConstructor(parameterTypes, constructorHint -> constructorHint.setModes(ExecutableMode.INVOKE)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTOR, this.stringGetDeclaredConstructor);
}
@Test
void classGetDeclaredConstructorsShouldMatchIntrospectDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
}
@Test
void classGetDeclaredConstructorsShouldMatchInvokeDeclaredConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_CONSTRUCTORS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
}
@Test
void classGetDeclaredConstructorsShouldNotMatchIntrospectPublicConstructorsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_CONSTRUCTORS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDCONSTRUCTORS, this.stringGetDeclaredConstructors);
}
@Test
void constructorNewInstanceShouldMatchInvokeHintOnConstructor() throws NoSuchMethodException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE)
.onInstance(String.class.getConstructor()).returnValue("").build();
hints.reflection().registerType(String.class, typeHint ->
typeHint.withConstructor(Collections.emptyList(), constructorHint -> constructorHint.withMode(ExecutableMode.INVOKE)));
assertThatInvocationMatches(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE, invocation);
}
@Test
void constructorNewInstanceShouldNotMatchIntrospectHintOnConstructor() throws NoSuchMethodException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE)
.onInstance(String.class.getConstructor()).returnValue("").build();
hints.reflection().registerType(String.class, typeHint ->
typeHint.withConstructor(Collections.emptyList(), constructorHint -> constructorHint.withMode(ExecutableMode.INTROSPECT)));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CONSTRUCTOR_NEWINSTANCE, invocation);
}
}
@Nested
class MethodReflectionInstrumentationTests {
RecordedInvocation stringGetToStringMethod;
RecordedInvocation stringGetScaleMethod;
@BeforeEach
void setup() throws Exception {
this.stringGetToStringMethod = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHOD)
.onInstance(String.class).withArguments("toString", new Class[0])
.returnValue(String.class.getMethod("toString")).build();
this.stringGetScaleMethod = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDMETHOD)
.onInstance(String.class).withArguments("scale", new Class[] {int.class, float.class})
.returnValue(String.class.getDeclaredMethod("scale", int.class, float.class)).build();
}
@Test
void classGetDeclaredMethodShouldMatchIntrospectDeclaredMethodsHint() throws NoSuchMethodException {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodShouldNotMatchIntrospectPublicMethodsHint() throws NoSuchMethodException {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodShouldMatchIntrospectMethodHint() throws NoSuchMethodException {
List<TypeReference> parameterTypes = List.of(TypeReference.of(int.class), TypeReference.of(float.class));
hints.reflection().registerType(String.class, typeHint ->
typeHint.withMethod("scale", parameterTypes, methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodShouldMatchInvokeMethodHint() throws NoSuchMethodException {
List<TypeReference> parameterTypes = List.of(TypeReference.of(int.class), TypeReference.of(float.class));
hints.reflection().registerType(String.class, typeHint ->
typeHint.withMethod("scale", parameterTypes, methodHint -> methodHint.withMode(ExecutableMode.INVOKE)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodsShouldMatchIntrospectDeclaredMethodsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod);
}
@Test
void classGetDeclaredMethodsShouldMatchInvokeDeclaredMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, invocation);
}
@Test
void classGetDeclaredMethodsShouldMatchIntrospectPublicMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDMETHODS, this.stringGetScaleMethod);
}
@Test
void classGetMethodsShouldMatchInstrospectDeclaredMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
}
@Test
void classGetMethodsShouldMatchInstrospectPublicMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, hint -> hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
}
@Test
void classGetMethodsShouldMatchInvokeDeclaredMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
}
@Test
void classGetMethodsShouldMatchInvokePublicMethodsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(String.class, hint -> hint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHODS, invocation);
}
@Test
void classGetMethodsShouldNotMatchForWrongType() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETMETHODS).onInstance(String.class).build();
hints.reflection().registerType(Integer.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHODS, invocation);
}
@Test
void classGetMethodShouldMatchInstrospectPublicMethodsHint() throws NoSuchMethodException {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchInvokePublicMethodsHint() throws NoSuchMethodException {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_PUBLIC_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchInstrospectDeclaredMethodsHint() throws NoSuchMethodException {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchInvokeDeclaredMethodsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INVOKE_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchIntrospectMethodHint() {
hints.reflection().registerType(String.class, typeHint ->
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.setModes(ExecutableMode.INTROSPECT)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldMatchInvokeMethodHint() throws Exception {
hints.reflection().registerType(String.class, typeHint ->
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.setModes(ExecutableMode.INVOKE)));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void classGetMethodShouldNotMatchInstrospectPublicMethodsHintWhenPrivate() throws Exception {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetMethodShouldMatchInstrospectDeclaredMethodsHintWhenPrivate() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_DECLARED_METHODS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetScaleMethod);
}
@Test
void classGetMethodShouldNotMatchForWrongType() {
hints.reflection().registerType(Integer.class, typeHint -> typeHint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETMETHOD, this.stringGetToStringMethod);
}
@Test
void methodInvokeShouldMatchInvokeHintOnMethod() throws NoSuchMethodException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_INVOKE)
.onInstance(String.class.getMethod("startsWith", String.class)).withArguments("testString", new Object[] {"test"}).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMethod("startsWith",
List.of(TypeReference.of(String.class)), methodHint -> methodHint.withMode(ExecutableMode.INVOKE)));
assertThatInvocationMatches(InstrumentedMethod.METHOD_INVOKE, invocation);
}
@Test
void methodInvokeShouldNotMatchIntrospectHintOnMethod() throws NoSuchMethodException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.METHOD_INVOKE)
.onInstance(String.class.getMethod("toString")).withArguments("", new Object[0]).build();
hints.reflection().registerType(String.class, typeHint ->
typeHint.withMethod("toString", Collections.emptyList(), methodHint -> methodHint.withMode(ExecutableMode.INTROSPECT)));
assertThatInvocationDoesNotMatch(InstrumentedMethod.METHOD_INVOKE, invocation);
}
}
@Nested
class FieldReflectionInstrumentationTests {
RecordedInvocation getPublicField;
RecordedInvocation stringGetDeclaredField;
@BeforeEach
void setup() throws Exception {
this.getPublicField = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD)
.onInstance(PublicField.class).withArgument("field")
.returnValue(PublicField.class.getField("field")).build();
this.stringGetDeclaredField = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELD)
.onInstance(String.class).withArgument("value").returnValue(String.class.getDeclaredField("value")).build();
}
@Test
void classGetDeclaredFieldShouldMatchDeclaredFieldsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELD, this.stringGetDeclaredField);
}
@Test
void classGetDeclaredFieldShouldNotMatchPublicFieldsHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELD, this.stringGetDeclaredField);
}
@Test
void classGetDeclaredFieldShouldMatchFieldHint() {
hints.reflection().registerType(String.class, typeHint -> typeHint.withField("value", builder -> {
}));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELD, this.stringGetDeclaredField);
}
@Test
void classGetDeclaredFieldsShouldMatchDeclaredFieldsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELDS).onInstance(String.class).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, invocation);
}
@Test
void classGetDeclaredFieldsShouldNotMatchPublicFieldsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETDECLAREDFIELDS).onInstance(String.class).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETDECLAREDFIELDS, invocation);
}
@Test
void classGetFieldShouldMatchPublicFieldsHint() {
hints.reflection().registerType(PublicField.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELD, this.getPublicField);
}
@Test
void classGetFieldShouldMatchDeclaredFieldsHint() {
hints.reflection().registerType(PublicField.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELD, this.getPublicField);
}
@Test
void classGetFieldShouldMatchFieldHint() {
hints.reflection().registerType(PublicField.class, typeHint -> typeHint.withField("field", builder -> {
}));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELD, this.getPublicField);
}
@Test
void classGetFieldShouldNotMatchPublicFieldsHintWhenPrivate() throws NoSuchFieldException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD)
.onInstance(String.class).withArgument("value").returnValue(null).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELD, invocation);
}
@Test
void classGetFieldShouldMatchDeclaredFieldsHintWhenPrivate() throws NoSuchFieldException {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD)
.onInstance(String.class).withArgument("value").returnValue(String.class.getDeclaredField("value")).build();
hints.reflection().registerType(String.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELD, invocation);
}
@Test
void classGetFieldShouldNotMatchForWrongType() throws Exception {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELD)
.onInstance(String.class).withArgument("value").returnValue(null).build();
hints.reflection().registerType(Integer.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETFIELD, invocation);
}
@Test
void classGetFieldsShouldMatchPublicFieldsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELDS)
.onInstance(PublicField.class).build();
hints.reflection().registerType(PublicField.class, typeHint -> typeHint.withMembers(MemberCategory.PUBLIC_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, invocation);
}
@Test
void classGetFieldsShouldMatchDeclaredFieldsHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETFIELDS)
.onInstance(PublicField.class).build();
hints.reflection().registerType(PublicField.class, typeHint -> typeHint.withMembers(MemberCategory.DECLARED_FIELDS));
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETFIELDS, invocation);
}
}
@Nested
class ResourcesInstrumentationTests {
@Test
void resourceBundleGetBundleShouldMatchBundleNameHint() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.RESOURCEBUNDLE_GETBUNDLE)
.withArgument("bundleName").build();
hints.resources().registerResourceBundle("bundleName");
assertThatInvocationMatches(InstrumentedMethod.RESOURCEBUNDLE_GETBUNDLE, invocation);
}
@Test
void resourceBundleGetBundleShouldNotMatchBundleNameHintWhenWrongName() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.RESOURCEBUNDLE_GETBUNDLE)
.withArgument("bundleName").build();
hints.resources().registerResourceBundle("wrongBundleName");
assertThatInvocationDoesNotMatch(InstrumentedMethod.RESOURCEBUNDLE_GETBUNDLE, invocation);
}
@Test
void classGetResourceShouldMatchResourcePatternWhenAbsolute() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
hints.resources().registerPattern("/some/*");
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
}
@Test
void classGetResourceShouldMatchResourcePatternWhenRelative() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("resource.txt").build();
hints.resources().registerPattern("/org/springframework/aot/agent/*");
assertThatInvocationMatches(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
}
@Test
void classGetResourceShouldNotMatchResourcePatternWhenInvalid() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
hints.resources().registerPattern("/other/*");
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
}
@Test
void classGetResourceShouldNotMatchResourcePatternWhenExcluded() {
RecordedInvocation invocation = RecordedInvocation.of(InstrumentedMethod.CLASS_GETRESOURCE)
.onInstance(InstrumentedMethodTests.class).withArgument("/some/path/resource.txt").build();
hints.resources().registerPattern(resourceHint -> resourceHint.includes("/some/*").excludes("/some/path/*"));
assertThatInvocationDoesNotMatch(InstrumentedMethod.CLASS_GETRESOURCE, invocation);
}
}
@Nested
class ProxiesInstrumentationTests {
RecordedInvocation newProxyInstance;
@BeforeEach
void setup() {
this.newProxyInstance = RecordedInvocation.of(InstrumentedMethod.PROXY_NEWPROXYINSTANCE)
.withArguments(ClassLoader.getSystemClassLoader(), new Class[] {AutoCloseable.class, Comparator.class}, null)
.returnValue(Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class[] {AutoCloseable.class, Comparator.class}, (proxy, method, args) -> null))
.build();
}
@Test
void proxyNewProxyInstanceShouldMatchWhenInterfacesMatch() {
hints.proxies().registerJdkProxy(AutoCloseable.class, Comparator.class);
assertThatInvocationMatches(InstrumentedMethod.PROXY_NEWPROXYINSTANCE, this.newProxyInstance);
}
@Test
void proxyNewProxyInstanceShouldNotMatchWhenInterfacesDoNotMatch() {
hints.proxies().registerJdkProxy(Comparator.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.PROXY_NEWPROXYINSTANCE, this.newProxyInstance);
}
@Test
void proxyNewProxyInstanceShouldNotMatchWhenWrongOrder() {
hints.proxies().registerJdkProxy(Comparator.class, AutoCloseable.class);
assertThatInvocationDoesNotMatch(InstrumentedMethod.PROXY_NEWPROXYINSTANCE, this.newProxyInstance);
}
}
private void assertThatInvocationMatches(InstrumentedMethod method, RecordedInvocation invocation) {
assertThat(method.matcher(invocation)).accepts(this.hints);
}
private void assertThatInvocationDoesNotMatch(InstrumentedMethod method, RecordedInvocation invocation) {
assertThat(method.matcher(invocation)).rejects(this.hints);
}
private static class PrivateConstructor {
private PrivateConstructor() {
}
}
static class PublicField {
public String field;
}
}