• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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    */
setDefault(Class<T> type, T value)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    */
ignore(Method method)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    */
testConstructors(Class<?> c, Visibility minimalVisibility)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    */
testAllPublicConstructors(Class<?> c)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    */
testStaticMethods(Class<?> c, Visibility minimalVisibility)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    */
testAllPublicStaticMethods(Class<?> c)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    */
testInstanceMethods(Object instance, Visibility minimalVisibility)142   public void testInstanceMethods(Object instance, Visibility minimalVisibility) {
143     for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) {
144       testMethod(instance, method);
145     }
146   }
147 
getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility)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    */
testAllPublicInstanceMethods(Object instance)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    */
testMethod(@ullable Object instance, Method method)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    */
testConstructor(Constructor<?> ctor)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    */
testMethodParameter( @ullable final Object instance, final Method method, int paramIndex)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    */
testConstructorParameter(Constructor<?> ctor, int paramIndex)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 {
isVisible(int modifiers)228       @Override boolean isVisible(int modifiers) {
229         return !Modifier.isPrivate(modifiers);
230       }
231     },
232 
233     PROTECTED {
isVisible(int modifiers)234       @Override boolean isVisible(int modifiers) {
235         return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers);
236       }
237     },
238 
239     PUBLIC {
isVisible(int modifiers)240       @Override boolean isVisible(int modifiers) {
241         return Modifier.isPublic(modifiers);
242       }
243     };
244 
isVisible(int modifiers)245     abstract boolean isVisible(int modifiers);
246 
247     /**
248      * Returns {@code true} if {@code member} is visible under {@code this}
249      * visibility.
250      */
isVisible(Member member)251     final boolean isVisible(Member member) {
252       return isVisible(member.getModifiers());
253     }
254 
getStaticMethods(Class<?> cls)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 
getInstanceMethods(Class<?> cls)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 
getVisibleMethods(Class<?> cls)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 
Signature(Method method)299     Signature(Method method) {
300       this(method.getName(), ImmutableList.copyOf(method.getParameterTypes()));
301     }
302 
Signature(String name, ImmutableList<Class<?>> parameterTypes)303     Signature(String name, ImmutableList<Class<?>> parameterTypes) {
304       this.name = name;
305       this.parameterTypes = parameterTypes;
306     }
307 
equals(Object obj)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 
hashCode()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    */
testParameter(Object instance, Invokable<?, ?> invokable, int paramIndex, Class<?> testedClass)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 
buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull)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 
getDefaultValue(TypeToken<T> type)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 
defaultConverter( final TypeToken<F> convertFromType, final TypeToken<T> convertToType)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