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