Coverage Summary for Class: ForwardingWrapperTester (com.google.common.testing)

Class Method, % Line, %
ForwardingWrapperTester 100% (9/9) 89.2% (58/65)
ForwardingWrapperTester$1 100% (2/2) 100% (2/2)
ForwardingWrapperTester$InteractionTester 75% (3/4) 88.9% (24/27)
Total 93.3% (14/15) 89.4% (84/94)


1 /* 2  * Copyright (C) 2012 The Guava 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 com.google.common.testing; 18  19 import static com.google.common.base.Preconditions.checkArgument; 20 import static com.google.common.base.Preconditions.checkNotNull; 21 import static com.google.common.base.Throwables.throwIfUnchecked; 22 import static junit.framework.Assert.assertEquals; 23 import static junit.framework.Assert.fail; 24  25 import com.google.common.annotations.Beta; 26 import com.google.common.annotations.GwtIncompatible; 27 import com.google.common.base.Function; 28 import com.google.common.base.Throwables; 29 import com.google.common.collect.Lists; 30 import com.google.common.reflect.AbstractInvocationHandler; 31 import com.google.common.reflect.Reflection; 32 import java.lang.reflect.AccessibleObject; 33 import java.lang.reflect.InvocationTargetException; 34 import java.lang.reflect.Method; 35 import java.lang.reflect.Modifier; 36 import java.util.List; 37 import java.util.concurrent.atomic.AtomicInteger; 38  39 /** 40  * Tester to ensure forwarding wrapper works by delegating calls to the corresponding method with 41  * the same parameters forwarded and return value forwarded back or exception propagated as is. 42  * 43  * <p>For example: 44  * 45  * <pre>{@code 46  * new ForwardingWrapperTester().testForwarding(Foo.class, new Function<Foo, Foo>() { 47  * public Foo apply(Foo foo) { 48  * return new ForwardingFoo(foo); 49  * } 50  * }); 51  * }</pre> 52  * 53  * @author Ben Yu 54  * @since 14.0 55  */ 56 @Beta 57 @GwtIncompatible 58 public final class ForwardingWrapperTester { 59  60  private boolean testsEquals = false; 61  62  /** 63  * Asks for {@link Object#equals} and {@link Object#hashCode} to be tested. That is, forwarding 64  * wrappers of equal instances should be equal. 65  */ 66  public ForwardingWrapperTester includingEquals() { 67  this.testsEquals = true; 68  return this; 69  } 70  71  /** 72  * Tests that the forwarding wrapper returned by {@code wrapperFunction} properly forwards method 73  * calls with parameters passed as is, return value returned as is, and exceptions propagated as 74  * is. 75  */ 76  public <T> void testForwarding( 77  Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) { 78  checkNotNull(wrapperFunction); 79  checkArgument(interfaceType.isInterface(), "%s isn't an interface", interfaceType); 80  Method[] methods = getMostConcreteMethods(interfaceType); 81  AccessibleObject.setAccessible(methods, true); 82  for (Method method : methods) { 83  // Under java 8, interfaces can have default methods that aren't abstract. 84  // No need to verify them. 85  // Can't check isDefault() for JDK 7 compatibility. 86  if (!Modifier.isAbstract(method.getModifiers())) { 87  continue; 88  } 89  // The interface could be package-private or private. 90  // filter out equals/hashCode/toString 91  if (method.getName().equals("equals") 92  && method.getParameterTypes().length == 1 93  && method.getParameterTypes()[0] == Object.class) { 94  continue; 95  } 96  if (method.getName().equals("hashCode") && method.getParameterTypes().length == 0) { 97  continue; 98  } 99  if (method.getName().equals("toString") && method.getParameterTypes().length == 0) { 100  continue; 101  } 102  testSuccessfulForwarding(interfaceType, method, wrapperFunction); 103  testExceptionPropagation(interfaceType, method, wrapperFunction); 104  } 105  if (testsEquals) { 106  testEquals(interfaceType, wrapperFunction); 107  } 108  testToString(interfaceType, wrapperFunction); 109  } 110  111  /** Returns the most concrete public methods from {@code type}. */ 112  private static Method[] getMostConcreteMethods(Class<?> type) { 113  Method[] methods = type.getMethods(); 114  for (int i = 0; i < methods.length; i++) { 115  try { 116  methods[i] = type.getMethod(methods[i].getName(), methods[i].getParameterTypes()); 117  } catch (Exception e) { 118  throwIfUnchecked(e); 119  throw new RuntimeException(e); 120  } 121  } 122  return methods; 123  } 124  125  private static <T> void testSuccessfulForwarding( 126  Class<T> interfaceType, Method method, Function<? super T, ? extends T> wrapperFunction) { 127  new InteractionTester<T>(interfaceType, method).testInteraction(wrapperFunction); 128  } 129  130  private static <T> void testExceptionPropagation( 131  Class<T> interfaceType, Method method, Function<? super T, ? extends T> wrapperFunction) { 132  final RuntimeException exception = new RuntimeException(); 133  T proxy = 134  Reflection.newProxy( 135  interfaceType, 136  new AbstractInvocationHandler() { 137  @Override 138  protected Object handleInvocation(Object p, Method m, Object[] args) 139  throws Throwable { 140  throw exception; 141  } 142  }); 143  T wrapper = wrapperFunction.apply(proxy); 144  try { 145  method.invoke(wrapper, getParameterValues(method)); 146  fail(method + " failed to throw exception as is."); 147  } catch (InvocationTargetException e) { 148  if (exception != e.getCause()) { 149  throw new RuntimeException(e); 150  } 151  } catch (IllegalAccessException e) { 152  throw new AssertionError(e); 153  } 154  } 155  156  private static <T> void testEquals( 157  Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) { 158  FreshValueGenerator generator = new FreshValueGenerator(); 159  T instance = generator.newFreshProxy(interfaceType); 160  new EqualsTester() 161  .addEqualityGroup(wrapperFunction.apply(instance), wrapperFunction.apply(instance)) 162  .addEqualityGroup(wrapperFunction.apply(generator.newFreshProxy(interfaceType))) 163  // TODO: add an overload to EqualsTester to print custom error message? 164  .testEquals(); 165  } 166  167  private static <T> void testToString( 168  Class<T> interfaceType, Function<? super T, ? extends T> wrapperFunction) { 169  T proxy = new FreshValueGenerator().newFreshProxy(interfaceType); 170  assertEquals( 171  "toString() isn't properly forwarded", 172  proxy.toString(), 173  wrapperFunction.apply(proxy).toString()); 174  } 175  176  private static Object[] getParameterValues(Method method) { 177  FreshValueGenerator paramValues = new FreshValueGenerator(); 178  final List<Object> passedArgs = Lists.newArrayList(); 179  for (Class<?> paramType : method.getParameterTypes()) { 180  passedArgs.add(paramValues.generateFresh(paramType)); 181  } 182  return passedArgs.toArray(); 183  } 184  185  /** Tests a single interaction against a method. */ 186  private static final class InteractionTester<T> extends AbstractInvocationHandler { 187  188  private final Class<T> interfaceType; 189  private final Method method; 190  private final Object[] passedArgs; 191  private final Object returnValue; 192  private final AtomicInteger called = new AtomicInteger(); 193  194  InteractionTester(Class<T> interfaceType, Method method) { 195  this.interfaceType = interfaceType; 196  this.method = method; 197  this.passedArgs = getParameterValues(method); 198  this.returnValue = new FreshValueGenerator().generateFresh(method.getReturnType()); 199  } 200  201  @Override 202  protected Object handleInvocation(Object p, Method calledMethod, Object[] args) 203  throws Throwable { 204  assertEquals(method, calledMethod); 205  assertEquals(method + " invoked more than once.", 0, called.get()); 206  for (int i = 0; i < passedArgs.length; i++) { 207  assertEquals( 208  "Parameter #" + i + " of " + method + " not forwarded", passedArgs[i], args[i]); 209  } 210  called.getAndIncrement(); 211  return returnValue; 212  } 213  214  void testInteraction(Function<? super T, ? extends T> wrapperFunction) { 215  T proxy = Reflection.newProxy(interfaceType, this); 216  T wrapper = wrapperFunction.apply(proxy); 217  boolean isPossibleChainingCall = interfaceType.isAssignableFrom(method.getReturnType()); 218  try { 219  Object actualReturnValue = method.invoke(wrapper, passedArgs); 220  // If we think this might be a 'chaining' call then we allow the return value to either 221  // be the wrapper or the returnValue. 222  if (!isPossibleChainingCall || wrapper != actualReturnValue) { 223  assertEquals( 224  "Return value of " + method + " not forwarded", returnValue, actualReturnValue); 225  } 226  } catch (IllegalAccessException e) { 227  throw new RuntimeException(e); 228  } catch (InvocationTargetException e) { 229  throw Throwables.propagate(e.getCause()); 230  } 231  assertEquals("Failed to forward to " + method, 1, called.get()); 232  } 233  234  @Override 235  public String toString() { 236  return "dummy " + interfaceType.getSimpleName(); 237  } 238  } 239 }