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