Skip to content

Replace Cglib with Bytebuddy to support Java 9+ - #300

Merged
henri-tremblay merged 23 commits into
masterfrom
bytebuddy
Oct 12, 2022
Merged

henri-tremblay merged 23 commits into
masterfrom
bytebuddy

Conversation

@henri-tremblay

Copy link
Copy Markdown
Contributor

No description provided.

@henri-tremblay henri-tremblay added this to the 4.4 milestone Oct 12, 2022
@henri-tremblay henri-tremblay self-assigned this Oct 12, 2022
@henri-tremblay
henri-tremblay merged commit a756637 into master Oct 12, 2022
@henri-tremblay

Copy link
Copy Markdown
Contributor Author

Close #274

@trancexpress

trancexpress commented Oct 28, 2022 •

Copy link
Copy Markdown

Hi @henri-tremblay ,

could you clarify the following mocking behavior?

I'm trying out using easymock 5.0.1 for our product, upgrading from 4.2.

I have a test where a mocked object seems to delegate to the "real" implementation with 4.2 (cglib seems to do this). With easymock 5.0.1, that is no longer the case.

The mock is created with EasyMock.createNiceControl() and EasyMock.replay(Object...) is called at the test start. There are some expects, which I don't think are relevant.

Unfortunately I'm not too into easymock, my expectation would have been that once the class is mocked, no actual code from the class is called? But that seems to not have been the case before? I'm confused.

The "real" method that is being called is package private, maybe this makes a difference somehow?

Best regards,
Simeon

@henri-tremblay
henri-tremblay deleted the bytebuddy branch October 28, 2022 13:21
@henri-tremblay

Copy link
Copy Markdown
Contributor Author

It should behave the exact same way as before. A class extending the original class is created. And that's your mock. Every call to it will be intercepted and the original code isn't called.

Everything else is a bug that should be fixed.

@trancexpress

Copy link
Copy Markdown

It should behave the exact same way as before. A class extending the original class is created. And that's your mock. Every call to it will be intercepted and the original code isn't called.

Everything else is a bug that should be fixed.

Thanks, I'll try to write a reproducer then.

@trancexpress

trancexpress commented Oct 28, 2022 •

Copy link
Copy Markdown

Thanks, I'll try to write a reproducer then.

I can reproduce this with Eclipse plug-ins, otherwise I can't.

TestEasyMockPlugins.zip

When I run the test MyTest as a JUnit plug-in test, I see the following output with easymock 4.2 in the platform:

WARNING: An illegal reflective access operation has occurred
WARNING: Illegal reflective access by org.easymock.cglib.core.ReflectUtils$1 (file:/workspaces/socbm775/sandreev/ws-master/opt/93000/src/segments/eclipse/packages/generated/TESTS_ONLY/eclipse/plugins/easymock-4.2.jar) to method java.lang.ClassLoader.defineClass(java.lang.String,byte[],int,int,java.security.ProtectionDomain)
WARNING: Please consider reporting this to the maintainers of org.easymock.cglib.core.ReflectUtils$1
WARNING: Use --illegal-access=warn to enable warnings of further illegal reflective access operations
WARNING: All illegal access operations will be denied in a future release
C
bar: C

With easymock 5.0.1, there is no output (this is the expected outcome, since the class that prints is mocked).

I couldn't debug cglib itself due to not having sources for it, but the JDK warning came out from the finally block in this code: ClassProxyFactory.createProxy()

    @SuppressWarnings("unchecked")
    public <T> T createProxy(final Class<T> toMock, InvocationHandler handler,
            Method[] mockedMethods, ConstructorArgs args) {
        Enhancer enhancer = createEnhancer(toMock);

        MockMethodInterceptor interceptor = new MockMethodInterceptor(handler);
        if (mockedMethods != null) {
            interceptor.setMockedMethods(mockedMethods);
        }
        enhancer.setCallbackType(interceptor.getClass());

        Class<?> mockClass;
        try {
            mockClass = enhancer.createClass();
        } catch (CodeGenerationException e) {
            // ///CLOVER:OFF (don't know how to test it automatically)
            // Probably caused by a NoClassDefFoundError, to use two class loaders at the same time
            // instead of the default one (which is the class to mock one)
            // This is required by Eclipse Plug-ins, the mock class loader doesn't see
            // cglib most of the time. Using EasyMock and the mock class loader at the same time solves this
            LinkedClassLoader linkedClassLoader = AccessController.doPrivileged((PrivilegedAction<LinkedClassLoader>) () -> new LinkedClassLoader(toMock.getClassLoader(), ClassProxyFactory.class.getClassLoader()));
            enhancer.setClassLoader(linkedClassLoader);
            mockClass = enhancer.createClass();
            // ///CLOVER:ON
        }

Considering that the bug is fixed in 5.x, I assume there is no need to open an issue? I mainly wrote my initial comment to confirm the expected behavior (that the mock should not be running production code).

@henri-tremblay

Copy link
Copy Markdown
Contributor Author

The error you see is one of the reason I had to switch to ByteBuddy. To remove the reflective access issues.

@trancexpress

Copy link
Copy Markdown

The error you see is one of the reason I had to switch to ByteBuddy. To remove the reflective access issues.

Alright, great to hear. Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants