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 com.google.common.annotations.Beta;
     20 import com.google.common.base.Function;
     21 import com.google.common.base.Functions;
     22 import com.google.common.base.Predicate;
     23 import com.google.common.base.Predicates;
     24 import com.google.common.base.Supplier;
     25 import com.google.common.base.Suppliers;
     26 import com.google.common.collect.Iterators;
     27 import com.google.common.collect.Lists;
     28 import com.google.common.collect.Maps;
     29 
     30 import junit.framework.Assert;
     31 import junit.framework.AssertionFailedError;
     32 
     33 import java.lang.annotation.Annotation;
     34 import java.lang.reflect.Constructor;
     35 import java.lang.reflect.InvocationTargetException;
     36 import java.lang.reflect.Member;
     37 import java.lang.reflect.Method;
     38 import java.lang.reflect.Modifier;
     39 import java.util.Arrays;
     40 import java.util.Collection;
     41 import java.util.Collections;
     42 import java.util.Comparator;
     43 import java.util.Iterator;
     44 import java.util.List;
     45 import java.util.Map;
     46 import java.util.Set;
     47 import java.util.SortedSet;
     48 import java.util.TreeSet;
     49 import java.util.concurrent.TimeUnit;
     50 import java.util.regex.Pattern;
     51 
     52 import javax.annotation.Nullable;
     53 
     54 /**
     55  * A test utility that verifies that your methods throw {@link
     56  * NullPointerException} or {@link UnsupportedOperationException} whenever any
     57  * of their parameters are null. To use it, you must first provide valid default
     58  * values for the parameter types used by the class.
     59  *
     60  * @author Kevin Bourrillion
     61  * @since 10.0
     62  */
     63 @Beta
     64 public final class NullPointerTester {
     65   private final Map<Class<?>, Object> defaults = Maps.newHashMap();
     66   private final List<Member> ignoredMembers = Lists.newArrayList();
     67 
     68   public NullPointerTester() {
     69     setCommonDefaults();
     70   }
     71 
     72   private final void setCommonDefaults() {
     73     setDefault(Appendable.class, new StringBuilder());
     74     setDefault(CharSequence.class, "");
     75     setDefault(Class.class, Class.class);
     76     setDefault(Collection.class, Collections.emptySet());
     77     setDefault(Comparable.class, 0);
     78     setDefault(Comparator.class, Collections.reverseOrder());
     79     setDefault(Function.class, Functions.identity());
     80     setDefault(Integer.class, 0);
     81     setDefault(Iterable.class, Collections.emptySet());
     82     setDefault(Iterator.class, Iterators.emptyIterator());
     83     setDefault(List.class, Collections.emptyList());
     84     setDefault(Map.class, Collections.emptyMap());
     85     setDefault(Object.class, new Object());
     86     setDefault(Object[].class, new Object[0]);
     87     setDefault(Pattern.class, Pattern.compile(""));
     88     setDefault(Predicate.class, Predicates.alwaysTrue());
     89     setDefault(Set.class, Collections.emptySet());
     90     setDefault(SortedSet.class, new TreeSet());
     91     setDefault(String.class, "");
     92     setDefault(Supplier.class, Suppliers.ofInstance(1));
     93     setDefault(Throwable.class, new Exception());
     94     setDefault(TimeUnit.class, TimeUnit.SECONDS);
     95     setDefault(int.class, 0);
     96     setDefault(long.class, 0L);
     97     setDefault(short.class, (short) 0);
     98     setDefault(char.class, 'a');
     99     setDefault(byte.class, (byte) 0);
    100     setDefault(float.class, 0.0f);
    101     setDefault(double.class, 0.0d);
    102     setDefault(boolean.class, false);
    103   }
    104 
    105   /**
    106    * Sets a default value that can be used for any parameter of type
    107    * {@code type}. Returns this object.
    108    */
    109   public <T> NullPointerTester setDefault(Class<T> type, T value) {
    110     defaults.put(type, value);
    111     return this;
    112   }
    113 
    114   /**
    115    * Ignore a member (constructor or method) in testAllXxx methods. Returns
    116    * this object.
    117    */
    118   public NullPointerTester ignore(Member member) {
    119     ignoredMembers.add(member);
    120     return this;
    121   }
    122 
    123   /**
    124    * Runs {@link #testConstructor} on every public constructor in class {@code
    125    * c}.
    126    */
    127   public void testAllPublicConstructors(Class<?> c) throws Exception {
    128     for (Constructor<?> constructor : c.getDeclaredConstructors()) {
    129       if (isPublic(constructor) && !isStatic(constructor)
    130           && !isIgnored(constructor)) {
    131         testConstructor(constructor);
    132       }
    133     }
    134   }
    135 
    136   /**
    137    * Runs {@link #testMethod} on every public static method in class
    138    * {@code c}.
    139    */
    140   public void testAllPublicStaticMethods(Class<?> c) throws Exception {
    141     for (Method method : c.getDeclaredMethods()) {
    142       if (isPublic(method) && isStatic(method) && !isIgnored(method)) {
    143         testMethod(null, method);
    144       }
    145     }
    146   }
    147 
    148   /**
    149    * Runs {@link #testMethod} on every public instance method of
    150    * {@code instance}.
    151    */
    152   public void testAllPublicInstanceMethods(Object instance) throws Exception {
    153     Class<?> c = instance.getClass();
    154     for (Method method : c.getDeclaredMethods()) {
    155       if (isPublic(method) && !isStatic(method) && !isIgnored(method)) {
    156         testMethod(instance, method);
    157       }
    158     }
    159   }
    160 
    161   /**
    162    * Verifies that {@code method} produces a {@link NullPointerException}
    163    * or {@link UnsupportedOperationException} whenever <i>any</i> of its
    164    * non-{@link Nullable} parameters are null.
    165    *
    166    * @param instance the instance to invoke {@code method} on, or null if
    167    *     {@code method} is static
    168    */
    169   public void testMethod(Object instance, Method method) throws Exception {
    170     Class<?>[] types = method.getParameterTypes();
    171     for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
    172       testMethodParameter(instance, method, nullIndex);
    173     }
    174   }
    175 
    176   /**
    177    * Verifies that {@code ctor} produces a {@link NullPointerException} or
    178    * {@link UnsupportedOperationException} whenever <i>any</i> of its
    179    * non-{@link Nullable} parameters are null.
    180    */
    181   public void testConstructor(Constructor<?> ctor) throws Exception {
    182     Class<?>[] types = ctor.getParameterTypes();
    183     for (int nullIndex = 0; nullIndex < types.length; nullIndex++) {
    184       testConstructorParameter(ctor, nullIndex);
    185     }
    186   }
    187 
    188   /**
    189    * Verifies that {@code method} produces a {@link NullPointerException} or
    190    * {@link UnsupportedOperationException} when the parameter in position {@code
    191    * paramIndex} is null.  If this parameter is marked {@link Nullable}, this
    192    * method does nothing.
    193    *
    194    * @param instance the instance to invoke {@code method} on, or null if
    195    *     {@code method} is static
    196    */
    197   public void testMethodParameter(Object instance, final Method method,
    198       int paramIndex) throws Exception {
    199     method.setAccessible(true);
    200     testFunctorParameter(instance, new Functor() {
    201         @Override public Class<?>[] getParameterTypes() {
    202           return method.getParameterTypes();
    203         }
    204         @Override public Annotation[][] getParameterAnnotations() {
    205           return method.getParameterAnnotations();
    206         }
    207         @Override public void invoke(Object instance, Object[] params)
    208             throws InvocationTargetException, IllegalAccessException {
    209           method.invoke(instance, params);
    210         }
    211         @Override public String toString() {
    212           return method.getName()
    213               + "(" + Arrays.toString(getParameterTypes()) + ")";
    214         }
    215       }, paramIndex, method.getDeclaringClass());
    216   }
    217 
    218   /**
    219    * Verifies that {@code ctor} produces a {@link NullPointerException} or
    220    * {@link UnsupportedOperationException} when the parameter in position {@code
    221    * paramIndex} is null.  If this parameter is marked {@link Nullable}, this
    222    * method does nothing.
    223    */
    224   public void testConstructorParameter(final Constructor<?> ctor,
    225       int paramIndex) throws Exception {
    226     ctor.setAccessible(true);
    227     testFunctorParameter(null, new Functor() {
    228         @Override public Class<?>[] getParameterTypes() {
    229           return ctor.getParameterTypes();
    230         }
    231         @Override public Annotation[][] getParameterAnnotations() {
    232           return ctor.getParameterAnnotations();
    233         }
    234         @Override public void invoke(Object instance, Object[] params)
    235             throws InvocationTargetException, IllegalAccessException,
    236             InstantiationException {
    237           ctor.newInstance(params);
    238         }
    239       }, paramIndex, ctor.getDeclaringClass());
    240   }
    241 
    242   /**
    243    * Verifies that {@code func} produces a {@link NullPointerException} or
    244    * {@link UnsupportedOperationException} when the parameter in position {@code
    245    * paramIndex} is null.  If this parameter is marked {@link Nullable}, this
    246    * method does nothing.
    247    *
    248    * @param instance the instance to invoke {@code func} on, or null if
    249    *     {@code func} is static
    250    */
    251   private void testFunctorParameter(Object instance, Functor func,
    252       int paramIndex, Class<?> testedClass) throws Exception {
    253     if (parameterIsPrimitiveOrNullable(func, paramIndex)) {
    254       return; // there's nothing to test
    255     }
    256     Object[] params = buildParamList(func, paramIndex);
    257     try {
    258       func.invoke(instance, params);
    259       Assert.fail("No exception thrown from " + func +
    260           Arrays.toString(params) + " for " + testedClass);
    261     } catch (InvocationTargetException e) {
    262       Throwable cause = e.getCause();
    263       if (cause instanceof NullPointerException ||
    264           cause instanceof UnsupportedOperationException) {
    265         return;
    266       }
    267       AssertionFailedError error = new AssertionFailedError(
    268           "wrong exception thrown from " + func + ": " + cause);
    269       error.initCause(cause);
    270       throw error;
    271     }
    272   }
    273 
    274   private static boolean parameterIsPrimitiveOrNullable(
    275       Functor func, int paramIndex) {
    276     if (func.getParameterTypes()[paramIndex].isPrimitive()) {
    277       return true;
    278     }
    279     Annotation[] annotations = func.getParameterAnnotations()[paramIndex];
    280     for (Annotation annotation : annotations) {
    281       if (annotation instanceof Nullable) {
    282         return true;
    283       }
    284     }
    285     return false;
    286   }
    287 
    288   private Object[] buildParamList(Functor func, int indexOfParamToSetToNull) {
    289     Class<?>[] types = func.getParameterTypes();
    290     Object[] params = new Object[types.length];
    291 
    292     for (int i = 0; i < types.length; i++) {
    293       if (i != indexOfParamToSetToNull) {
    294         params[i] = defaults.get(types[i]);
    295         if (!parameterIsPrimitiveOrNullable(func, i)) {
    296           Assert.assertTrue("No default value found for " + types[i].getName(),
    297               params[i] != null);
    298         }
    299       }
    300     }
    301     return params;
    302   }
    303 
    304   private interface Functor {
    305     Class<?>[] getParameterTypes();
    306     Annotation[][] getParameterAnnotations();
    307     void invoke(Object o, Object[] params) throws Exception;
    308   }
    309 
    310   private static boolean isPublic(Member member) {
    311     return Modifier.isPublic(member.getModifiers());
    312   }
    313 
    314   private static boolean isStatic(Member member) {
    315     return Modifier.isStatic(member.getModifiers());
    316   }
    317 
    318   private boolean isIgnored(Member member) {
    319     return member.isSynthetic() || ignoredMembers.contains(member);
    320   }
    321 }
    322