How to use mockIsCurrentlyBeingReplaced method of org.mockito.internal.creation.bytebuddy.MockMethodInterceptor class

Best Mockito code snippet using org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.mockIsCurrentlyBeingReplaced

Run Mockito automation tests on LambdaTest cloud grid

Perform automation testing on 3000+ real desktop and mobile devices online.

copy
1/*
2 * Copyright (c) 2007 Mockito contributors
3 * This program is made available under the terms of the MIT License.
4 */
5
6package org.mockito.internal.creation.bytebuddy;
7
8import static org.mockito.internal.creation.bytebuddy.MockFeatures.withMockFeatures;
9import static org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.*;
10import static org.mockito.internal.util.StringJoiner.join;
11import java.io.ByteArrayInputStream;
12import java.io.ByteArrayOutputStream;
13import java.io.IOException;
14import java.io.InputStream;
15import java.io.ObjectInputStream;
16import java.io.ObjectOutputStream;
17import java.io.ObjectStreamClass;
18import java.io.ObjectStreamException;
19import java.io.Serializable;
20import java.lang.reflect.Field;
21import java.util.Set;
22import java.util.concurrent.locks.Lock;
23import java.util.concurrent.locks.ReentrantLock;
24import org.mockito.Incubating;
25import org.mockito.exceptions.base.MockitoSerializationIssue;
26import org.mockito.internal.configuration.plugins.Plugins;
27import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.ForWriteReplace;
28import org.mockito.internal.util.MockUtil;
29import org.mockito.internal.util.reflection.FieldSetter;
30import org.mockito.mock.MockCreationSettings;
31import org.mockito.mock.MockName;
32
33/**
34 * This is responsible for serializing a mock, it is enabled if the mock is implementing {@link Serializable}.
35 *
36 * <p>
37 *     The way it works is to enable serialization via the flag {@link MockFeatures#crossClassLoaderSerializable},
38 *     if the mock settings is set to be serializable the mock engine will implement the
39 *     {@link CrossClassLoaderSerializableMock} marker interface.
40 *     This interface defines a the {@link CrossClassLoaderSerializableMock#writeReplace()}
41 *     whose signature match the one that is looked by the standard Java serialization.
42 * </p>
43 *
44 * <p>
45 *     Then in the proxy class there will be a generated <code>writeReplace</code> that will delegate to
46 *     {@link ForWriteReplace#doWriteReplace(MockAccess)} of mockito, and in turn will delegate to the custom
47 *     implementation of this class {@link #writeReplace(Object)}. This method has a specific
48 *     knowledge on how to serialize a mockito mock that is based on ByteBuddy and will ignore other Mockito MockMakers.
49 * </p>
50 *
51 * <p><strong>Only one instance per mock! See {@link MockMethodInterceptor}</strong></p>
52 *
53 * TODO check the class is mockable in the deserialization side
54 *
55 * @see org.mockito.internal.creation.bytebuddy.ByteBuddyMockMaker
56 * @see org.mockito.internal.creation.bytebuddy.MockMethodInterceptor
57 * @author Brice Dutheil
58 * @since 1.10.0
59 */
60@Incubating
61class ByteBuddyCrossClassLoaderSerializationSupport implements Serializable {
62    private static final long serialVersionUID = 7411152578314420778L;
63    private static final String MOCKITO_PROXY_MARKER = "ByteBuddyMockitoProxyMarker";
64    private boolean instanceLocalCurrentlySerializingFlag = false;
65    private final Lock mutex = new ReentrantLock();
66
67    /**
68     * Custom implementation of the <code>writeReplace</code> method for serialization.
69     * <p/>
70     * Here's how it's working and why :
71     * <ol>
72     *
73     *     <li>
74     *         <p>When first entering in this method, it's because some is serializing the mock, with some code like :</p>
75     *
76     * <pre class="code"><code class="java">
77     * objectOutputStream.writeObject(mock);
78     * </code></pre>
79     *
80     *         <p>So, {@link ObjectOutputStream} will track the <code>writeReplace</code> method in the instance and
81     *         execute it, which is wanted to replace the mock by another type that will encapsulate the actual mock.
82     *         At this point, the code will return an
83     *         {@link CrossClassLoaderSerializableMock}.</p>
84     *     </li>
85     *     <li>
86     *         <p>Now, in the constructor
87     *         {@link CrossClassLoaderSerializationProxy#CrossClassLoaderSerializationProxy(java.lang.Object)}
88     *         the mock is being serialized in a custom way (using {@link MockitoMockObjectOutputStream}) to a
89     *         byte array. So basically it means the code is performing double nested serialization of the passed
90     *         <code>mockitoMock</code>.</p>
91     *
92     *         <p>However the <code>ObjectOutputStream</code> will still detect the custom
93     *         <code>writeReplace</code> and execute it.
94     *         <em>(For that matter disabling replacement via {@link ObjectOutputStream#enableReplaceObject(boolean)}
95     *         doesn't disable the <code>writeReplace</code> call, but just just toggle replacement in the
96     *         written stream, <strong><code>writeReplace</code> is always called by
97     *         <code>ObjectOutputStream</code></strong>.)</em></p>
98     *
99     *         <p>In order to avoid this recursion, obviously leading to a {@link StackOverflowError}, this method is using
100     *         a flag that marks the mock as already being replaced, and then shouldn't replace itself again.
101     *         <strong>This flag is local to this class</strong>, which means the flag of this class unfortunately needs
102     *         to be protected against concurrent access, hence the reentrant lock.</p>
103     *     </li>
104     * </ol>
105     *
106     * @param mockitoMock The Mockito mock to be serialized.
107     * @return A wrapper ({@link CrossClassLoaderSerializationProxy}) to be serialized by the calling ObjectOutputStream.
108     * @throws java.io.ObjectStreamException
109     */
110    public Object writeReplace(Object mockitoMock) throws ObjectStreamException {
111        // reentrant lock for critical section. could it be improved ?
112        mutex.lock();
113        try {
114            // mark started flag // per thread, not per instance
115            // temporary loosy hack to avoid stackoverflow
116            if (mockIsCurrentlyBeingReplaced()) {
117                return mockitoMock;
118            }
119            mockReplacementStarted();
120
121            return new CrossClassLoaderSerializationProxy(mockitoMock);
122        } catch (IOException ioe) {
123            MockUtil mockUtil = new MockUtil();
124            MockName mockName = mockUtil.getMockName(mockitoMock);
125            String mockedType = mockUtil.getMockSettings(mockitoMock).getTypeToMock().getCanonicalName();
126            throw new MockitoSerializationIssue(join(
127                    "The mock '" + mockName + "' of type '" + mockedType + "'",
128                    "The Java Standard Serialization reported an '" + ioe.getClass().getSimpleName() + "' saying :",
129                    "  " + ioe.getMessage()
130            ), ioe);
131        } finally {
132            // unmark
133            mockReplacementCompleted();
134            mutex.unlock();
135        }
136    }
137
138
139    private void mockReplacementCompleted() {
140        instanceLocalCurrentlySerializingFlag = false;
141    }
142
143
144    private void mockReplacementStarted() {
145        instanceLocalCurrentlySerializingFlag = true;
146    }
147
148
149    private boolean mockIsCurrentlyBeingReplaced() {
150        return instanceLocalCurrentlySerializingFlag;
151    }
152
153    /**
154     * This is the serialization proxy that will encapsulate the real mock data as a byte array.
155     * <p/>
156     * <p>When called in the constructor it will serialize the mock in a byte array using a
157     * custom {@link MockitoMockObjectOutputStream} that will annotate the mock class in the stream.
158     * Other information are used in this class in order to facilitate deserialization.
159     * </p>
160     * <p/>
161     * <p>Deserialization of the mock will be performed by the {@link #readResolve()} method via
162     * the custom {@link MockitoMockObjectInputStream} that will be in charge of creating the mock class.</p>
163     */
164    public static class CrossClassLoaderSerializationProxy implements Serializable {
165
166        private static final long serialVersionUID = -7600267929109286514L;
167        private final byte[] serializedMock;
168        private final Class typeToMock;
169        private final Set<Class> extraInterfaces;
170
171        /**
172         * Creates the wrapper that be used in the serialization stream.
173         *
174         * <p>Immediately serializes the Mockito mock using specifically crafted {@link MockitoMockObjectOutputStream},
175         * in a byte array.</p>
176         *
177         * @param mockitoMock The Mockito mock to serialize.
178         * @throws java.io.IOException
179         */
180        public CrossClassLoaderSerializationProxy(Object mockitoMock) throws IOException {
181            ByteArrayOutputStream out = new ByteArrayOutputStream();
182            ObjectOutputStream objectOutputStream = new MockitoMockObjectOutputStream(out);
183
184            objectOutputStream.writeObject(mockitoMock);
185
186            objectOutputStream.close();
187            out.close();
188
189            MockCreationSettings mockSettings = new MockUtil().getMockSettings(mockitoMock);
190            this.serializedMock = out.toByteArray();
191            this.typeToMock = mockSettings.getTypeToMock();
192            this.extraInterfaces = mockSettings.getExtraInterfaces();
193        }
194
195        /**
196         * Resolves the proxy to a new deserialized instance of the Mockito mock.
197         * <p/>
198         * <p>Uses the custom crafted {@link MockitoMockObjectInputStream} to deserialize the mock.</p>
199         *
200         * @return A deserialized instance of the Mockito mock.
201         * @throws java.io.ObjectStreamException
202         */
203        private Object readResolve() throws ObjectStreamException {
204            try {
205                ByteArrayInputStream bis = new ByteArrayInputStream(serializedMock);
206                ObjectInputStream objectInputStream = new MockitoMockObjectInputStream(bis, typeToMock, extraInterfaces);
207
208                Object deserializedMock = objectInputStream.readObject();
209
210                bis.close();
211                objectInputStream.close();
212
213                return deserializedMock;
214            } catch (IOException ioe) {
215                throw new MockitoSerializationIssue(join(
216                        "Mockito mock cannot be deserialized to a mock of '" + typeToMock.getCanonicalName() + "'. The error was :",
217                        "  " + ioe.getMessage(),
218                        "If you are unsure what is the reason of this exception, feel free to contact us on the mailing list."
219                ), ioe);
220            } catch (ClassNotFoundException cce) {
221                throw new MockitoSerializationIssue(join(
222                        "A class couldn't be found while deserializing a Mockito mock, you should check your classpath. The error was :",
223                        "  " + cce.getMessage(),
224                        "If you are still unsure what is the reason of this exception, feel free to contact us on the mailing list."
225                ), cce);
226            }
227        }
228    }
229
230
231    /**
232     * Special Mockito aware <code>ObjectInputStream</code> that will resolve the Mockito proxy class.
233     * <p/>
234     * <p>
235     *     This specifically crafted ObjectInoutStream has the most important role to resolve the Mockito generated
236     *     class. It is doing so via the {@link #resolveClass(ObjectStreamClass)} which looks in the stream
237     *     for a Mockito marker. If this marker is found it will try to resolve the mockito class otherwise it
238     *     delegates class resolution to the default super behavior.
239     *     The mirror method used for serializing the mock is {@link MockitoMockObjectOutputStream#annotateClass(Class)}.
240     * </p>
241     * <p/>
242     * <p>
243     *     When this marker is found, {@link ByteBuddyMockMaker#createProxyClass(MockFeatures)} methods are being used
244     *     to create the mock class.
245     * </p>
246     */
247    public static class MockitoMockObjectInputStream extends ObjectInputStream {
248        private final Class typeToMock;
249        private final Set<Class> extraInterfaces;
250
251        public MockitoMockObjectInputStream(InputStream in, Class typeToMock, Set<Class> extraInterfaces) throws IOException {
252            super(in);
253            this.typeToMock = typeToMock;
254            this.extraInterfaces = extraInterfaces;
255            enableResolveObject(true); // ensure resolving is enabled
256        }
257
258        /**
259         * Resolve the Mockito proxy class if it is marked as such.
260         * <p/>
261         * <p>Uses the fields {@link #typeToMock} and {@link #extraInterfaces} to
262         * create the Mockito proxy class as the <code>ObjectStreamClass</code>
263         * doesn't carry useful information for this purpose.</p>
264         *
265         * @param desc Description of the class in the stream, not used.
266         * @return The class that will be used to deserialize the instance mock.
267         * @throws java.io.IOException
268         * @throws ClassNotFoundException
269         */
270        @Override
271        protected Class<?> resolveClass(ObjectStreamClass desc) throws IOException, ClassNotFoundException {
272            if (notMarkedAsAMockitoMock(readObject())) {
273                return super.resolveClass(desc);
274            }
275
276            // TODO check the class is mockable in the deserialization side
277
278            // create the Mockito mock class before it can even be deserialized
279
280            Class<?> proxyClass = ((ByteBuddyMockMaker) Plugins.getMockMaker())
281                    .createProxyClass(withMockFeatures(typeToMock, extraInterfaces, true));
282
283            hackClassNameToMatchNewlyCreatedClass(desc, proxyClass);
284            return proxyClass;
285        }
286
287        /**
288         * Hack the <code>name</code> field of the given <code>ObjectStreamClass</code> with
289         * the <code>newProxyClass</code>.
290         * <p/>
291         * The parent ObjectInputStream will check the name of the class in the stream matches the name of the one
292         * that is created in this method.
293         * <p/>
294         * The CGLIB classes uses a hash of the classloader and/or maybe some other data that allow them to be
295         * relatively unique in a JVM.
296         * <p/>
297         * When names differ, which happens when the mock is deserialized in another ClassLoader, a
298         * <code>java.io.InvalidObjectException</code> is thrown, so this part of the code is hacking through
299         * the given <code>ObjectStreamClass</code> to change the name with the newly created class.
300         *
301         * @param descInstance The <code>ObjectStreamClass</code> that will be hacked.
302         * @param proxyClass   The proxy class whose name will be applied.
303         * @throws java.io.InvalidObjectException
304         */
305        private void hackClassNameToMatchNewlyCreatedClass(ObjectStreamClass descInstance, Class<?> proxyClass) throws ObjectStreamException {
306            try {
307                Field classNameField = descInstance.getClass().getDeclaredField("name");
308                new FieldSetter(descInstance, classNameField).set(proxyClass.getCanonicalName());
309            } catch (NoSuchFieldException nsfe) {
310                throw new MockitoSerializationIssue(join(
311                        "Wow, the class 'ObjectStreamClass' in the JDK don't have the field 'name',",
312                        "this is definitely a bug in our code as it means the JDK team changed a few internal things.",
313                        "",
314                        "Please report an issue with the JDK used, a code sample and a link to download the JDK would be welcome."
315                ), nsfe);
316            }
317        }
318
319        /**
320         * Read the stream class annotation and identify it as a Mockito mock or not.
321         *
322         * @param marker The marker to identify.
323         * @return <code>true</code> if not marked as a Mockito, <code>false</code> if the class annotation marks a Mockito mock.
324         * @throws java.io.IOException
325         * @throws ClassNotFoundException
326         */
327        private boolean notMarkedAsAMockitoMock(Object marker) throws IOException, ClassNotFoundException {
328            return !MOCKITO_PROXY_MARKER.equals(marker);
329        }
330    }
331
332
333    /**
334     * Special Mockito aware <code>ObjectOutputStream</code>.
335     * <p/>
336     * <p>
337     * This output stream has the role of marking in the stream the Mockito class. This
338     * marking process is necessary to identify the proxy class that will need to be recreated.
339     * <p/>
340     * The mirror method used for deserializing the mock is
341     * {@link MockitoMockObjectInputStream#resolveClass(ObjectStreamClass)}.
342     * </p>
343     */
344    private static class MockitoMockObjectOutputStream extends ObjectOutputStream {
345        private static final String NOTHING = "";
346
347        public MockitoMockObjectOutputStream(ByteArrayOutputStream out) throws IOException {
348            super(out);
349        }
350
351        /**
352         * Annotates (marks) the class if this class is a Mockito mock.
353         *
354         * @param cl The class to annotate.
355         * @throws java.io.IOException
356         */
357        @Override
358        protected void annotateClass(Class<?> cl) throws IOException {
359            writeObject(mockitoProxyClassMarker(cl));
360            // might be also useful later, for embedding classloader info ...maybe ...maybe not
361        }
362
363        /**
364         * Returns the Mockito marker if this class is a Mockito mock.
365         *
366         * @param cl The class to mark.
367         * @return The marker if this is a Mockito proxy class, otherwise returns a void marker.
368         */
369        private String mockitoProxyClassMarker(Class<?> cl) {
370            if (CrossClassLoaderSerializableMock.class.isAssignableFrom(cl)) {
371                return MOCKITO_PROXY_MARKER;
372            } else {
373                return NOTHING;
374            }
375        }
376    }
377
378
379    /**
380     * Simple interface that hold a correct <code>writeReplace</code> signature that can be seen by an
381     * <code>ObjectOutputStream</code>.
382     * <p/>
383     * It will be applied before the creation of the mock when the mock setting says it should serializable.
384     */
385    public interface CrossClassLoaderSerializableMock {
386        Object writeReplace() throws ObjectStreamException;
387    }
388}
389
Full Screen

Accelerate Your Automation Test Cycles With LambdaTest

Leverage LambdaTest’s cloud-based platform to execute your automation tests in parallel and trim down your test execution time significantly. Your first 100 automation testing minutes are on us.

Try LambdaTest
LambdaTestX

We use cookies to give you the best experience. Cookies help to provide a more personalized experience and relevant advertising for you, and web analytics for us. Learn More in our Cookies policy, Privacy & Terms of service

Allow Cookie