hazendaz / javabean-tester

JavaBean Tester
http://codebox.org.uk/pages/articles/unit-testing-javabeans
Apache License 2.0
9 stars 5 forks source link

Test passes under Gradle Wrapper 8.1.1, fails under 8.2 and later #712

Closed Thunderforge closed 5 months ago

Thunderforge commented 1 year ago

The following test passes under Gradle Wrapper 8.1.1 and fails under Gradle Wrapper 8.2 and 8.3 (using Javabean Tester 2.5.3, Java 17).

package com.example;

import com.codebox.bean.JavaBeanTester;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.junit.jupiter.api.Test;

class FooTest {

    @Test
    void testJavaBean() {
        JavaBeanTester.builder(Foo.class)
                .checkEquals()
                .loadData()
                .test();
    }

    public static class Foo {
        private String bar;

        public String getBar() {
            return bar;
        }

        public void setBar(String bar) {
            this.bar = bar;
        }

        @Override
        public boolean equals(Object o) {
            if (this == o) {
                return true;
            }

            if (o == null || getClass() != o.getClass()) {
                return false;
            }

            return EqualsBuilder.reflectionEquals(this, o);
        }

        @Override
        public int hashCode() {
            return HashCodeBuilder.reflectionHashCode(this);
        }

        @Override
        public String toString() {
            return ToStringBuilder.reflectionToString(this, ToStringStyle.SHORT_PREFIX_STYLE);
        }
    }
}

Under Gradle 8.2 and later, it fails with the following error:

java.lang.ExceptionInInitializerError
    at com.codebox.bean.JavaBeanTesterWorker.equalsHashCodeToStringSymmetricTest(JavaBeanTesterWorker.java:524)
    at com.codebox.bean.JavaBeanTesterWorker.test(JavaBeanTesterWorker.java:174)
    at com.codebox.bean.JavaBeanTesterBuilder.test(JavaBeanTesterBuilder.java:204)
    at com.example.FooTest.testJavaBean(FooTest.java:17)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    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:1511)
    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:1511)
    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:107)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
    at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
    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.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:110)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:90)
    at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:85)
    at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:62)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:568)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
    at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
    at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
    at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
    at jdk.proxy2/jdk.proxy2.$Proxy5.stop(Unknown Source)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
    at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
    at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:113)
    at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:65)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
    at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: net.sf.cglib.core.CodeGenerationException: java.lang.reflect.InaccessibleObjectException-->Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @3ba987b8
    at net.sf.cglib.core.ReflectUtils.defineClass(ReflectUtils.java:464)
    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:339)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:96)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData$3.apply(AbstractClassGenerator.java:94)
    at net.sf.cglib.core.internal.LoadingCache$2.call(LoadingCache.java:54)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at net.sf.cglib.core.internal.LoadingCache.createEntry(LoadingCache.java:61)
    at net.sf.cglib.core.internal.LoadingCache.get(LoadingCache.java:34)
    at net.sf.cglib.core.AbstractClassGenerator$ClassLoaderData.get(AbstractClassGenerator.java:119)
    at net.sf.cglib.core.AbstractClassGenerator.create(AbstractClassGenerator.java:294)
    at net.sf.cglib.core.KeyFactory$Generator.create(KeyFactory.java:221)
    at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:174)
    at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:157)
    at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:149)
    at net.sf.cglib.core.KeyFactory.create(KeyFactory.java:145)
    at net.sf.cglib.beans.BeanCopier.<clinit>(BeanCopier.java:32)
    ... 88 more
Caused by: java.lang.reflect.InaccessibleObjectException: Unable to make protected final java.lang.Class java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain) throws java.lang.ClassFormatError accessible: module java.base does not "opens java.lang" to unnamed module @3ba987b8
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:354)
    at java.base/java.lang.reflect.AccessibleObject.checkCanSetAccessible(AccessibleObject.java:297)
    at java.base/java.lang.reflect.Method.checkCanSetAccessible(Method.java:199)
    at java.base/java.lang.reflect.Method.setAccessible(Method.java:193)
    at net.sf.cglib.core.ReflectUtils$1.run(ReflectUtils.java:61)
    at java.base/java.security.AccessController.doPrivileged(AccessController.java:569)
    at net.sf.cglib.core.ReflectUtils.<clinit>(ReflectUtils.java:52)
    at net.sf.cglib.core.KeyFactory$Generator.generateClass(KeyFactory.java:243)
    at net.sf.cglib.core.DefaultGeneratorStrategy.generate(DefaultGeneratorStrategy.java:25)
    at net.sf.cglib.core.AbstractClassGenerator.generate(AbstractClassGenerator.java:332)
    ... 102 more
hazendaz commented 1 year ago

You need to add '--add-opens java.base/java.lang=ALL-UNNAMED' fore unit tests to run. If I had to guess gradle has jacoco built in and jacoco removed that in recent versions so its now up to user to do so.

The root issue is cglib which is no longer supported but still works. Long term cglib needs pulled out of this project in favor of byte buddy. It would be nice to see a community attempt at raising a PR for byte buddy replacement which is also already in the project. Without that, it will be a long time until I personally start hitting the issue and I'm forced to make a change. Since there is a clear work around and one that was already present in earlier gradle for you, its more of an issue of just defining needs of your testing.

This project also does the same, see the pom.xml and search on java.lang. Look at commit history as well 28cb9e5fded3e6b8a0c5e24879a9947b7b771191

Thunderforge commented 1 year ago

We do use Jacoco in our project, so that does appear to be the root cause.

Staying on Gradle 8.1.1 isn't a viable solution because we want Java 20 support (and eventually Java 21 support when it's released).

I don't think that adding --add-opens java.base/java.lang=ALL-UNNAMED every time we run gradlew build is an acceptable solution. We run builds locally on developer machines and in various ways through CI, and this would be an unnecessary burden.

Thanks for the information about cglib being the root cause. I don't think that we will be in a position to create a replacement with bytebuddy.

We'll continue looking for workarounds that are acceptable. If one isn't forthcoming, we'll have to consider removing Javabean Tester.

hazendaz commented 1 year ago

Not sure you understood fully. The add opens you add to your gradle build scripts so user doesn't do anything special. It's just there across platforms. Jacoco was doing this for users until their recent release to support latest jvms. It doesn't mean remove javabean tester. It's working fine through jdk 21 now. You will find the same issue with other libraries. It's rather routine to understand that with newer jdks. Any byte manipulation library is usual suspect to needing this. They do after all touch java internals in interesting ways. Libraries like error prone, lombok, and many others have very similar requirements. In example I gave here it's for maven Surefire, I'm sure you can config same for gradle since gradle claims they are superior. Do go to jacoco and look at their change logs. They do explain why they removed it, mostly because user should add themselves for clarity and they eventually rewrote away from their need.

As a quick patch here, I could look to just copy the code jacoco had but it may be just as quick to switch over fully to byte buddy which is also already in this library. It's not a critical issue though either way and simple to work with as is.

For what it's worth, I've been running this with jdk 20 at scale without issue for some time. This includes multiple ci systems, jenkins and github actions.

Sent from my Verizon, Samsung Galaxy smartphone Get Outlook for Androidhttps://aka.ms/AAb9ysg


From: Thunderforge @.> Sent: Saturday, August 26, 2023 5:56:46 PM To: hazendaz/javabean-tester @.> Cc: Jeremy Landis @.>; Comment @.> Subject: Re: [hazendaz/javabean-tester] Test passes under Gradle Wrapper 8.1.1, fails under 8.2 and later (Issue #712)

We do use Jacoco in our project, so that does appear to be the root cause.

Staying on Gradle 8.1.1 isn't a viable solution because we want Java 20 support (and eventually Java 21 support when it's released).

I don't think that adding --add-opens java.base/java.lang=ALL-UNNAMED every time we run gradlew build is an acceptable solution. We run builds locally on developer machines and in various ways through CI, and this would be an unnecessary burden.

Thanks for the information about cglib being the root cause. I don't think that we will be in a position to create a replacement with bytebuddy.

We'll continue looking for workarounds that are acceptable. If one isn't forthcoming, we'll have to consider removing Javabean Tester.

— Reply to this email directly, view it on GitHubhttps://github.com/hazendaz/javabean-tester/issues/712#issuecomment-1694508965, or unsubscribehttps://github.com/notifications/unsubscribe-auth/AAHODI54NQRJ4UWQYYIBVQTXXJWJ5ANCNFSM6AAAAAA33XWUOA. You are receiving this because you commented.Message ID: @.***>

hazendaz commented 1 year ago

this may help https://github.com/jacoco/jacoco/issues/1487 as it shows how to address with gradle.

I am looking at this further as well.

hazendaz commented 1 year ago

General scan of the 100+ projects I work on github shows same is exported in at least a dozen of them. Further looking into this, cglib bean copier is still best option. Further, spring even has a copy of it internally and they state to do the same. What I have done here for time being is stopped using cglib no dep which comes with embedded asm that is really old and does not support newer jdks well. Instead, I'm now letting it bring asm by itself at 9.5.

For now I think this is about all I can do. I'm considering pushing a release as-is like this as 2.6.0 with the asm change but will think on that a few more days.

Thunderforge commented 1 year ago

I confirm that adding

test {
    // Other stuff

    jvmArgs += ["--add-opens", "java.base/java.lang=ALL-UNNAMED"]
}

does indeed fix this error under Gradle 8.3. But it sounds like your planned 2.6.0 version eliminates the need for this. Is that correct? If so and you are planing on releasing it in the next few days, we'll just wait for that instead of adding it to all of our 20 or so projects.

hazendaz commented 1 year ago

Best to use solution you have above. After reviewing cglib usage including springs, they are suggesting the same. What jacoco did originally I don't intend to try to add as too complicated.

hazendaz commented 5 months ago

closing as done per noted. User must add add opens as noted.