Skip to content

Replace Cglib with Bytebuddy to support Java 9+ #300

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

Merged
merged 23 commits into from
Oct 12, 2022
Merged

Conversation

henri-tremblay
Copy link
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
Contributor Author

Close #274

@trancexpress
Copy link

trancexpress commented Oct 28, 2022

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
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

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
Copy link

trancexpress commented Oct 28, 2022

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
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

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