-
Notifications
You must be signed in to change notification settings - Fork 73
[GR-60258] Refactor JUnit feature #693
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks David. Can you give a description of the changes that this makes? For example you mention that the tests are discovered at runtime but in practice you are still using the test-ids.
There are changes which need to be reverted on the samples: build plugins versions and maven repositories.
Hey @melix the PR is not ready yet (it is in draft state). I will update PR description and revert unnecessary changes after I finish testing. |
29c75fe
to
b80fd65
Compare
Please clean up the commit history: https://github.com/graalvm/native-build-tools/pull/693/commits E.g. fold |
common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java
Show resolved
Hide resolved
@@ -12,7 +12,7 @@ mavenResolver = "1.9.22" | |||
graalvm = "23.0.2" | |||
openjson = "1.0.13" | |||
junitPlatform = "1.10.0" | |||
junitJupiter = "5.10.0" | |||
junitJupiter = "5.11.0" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I updated this in order to provide support for @FieldSource
annotation introduced in 5.11.0
(see this)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please note that we recently released JUnit 5.12.
https://github.com/junit-team/junit5/releases/tag/r5.12.0
So, it's probably a good idea to upgrade to that. I can create a separate GitHub issue for that if you like.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side note, JUnit 5.12 includes native-image.properties
files (see release notes).
All affected JAR files now include
native-image.properties
files that contain the--initialize-at-build-time
option to avoid breakages in GraalVM projects when updating to newer versions of JUnit.
So, that might affect your refactoring effort.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
- See also: Plan for JUnit 6 support #695
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As a side note, JUnit 5.12 includes native-image.properties files (see release notes).
@dnestoro , that means we have to use --exclude-config ...
to ensure we do not get those --initialize-at-build-time
options for your new implementation that does not require any initialize-at-build-time classes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If anything is incomplete in the out-of-the-box properties files in the JUnit 5 JARs, please let us (the JUnit Team) know, so that we can address that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hey @sbrannen, I tried JUnit 5.12
with both my implementation and the current master. My findings are the following:
- If I bump JUnit version from
5.10
to5.12
image build command contains multiple lines like:
-H:ClassInitialization@jar:.../.gradle/caches/modules-2/files-2.1/org.junit.platform/junit-platform-reporting/.../META-INF/native-image/org.junit.platform/junit-platform-reporting/native-image.properties+api=org.junit.platform.reporting.open.xml.OpenTestReportGeneratingListener...
- So
native-image.properties
are really provided to the native image - But if I remove our own
initializeAtBuildtime
(either on master or in my implementation) I am facing the following error:
Fatal error: com.oracle.graal.pointsto.constraints.UnsupportedFeatureException: An object of type 'org.junit.platform.commons.support.scanning.DefaultClasspathScanner' was found in the image heap. This type, however, is marked for initialization at image run time for the following reason: classes are initialized at run time by default.
This is not allowed for correctness reasons: All objects that are stored in the image heap must be initialized at build time.
...
- So, it looks like properties files provided by JUnit are not complete
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@marcphilipp, please take a look. Thanks!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I doubt that's the only class that's missing. I could copy the list of classes from the hard-coded lists in the feature implementation but I'd rather extend our existing tests so it exercises all relevant code.
But if I remove our own initializeAtBuildtime (either on master or in my implementation) I am facing the following error
@dnestoro What do I need run to reproduce this locally?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am so confused now... I tried to reproduce this error again, but now everything works correctly on master (while on my branch it fails with different errors depending on the GraalVM version I use). I will double check what happened here (maybe something got cached) because I am 100% sure that it was failing on master as well.
@marcphilipp @sbrannen Sorry for the false alarm. I will investigate this deeper to see if something is still missing on master and give you a reproducer if something fails.
2448625
to
e5d1f01
Compare
While not so concerning for testing, how much bigger do such test images get when launcher and testPlan are no longer ImageSingletons? Bigger or smaller? :) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't finish the review but left a few comments. I'm still confused and would appreciate a few more details:
- you are saying that we should discover the test ids at runtime, however, we still run the tests in JVM mode to get the test ids?
- if the test ids are not needed anymore, maybe we should change the name of the parameters which are used
import java.nio.file.Files | ||
|
||
class JUnitFunctionalTests extends AbstractFunctionalTest { | ||
@Unroll("test if JUint support works with various annotations, reflection and resources") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
:nit: this is not an unrolled test. I guess you just wanted a display name, in which case you can put this directly in the test method name (also, there's a typo in JUnit)
native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java
Show resolved
Hide resolved
...junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java
Outdated
Show resolved
Hide resolved
On a first look, the tests image size gets around 5% of increase. But I will run more tests and provide more accurate information soon |
No, I am just saying that we should use test ids to compute test plan (to discover it with |
@fniephaus after deeper investigation, I came to the following results regarding image size:
The more tests, the more benefits we get! |
Thanks for checking, @dnestoro! |
common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java
Show resolved
Hide resolved
...junit-platform-native/src/main/java/org/graalvm/junit/platform/NativeImageJUnitLauncher.java
Outdated
Show resolved
Hide resolved
… JDKs in a separate file
9637662
to
c890433
Compare
common/junit-platform-native/src/main/resources/initialize-at-buildtime
Outdated
Show resolved
Hide resolved
common/junit-platform-native/gradle/native-image-testing.gradle
Outdated
Show resolved
Hide resolved
native-gradle-plugin/src/main/java/org/graalvm/buildtools/gradle/NativeImagePlugin.java
Outdated
Show resolved
Hide resolved
common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java
Outdated
Show resolved
Hide resolved
common/junit-platform-native/src/main/java/org/graalvm/junit/platform/JUnitPlatformFeature.java
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM 👍
(I lack permissions to formally approve)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you, @dnestoro!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good catch on the recursive type declarations! 👍
|
||
private final Set<Class<?>> registeredClasses = new HashSet<>(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this should be a field. Instead, it should be possible to pass it as a paramter to registerTestClassForReflection
and shouldRegisterClass
.
private final Set<Class<?>> registeredClasses = new HashSet<>(); |
LauncherDiscoveryRequest request = LauncherDiscoveryRequestBuilder.request() | ||
.selectors(selectors) | ||
.build(); | ||
|
||
Launcher launcher = LauncherFactory.create(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Launcher launcher = LauncherFactory.create(); | |
Set<Class<?>> registeredClasses = new HashSet<>(); | |
Launcher launcher = LauncherFactory.create(); |
@@ -143,14 +168,44 @@ private TestPlan discoverTestsAndRegisterTestClassesForReflection(Launcher launc | |||
.map(ClassSource.class::cast) | |||
.map(ClassSource::getJavaClass) | |||
.forEach(this::registerTestClassForReflection); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
.forEach(this::registerTestClassForReflection); | |
.forEach(clazz -> registerTestClassForReflection(clazz, registeredClasses)); |
|
||
private final Set<Class<?>> registeredClasses = new HashSet<>(); | ||
|
||
private boolean shouldRegisterClass(Class<?> clazz) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private boolean shouldRegisterClass(Class<?> clazz) { | |
private boolean shouldRegisterClass(Class<?> clazz, Set<Class<?>> registeredClasses) { |
if (registeredClasses.contains(clazz)) { | ||
return false; | ||
} | ||
registeredClasses.add(clazz); | ||
|
||
return testPlan; | ||
return true; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This can be simplified to the following, but your version may be more readable.
return registeredClasses.add(clazz);
private void registerTestClassForReflection(Class<?> clazz) { | ||
if (!shouldRegisterClass(clazz)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
private void registerTestClassForReflection(Class<?> clazz) { | |
if (!shouldRegisterClass(clazz)) { | |
private void registerTestClassForReflection(Class<?> clazz, Set<Class<?>> registeredClasses) { | |
if (!shouldRegisterClass(clazz, registeredClasses)) { |
|
||
Class<?>[] declaredClasses = clazz.getDeclaredClasses(); | ||
for (Class<?> declaredClass : declaredClasses) { | ||
registerTestClassForReflection(declaredClass); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
registerTestClassForReflection(declaredClass); | |
registerTestClassForReflection(declaredClass, registeredClasses); |
|
||
Class<?>[] interfaces = clazz.getInterfaces(); | ||
for (Class<?> inter : interfaces) { | ||
registerTestClassForReflection(inter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
registerTestClassForReflection(inter); | |
registerTestClassForReflection(inter, registeredClasses); |
for (Class<?> inter : interfaces) { | ||
registerTestClassForReflection(inter); | ||
} | ||
|
||
Class<?> superClass = clazz.getSuperclass(); | ||
if (superClass != null && superClass != Object.class) { | ||
registerTestClassForReflection(superClass); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
registerTestClassForReflection(superClass); | |
registerTestClassForReflection(superClass, registeredClasses); |
Problem:
Idea:
Solution:
test-ids
generated before test compilation to discover tests (currently on master as well)test-ids
to find tests and execute themTest-ids
location should be passed to the NativeImageJUnitLauncher class, so we simply setsystemProperty
with this location in order to read it at runtime (we do this automatically for both gradle and maven)This PR:
initializeAtBuildtime
(here, here, and here)launcher
andtestPlan
from ImageSingletons (here) => therefore we don't need classes to be initialized at buildtimetestPlan
at runtimeIn addition:
@FieldSource
annotations to solve Add support for@FieldSource
for@ParameterizedTest
methods #638org.junit.rules.ExpectedException
(code originally taken from this PR cc @tzezula)5.10.0
to5.11.0
(see this)