NullPointerTester.java revision 3c77433663281544363151bf284b0240dfd22a42
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 17package com.google.common.testing; 18 19import static com.google.common.base.Preconditions.checkArgument; 20import static com.google.common.base.Preconditions.checkNotNull; 21 22import com.google.common.annotations.Beta; 23import com.google.common.base.Objects; 24import com.google.common.collect.ClassToInstanceMap; 25import com.google.common.collect.ImmutableList; 26import com.google.common.collect.Lists; 27import com.google.common.collect.Maps; 28import com.google.common.collect.MutableClassToInstanceMap; 29import com.google.common.reflect.Invokable; 30import com.google.common.reflect.Parameter; 31import com.google.common.reflect.Reflection; 32import com.google.common.reflect.TypeToken; 33 34import junit.framework.Assert; 35import junit.framework.AssertionFailedError; 36 37import java.lang.reflect.Constructor; 38import java.lang.reflect.InvocationTargetException; 39import java.lang.reflect.Member; 40import java.lang.reflect.Method; 41import java.lang.reflect.Modifier; 42import java.lang.reflect.ParameterizedType; 43import java.lang.reflect.Type; 44import java.util.Arrays; 45import java.util.List; 46import java.util.concurrent.ConcurrentMap; 47 48import javax.annotation.Nullable; 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 68public final class NullPointerTester { 69 70 private final ClassToInstanceMap<Object> defaults = 71 MutableClassToInstanceMap.create(); 72 private final List<Member> ignoredMembers = Lists.newArrayList(); 73 74 /** 75 * Sets a default value that can be used for any parameter of type 76 * {@code type}. Returns this object. 77 */ 78 public <T> NullPointerTester setDefault(Class<T> type, T value) { 79 defaults.putInstance(type, checkNotNull(value)); 80 return this; 81 } 82 83 /** 84 * Ignore {@code method} in the tests that follow. Returns this object. 85 * 86 * @since 13.0 87 */ 88 public NullPointerTester ignore(Method method) { 89 ignoredMembers.add(checkNotNull(method)); 90 return this; 91 } 92 93 /** 94 * Runs {@link #testConstructor} on every constructor in class {@code c} that 95 * has at least {@code minimalVisibility}. 96 */ 97 public void testConstructors(Class<?> c, Visibility minimalVisibility) { 98 for (Constructor<?> constructor : c.getDeclaredConstructors()) { 99 if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) { 100 testConstructor(constructor); 101 } 102 } 103 } 104 105 /** 106 * Runs {@link #testConstructor} on every public constructor in class {@code 107 * c}. 108 */ 109 public void testAllPublicConstructors(Class<?> c) { 110 testConstructors(c, Visibility.PUBLIC); 111 } 112 113 /** 114 * Runs {@link #testMethod} on every static method of class {@code c} that has 115 * at least {@code minimalVisibility}, including those "inherited" from 116 * superclasses of the same package. 117 */ 118 public void testStaticMethods(Class<?> c, Visibility minimalVisibility) { 119 for (Method method : minimalVisibility.getStaticMethods(c)) { 120 if (!isIgnored(method)) { 121 testMethod(null, method); 122 } 123 } 124 } 125 126 /** 127 * Runs {@link #testMethod} on every public static method of class {@code c}, 128 * including those "inherited" from superclasses of the same package. 129 */ 130 public void testAllPublicStaticMethods(Class<?> c) { 131 testStaticMethods(c, Visibility.PUBLIC); 132 } 133 134 /** 135 * Runs {@link #testMethod} on every instance method of the class of 136 * {@code instance} with at least {@code minimalVisibility}, including those 137 * inherited from superclasses of the same package. 138 */ 139 public void testInstanceMethods(Object instance, Visibility minimalVisibility) { 140 for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) { 141 testMethod(instance, method); 142 } 143 } 144 145 ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) { 146 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 147 for (Method method : minimalVisibility.getInstanceMethods(c)) { 148 if (!isIgnored(method)) { 149 builder.add(method); 150 } 151 } 152 return builder.build(); 153 } 154 155 /** 156 * Runs {@link #testMethod} on every public instance method of the class of 157 * {@code instance}, including those inherited from superclasses of the same 158 * package. 159 */ 160 public void testAllPublicInstanceMethods(Object instance) { 161 testInstanceMethods(instance, Visibility.PUBLIC); 162 } 163 164 /** 165 * Verifies that {@code method} produces a {@link NullPointerException} 166 * or {@link UnsupportedOperationException} whenever <i>any</i> of its 167 * non-{@link Nullable} parameters are null. 168 * 169 * @param instance the instance to invoke {@code method} on, or null if 170 * {@code method} is static 171 */ 172 public void testMethod(@Nullable Object instance, Method method) { 173 Class<?>[] types = method.getParameterTypes(); 174 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { 175 testMethodParameter(instance, method, nullIndex); 176 } 177 } 178 179 /** 180 * Verifies that {@code ctor} produces a {@link NullPointerException} or 181 * {@link UnsupportedOperationException} whenever <i>any</i> of its 182 * non-{@link Nullable} parameters are null. 183 */ 184 public void testConstructor(Constructor<?> ctor) { 185 Class<?> declaringClass = ctor.getDeclaringClass(); 186 checkArgument(Modifier.isStatic(declaringClass.getModifiers()) 187 || declaringClass.getEnclosingClass() == null, 188 "Cannot test constructor of non-static inner class: %s", declaringClass.getName()); 189 Class<?>[] types = ctor.getParameterTypes(); 190 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { 191 testConstructorParameter(ctor, nullIndex); 192 } 193 } 194 195 /** 196 * Verifies that {@code method} produces a {@link NullPointerException} or 197 * {@link UnsupportedOperationException} when the parameter in position {@code 198 * paramIndex} is null. If this parameter is marked {@link Nullable}, this 199 * method does nothing. 200 * 201 * @param instance the instance to invoke {@code method} on, or null if 202 * {@code method} is static 203 */ 204 public void testMethodParameter( 205 @Nullable final Object instance, final Method method, int paramIndex) { 206 method.setAccessible(true); 207 testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass()); 208 } 209 210 /** 211 * Verifies that {@code ctor} produces a {@link NullPointerException} or 212 * {@link UnsupportedOperationException} when the parameter in position {@code 213 * paramIndex} is null. If this parameter is marked {@link Nullable}, this 214 * method does nothing. 215 */ 216 public void testConstructorParameter(Constructor<?> ctor, int paramIndex) { 217 ctor.setAccessible(true); 218 testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass()); 219 } 220 221 /** Visibility of any method or constructor. */ 222 public enum Visibility { 223 224 PACKAGE { 225 @Override boolean isVisible(int modifiers) { 226 return !Modifier.isPrivate(modifiers); 227 } 228 }, 229 230 PROTECTED { 231 @Override boolean isVisible(int modifiers) { 232 return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); 233 } 234 }, 235 236 PUBLIC { 237 @Override boolean isVisible(int modifiers) { 238 return Modifier.isPublic(modifiers); 239 } 240 }; 241 242 abstract boolean isVisible(int modifiers); 243 244 /** 245 * Returns {@code true} if {@code member} is visible under {@code this} 246 * visibility. 247 */ 248 final boolean isVisible(Member member) { 249 return isVisible(member.getModifiers()); 250 } 251 252 final Iterable<Method> getStaticMethods(Class<?> cls) { 253 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 254 for (Method method : getVisibleMethods(cls)) { 255 if (Invokable.from(method).isStatic()) { 256 builder.add(method); 257 } 258 } 259 return builder.build(); 260 } 261 262 final Iterable<Method> getInstanceMethods(Class<?> cls) { 263 ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap(); 264 for (Method method : getVisibleMethods(cls)) { 265 if (!Invokable.from(method).isStatic()) { 266 map.putIfAbsent(new Signature(method), method); 267 } 268 } 269 return map.values(); 270 } 271 272 private ImmutableList<Method> getVisibleMethods(Class<?> cls) { 273 // Don't use cls.getPackage() because it does nasty things like reading 274 // a file. 275 String visiblePackage = Reflection.getPackageName(cls); 276 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 277 for (Class<?> type : TypeToken.of(cls).getTypes().classes().rawTypes()) { 278 if (!Reflection.getPackageName(type).equals(visiblePackage)) { 279 break; 280 } 281 for (Method method : type.getDeclaredMethods()) { 282 if (!method.isSynthetic() && isVisible(method)) { 283 builder.add(method); 284 } 285 } 286 } 287 return builder.build(); 288 } 289 } 290 291 // TODO(benyu): Use labs/reflect/Signature if it graduates. 292 private static final class Signature { 293 private final String name; 294 private final ImmutableList<Class<?>> parameterTypes; 295 296 Signature(Method method) { 297 this(method.getName(), ImmutableList.copyOf(method.getParameterTypes())); 298 } 299 300 Signature(String name, ImmutableList<Class<?>> parameterTypes) { 301 this.name = name; 302 this.parameterTypes = parameterTypes; 303 } 304 305 @Override public boolean equals(Object obj) { 306 if (obj instanceof Signature) { 307 Signature that = (Signature) obj; 308 return name.equals(that.name) 309 && parameterTypes.equals(that.parameterTypes); 310 } 311 return false; 312 } 313 314 @Override public int hashCode() { 315 return Objects.hashCode(name, parameterTypes); 316 } 317 } 318 319 /** 320 * Verifies that {@code invokable} produces a {@link NullPointerException} or 321 * {@link UnsupportedOperationException} when the parameter in position {@code 322 * paramIndex} is null. If this parameter is marked {@link Nullable}, this 323 * method does nothing. 324 * 325 * @param instance the instance to invoke {@code invokable} on, or null if 326 * {@code invokable} is static 327 */ 328 private void testParameter(Object instance, Invokable<?, ?> invokable, 329 int paramIndex, Class<?> testedClass) { 330 if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) { 331 return; // there's nothing to test 332 } 333 Object[] params = buildParamList(invokable, paramIndex); 334 try { 335 @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong. 336 Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable; 337 unsafe.invoke(instance, params); 338 Assert.fail("No exception thrown from " + invokable + 339 Arrays.toString(params) + " for " + testedClass); 340 } catch (InvocationTargetException e) { 341 Throwable cause = e.getCause(); 342 if (cause instanceof NullPointerException || 343 cause instanceof UnsupportedOperationException) { 344 return; 345 } 346 AssertionFailedError error = new AssertionFailedError( 347 "wrong exception thrown from " + invokable + ": " + cause); 348 error.initCause(cause); 349 throw error; 350 } catch (IllegalAccessException e) { 351 throw new RuntimeException(e); 352 } 353 } 354 355 private Object[] buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull) { 356 ImmutableList<Parameter> params = invokable.getParameters(); 357 Object[] args = new Object[params.size()]; 358 359 for (int i = 0; i < args.length; i++) { 360 Parameter param = params.get(i); 361 if (i != indexOfParamToSetToNull) { 362 args[i] = getDefaultValue(param.getType()); 363 if (!isPrimitiveOrNullable(param)) { 364 Assert.assertTrue("No default value found for " + param + " of "+ invokable, 365 args[i] != null); 366 } 367 } 368 } 369 return args; 370 } 371 372 private <T> T getDefaultValue(TypeToken<T> type) { 373 // We assume that all defaults are generics-safe, even if they aren't, 374 // we take the risk. 375 @SuppressWarnings("unchecked") 376 T defaultValue = (T) defaults.getInstance(type.getRawType()); 377 if (defaultValue != null) { 378 return defaultValue; 379 } 380 @SuppressWarnings("unchecked") // All null values are generics-safe 381 T nullValue = (T) ArbitraryInstances.get(type.getRawType()); 382 if (nullValue != null) { 383 return nullValue; 384 } 385 if (type.getRawType() == Class.class) { 386 // If parameter is Class<? extends Foo>, we return Foo.class 387 @SuppressWarnings("unchecked") 388 T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType(); 389 return defaultClass; 390 } 391 if (type.getRawType() == TypeToken.class) { 392 // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>. 393 @SuppressWarnings("unchecked") 394 T defaultType = (T) getFirstTypeParameter(type.getType()); 395 return defaultType; 396 } 397 if (type.getRawType().isInterface()) { 398 return newDefaultReturningProxy(type); 399 } 400 return null; 401 } 402 403 private static TypeToken<?> getFirstTypeParameter(Type type) { 404 if (type instanceof ParameterizedType) { 405 return TypeToken.of( 406 ((ParameterizedType) type).getActualTypeArguments()[0]); 407 } else { 408 return TypeToken.of(Object.class); 409 } 410 } 411 412 private <T> T newDefaultReturningProxy(final TypeToken<T> type) { 413 return new DummyProxy() { 414 @Override <R> R dummyReturnValue(TypeToken<R> returnType) { 415 return getDefaultValue(returnType); 416 } 417 }.newProxy(type); 418 } 419 420 private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) { 421 if (instance == null) { 422 return Invokable.from(method); 423 } else { 424 return TypeToken.of(instance.getClass()).method(method); 425 } 426 } 427 428 static boolean isPrimitiveOrNullable(Parameter param) { 429 return param.getType().getRawType().isPrimitive() || param.isAnnotationPresent(Nullable.class); 430 } 431 432 private boolean isIgnored(Member member) { 433 return member.isSynthetic() || ignoredMembers.contains(member); 434 } 435} 436