001/* 002 * Copyright (C) 2005 The Guava Authors 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package com.google.common.testing; 018 019import static com.google.common.base.Preconditions.checkArgument; 020import static com.google.common.base.Preconditions.checkNotNull; 021 022import com.google.common.annotations.Beta; 023import com.google.common.annotations.GwtIncompatible; 024import com.google.common.base.Converter; 025import com.google.common.base.Objects; 026import com.google.common.collect.ClassToInstanceMap; 027import com.google.common.collect.ImmutableList; 028import com.google.common.collect.ImmutableSet; 029import com.google.common.collect.Lists; 030import com.google.common.collect.Maps; 031import com.google.common.collect.MutableClassToInstanceMap; 032import com.google.common.reflect.Invokable; 033import com.google.common.reflect.Parameter; 034import com.google.common.reflect.Reflection; 035import com.google.common.reflect.TypeToken; 036import java.lang.annotation.Annotation; 037import java.lang.reflect.AnnotatedElement; 038import java.lang.reflect.Constructor; 039import java.lang.reflect.InvocationTargetException; 040import java.lang.reflect.Member; 041import java.lang.reflect.Method; 042import java.lang.reflect.Modifier; 043import java.lang.reflect.ParameterizedType; 044import java.lang.reflect.Type; 045import java.util.Arrays; 046import java.util.List; 047import java.util.concurrent.ConcurrentMap; 048import junit.framework.Assert; 049import junit.framework.AssertionFailedError; 050import org.checkerframework.checker.nullness.compatqual.NullableDecl; 051 052/** 053 * A test utility that verifies that your methods and constructors throw {@link 054 * NullPointerException} or {@link UnsupportedOperationException} whenever null is passed to a 055 * parameter that isn't annotated with {@link javax.annotation.Nullable}, {@link 056 * javax.annotation.CheckForNull}, or {@link 057 * org.checkerframework.checker.nullness.compatqual.NullableDecl}. 058 * 059 * <p>The tested methods and constructors are invoked -- each time with one parameter being null and 060 * the rest not null -- and the test fails if no expected exception is thrown. {@code 061 * NullPointerTester} uses best effort to pick non-null default values for many common JDK and Guava 062 * types, and also for interfaces and public classes that have public parameter-less constructors. 063 * When the non-null default value for a particular parameter type cannot be provided by {@code 064 * NullPointerTester}, the caller can provide a custom non-null default value for the parameter type 065 * via {@link #setDefault}. 066 * 067 * @author Kevin Bourrillion 068 * @since 10.0 069 */ 070@Beta 071@GwtIncompatible 072public final class NullPointerTester { 073 074 private final ClassToInstanceMap<Object> defaults = MutableClassToInstanceMap.create(); 075 private final List<Member> ignoredMembers = Lists.newArrayList(); 076 077 private ExceptionTypePolicy policy = ExceptionTypePolicy.NPE_OR_UOE; 078 079 /** 080 * Sets a default value that can be used for any parameter of type {@code type}. Returns this 081 * object. 082 */ 083 public <T> NullPointerTester setDefault(Class<T> type, T value) { 084 defaults.putInstance(type, checkNotNull(value)); 085 return this; 086 } 087 088 /** 089 * Ignore {@code method} in the tests that follow. Returns this object. 090 * 091 * @since 13.0 092 */ 093 public NullPointerTester ignore(Method method) { 094 ignoredMembers.add(checkNotNull(method)); 095 return this; 096 } 097 098 /** 099 * Ignore {@code constructor} in the tests that follow. Returns this object. 100 * 101 * @since 22.0 102 */ 103 public NullPointerTester ignore(Constructor<?> constructor) { 104 ignoredMembers.add(checkNotNull(constructor)); 105 return this; 106 } 107 108 /** 109 * Runs {@link #testConstructor} on every constructor in class {@code c} that has at least {@code 110 * minimalVisibility}. 111 */ 112 public void testConstructors(Class<?> c, Visibility minimalVisibility) { 113 for (Constructor<?> constructor : c.getDeclaredConstructors()) { 114 if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) { 115 testConstructor(constructor); 116 } 117 } 118 } 119 120 /** Runs {@link #testConstructor} on every public constructor in class {@code c}. */ 121 public void testAllPublicConstructors(Class<?> c) { 122 testConstructors(c, Visibility.PUBLIC); 123 } 124 125 /** 126 * Runs {@link #testMethod} on every static method of class {@code c} that has at least {@code 127 * minimalVisibility}, including those "inherited" from superclasses of the same package. 128 */ 129 public void testStaticMethods(Class<?> c, Visibility minimalVisibility) { 130 for (Method method : minimalVisibility.getStaticMethods(c)) { 131 if (!isIgnored(method)) { 132 testMethod(null, method); 133 } 134 } 135 } 136 137 /** 138 * Runs {@link #testMethod} on every public static method of class {@code c}, including those 139 * "inherited" from superclasses of the same package. 140 */ 141 public void testAllPublicStaticMethods(Class<?> c) { 142 testStaticMethods(c, Visibility.PUBLIC); 143 } 144 145 /** 146 * Runs {@link #testMethod} on every instance method of the class of {@code instance} with at 147 * least {@code minimalVisibility}, including those inherited from superclasses of the same 148 * package. 149 */ 150 public void testInstanceMethods(Object instance, Visibility minimalVisibility) { 151 for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) { 152 testMethod(instance, method); 153 } 154 } 155 156 ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) { 157 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 158 for (Method method : minimalVisibility.getInstanceMethods(c)) { 159 if (!isIgnored(method)) { 160 builder.add(method); 161 } 162 } 163 return builder.build(); 164 } 165 166 /** 167 * Runs {@link #testMethod} on every public instance method of the class of {@code instance}, 168 * including those inherited from superclasses of the same package. 169 */ 170 public void testAllPublicInstanceMethods(Object instance) { 171 testInstanceMethods(instance, Visibility.PUBLIC); 172 } 173 174 /** 175 * Verifies that {@code method} produces a {@link NullPointerException} or {@link 176 * UnsupportedOperationException} whenever <i>any</i> of its non-nullable parameters are null. 177 * 178 * @param instance the instance to invoke {@code method} on, or null if {@code method} is static 179 */ 180 public void testMethod(@NullableDecl Object instance, Method method) { 181 Class<?>[] types = method.getParameterTypes(); 182 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { 183 testMethodParameter(instance, method, nullIndex); 184 } 185 } 186 187 /** 188 * Verifies that {@code ctor} produces a {@link NullPointerException} or {@link 189 * UnsupportedOperationException} whenever <i>any</i> of its non-nullable parameters are null. 190 */ 191 public void testConstructor(Constructor<?> ctor) { 192 Class<?> declaringClass = ctor.getDeclaringClass(); 193 checkArgument( 194 Modifier.isStatic(declaringClass.getModifiers()) 195 || declaringClass.getEnclosingClass() == null, 196 "Cannot test constructor of non-static inner class: %s", 197 declaringClass.getName()); 198 Class<?>[] types = ctor.getParameterTypes(); 199 for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { 200 testConstructorParameter(ctor, nullIndex); 201 } 202 } 203 204 /** 205 * Verifies that {@code method} produces a {@link NullPointerException} or {@link 206 * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If 207 * this parameter is marked nullable, this method does nothing. 208 * 209 * @param instance the instance to invoke {@code method} on, or null if {@code method} is static 210 */ 211 public void testMethodParameter( 212 @NullableDecl final Object instance, final Method method, int paramIndex) { 213 method.setAccessible(true); 214 testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass()); 215 } 216 217 /** 218 * Verifies that {@code ctor} produces a {@link NullPointerException} or {@link 219 * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If 220 * this parameter is marked nullable, this method does nothing. 221 */ 222 public void testConstructorParameter(Constructor<?> ctor, int paramIndex) { 223 ctor.setAccessible(true); 224 testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass()); 225 } 226 227 /** Visibility of any method or constructor. */ 228 public enum Visibility { 229 PACKAGE { 230 @Override 231 boolean isVisible(int modifiers) { 232 return !Modifier.isPrivate(modifiers); 233 } 234 }, 235 236 PROTECTED { 237 @Override 238 boolean isVisible(int modifiers) { 239 return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); 240 } 241 }, 242 243 PUBLIC { 244 @Override 245 boolean isVisible(int modifiers) { 246 return Modifier.isPublic(modifiers); 247 } 248 }; 249 250 abstract boolean isVisible(int modifiers); 251 252 /** Returns {@code true} if {@code member} is visible under {@code this} visibility. */ 253 final boolean isVisible(Member member) { 254 return isVisible(member.getModifiers()); 255 } 256 257 final Iterable<Method> getStaticMethods(Class<?> cls) { 258 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 259 for (Method method : getVisibleMethods(cls)) { 260 if (Invokable.from(method).isStatic()) { 261 builder.add(method); 262 } 263 } 264 return builder.build(); 265 } 266 267 final Iterable<Method> getInstanceMethods(Class<?> cls) { 268 ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap(); 269 for (Method method : getVisibleMethods(cls)) { 270 if (!Invokable.from(method).isStatic()) { 271 map.putIfAbsent(new Signature(method), method); 272 } 273 } 274 return map.values(); 275 } 276 277 private ImmutableList<Method> getVisibleMethods(Class<?> cls) { 278 // Don't use cls.getPackage() because it does nasty things like reading 279 // a file. 280 String visiblePackage = Reflection.getPackageName(cls); 281 ImmutableList.Builder<Method> builder = ImmutableList.builder(); 282 for (Class<?> type : TypeToken.of(cls).getTypes().rawTypes()) { 283 if (!Reflection.getPackageName(type).equals(visiblePackage)) { 284 break; 285 } 286 for (Method method : type.getDeclaredMethods()) { 287 if (!method.isSynthetic() && isVisible(method)) { 288 builder.add(method); 289 } 290 } 291 } 292 return builder.build(); 293 } 294 } 295 296 private static final class Signature { 297 private final String name; 298 private final ImmutableList<Class<?>> parameterTypes; 299 300 Signature(Method method) { 301 this(method.getName(), ImmutableList.copyOf(method.getParameterTypes())); 302 } 303 304 Signature(String name, ImmutableList<Class<?>> parameterTypes) { 305 this.name = name; 306 this.parameterTypes = parameterTypes; 307 } 308 309 @Override 310 public boolean equals(Object obj) { 311 if (obj instanceof Signature) { 312 Signature that = (Signature) obj; 313 return name.equals(that.name) && parameterTypes.equals(that.parameterTypes); 314 } 315 return false; 316 } 317 318 @Override 319 public int hashCode() { 320 return Objects.hashCode(name, parameterTypes); 321 } 322 } 323 324 /** 325 * Verifies that {@code invokable} produces a {@link NullPointerException} or {@link 326 * UnsupportedOperationException} when the parameter in position {@code paramIndex} is null. If 327 * this parameter is marked nullable, this method does nothing. 328 * 329 * @param instance the instance to invoke {@code invokable} on, or null if {@code invokable} is 330 * static 331 */ 332 private void testParameter( 333 Object instance, Invokable<?, ?> invokable, int paramIndex, Class<?> testedClass) { 334 if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) { 335 return; // there's nothing to test 336 } 337 Object[] params = buildParamList(invokable, paramIndex); 338 try { 339 @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong. 340 Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable; 341 unsafe.invoke(instance, params); 342 Assert.fail( 343 "No exception thrown for parameter at index " 344 + paramIndex 345 + " from " 346 + invokable 347 + Arrays.toString(params) 348 + " for " 349 + testedClass); 350 } catch (InvocationTargetException e) { 351 Throwable cause = e.getCause(); 352 if (policy.isExpectedType(cause)) { 353 return; 354 } 355 AssertionFailedError error = 356 new AssertionFailedError( 357 String.format( 358 "wrong exception thrown from %s when passing null to %s parameter at index %s.%n" 359 + "Full parameters: %s%n" 360 + "Actual exception message: %s", 361 invokable, 362 invokable.getParameters().get(paramIndex).getType(), 363 paramIndex, 364 Arrays.toString(params), 365 cause)); 366 error.initCause(cause); 367 throw error; 368 } catch (IllegalAccessException e) { 369 throw new RuntimeException(e); 370 } 371 } 372 373 private Object[] buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull) { 374 ImmutableList<Parameter> params = invokable.getParameters(); 375 Object[] args = new Object[params.size()]; 376 377 for (int i = 0; i < args.length; i++) { 378 Parameter param = params.get(i); 379 if (i != indexOfParamToSetToNull) { 380 args[i] = getDefaultValue(param.getType()); 381 Assert.assertTrue( 382 "Can't find or create a sample instance for type '" 383 + param.getType() 384 + "'; please provide one using NullPointerTester.setDefault()", 385 args[i] != null || isNullable(param)); 386 } 387 } 388 return args; 389 } 390 391 private <T> T getDefaultValue(TypeToken<T> type) { 392 // We assume that all defaults are generics-safe, even if they aren't, 393 // we take the risk. 394 @SuppressWarnings("unchecked") 395 T defaultValue = (T) defaults.getInstance(type.getRawType()); 396 if (defaultValue != null) { 397 return defaultValue; 398 } 399 @SuppressWarnings("unchecked") // All arbitrary instances are generics-safe 400 T arbitrary = (T) ArbitraryInstances.get(type.getRawType()); 401 if (arbitrary != null) { 402 return arbitrary; 403 } 404 if (type.getRawType() == Class.class) { 405 // If parameter is Class<? extends Foo>, we return Foo.class 406 @SuppressWarnings("unchecked") 407 T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType(); 408 return defaultClass; 409 } 410 if (type.getRawType() == TypeToken.class) { 411 // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>. 412 @SuppressWarnings("unchecked") 413 T defaultType = (T) getFirstTypeParameter(type.getType()); 414 return defaultType; 415 } 416 if (type.getRawType() == Converter.class) { 417 TypeToken<?> convertFromType = type.resolveType(Converter.class.getTypeParameters()[0]); 418 TypeToken<?> convertToType = type.resolveType(Converter.class.getTypeParameters()[1]); 419 @SuppressWarnings("unchecked") // returns default for both F and T 420 T defaultConverter = (T) defaultConverter(convertFromType, convertToType); 421 return defaultConverter; 422 } 423 if (type.getRawType().isInterface()) { 424 return newDefaultReturningProxy(type); 425 } 426 return null; 427 } 428 429 private <F, T> Converter<F, T> defaultConverter( 430 final TypeToken<F> convertFromType, final TypeToken<T> convertToType) { 431 return new Converter<F, T>() { 432 @Override 433 protected T doForward(F a) { 434 return doConvert(convertToType); 435 } 436 437 @Override 438 protected F doBackward(T b) { 439 return doConvert(convertFromType); 440 } 441 442 private /*static*/ <S> S doConvert(TypeToken<S> type) { 443 return checkNotNull(getDefaultValue(type)); 444 } 445 }; 446 } 447 448 private static TypeToken<?> getFirstTypeParameter(Type type) { 449 if (type instanceof ParameterizedType) { 450 return TypeToken.of(((ParameterizedType) type).getActualTypeArguments()[0]); 451 } else { 452 return TypeToken.of(Object.class); 453 } 454 } 455 456 private <T> T newDefaultReturningProxy(final TypeToken<T> type) { 457 return new DummyProxy() { 458 @Override 459 <R> R dummyReturnValue(TypeToken<R> returnType) { 460 return getDefaultValue(returnType); 461 } 462 }.newProxy(type); 463 } 464 465 private static Invokable<?, ?> invokable(@NullableDecl Object instance, Method method) { 466 if (instance == null) { 467 return Invokable.from(method); 468 } else { 469 return TypeToken.of(instance.getClass()).method(method); 470 } 471 } 472 473 static boolean isPrimitiveOrNullable(Parameter param) { 474 return param.getType().getRawType().isPrimitive() || isNullable(param); 475 } 476 477 private static final ImmutableSet<String> NULLABLE_ANNOTATIONS = 478 ImmutableSet.of( 479 "javax.annotation.CheckForNull", 480 "javax.annotation.Nullable", 481 "org.checkerframework.checker.nullness.compatqual.NullableDecl"); 482 483 static boolean isNullable(AnnotatedElement e) { 484 for (Annotation annotation : e.getAnnotations()) { 485 if (NULLABLE_ANNOTATIONS.contains(annotation.annotationType().getName())) { 486 return true; 487 } 488 } 489 return false; 490 } 491 492 private boolean isIgnored(Member member) { 493 return member.isSynthetic() || ignoredMembers.contains(member) || isEquals(member); 494 } 495 496 /** 497 * Returns true if the the given member is a method that overrides {@link Object#equals(Object)}. 498 * 499 * <p>The documentation for {@link Object#equals} says it should accept null, so don't require an 500 * explicit {@code @NullableDecl} annotation (see <a 501 * href="https://github.com/google/guava/issues/1819">#1819</a>). 502 * 503 * <p>It is not necessary to consider visibility, return type, or type parameter declarations. The 504 * declaration of a method with the same name and formal parameters as {@link Object#equals} that 505 * is not public and boolean-returning, or that declares any type parameters, would be rejected at 506 * compile-time. 507 */ 508 private static boolean isEquals(Member member) { 509 if (!(member instanceof Method)) { 510 return false; 511 } 512 Method method = (Method) member; 513 if (!method.getName().contentEquals("equals")) { 514 return false; 515 } 516 Class<?>[] parameters = method.getParameterTypes(); 517 if (parameters.length != 1) { 518 return false; 519 } 520 if (!parameters[0].equals(Object.class)) { 521 return false; 522 } 523 return true; 524 } 525 526 /** Strategy for exception type matching used by {@link NullPointerTester}. */ 527 private enum ExceptionTypePolicy { 528 529 /** 530 * Exceptions should be {@link NullPointerException} or {@link UnsupportedOperationException}. 531 */ 532 NPE_OR_UOE() { 533 @Override 534 public boolean isExpectedType(Throwable cause) { 535 return cause instanceof NullPointerException 536 || cause instanceof UnsupportedOperationException; 537 } 538 }, 539 540 /** 541 * Exceptions should be {@link NullPointerException}, {@link IllegalArgumentException}, or 542 * {@link UnsupportedOperationException}. 543 */ 544 NPE_IAE_OR_UOE() { 545 @Override 546 public boolean isExpectedType(Throwable cause) { 547 return cause instanceof NullPointerException 548 || cause instanceof IllegalArgumentException 549 || cause instanceof UnsupportedOperationException; 550 } 551 }; 552 553 public abstract boolean isExpectedType(Throwable cause); 554 } 555}