| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you under the Apache License, Version 2.0 (the |
| * "License"); you may not use this file except in compliance |
| * with the License. You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| package org.apache.flink.util; |
| |
| import org.mockito.Mockito; |
| import org.mockito.internal.util.MockUtil; |
| |
| import java.lang.reflect.Array; |
| import java.lang.reflect.InvocationTargetException; |
| import java.lang.reflect.Method; |
| import java.util.Collections; |
| import java.util.Set; |
| import java.util.function.Function; |
| import java.util.function.Supplier; |
| |
| import static org.mockito.Mockito.reset; |
| import static org.mockito.Mockito.spy; |
| |
| /** |
| * Helper class with a method that attempts to automatically test method forwarding between a delegate and a wrapper. |
| */ |
| public class MethodForwardingTestUtil { |
| |
| /** |
| * This is a best effort automatic test for method forwarding between a delegate and its wrapper, where the wrapper |
| * class is a subtype of the delegate. This ignores methods that are inherited from Object. |
| * |
| * @param delegateClass the class for the delegate. |
| * @param wrapperFactory factory that produces a wrapper from a delegate. |
| * @param <D> type of the delegate |
| * @param <W> type of the wrapper |
| */ |
| public static <D, W> void testMethodForwarding( |
| Class<D> delegateClass, |
| Function<D, W> wrapperFactory) |
| throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { |
| testMethodForwarding(delegateClass, wrapperFactory, () -> spy(delegateClass), Collections.emptySet()); |
| } |
| |
| /** |
| * This is a best effort automatic test for method forwarding between a delegate and its wrapper, where the wrapper |
| * class is a subtype of the delegate. This ignores methods that are inherited from Object. |
| * |
| * @param delegateClass the class for the delegate. |
| * @param wrapperFactory factory that produces a wrapper from a delegate. |
| * @param delegateObjectSupplier supplier for the delegate object passed to the wrapper factory. |
| * @param <D> type of the delegate |
| * @param <W> type of the wrapper |
| * @param <I> type of the object created as delegate, is a subtype of D. |
| */ |
| public static <D, W, I extends D> void testMethodForwarding( |
| Class<D> delegateClass, |
| Function<I, W> wrapperFactory, |
| Supplier<I> delegateObjectSupplier) |
| throws NoSuchMethodException, IllegalAccessException, InvocationTargetException { |
| testMethodForwarding(delegateClass, wrapperFactory, delegateObjectSupplier, Collections.emptySet()); |
| } |
| |
| /** |
| * This is a best effort automatic test for method forwarding between a delegate and its wrapper, where the wrapper |
| * class is a subtype of the delegate. Methods can be remapped in case that the implementation does not call the |
| * original method. Remapping to null skips the method. This ignores methods that are inherited from Object. |
| * |
| * @param delegateClass the class for the delegate. |
| * @param wrapperFactory factory that produces a wrapper from a delegate. |
| * @param delegateObjectSupplier supplier for the delegate object passed to the wrapper factory. |
| * @param skipMethodSet set of methods to ignore. |
| * @param <D> type of the delegate |
| * @param <W> type of the wrapper |
| * @param <I> type of the object created as delegate, is a subtype of D. |
| */ |
| public static <D, W, I extends D> void testMethodForwarding( |
| Class<D> delegateClass, |
| Function<I, W> wrapperFactory, |
| Supplier<I> delegateObjectSupplier, |
| Set<Method> skipMethodSet) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { |
| |
| Preconditions.checkNotNull(delegateClass); |
| Preconditions.checkNotNull(wrapperFactory); |
| Preconditions.checkNotNull(skipMethodSet); |
| |
| I delegate = delegateObjectSupplier.get(); |
| |
| //check if we need to wrap the delegate object as a spy, or if it is already testable with Mockito. |
| MockUtil mockUtil = new MockUtil(); |
| if (!mockUtil.isSpy(delegate) || !mockUtil.isMock(delegate)) { |
| delegate = spy(delegate); |
| } |
| |
| W wrapper = wrapperFactory.apply(delegate); |
| |
| // ensure that wrapper is a subtype of delegate |
| Preconditions.checkArgument(delegateClass.isAssignableFrom(wrapper.getClass())); |
| |
| for (Method delegateMethod : delegateClass.getMethods()) { |
| |
| if (checkSkipMethodForwardCheck(delegateMethod, skipMethodSet)) { |
| continue; |
| } |
| |
| // find the correct method to substitute the bridge for erased generic types. |
| // if this doesn't work, the user need to exclude the method and write an additional test. |
| Method wrapperMethod = wrapper.getClass().getMethod( |
| delegateMethod.getName(), |
| delegateMethod.getParameterTypes()); |
| |
| // things get a bit fuzzy here, best effort to find a match but this might end up with a wrong method. |
| if (wrapperMethod.isBridge()) { |
| for (Method method : wrapper.getClass().getMethods()) { |
| if (!method.isBridge() |
| && method.getName().equals(wrapperMethod.getName()) |
| && method.getParameterCount() == wrapperMethod.getParameterCount()) { |
| wrapperMethod = method; |
| break; |
| } |
| } |
| } |
| |
| Class<?>[] parameterTypes = wrapperMethod.getParameterTypes(); |
| Object[] arguments = new Object[parameterTypes.length]; |
| for (int j = 0; j < arguments.length; j++) { |
| Class<?> parameterType = parameterTypes[j]; |
| if (parameterType.isArray()) { |
| arguments[j] = Array.newInstance(parameterType.getComponentType(), 0); |
| } else if (parameterType.isPrimitive()) { |
| if (boolean.class.equals(parameterType)) { |
| arguments[j] = false; |
| } else if (char.class.equals(parameterType)) { |
| arguments[j] = 'a'; |
| } else { |
| arguments[j] = (byte) 0; |
| } |
| } else { |
| arguments[j] = Mockito.mock(parameterType); |
| } |
| } |
| |
| wrapperMethod.invoke(wrapper, arguments); |
| delegateMethod.invoke(Mockito.verify(delegate, Mockito.times(1)), arguments); |
| reset(delegate); |
| } |
| } |
| |
| /** |
| * Test if this method should be skipped in our check for proper forwarding, e.g. because it is just a bridge. |
| */ |
| private static boolean checkSkipMethodForwardCheck(Method delegateMethod, Set<Method> skipMethods) { |
| |
| if (delegateMethod.isBridge() |
| || delegateMethod.isDefault() |
| || skipMethods.contains(delegateMethod)) { |
| return true; |
| } |
| |
| // skip methods declared in Object (Mockito doesn't like them) |
| try { |
| Object.class.getMethod(delegateMethod.getName(), delegateMethod.getParameterTypes()); |
| return true; |
| } catch (Exception ignore) { |
| } |
| return false; |
| } |
| } |