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