diff options
Diffstat (limited to 'guava-testlib/src/com/google/common/testing/NullPointerTester.java')
-rw-r--r-- | guava-testlib/src/com/google/common/testing/NullPointerTester.java | 422 |
1 files changed, 154 insertions, 268 deletions
diff --git a/guava-testlib/src/com/google/common/testing/NullPointerTester.java b/guava-testlib/src/com/google/common/testing/NullPointerTester.java index b643182..1142056 100644 --- a/guava-testlib/src/com/google/common/testing/NullPointerTester.java +++ b/guava-testlib/src/com/google/common/testing/NullPointerTester.java @@ -16,149 +16,146 @@ package com.google.common.testing; -import static com.google.common.base.Preconditions.checkArgument; -import static com.google.common.base.Preconditions.checkNotNull; - import com.google.common.annotations.Beta; -import com.google.common.base.Objects; -import com.google.common.collect.ClassToInstanceMap; -import com.google.common.collect.ImmutableList; +import com.google.common.base.Function; +import com.google.common.base.Functions; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.base.Supplier; +import com.google.common.base.Suppliers; +import com.google.common.collect.Iterators; import com.google.common.collect.Lists; import com.google.common.collect.Maps; -import com.google.common.collect.MutableClassToInstanceMap; -import com.google.common.reflect.Invokable; -import com.google.common.reflect.Parameter; -import com.google.common.reflect.Reflection; -import com.google.common.reflect.TypeToken; import junit.framework.Assert; import junit.framework.AssertionFailedError; +import java.lang.annotation.Annotation; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.Comparator; +import java.util.Iterator; import java.util.List; -import java.util.concurrent.ConcurrentMap; +import java.util.Map; +import java.util.Set; +import java.util.SortedSet; +import java.util.TreeSet; +import java.util.concurrent.TimeUnit; +import java.util.regex.Pattern; import javax.annotation.Nullable; /** - * A test utility that verifies that your methods and constructors throw {@link - * NullPointerException} or {@link UnsupportedOperationException} whenever null - * is passed to a parameter that isn't annotated with {@link Nullable}. - * - * <p>The tested methods and constructors are invoked -- each time with one - * parameter being null and the rest not null -- and the test fails if no - * expected exception is thrown. {@code NullPointerTester} uses best effort to - * pick non-null default values for many common JDK and Guava types, and also - * for interfaces and public classes that have public parameter-less - * constructors. When the non-null default value for a particular parameter type - * cannot be provided by {@code NullPointerTester}, the caller can provide a - * custom non-null default value for the parameter type via {@link #setDefault}. + * A test utility that verifies that your methods throw {@link + * NullPointerException} or {@link UnsupportedOperationException} whenever any + * of their parameters are null. To use it, you must first provide valid default + * values for the parameter types used by the class. * * @author Kevin Bourrillion * @since 10.0 */ @Beta public final class NullPointerTester { - - private final ClassToInstanceMap<Object> defaults = - MutableClassToInstanceMap.create(); + private final Map<Class<?>, Object> defaults = Maps.newHashMap(); private final List<Member> ignoredMembers = Lists.newArrayList(); + public NullPointerTester() { + setCommonDefaults(); + } + + private final void setCommonDefaults() { + setDefault(Appendable.class, new StringBuilder()); + setDefault(CharSequence.class, ""); + setDefault(Class.class, Class.class); + setDefault(Collection.class, Collections.emptySet()); + setDefault(Comparable.class, 0); + setDefault(Comparator.class, Collections.reverseOrder()); + setDefault(Function.class, Functions.identity()); + setDefault(Integer.class, 0); + setDefault(Iterable.class, Collections.emptySet()); + setDefault(Iterator.class, Iterators.emptyIterator()); + setDefault(List.class, Collections.emptyList()); + setDefault(Map.class, Collections.emptyMap()); + setDefault(Object.class, new Object()); + setDefault(Object[].class, new Object[0]); + setDefault(Pattern.class, Pattern.compile("")); + setDefault(Predicate.class, Predicates.alwaysTrue()); + setDefault(Set.class, Collections.emptySet()); + setDefault(SortedSet.class, new TreeSet()); + setDefault(String.class, ""); + setDefault(Supplier.class, Suppliers.ofInstance(1)); + setDefault(Throwable.class, new Exception()); + setDefault(TimeUnit.class, TimeUnit.SECONDS); + setDefault(int.class, 0); + setDefault(long.class, 0L); + setDefault(short.class, (short) 0); + setDefault(char.class, 'a'); + setDefault(byte.class, (byte) 0); + setDefault(float.class, 0.0f); + setDefault(double.class, 0.0d); + setDefault(boolean.class, false); + } + /** * Sets a default value that can be used for any parameter of type * {@code type}. Returns this object. */ public <T> NullPointerTester setDefault(Class<T> type, T value) { - defaults.putInstance(type, checkNotNull(value)); + defaults.put(type, value); return this; } /** - * Ignore {@code method} in the tests that follow. Returns this object. - * - * @since 13.0 + * Ignore a member (constructor or method) in testAllXxx methods. Returns + * this object. */ - public NullPointerTester ignore(Method method) { - ignoredMembers.add(checkNotNull(method)); + public NullPointerTester ignore(Member member) { + ignoredMembers.add(member); return this; } /** - * Runs {@link #testConstructor} on every constructor in class {@code c} that - * has at least {@code minimalVisibility}. + * Runs {@link #testConstructor} on every public constructor in class {@code + * c}. */ - public void testConstructors(Class<?> c, Visibility minimalVisibility) { + public void testAllPublicConstructors(Class<?> c) throws Exception { for (Constructor<?> constructor : c.getDeclaredConstructors()) { - if (minimalVisibility.isVisible(constructor) && !isIgnored(constructor)) { + if (isPublic(constructor) && !isStatic(constructor) + && !isIgnored(constructor)) { testConstructor(constructor); } } } /** - * Runs {@link #testConstructor} on every public constructor in class {@code - * c}. - */ - public void testAllPublicConstructors(Class<?> c) { - testConstructors(c, Visibility.PUBLIC); - } - - /** - * Runs {@link #testMethod} on every static method of class {@code c} that has - * at least {@code minimalVisibility}, including those "inherited" from - * superclasses of the same package. + * Runs {@link #testMethod} on every public static method in class + * {@code c}. */ - public void testStaticMethods(Class<?> c, Visibility minimalVisibility) { - for (Method method : minimalVisibility.getStaticMethods(c)) { - if (!isIgnored(method)) { + public void testAllPublicStaticMethods(Class<?> c) throws Exception { + for (Method method : c.getDeclaredMethods()) { + if (isPublic(method) && isStatic(method) && !isIgnored(method)) { testMethod(null, method); } } } /** - * Runs {@link #testMethod} on every public static method of class {@code c}, - * including those "inherited" from superclasses of the same package. - */ - public void testAllPublicStaticMethods(Class<?> c) { - testStaticMethods(c, Visibility.PUBLIC); - } - - /** - * Runs {@link #testMethod} on every instance method of the class of - * {@code instance} with at least {@code minimalVisibility}, including those - * inherited from superclasses of the same package. + * Runs {@link #testMethod} on every public instance method of + * {@code instance}. */ - public void testInstanceMethods(Object instance, Visibility minimalVisibility) { - for (Method method : getInstanceMethodsToTest(instance.getClass(), minimalVisibility)) { - testMethod(instance, method); - } - } - - ImmutableList<Method> getInstanceMethodsToTest(Class<?> c, Visibility minimalVisibility) { - ImmutableList.Builder<Method> builder = ImmutableList.builder(); - for (Method method : minimalVisibility.getInstanceMethods(c)) { - if (!isIgnored(method)) { - builder.add(method); + public void testAllPublicInstanceMethods(Object instance) throws Exception { + Class<?> c = instance.getClass(); + for (Method method : c.getDeclaredMethods()) { + if (isPublic(method) && !isStatic(method) && !isIgnored(method)) { + testMethod(instance, method); } } - return builder.build(); - } - - /** - * Runs {@link #testMethod} on every public instance method of the class of - * {@code instance}, including those inherited from superclasses of the same - * package. - */ - public void testAllPublicInstanceMethods(Object instance) { - testInstanceMethods(instance, Visibility.PUBLIC); } /** @@ -169,7 +166,7 @@ public final class NullPointerTester { * @param instance the instance to invoke {@code method} on, or null if * {@code method} is static */ - public void testMethod(@Nullable Object instance, Method method) { + public void testMethod(Object instance, Method method) throws Exception { Class<?>[] types = method.getParameterTypes(); for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { testMethodParameter(instance, method, nullIndex); @@ -181,11 +178,7 @@ public final class NullPointerTester { * {@link UnsupportedOperationException} whenever <i>any</i> of its * non-{@link Nullable} parameters are null. */ - public void testConstructor(Constructor<?> ctor) { - Class<?> declaringClass = ctor.getDeclaringClass(); - checkArgument(Modifier.isStatic(declaringClass.getModifiers()) - || declaringClass.getEnclosingClass() == null, - "Cannot test constructor of non-static inner class: %s", declaringClass.getName()); + public void testConstructor(Constructor<?> ctor) throws Exception { Class<?>[] types = ctor.getParameterTypes(); for (int nullIndex = 0; nullIndex < types.length; nullIndex++) { testConstructorParameter(ctor, nullIndex); @@ -201,10 +194,25 @@ public final class NullPointerTester { * @param instance the instance to invoke {@code method} on, or null if * {@code method} is static */ - public void testMethodParameter( - @Nullable final Object instance, final Method method, int paramIndex) { + public void testMethodParameter(Object instance, final Method method, + int paramIndex) throws Exception { method.setAccessible(true); - testParameter(instance, invokable(instance, method), paramIndex, method.getDeclaringClass()); + testFunctorParameter(instance, new Functor() { + @Override public Class<?>[] getParameterTypes() { + return method.getParameterTypes(); + } + @Override public Annotation[][] getParameterAnnotations() { + return method.getParameterAnnotations(); + } + @Override public void invoke(Object instance, Object[] params) + throws InvocationTargetException, IllegalAccessException { + method.invoke(instance, params); + } + @Override public String toString() { + return method.getName() + + "(" + Arrays.toString(getParameterTypes()) + ")"; + } + }, paramIndex, method.getDeclaringClass()); } /** @@ -213,129 +221,42 @@ public final class NullPointerTester { * paramIndex} is null. If this parameter is marked {@link Nullable}, this * method does nothing. */ - public void testConstructorParameter(Constructor<?> ctor, int paramIndex) { + public void testConstructorParameter(final Constructor<?> ctor, + int paramIndex) throws Exception { ctor.setAccessible(true); - testParameter(null, Invokable.from(ctor), paramIndex, ctor.getDeclaringClass()); - } - - /** Visibility of any method or constructor. */ - public enum Visibility { - - PACKAGE { - @Override boolean isVisible(int modifiers) { - return !Modifier.isPrivate(modifiers); - } - }, - - PROTECTED { - @Override boolean isVisible(int modifiers) { - return Modifier.isPublic(modifiers) || Modifier.isProtected(modifiers); - } - }, - - PUBLIC { - @Override boolean isVisible(int modifiers) { - return Modifier.isPublic(modifiers); - } - }; - - abstract boolean isVisible(int modifiers); - - /** - * Returns {@code true} if {@code member} is visible under {@code this} - * visibility. - */ - final boolean isVisible(Member member) { - return isVisible(member.getModifiers()); - } - - final Iterable<Method> getStaticMethods(Class<?> cls) { - ImmutableList.Builder<Method> builder = ImmutableList.builder(); - for (Method method : getVisibleMethods(cls)) { - if (Invokable.from(method).isStatic()) { - builder.add(method); - } - } - return builder.build(); - } - - final Iterable<Method> getInstanceMethods(Class<?> cls) { - ConcurrentMap<Signature, Method> map = Maps.newConcurrentMap(); - for (Method method : getVisibleMethods(cls)) { - if (!Invokable.from(method).isStatic()) { - map.putIfAbsent(new Signature(method), method); + testFunctorParameter(null, new Functor() { + @Override public Class<?>[] getParameterTypes() { + return ctor.getParameterTypes(); } - } - return map.values(); - } - - private ImmutableList<Method> getVisibleMethods(Class<?> cls) { - // Don't use cls.getPackage() because it does nasty things like reading - // a file. - String visiblePackage = Reflection.getPackageName(cls); - ImmutableList.Builder<Method> builder = ImmutableList.builder(); - for (Class<?> type : TypeToken.of(cls).getTypes().classes().rawTypes()) { - if (!Reflection.getPackageName(type).equals(visiblePackage)) { - break; + @Override public Annotation[][] getParameterAnnotations() { + return ctor.getParameterAnnotations(); } - for (Method method : type.getDeclaredMethods()) { - if (!method.isSynthetic() && isVisible(method)) { - builder.add(method); - } + @Override public void invoke(Object instance, Object[] params) + throws InvocationTargetException, IllegalAccessException, + InstantiationException { + ctor.newInstance(params); } - } - return builder.build(); - } - } - - // TODO(benyu): Use labs/reflect/Signature if it graduates. - private static final class Signature { - private final String name; - private final ImmutableList<Class<?>> parameterTypes; - - Signature(Method method) { - this(method.getName(), ImmutableList.copyOf(method.getParameterTypes())); - } - - Signature(String name, ImmutableList<Class<?>> parameterTypes) { - this.name = name; - this.parameterTypes = parameterTypes; - } - - @Override public boolean equals(Object obj) { - if (obj instanceof Signature) { - Signature that = (Signature) obj; - return name.equals(that.name) - && parameterTypes.equals(that.parameterTypes); - } - return false; - } - - @Override public int hashCode() { - return Objects.hashCode(name, parameterTypes); - } + }, paramIndex, ctor.getDeclaringClass()); } /** - * Verifies that {@code invokable} produces a {@link NullPointerException} or + * Verifies that {@code func} produces a {@link NullPointerException} or * {@link UnsupportedOperationException} when the parameter in position {@code * paramIndex} is null. If this parameter is marked {@link Nullable}, this * method does nothing. * - * @param instance the instance to invoke {@code invokable} on, or null if - * {@code invokable} is static + * @param instance the instance to invoke {@code func} on, or null if + * {@code func} is static */ - private void testParameter(Object instance, Invokable<?, ?> invokable, - int paramIndex, Class<?> testedClass) { - if (isPrimitiveOrNullable(invokable.getParameters().get(paramIndex))) { + private void testFunctorParameter(Object instance, Functor func, + int paramIndex, Class<?> testedClass) throws Exception { + if (parameterIsPrimitiveOrNullable(func, paramIndex)) { return; // there's nothing to test } - Object[] params = buildParamList(invokable, paramIndex); + Object[] params = buildParamList(func, paramIndex); try { - @SuppressWarnings("unchecked") // We'll get a runtime exception if the type is wrong. - Invokable<Object, ?> unsafe = (Invokable<Object, ?>) invokable; - unsafe.invoke(instance, params); - Assert.fail("No exception thrown from " + invokable + + func.invoke(instance, params); + Assert.fail("No exception thrown from " + func + Arrays.toString(params) + " for " + testedClass); } catch (InvocationTargetException e) { Throwable cause = e.getCause(); @@ -344,89 +265,54 @@ public final class NullPointerTester { return; } AssertionFailedError error = new AssertionFailedError( - "wrong exception thrown from " + invokable + ": " + cause); + "wrong exception thrown from " + func + ": " + cause); error.initCause(cause); throw error; - } catch (IllegalAccessException e) { - throw new RuntimeException(e); } } - private Object[] buildParamList(Invokable<?, ?> invokable, int indexOfParamToSetToNull) { - ImmutableList<Parameter> params = invokable.getParameters(); - Object[] args = new Object[params.size()]; - - for (int i = 0; i < args.length; i++) { - Parameter param = params.get(i); - if (i != indexOfParamToSetToNull) { - args[i] = getDefaultValue(param.getType()); - if (!isPrimitiveOrNullable(param)) { - Assert.assertTrue("No default value found for " + param + " of "+ invokable, - args[i] != null); - } + private static boolean parameterIsPrimitiveOrNullable( + Functor func, int paramIndex) { + if (func.getParameterTypes()[paramIndex].isPrimitive()) { + return true; + } + Annotation[] annotations = func.getParameterAnnotations()[paramIndex]; + for (Annotation annotation : annotations) { + if (annotation instanceof Nullable) { + return true; } } - return args; + return false; } - private <T> T getDefaultValue(TypeToken<T> type) { - // We assume that all defaults are generics-safe, even if they aren't, - // we take the risk. - @SuppressWarnings("unchecked") - T defaultValue = (T) defaults.getInstance(type.getRawType()); - if (defaultValue != null) { - return defaultValue; - } - @SuppressWarnings("unchecked") // All null values are generics-safe - T nullValue = (T) ArbitraryInstances.get(type.getRawType()); - if (nullValue != null) { - return nullValue; - } - if (type.getRawType() == Class.class) { - // If parameter is Class<? extends Foo>, we return Foo.class - @SuppressWarnings("unchecked") - T defaultClass = (T) getFirstTypeParameter(type.getType()).getRawType(); - return defaultClass; - } - if (type.getRawType() == TypeToken.class) { - // If parameter is TypeToken<? extends Foo>, we return TypeToken<Foo>. - @SuppressWarnings("unchecked") - T defaultType = (T) getFirstTypeParameter(type.getType()); - return defaultType; - } - if (type.getRawType().isInterface()) { - return newDefaultReturningProxy(type); - } - return null; - } + private Object[] buildParamList(Functor func, int indexOfParamToSetToNull) { + Class<?>[] types = func.getParameterTypes(); + Object[] params = new Object[types.length]; - private static TypeToken<?> getFirstTypeParameter(Type type) { - if (type instanceof ParameterizedType) { - return TypeToken.of( - ((ParameterizedType) type).getActualTypeArguments()[0]); - } else { - return TypeToken.of(Object.class); + for (int i = 0; i < types.length; i++) { + if (i != indexOfParamToSetToNull) { + params[i] = defaults.get(types[i]); + if (!parameterIsPrimitiveOrNullable(func, i)) { + Assert.assertTrue("No default value found for " + types[i].getName(), + params[i] != null); + } + } } + return params; } - private <T> T newDefaultReturningProxy(final TypeToken<T> type) { - return new DummyProxy() { - @Override <R> R dummyReturnValue(TypeToken<R> returnType) { - return getDefaultValue(returnType); - } - }.newProxy(type); + private interface Functor { + Class<?>[] getParameterTypes(); + Annotation[][] getParameterAnnotations(); + void invoke(Object o, Object[] params) throws Exception; } - private static Invokable<?, ?> invokable(@Nullable Object instance, Method method) { - if (instance == null) { - return Invokable.from(method); - } else { - return TypeToken.of(instance.getClass()).method(method); - } + private static boolean isPublic(Member member) { + return Modifier.isPublic(member.getModifiers()); } - static boolean isPrimitiveOrNullable(Parameter param) { - return param.getType().getRawType().isPrimitive() || param.isAnnotationPresent(Nullable.class); + private static boolean isStatic(Member member) { + return Modifier.isStatic(member.getModifiers()); } private boolean isIgnored(Member member) { |