Home | History | Annotate | Download | only in testing
      1 /*
      2  * Copyright (C) 2005 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 
     22 import com.google.common.annotations.Beta;
     23 import com.google.common.base.Converter;
     24 import com.google.common.base.Objects;
     25 import com.google.common.collect.ClassToInstanceMap;
     26 import com.google.common.collect.ImmutableList;
     27 import com.google.common.collect.Lists;
     28 import com.google.common.collect.Maps;
     29 import com.google.common.collect.MutableClassToInstanceMap;
     30 import com.google.common.reflect.Invokable;
     31 import com.google.common.reflect.Parameter;
     32 import com.google.common.reflect.Reflection;
     33 import com.google.common.reflect.TypeToken;
     34 
     35 import junit.framework.Assert;
     36 import junit.framework.AssertionFailedError;
     37 
     38 import java.lang.reflect.Constructor;
     39 import java.lang.reflect.InvocationTargetException;
     40 import java.lang.reflect.Member;
     41 import java.lang.reflect.Method;
     42 import java.lang.reflect.Modifier;
     43 import java.lang.reflect.ParameterizedType;
     44 import java.lang.reflect.Type;
     45 import java.util.Arrays;
     46 import java.util.List;
     47 import java.util.concurrent.ConcurrentMap;
     48 
     49 import javax.annotation.Nullable;
     50 
     51 /**
     52  * A test utility that verifies that your methods and constructors throw {@link
     53  * NullPointerException} or {@link UnsupportedOperationException} whenever null
     54  * is passed to a parameter that isn't annotated with {@link Nullable}.
     55  *
     56  * <p>The tested methods and constructors are invoked -- each time with one
     57  * parameter being null and the rest not null -- and the test fails if no
     58  * expected exception is thrown. {@code NullPointerTester} uses best effort to
     59  * pick non-null default values for many common JDK and Guava types, and also
     60  * for interfaces and public classes that have public parameter-less
     61  * constructors. When the non-null default value for a particular parameter type
     62  * cannot be provided by {@code NullPointerTester}, the caller can provide a
     63  * custom non-null default value for the parameter type via {@link #setDefault}.
     64  *
     65  * @author Kevin Bourrillion
     66  * @since 10.0
     67  */
     68 @Beta
     69 public final class NullPointerTester {
     70 
     71   private final ClassToInstanceMap<Object> defaults =
     72       MutableClassToInstanceMap.create();
     73   private final List<Member> ignoredMembers = Lists.newArrayList();
     74 
     75   private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE;
     76 
     77   /**
     78    * Sets a default value that can be used for any parameter of type
     79    * {@code type}. Returns this object.
     80    */
     81   public <T> NullPointerTester setDefault(Class<T> type, T value) {
     82     defaults.putInstance(type, checkNotNull(value));
     83     return this;
     84   }
     85 
     86   /**
     87    * Ignore {@code method} in the tests that follow. Returns this object.
     88    *
     89    * @since 13.0
     90    */
     91   public NullPointerTester ignore(Method method) {
     92     ignoredMembers.add(checkNotNull(method));
     93     return this;
     94   }
     95 
     96   /**
     97    * Runs {@link #testConstructor} on every constructor in class {@code c} that
     98    * has at least {@code minimalVisibility}.
     99    */
    100   public void testConstructors(Class<?> c, Visibility minimalVisibility) {
    101     for (Constructor<?> constructor : c.getDeclaredConstructors()) {
    102       if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) {
    103         testConstructor(constructor);
    104       }
    105     }
    106   }
    107 
    108   /**
    109    * Runs {@link #testConstructor} on every public constructor in class {@code
    110    * c}.
    111    */
    112   public void testAllPublicConstructors(Class<?> c) {
    113     testConstructors(c, Visibility.PUBLIC);
    114   }
    115 
    116   /**
    117    * Runs {@link #testMethod} on every static method of class {@code c} that has
    118    * at least {@code minimalVisibility}, including those "inherited" from
    119    * superclasses of the same package.
    120    */
    121   public void testStaticMethods(Class<?> c, Visibility minimalVisibility) {
    122     for (Method method : minimalVisibility.getStaticMethods(c)) {
    123       if (!isIgnored(method)) {
    124         testMethod(null, method);
    125       }
    126     }
    127   }
    128 
    129   /**
    130    * Runs {@link #testMethod} on every public static method of class {@code c},
    131    * including those "inherited" from superclasses of the same package.
    132    */
    133   public void testAllPublicStaticMethods(Class<?> c) {
    134     testStaticMethods(c, Visibility.PUBLIC);
    135   }
    136 
    137   /**
    138    * Runs {@link #testMethod} on every instance method of the class of
    139    * {@code instance} with at least {@code minimalVisibility}, including those
    140    * inherited from superclasses of the same package.
    141    */
    142   public void testInstanceMethods(Object instance, Visibility minimalVisibility) {
    143     for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) {
    144       testMethod(instance, method);
    145     }
    146   }
    147 
    148   ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) {
    149     ImmutableList.Builder<Method> builder = ImmutableList.builder();
    150     for (Method method : minimalVisibility.getInstanceMethods(c)) {
    151       if (!isIgnored(method)) {
    152         builder.add(method);
    153       }
    154     }
    155     return builder.build();
    156   }
    157 
    158   /**
    159    * Runs {@link #testMethod} on every public instance method of the class of
    160    * {@code instance}, including those inherited from superclasses of the same
    161    * package.
    162    */
    163   public void testAllPublicInstanceMethods(Object instance) {
    164     testInstanceMethods(instance, Visibility.PUBLIC);
    165   }
    166 
    167   /**
    168    * Verifies that {@code method} produces a {@link NullPointerException}
    169    * or {@link UnsupportedOperationException} whenever <i>any</i> of its
    170    * non-{@link Nullable} parameters are null.
    171    *
    172    * @param instance the instance to invoke {@code method} on, or null if
    173    *     {@code method} is static
    174    */
    175   public void testMethod(@Nullable Object instance, Method method) {
    176     Class<?>[] types = method.getParameterTypes();
    177     for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
    178       testMethodParameter(instance, method, nullIndex);
    179     }
    180   }
    181 
    182   /**
    183    * Verifies that {@code ctor} produces a {@link NullPointerException} or
    184    * {@link UnsupportedOperationException} whenever <i>any</i> of its
    185    * non-{@link Nullable} parameters are null.
    186    */
    187   public void testConstructor(Constructor<?> ctor) {
    188     Class<?> declaringClass = ctor.getDeclaringClass();
    189     checkArgument(Modifier.isStatic(declaringClass.getModifiers())
    190         || declaringClass.getEnclosingClass() == null,
    191         "Cannot test constructor of non-static inner class: %s", declaringClass.getName());
    192     Class<?>[] types = ctor.getParameterTypes();
    193     for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
    194       testConstructorParameter(ctor, nullIndex);
    195     }
    196   }
    197 
    198   /**
    199    * Verifies that {@code method} produces a {@link NullPointerException} or
    200    * {@link UnsupportedOperationException} when the parameter in position {@code
    201    * paramIndex} is null.  If this parameter is marked {@link Nullable}, this
    202    * method does nothing.
    203    *
    204    * @param instance the instance to invoke {@code method} on, or null if
    205    *     {@code method} is static
    206    */
    207   public void testMethodParameter(
    208       @Nullable final Object instance, final Method method, int paramIndex) {
    209     method.setAccessible(true);
    210     testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass());
    211   }
    212 
    213   /**
    214    * Verifies that {@code ctor} produces a {@link NullPointerException} or
    215    * {@link UnsupportedOperationException} when the parameter in position {@code
    216    * paramIndex} is null.  If this parameter is marked {@link Nullable}, this
    217    * method does nothing.
    218    */
    219   public void testConstructorParameter(Constructor<?> ctor, int paramIndex) {
    220     ctor.setAccessible(true);
    221     testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass());
    222   }
    223 
    224   /** Visibility of any method or constructor. */
    225   public enum Visibility {
    226 
    227     PACKAGE {
    228       @Override boolean isVisible(int modifiers) {
    229         return !Modifier.isPrivate(modifiers);
    230       }
    231     },
    232 
    233     PROTECTED {
    234       @Override boolean isVisible(int modifiers) {
    235         return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers);
    236       }
    237     },
    238 
    239     PUBLIC {
    240       @Override boolean isVisible(int modifiers) {
    241         return Modifier.isPublic(modifiers);
    242       }
    243     };
    244 
    245     abstract boolean isVisible(int modifiers);
    246 
    247     /**
    248      * Returns {@code true} if {@code member} is visible under {@code this}
    249      * visibility.
    250      */
    251     final boolean isVisible(Member member) {
    252       return isVisible(member.getModifiers());
    253     }
    254 
    255     final Iterable<Method> getStaticMethods(Class<?> cls) {
    256       ImmutableList.Builder<Method> builder = ImmutableList.builder();
    257       for (Method method : getVisibleMethods(cls)) {
    258         if (Invokable.from(method).isStatic()) {
    259           builder.add(method);
    260         }
    261       }
    262       return builder.build();
    263     }
    264 
    265     final Iterable<Method> getInstanceMethods(Class<?> cls) {
    266       ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap();
    267       for (Method method : getVisibleMethods(cls)) {
    268         if (!Invokable.from(method).isStatic()) {
    269           map.putIfAbsent(new Signature(method), method);
    270         }
    271       }
    272       return map.values();
    273     }
    274 
    275     private ImmutableList<Method> getVisibleMethods(Class<?> cls) {
    276       // Don't use cls.getPackage() because it does nasty things like reading
    277       // a file.
    278       String visiblePackage = Reflection.getPackageName(cls);
    279       ImmutableList.Builder<Method> builder = ImmutableList.builder();
    280       for (Class<?> type : TypeToken.of(cls).getTypes().classes().rawTypes()) {
    281         if (!Reflection.getPackageName(type).equals(visiblePackage)) {
    282           break;
    283         }
    284         for (Method method : type.getDeclaredMethods()) {
    285           if (!method.isSynthetic() && isVisible(method)) {
    286             builder.add(method);
    287           }
    288         }
    289       }
    290       return builder.build();
    291     }
    292   }
    293 
    294   // TODO(benyu): Use labs/reflect/Signature if it graduates.
    295   private static final class Signature {
    296     private final String name;
    297     private final ImmutableList<Class<?>> parameterTypes;
    298 
    299     Signature(Method method) {
    300       this(method.getName(), ImmutableList.copyOf(method.getParameterTypes()));
    301     }
    302 
    303     Signature(String name, ImmutableList<Class<?>> parameterTypes) {
    304       this.name = name;
    305       this.parameterTypes = parameterTypes;
    306     }
    307 
    308     @Override public boolean equals(Object obj) {
    309       if (obj instanceof Signature) {
    310         Signature that = (Signature) obj;
    311         return name.equals(that.name)
    312             && parameterTypes.equals(that.parameterTypes);
    313       }
    314       return false;
    315     }
    316 
    317     @Override public int hashCode() {
    318       return Objects.hashCode(name, parameterTypes);
    319     }
    320   }
    321 
    322   /**
    323    * Verifies that {@code invokable} produces a {@link NullPointerException} or
    324    * {@link UnsupportedOperationException} when the parameter in position {@code
    325    * paramIndex} is null.  If this parameter is marked {@link Nullable}, this
    326    * method does nothing.
    327    *
    328    * @param instance the instance to invoke {@code invokable} on, or null if
    329    *     {@code invokable} is static
    330    */
    331   private void testParameter(Object instance, Invokable<?, ?> invokable,
    332       int paramIndex, Class<?> testedClass) {
    333     if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) {
    334       return; // there's nothing to test
    335     }
    336     Object[] params = buildParamList(invokable, paramIndex);
    337     try {
    338       @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong.
    339       Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable;
    340       unsafe.invoke(instance, params);
    341       Assert.fail("No exception thrown for parameter at index " + paramIndex
    342           + " from " + invokable + Arrays.toString(params) + " for " + testedClass);
    343     } catch (InvocationTargetException e) {
    344       Throwable cause = e.getCause();
    345       if (policy.isExpectedType(cause)) {
    346         return;
    347       }
    348       AssertionFailedError error = new AssertionFailedError(
    349           "wrong exception thrown from " + invokable + ": " + cause);
    350       error.initCause(cause);
    351       throw error;
    352     } catch (IllegalAccessException e) {
    353       throw new RuntimeException(e);
    354     }
    355   }
    356 
    357   private Object[] buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull) {
    358     ImmutableList<Parameter> params = invokable.getParameters();
    359     Object[] args = new Object[params.size()];
    360 
    361     for (int i = 0; i < args.length; i++) {
    362       Parameter param = params.get(i);
    363       if (i != indexOfParamToSetToNull) {
    364         args[i] = getDefaultValue(param.getType());
    365         Assert.assertTrue(
    366             "Can't find or create a sample instance for type '"
    367                 + param.getType()
    368                 + "'; please provide one using NullPointerTester.setDefault()",
    369             args[i] != null || isNullable(param));
    370       }
    371     }
    372     return args;
    373   }
    374 
    375   private <T> T getDefaultValue(TypeToken<T> type) {
    376     // We assume that all defaults are generics-safe, even if they aren't,
    377     // we take the risk.
    378     @SuppressWarnings("unchecked")
    379     T defaultValue = (T) defaults.getInstance(type.getRawType());
    380     if (defaultValue != null) {
    381       return defaultValue;
    382     }
    383     @SuppressWarnings("unchecked") // All arbitrary instances are generics-safe
    384     T arbitrary = (T) ArbitraryInstances.get(type.getRawType());
    385     if (arbitrary != null) {
    386       return arbitrary;
    387     }
    388     if (type.getRawType() == Class.class) {
    389       // If parameter is Class<? extends Foo>, we return Foo.class
    390       @SuppressWarnings("unchecked")
    391       T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType();
    392       return defaultClass;
    393     }
    394     if (type.getRawType() == TypeToken.class) {
    395       // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>.
    396       @SuppressWarnings("unchecked")
    397       T defaultType = (T) getFirstTypeParameter(type.getType());
    398       return defaultType;
    399     }
    400     if (type.getRawType() == Converter.class) {
    401       TypeToken<?> convertFromType = type.resolveType(
    402           Converter.class.getTypeParameters()[0]);
    403       TypeToken<?> convertToType = type.resolveType(
    404           Converter.class.getTypeParameters()[1]);
    405       @SuppressWarnings("unchecked") // returns default for both F and T
    406       T defaultConverter = (T) defaultConverter(convertFromType, convertToType);
    407       return defaultConverter;
    408     }
    409     if (type.getRawType().isInterface()) {
    410       return newDefaultReturningProxy(type);
    411     }
    412     return null;
    413   }
    414 
    415   private <F, T> Converter<F, T> defaultConverter(
    416       final TypeToken<F> convertFromType, final TypeToken<T> convertToType) {
    417     return new Converter<F, T>() {
    418       @Override protected T doForward(F a) {
    419         return doConvert(convertToType);
    420       }
    421       @Override protected F doBackward(T b) {
    422         return doConvert(convertFromType);
    423       }
    424 
    425       private /*static*/ <S> S doConvert(TypeToken<S> type) {
    426         return checkNotNull(getDefaultValue(type));
    427       }
    428     };
    429   }
    430 
    431   private static TypeToken<?> getFirstTypeParameter(Type type) {
    432     if (type instanceof ParameterizedType) {
    433       return TypeToken.of(
    434           ((ParameterizedType) type).getActualTypeArguments()[0]);
    435     } else {
    436       return TypeToken.of(Object.class);
    437     }
    438   }
    439 
    440   private <T> T newDefaultReturningProxy(final TypeToken<T> type) {
    441     return new DummyProxy() {
    442       @Override <R> R dummyReturnValue(TypeToken<R> returnType) {
    443         return getDefaultValue(returnType);
    444       }
    445     }.newProxy(type);
    446   }
    447 
    448   private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) {
    449     if (instance == null) {
    450       return Invokable.from(method);
    451     } else {
    452       return TypeToken.of(instance.getClass()).method(method);
    453     }
    454   }
    455 
    456   static boolean isPrimitiveOrNullable(Parameter param) {
    457     return param.getType().getRawType().isPrimitive() || isNullable(param);
    458   }
    459 
    460   private static boolean isNullable(Parameter param) {
    461     return param.isAnnotationPresent(Nullable.class);
    462   }
    463 
    464   private boolean isIgnored(Member member) {
    465     return member.isSynthetic() || ignoredMembers.contains(member);
    466   }
    467 
    468   /**
    469    * Strategy for exception type matching used by {@link NullPointerTester}.
    470    */
    471   private enum ExceptionTypePolicy {
    472 
    473     /**
    474      * Exceptions should be {@link NullPointerException} or
    475      * {@link UnsupportedOperationException}.
    476      */
    477     NPE_OR_UOE() {
    478       @Override
    479       public boolean isExpectedType(Throwable cause) {
    480         return cause instanceof NullPointerException
    481             || cause instanceof UnsupportedOperationException;
    482       }
    483     },
    484 
    485     /**
    486      * Exceptions should be {@link NullPointerException},
    487      * {@link IllegalArgumentException}, or
    488      * {@link UnsupportedOperationException}.
    489      */
    490     NPE_IAE_OR_UOE() {
    491       @Override
    492       public boolean isExpectedType(Throwable cause) {
    493         return cause instanceof NullPointerException
    494             || cause instanceof IllegalArgumentException
    495             || cause instanceof UnsupportedOperationException;
    496       }
    497     };
    498 
    499     public abstract boolean isExpectedType(Throwable cause);
    500   }
    501 }
    502