Prior to this commit, the `RuntimeHintsAgent` would instrument
`Method.invoke` in a way that fails when invoked methods are not public.
This commit ensures that before delegating the invocation call, the
instrumentation makes the method accessible before delegating the call.
Fixes gh-29046
This commit adapts the registration of fields, constructors, and methods
to provide the same convenience than the reflection-based one available
in ReflectionHints.
See gh-29011
This commit harmonizes the registration of an executable so that
the default method and the method that takes an empty customizer
produces the same hint. The same applies to the readable flag of
a field hint.
Rather than returning a list of executable modes, the "highest" mode
is retained.
See gh-29011
Based on the feedback in #28977 an easy way to create a list of
type references based on a vararg of classes is helpful when
registering the same hints for several types.
This commit fixes `RecordedInvocation` and
`RuntimeHintsInvocationsAssert` so that they don't refer to the recorded
instance for static calls.
This also consistently resolves the `TypeReference` of recorded
instances.
Fixes gh-28907
CompileWithTargetClassAccessClassLoader is currently only used within the
CompileWithTargetClassAccessExtension which is dedicated to JUnit Jupiter
support which in turn should not have any direct dependencies on Hamcrest.
In other words, the JupiterTestEngine should not load any Hamcrest types
that would cause issues with the CompileWithTargetClassAccessClassLoader.
Prior to this commit, CompileWithTargetClassAccessExtension failed to
properly select overloaded test methods because it ignored the method
parameter list when looking up the test method.
This commit addresses this issue by selecting the test method using its
fully qualified method name which takes in account the class name,
method name, and parameter names.
Closes gh-28901
Commit 9dd7f5412a (which has now been
reverted) addressed the issue of having the TestNG TestEngine for the
JUnit Platform on the test runtime classpath by allowing `org.testng`
types to pass through to the original ClassLoader; however, that fix
merely obfuscated the underlying issue.
The underlying issue is that the CompileWithTargetClassAccessExtension
is only applicable to JUnit Jupiter tests and therefore should launch
the JUnit Platform with only the JUnit Jupiter TestEngine active.
This commit addresses this issue by applying an EngineFilter to include
only the "junit-jupiter" test engine.
Closes gh-28900
Since the RuntimeHintsAgentCondition is based solely on the result of
invoking a boolean static method, there is no need to implement a custom
ExecutionCondition. For such use cases, the @EnabledIf support in JUnit
Jupiter is sufficient.
This commit therefore replaces the custom RuntimeHintsAgentCondition
with use of @EnabledIf as a meta-annotation.
This commit adds the supporting testing infrastructure using the
`RuntimeHintsAgent`. Given that the agent is loaded by the JVM running
the test suite, we can then use it to record method invocations at
runtime and check whether the prepared `RuntimeHints` match the expected
behavior.
This commit contributes the `RuntimeHintsRecorder`. With this, we can
record relevant method invocations for a given lambda, focusing on a
specific part of the code behavior. This returns a
`RuntimeHintsInvocations` instance, which is an AssertJ assert provider.
From there, we can perform assertions on the recorded invocations and
check that a given collection of hints cover the reflection, resources
and proxies needs at runtime.
This also ships the `@EnabledIfRuntimeHintsAgent` opinionated
annotation: this applies the `RuntimeHintsAgentCondition` JUnit
extension that detects whether the `RuntimeHintsAgent` is loaded by the
current JVM. Tests annotated with this will be skipped if the agent is
not present. This annotation is also tagged with a JUnit `@Tag` to
gather such tests in a specific `"RuntimeHintsTests"` test suite.
In the Spring Framework build, we have chosen to isolate such tests and
not load the agent for the main test suite ("RuntimeHintsTests" tests
are excluded from the main suite). While the agent's intent is to be as
transparent as possible, there are security and access considerations
that could interefere with other tests.
With this approach, we can then create a separate test suite and run
agent tests in a dedicated JVM.
Note that projects using this infrastructure can choose to use the
condition by itself in a custom annotation.
Here is an example of this testing infrastructure:
```
@EnabledIfRuntimeHintsAgent
class MyTestCases {
@Test
void hintsForMethodsReflectionShouldMatch() {
RuntimeHints hints = new RuntimeHints();
hints.reflection().registerType(String.class,
hint -> hint.withMembers(MemberCategory.INTROSPECT_PUBLIC_METHODS));
RuntimeHintsInvocations invocations = RuntimeHintsRecorder.record(() -> {
Method[] methods = String.class.getMethods();
});
assertThat(invocations).match(hints);
}
}
```
See gh-27981
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
Update the `TestCompiler` so that classes can be defined using
a `Lookup`. This update allows package-private classes to be
accessed without needing a quite so unusual classloader setup.
The `@CompileWithTargetClassAccess` should be added to any
test that needs to use `Lookup` based defines. The test will
run with a completed forked classloader so not to pollute the
main classloader.
This commit also adds some useful additional APIs.
See gh-28120
Add a new unpublished `spring-core-test` module to support testing of
generated code. The module include a `TestCompiler` class which can be
used to dynamically compile generated Java code. It also include an
AssertJ friendly `SourceFile` class which uses qdox to provide targeted
assertions on specific parts of a generated source file.
See gh-28120