Skip to content
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

[Bug] Unable to get EqualsVerifier to ignore inaccessible private static final field #981

Open
cmacq2 opened this issue Jul 29, 2024 · 1 comment

Comments

@cmacq2
Copy link

cmacq2 commented Jul 29, 2024

Describe the bug

It appears equals verifier also attempts to reflect on static final class fields. This causes confusing/unexpected behaviour when those fields are not accessible (Java module system).

Steps to reproduce

  1. Create a record with some value such as byte[].
  2. Add a private static final object of some inaccessible type such as HexFormat (delimiter field is inaccessible)
  3. Observe the reported errors, even when attempting to steer EqualsVerifier away from the offending field by asking it to ignore it.

Error message and version number

java.lang.AssertionError: EqualsVerifier found a problem in class <package>.Test$Foo.
-> The class is not accessible via the Java Module system. Consider opening the module that contains it.

For more information, go to: https://www.jqno.nl/equalsverifier/errormessages
(EqualsVerifier 3.16.1, JDK 21.0.2 on Linux)

	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:349)
	at <package>.Test.test(Test.java:66)
	at java.base/jdk.internal.reflect.DirectMethodHandleAccessor.invoke(DirectMethodHandleAccessor.java:103)
	at java.base/java.lang.reflect.Method.invoke(Method.java:580)
	at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:727)
	at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
	at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:156)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:147)
	at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:86)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(InterceptingExecutableInvoker.java:103)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.lambda$invoke$0(InterceptingExecutableInvoker.java:93)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
	at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:92)
	at org.junit.jupiter.engine.execution.InterceptingExecutableInvoker.invoke(InterceptingExecutableInvoker.java:86)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:217)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:213)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:138)
	at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:68)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at java.base/java.util.ArrayList.forEach(ArrayList.java:1596)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
	at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
	at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
	at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
	at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
	at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:147)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:127)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:90)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:55)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:102)
	at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:54)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:114)
	at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:86)
	at org.junit.platform.launcher.core.DefaultLauncherSession$DelegatingLauncher.execute(DefaultLauncherSession.java:86)
	at org.junit.platform.launcher.core.SessionPerRequestLauncher.execute(SessionPerRequestLauncher.java:53)
	at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:57)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater$1.execute(IdeaTestRunner.java:38)
	at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
	at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
	at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:232)
	at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:55)
Caused by: nl.jqno.equalsverifier.internal.exceptions.ModuleException
	at nl.jqno.equalsverifier.internal.reflection.InPlaceObjectAccessor.handleInaccessibleObjectException(InPlaceObjectAccessor.java:97)
	at nl.jqno.equalsverifier.internal.reflection.InPlaceObjectAccessor.scrambleInternal(InPlaceObjectAccessor.java:86)
	at nl.jqno.equalsverifier.internal.reflection.InPlaceObjectAccessor.scramble(InPlaceObjectAccessor.java:58)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.getRedAccessor(ClassAccessor.java:225)
	at nl.jqno.equalsverifier.internal.reflection.ClassAccessor.getRedObject(ClassAccessor.java:199)
	at nl.jqno.equalsverifier.internal.prefabvalues.factories.FallbackFactory.giveInstances(FallbackFactory.java:99)
	at nl.jqno.equalsverifier.internal.prefabvalues.factories.FallbackFactory.createValues(FallbackFactory.java:40)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.createTuple(PrefabValues.java:189)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.realizeCacheFor(PrefabValues.java:172)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.giveTuple(PrefabValues.java:93)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.giveOther(PrefabValues.java:131)
	at nl.jqno.equalsverifier.internal.prefabvalues.PrefabValues.giveOther(PrefabValues.java:107)
	at nl.jqno.equalsverifier.internal.reflection.RecordObjectAccessor.withChangedField(RecordObjectAccessor.java:108)
	at nl.jqno.equalsverifier.internal.checkers.fieldchecks.MutableStateFieldCheck.execute(MutableStateFieldCheck.java:49)
	at nl.jqno.equalsverifier.internal.checkers.FieldInspector.check(FieldInspector.java:29)
	at nl.jqno.equalsverifier.internal.checkers.FieldsChecker.check(FieldsChecker.java:73)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verifyWithExamples(SingleTypeEqualsVerifierApi.java:466)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.performVerification(SingleTypeEqualsVerifierApi.java:420)
	at nl.jqno.equalsverifier.api.SingleTypeEqualsVerifierApi.verify(SingleTypeEqualsVerifierApi.java:347)
	... 69 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make field private final java.lang.String java.util.HexFormat.delimiter accessible: module java.base does not "opens java.util" to unnamed module @77cd7a0
	at java.base/java.lang.reflect.AccessibleObject.throwInaccessibleObjectException(AccessibleObject.java:391)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:367)
	at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:315)
	at java.base/java.lang.reflect.Field.checkCanSetAccessible(Field.java:183)
	at java.base/java.lang.reflect.Field.setAccessible(Field.java:177)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.change(FieldModifier.java:134)
	at nl.jqno.equalsverifier.internal.reflection.FieldModifier.changeField(FieldModifier.java:122)
	at nl.jqno.equalsverifier.internal.reflection.InPlaceObjectAccessor.scrambleInternal(InPlaceObjectAccessor.java:80)
	... 86 more

Code: EqualsVerifier invocation

EqualsVerifier.forClass(Foo.class).verify();
EqualsVerifier.forClass(Foo.class).withIgnoredFields("HEX").verify();
EqualsVerifier.forClass(Foo.class).withOnlyTheseFields("data").verify();

Code: class under test

    public record Foo(byte[] data) {
        private static final HexFormat HEX = HexFormat.ofDelimiter(" ");

        @Override
        public boolean equals(Object obj) {
            return obj instanceof Foo && Arrays.equals(data, ((Foo) obj).data);
        }

        @Override
        public int hashCode() {
            return Arrays.hashCode(data);
        }

        @Override
        public String toString() {
            return "Foo{" + HEX.formatHex(data) + "}";
        }
    }

Additional context

No response

@jqno
Copy link
Owner

jqno commented Aug 4, 2024

Sorry for the slow response, I was out of town for a few days.

I'm assuming that you're running your tests on the modulepath, not on the classpath, is that right?

Ignoring fields is intended for whether or not fields should participate in the equals contract. EqualsVerifier still needs to be able to instantiate every field, though, whether it's ignored or not. I realise that this is sometimes unclear, and I'm thinking about ways to make that clearer.

The workaround for your problem is not to ignore the HEX field, but to add prefab values for it:

EqualsVerifier
    .forClass(Foo.class)
    .withPrefabValues(HexFormat.class, HexFormat.ofDelimiter(" "), HexFormat.ofDelimiter("/"))
    .verify();

Please let me know if that helps.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants