1 /* 2 * Copyright 2017 The gRPC Authors 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package io.grpc; 18 19 import static junit.framework.TestCase.assertFalse; 20 import static org.junit.Assert.assertEquals; 21 import static org.junit.Assert.assertTrue; 22 import static org.mockito.Mockito.mockingDetails; 23 import static org.mockito.Mockito.verify; 24 25 import com.google.common.base.Defaults; 26 import com.google.common.base.MoreObjects; 27 import java.lang.reflect.InvocationTargetException; 28 import java.lang.reflect.Method; 29 import java.lang.reflect.Modifier; 30 import java.util.Collection; 31 import javax.annotation.Nullable; 32 33 /** 34 * A util class to help test forwarding classes. 35 */ 36 public final class ForwardingTestUtil { 37 /** 38 * Use reflection to perform a basic sanity test. The forwarding class should forward all public 39 * methods to the delegate, except for those in skippedMethods. This does NOT verify that 40 * arguments or return values are forwarded properly. 41 * 42 * @param delegateClass The class whose methods should be forwarded. 43 * @param mockDelegate The mockito mock of the delegate class. 44 * @param forwarder The forwarder object that forwards to the mockDelegate. 45 * @param skippedMethods A collection of methods that are skipped by the test. 46 */ testMethodsForwarded( Class<T> delegateClass, T mockDelegate, T forwarder, Collection<Method> skippedMethods)47 public static <T> void testMethodsForwarded( 48 Class<T> delegateClass, 49 T mockDelegate, 50 T forwarder, 51 Collection<Method> skippedMethods) throws Exception { 52 testMethodsForwarded( 53 delegateClass, mockDelegate, forwarder, skippedMethods, 54 new ArgumentProvider() { 55 @Override 56 public Object get(Method method, int argPos, Class<?> clazz) { 57 return null; 58 } 59 }); 60 } 61 62 /** 63 * Use reflection to perform a basic sanity test. The forwarding class should forward all public 64 * methods to the delegate, except for those in skippedMethods. This does NOT verify that return 65 * values are forwarded properly, and can only verify the propagation of arguments for which 66 * {@code argProvider} returns distinctive non-null values. 67 * 68 * @param delegateClass The class whose methods should be forwarded. 69 * @param mockDelegate The mockito mock of the delegate class. 70 * @param forwarder The forwarder object that forwards to the mockDelegate. 71 * @param skippedMethods A collection of methods that are skipped by the test. 72 * @param argProvider provides argument to be passed to tested forwarding methods. 73 */ testMethodsForwarded( Class<T> delegateClass, T mockDelegate, T forwarder, Collection<Method> skippedMethods, ArgumentProvider argProvider)74 public static <T> void testMethodsForwarded( 75 Class<T> delegateClass, 76 T mockDelegate, 77 T forwarder, 78 Collection<Method> skippedMethods, 79 ArgumentProvider argProvider) throws Exception { 80 assertTrue(mockingDetails(mockDelegate).isMock()); 81 assertFalse(mockingDetails(forwarder).isMock()); 82 83 for (Method method : delegateClass.getDeclaredMethods()) { 84 if (Modifier.isStatic(method.getModifiers()) 85 || Modifier.isPrivate(method.getModifiers()) 86 || Modifier.isFinal(method.getModifiers()) 87 || skippedMethods.contains(method)) { 88 continue; 89 } 90 Class<?>[] argTypes = method.getParameterTypes(); 91 Object[] args = new Object[argTypes.length]; 92 for (int i = 0; i < argTypes.length; i++) { 93 if ((args[i] = argProvider.get(method, i, argTypes[i])) == null) { 94 args[i] = Defaults.defaultValue(argTypes[i]); 95 } 96 } 97 method.invoke(forwarder, args); 98 try { 99 method.invoke(verify(mockDelegate), args); 100 } catch (InvocationTargetException e) { 101 AssertionError ae = 102 new AssertionError(String.format("Method was not forwarded: %s", method)); 103 ae.initCause(e); 104 throw ae; 105 } 106 } 107 108 boolean skipToString = false; 109 for (Method method : skippedMethods) { 110 if (method.getName().equals("toString")) { 111 skipToString = true; 112 break; 113 } 114 } 115 if (!skipToString) { 116 String actual = forwarder.toString(); 117 String expected = 118 MoreObjects.toStringHelper(forwarder).add("delegate", mockDelegate).toString(); 119 assertEquals("Method toString() was not forwarded properly", expected, actual); 120 } 121 } 122 123 /** 124 * Provides arguments for forwarded methods tested in {@link #testMethodsForwarded}. 125 */ 126 public interface ArgumentProvider { 127 /** 128 * Return an instance of the given class to be used as an argument passed to one method call. 129 * If one method has multiple arguments with the same type, each occurrence will call this 130 * method once. It is recommended that each invocation returns a distinctive object for the 131 * same type, in order to verify that arguments are passed by the tested class correctly. 132 * 133 * @return a value to be passed as an argument. If {@code null}, {@link Defaults#defaultValue} 134 * will be used. 135 */ get(Method method, int argPos, Class<?> clazz)136 @Nullable Object get(Method method, int argPos, Class<?> clazz); 137 } 138 } 139