blob: 8a9e8cad18539c14dd344405f76d86c0ca1344c0 [file] [log] [blame]
/*
* 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;
}
}