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 bestAvailableString( TypeToken type, int position, Invokable<?, ?> invokable)357 private static String bestAvailableString( 358 TypeToken type, int position, Invokable<?, ?> invokable) { 359 checkNotNull(type); 360 try { 361 return type.toString(); 362 } catch (NullPointerException androidBug6636) { 363 // http://stackoverflow.com/a/8169250/28465 364 return String.format("unknown (arg %s of %s)", position, invokable); 365 } 366 } 367 buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull)368 private Object[] buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull) { 369 ImmutableList<Parameter> params = invokable.getParameters(); 370 Object[] args = new Object[params.size()]; 371 372 for (int i = 0; i < args.length; i++) { 373 Parameter param = params.get(i); 374 if (i != indexOfParamToSetToNull) { 375 args[i] = getDefaultValue(param.getType()); 376 Assert.assertTrue( 377 "Can't find or create a sample instance for type '" 378 + bestAvailableString(param.getType(), i, invokable) 379 + "'; please provide one using NullPointerTester.setDefault()", 380 args[i] != null || isNullable(param)); 381 } 382 } 383 return args; 384 } 385 getDefaultValue(TypeToken<T> type)386 private <T> T getDefaultValue(TypeToken<T> type) { 387 // We assume that all defaults are generics-safe, even if they aren't, 388 // we take the risk. 389 @SuppressWarnings("unchecked") 390 T defaultValue = (T) defaults.getInstance(type.getRawType()); 391 if (defaultValue != null) { 392 return defaultValue; 393 } 394 @SuppressWarnings("unchecked") // All arbitrary instances are generics-safe 395 T arbitrary = (T) ArbitraryInstances.get(type.getRawType()); 396 if (arbitrary != null) { 397 return arbitrary; 398 } 399 if (type.getRawType() == Class.class) { 400 // If parameter is Class<? extends Foo>, we return Foo.class 401 @SuppressWarnings("unchecked") 402 T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType(); 403 return defaultClass; 404 } 405 if (type.getRawType() == TypeToken.class) { 406 // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>. 407 @SuppressWarnings("unchecked") 408 T defaultType = (T) getFirstTypeParameter(type.getType()); 409 return defaultType; 410 } 411 if (type.getRawType() == Converter.class) { 412 TypeToken<?> convertFromType = type.resolveType( 413 Converter.class.getTypeParameters()[0]); 414 TypeToken<?> convertToType = type.resolveType( 415 Converter.class.getTypeParameters()[1]); 416 @SuppressWarnings("unchecked") // returns default for both F and T 417 T defaultConverter = (T) defaultConverter(convertFromType, convertToType); 418 return defaultConverter; 419 } 420 if (type.getRawType().isInterface()) { 421 return newDefaultReturningProxy(type); 422 } 423 return null; 424 } 425 defaultConverter( final TypeToken<F> convertFromType, final TypeToken<T> convertToType)426 private <F, T> Converter<F, T> defaultConverter( 427 final TypeToken<F> convertFromType, final TypeToken<T> convertToType) { 428 return new Converter<F, T>() { 429 @Override protected T doForward(F a) { 430 return doConvert(convertToType); 431 } 432 @Override protected F doBackward(T b) { 433 return doConvert(convertFromType); 434 } 435 436 private /*static*/ <S> S doConvert(TypeToken<S> type) { 437 return checkNotNull(getDefaultValue(type)); 438 } 439 }; 440 } 441 442 private static TypeToken<?> getFirstTypeParameter(Type type) { 443 if (type instanceof ParameterizedType) { 444 return TypeToken.of( 445 ((ParameterizedType) type).getActualTypeArguments()[0]); 446 } else { 447 return TypeToken.of(Object.class); 448 } 449 } 450 451 private <T> T newDefaultReturningProxy(final TypeToken<T> type) { 452 return new DummyProxy() { 453 @Override <R> R dummyReturnValue(TypeToken<R> returnType) { 454 return getDefaultValue(returnType); 455 } 456 }.newProxy(type); 457 } 458 459 private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) { 460 if (instance == null) { 461 return Invokable.from(method); 462 } else { 463 return TypeToken.of(instance.getClass()).method(method); 464 } 465 } 466 467 static boolean isPrimitiveOrNullable(Parameter param) { 468 return param.getType().getRawType().isPrimitive() || isNullable(param); 469 } 470 471 private static boolean isNullable(Parameter param) { 472 return param.isAnnotationPresent(Nullable.class); 473 } 474 475 private boolean isIgnored(Member member) { 476 return member.isSynthetic() || ignoredMembers.contains(member); 477 } 478 479 /** 480 * Strategy for exception type matching used by {@link NullPointerTester}. 481 */ 482 private enum ExceptionTypePolicy { 483 484 /** 485 * Exceptions should be {@link NullPointerException} or 486 * {@link UnsupportedOperationException}. 487 */ 488 NPE_OR_UOE() { 489 @Override 490 public boolean isExpectedType(Throwable cause) { 491 return cause instanceof NullPointerException 492 || cause instanceof UnsupportedOperationException; 493 } 494 }, 495 496 /** 497 * Exceptions should be {@link NullPointerException}, 498 * {@link IllegalArgumentException}, or 499 * {@link UnsupportedOperationException}. 500 */ 501 NPE_IAE_OR_UOE() { 502 @Override 503 public boolean isExpectedType(Throwable cause) { 504 return cause instanceof NullPointerException 505 || cause instanceof IllegalArgumentException 506 || cause instanceof UnsupportedOperationException; 507 } 508 }; 509 510 public abstract boolean isExpectedType(Throwable cause); 511 } 512 } 513