-
Notifications
You must be signed in to change notification settings - Fork 64
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
JUnit tests fail with InaccessibleObjectException with version 1.7 #393
Comments
@duponter Thanks for catching that. To be frank, my knowledge about Java’s module system is very limited. I’ll ask my personal module system liaison if they have a good idea about how to solve that problem. Apart from that, I’d very much appreciate if you’re approach turns out to work. |
@sormuras Any quick idea how to tackle this? |
Assuming that the code under test is also in a module, it should permit deep reflection for jqwik into some of its packages. Like: module a.b.c {
// ...
requires net.jqwik.api;
// ...
opens a.b.c to
net.jqwik.api;
} I usually use |
I'm also seeing this:
|
@jlink You might want to consider accepting an instance of java.lang.invoke.MethodHandles.Lookup in |
As a workaround, I could ignore In addition, a warning could be logged: "Please add |
I failed to reproduce the problem locally. Can anyone provide a failing sample? |
@osi posted a stacktrace with jqwik being on the class path:
Note the missing |
I have a working solution, based on my own assumptions and the suggestions by @jlink...
... and @sormuras
When changing the implementation of private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static boolean fieldIsEqualIn(Field field, Object left, Object right) {.
try {
MethodHandle handle = LOOKUP.unreflectGetter(field);
// If field is a functional type use LambdaSupport.areEqual().
// TODO: Could there be circular references among functional types?
if (isFunctionalType(field.getType())) {
return areEqual(handle.invoke(left), handle.invoke(right));
}
return handle.invoke(left).equals(handle.invoke(right));
} catch (Throwable e) {
return false;
}
} However, there are some important remarks/comments to consider:
java.lang.IllegalAccessException: class is not public:
java.util.function.Predicate$$Lambda$203/0x00000007c0149250.arg$1/java.util.function.Predicate/getField, from class net.jqwik.api.support.LambdaSupport (module net.jqwik.api)
|
So it's failing due to a newer Java version that doesn't allow this kind of reflection any more?
If opening the module under test for jqwik does not suffice (as implied above), there is no remediation, or is there? |
True, basically as of Java 9+.
Use the remediation I posted earlier, it compiles on JDK 8: private static final MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
private static boolean fieldIsEqualIn(Field field, Object left, Object right) {.
try {
MethodHandle handle = LOOKUP.unreflectGetter(field);
// If field is a functional type use LambdaSupport.areEqual().
// TODO: Could there be circular references among functional types?
if (isFunctionalType(field.getType())) {
return areEqual(handle.invoke(left), handle.invoke(right));
}
return handle.invoke(left).equals(handle.invoke(right));
} catch (Throwable e) {
return false;
}
} I am afraid these problems will occur more often in the future as applications start using recent JDK versions more frequently, either with or without the Java Module system. |
I still don’t really understand. I build and run with JDKs 8, 11, 17 and 18 and the problem doesn’t show up in my test suite. That’s why I’d really like to be able to reproduce it. Can you provide a minimal example? |
With the help of a colleague, I finally managed to reproduce the stacktrace with a minimal example. It has nothing to do with Java Platform Modules. In hindsight it seems logical as @sormuras already pointed out on the stacktrace posted by @osi that there is a difference (no versions vs lib versions shown) in the stacktraces produced on the classpath and those on the modulepath. Instead, the issue is caused by a combination of the @Test
void test1FailsBecauseDifferentArbitraryInstancesWithSameDataAndNegated() {
String first = Arbitraries.of("NL", "DE", "BE").filter(not(isoCode -> "BE".equals(isoCode))).sample();
String second = Arbitraries.of("NL", "DE", "BE").filter(not(isoCode -> "BE".equals(isoCode))).sample();
}
@Test
void test2SucceedsBecauseDifferentArbitraryInstancesWithSameDataButNotNegated() {
String first = Arbitraries.of("NL", "DE", "BE").filter(isoCode -> "BE".equals(isoCode)).sample();
String second = Arbitraries.of("NL", "DE", "BE").filter(isoCode -> "BE".equals(isoCode)).sample();
}
@Test
void test3SucceedsBecauseArbitraryInstancesWithDifferentData() {
String first = Arbitraries.of("DE", "BE", "FR").filter(not(isoCode -> "BE".equals(isoCode))).sample();
String second = Arbitraries.of("DE", "BE").filter(not(isoCode -> "BE".equals(isoCode))).sample();
}
@Test
void test4SucceedsBecauseSameArbitraryInstanceIsReused() {
Arbitrary<String> arbitrary = Arbitraries.of("BR", "AR", "MX").filter(not(isoCode -> "BE".equals(isoCode)));
String first = arbitrary.sample();
String second = arbitrary.sample();
} When |
@duponter Thanks a lot. That gives me a good start! |
Fixed in 747bc44 I decided to not log if the reflection exception shows up, because there's not really a lot people can do, and in most cases, it won't affect performance anyway. |
The fix is present in 1.7.1-SNAPSHOT. Please try it out. |
The SNAPSHOT version fixes the issue. As mentioned in the commit:
|
@jlink After upgrading to the freshly released 1.7.1 version, all tests complete successfully. |
@duponter The additional heap size might be a coincidence (there's randomness involved) or it could be related. Maybe you can report when it turns out - after more samples - that it's really related. |
Testing Problem
In our project we are running Junit tests that are using jqwik on the JPMS module path.
While on version 1.6.5, all these tests execute successfully.
However, after upgrading to version 1.7.0, several
InaccessibleObjectException
s are thrown during test execution:In the newly introduced
LambdaSupport
class, field values of incoming objects are read using reflection. This is a practice JPMS blocks to prevent unwanted access to a class's internals.In this particular case, the JDK does not allow to inspect classes in the
java.util.function
package.Suggested Solution
From a technical point of view, there are some ideas to solve this issue. However, I am not sure what the implications are for the jqwik functionalities that use the
LambdaSupport
class:1. Avoid reflection
Avoid using reflection at all, rewrite the functionality using other techniques. This solution might break some use cases of the new jqwik functionality or requires to make some breaking changes, which you probably would like to avoid.
2. MethodHandles
Replace some reflection calls with their newer alternatives:
MethodHandles
andVarHandle
. Although I have little to no experience with these alternatives, I quickly tried this approach:This resulted in a
IllegalAccessException
, but much closer to my own code:With some additional experimenting, I am hopeful to make it work properly.
3. Update current implementation
Keep the current implementation, but prevent it from crawling into the internals of the JDK.
Discussion
Until now, I only performed some superficial research of this issue. If time permits, I'll try to provide a reproducible test setup.
Also, I will try to dig a little deeper into the second solution (
MethodHandles
).For now, we will stay on version 1.6.5.
The text was updated successfully, but these errors were encountered: