• 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 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 
NullPointerTester()68   public NullPointerTester() {
69     setCommonDefaults();
70   }
71 
setCommonDefaults()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    */
setDefault(Class<T> type, T value)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    */
ignore(Member member)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    */
testAllPublicConstructors(Class<?> c)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    */
testAllPublicStaticMethods(Class<?> c)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    */
testAllPublicInstanceMethods(Object instance)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    */
testMethod(Object instance, Method method)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    */
testConstructor(Constructor<?> ctor)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    */
testMethodParameter(Object instance, final Method method, int paramIndex)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    */
testConstructorParameter(final Constructor<?> ctor, int paramIndex)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    */
testFunctorParameter(Object instance, Functor func, int paramIndex, Class<?> testedClass)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 
parameterIsPrimitiveOrNullable( Functor func, int paramIndex)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 
buildParamList(Functor func, int indexOfParamToSetToNull)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 {
getParameterTypes()305     Class<?>[] getParameterTypes();
getParameterAnnotations()306     Annotation[][] getParameterAnnotations();
invoke(Object o, Object[] params)307     void invoke(Object o, Object[] params) throws Exception;
308   }
309 
isPublic(Member member)310   private static boolean isPublic(Member member) {
311     return Modifier.isPublic(member.getModifiers());
312   }
313 
isStatic(Member member)314   private static boolean isStatic(Member member) {
315     return Modifier.isStatic(member.getModifiers());
316   }
317 
isIgnored(Member member)318   private boolean isIgnored(Member member) {
319     return member.isSynthetic() || ignoredMembers.contains(member);
320   }
321 }
322