blob: 0c8daf08e1550ec09512bdd960e1e9b013037039 [file] [log] [blame]
/*
* Copyright (C) 2012 The Guava Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.common.testing;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Throwables.throwIfUnchecked;
import static com.google.common.testing.NullPointerTester.isNullable;
import com.google.common.annotations.Beta;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.base.Objects;
import com.google.common.collect.ArrayListMultimap;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ListMultimap;
import com.google.common.collect.Lists;
import com.google.common.collect.MutableClassToInstanceMap;
import com.google.common.collect.Ordering;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
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 com.google.common.testing.NullPointerTester.Visibility;
import com.google.common.testing.RelationshipTester.Item;
import com.google.common.testing.RelationshipTester.ItemReporter;
import java.io.Serializable;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.List;
import java.util.Map.Entry;
import java.util.Set;
import junit.framework.Assert;
import junit.framework.AssertionFailedError;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Tester that runs automated sanity tests for any given class. A typical use case is to test static
* factory classes like:
*
* <pre>
* interface Book {...}
* public class Books {
* public static Book hardcover(String title) {...}
* public static Book paperback(String title) {...}
* }
* </pre>
*
* <p>And all the created {@code Book} instances can be tested with:
*
* <pre>
* new ClassSanityTester()
* .forAllPublicStaticMethods(Books.class)
* .thatReturn(Book.class)
* .testEquals(); // or testNulls(), testSerializable() etc.
* </pre>
*
* @author Ben Yu
* @since 14.0
*/
@Beta
@GwtIncompatible
public final class ClassSanityTester {
private static final Ordering<Invokable<?, ?>> BY_METHOD_NAME =
new Ordering<Invokable<?, ?>>() {
@Override
public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
return left.getName().compareTo(right.getName());
}
};
private static final Ordering<Invokable<?, ?>> BY_PARAMETERS =
new Ordering<Invokable<?, ?>>() {
@Override
public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
return Ordering.usingToString().compare(left.getParameters(), right.getParameters());
}
};
private static final Ordering<Invokable<?, ?>> BY_NUMBER_OF_PARAMETERS =
new Ordering<Invokable<?, ?>>() {
@Override
public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
return Ints.compare(left.getParameters().size(), right.getParameters().size());
}
};
private final MutableClassToInstanceMap<Object> defaultValues =
MutableClassToInstanceMap.create();
private final ListMultimap<Class<?>, Object> distinctValues = ArrayListMultimap.create();
private final NullPointerTester nullPointerTester = new NullPointerTester();
public ClassSanityTester() {
// TODO(benyu): bake these into ArbitraryInstances.
setDefault(byte.class, (byte) 1);
setDefault(Byte.class, (byte) 1);
setDefault(short.class, (short) 1);
setDefault(Short.class, (short) 1);
setDefault(int.class, 1);
setDefault(Integer.class, 1);
setDefault(long.class, 1L);
setDefault(Long.class, 1L);
setDefault(float.class, 1F);
setDefault(Float.class, 1F);
setDefault(double.class, 1D);
setDefault(Double.class, 1D);
setDefault(Class.class, Class.class);
}
/**
* Sets the default value for {@code type}. The default value isn't used in testing {@link
* Object#equals} because more than one sample instances are needed for testing inequality. To set
* distinct values for equality testing, use {@link #setDistinctValues} instead.
*/
public <T> ClassSanityTester setDefault(Class<T> type, T value) {
nullPointerTester.setDefault(type, value);
defaultValues.putInstance(type, value);
return this;
}
/**
* Sets distinct values for {@code type}, so that when a class {@code Foo} is tested for {@link
* Object#equals} and {@link Object#hashCode}, and its construction requires a parameter of {@code
* type}, the distinct values of {@code type} can be passed as parameters to create {@code Foo}
* instances that are unequal.
*
* <p>Calling {@code setDistinctValues(type, v1, v2)} also sets the default value for {@code type}
* that's used for {@link #testNulls}.
*
* <p>Only necessary for types where {@link ClassSanityTester} doesn't already know how to create
* distinct values.
*
* @return this tester instance
* @since 17.0
*/
public <T> ClassSanityTester setDistinctValues(Class<T> type, T value1, T value2) {
checkNotNull(type);
checkNotNull(value1);
checkNotNull(value2);
checkArgument(!Objects.equal(value1, value2), "Duplicate value provided.");
distinctValues.replaceValues(type, ImmutableList.of(value1, value2));
setDefault(type, value1);
return this;
}
/**
* Tests that {@code cls} properly checks null on all constructor and method parameters that
* aren't annotated nullable (according to the rules of {@link NullPointerTester}). In details:
*
* <ul>
* <li>All non-private static methods are checked such that passing null for any parameter
* that's not annotated nullable should throw {@link NullPointerException}.
* <li>If there is any non-private constructor or non-private static factory method declared by
* {@code cls}, all non-private instance methods will be checked too using the instance
* created by invoking the constructor or static factory method.
* <li>If there is any non-private constructor or non-private static factory method declared by
* {@code cls}:
* <ul>
* <li>Test will fail if default value for a parameter cannot be determined.
* <li>Test will fail if the factory method returns null so testing instance methods is
* impossible.
* <li>Test will fail if the constructor or factory method throws exception.
* </ul>
* <li>If there is no non-private constructor or non-private static factory method declared by
* {@code cls}, instance methods are skipped for nulls test.
* <li>Nulls test is not performed on method return values unless the method is a non-private
* static factory method whose return type is {@code cls} or {@code cls}'s subtype.
* </ul>
*/
public void testNulls(Class<?> cls) {
try {
doTestNulls(cls, Visibility.PACKAGE);
} catch (Exception e) {
throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
void doTestNulls(Class<?> cls, Visibility visibility)
throws ParameterNotInstantiableException, IllegalAccessException, InvocationTargetException,
FactoryMethodReturnsNullException {
if (!Modifier.isAbstract(cls.getModifiers())) {
nullPointerTester.testConstructors(cls, visibility);
}
nullPointerTester.testStaticMethods(cls, visibility);
if (hasInstanceMethodToTestNulls(cls, visibility)) {
Object instance = instantiate(cls);
if (instance != null) {
nullPointerTester.testInstanceMethods(instance, visibility);
}
}
}
private boolean hasInstanceMethodToTestNulls(Class<?> c, Visibility visibility) {
for (Method method : nullPointerTester.getInstanceMethodsToTest(c, visibility)) {
for (Parameter param : Invokable.from(method).getParameters()) {
if (!NullPointerTester.isPrimitiveOrNullable(param)) {
return true;
}
}
}
return false;
}
/**
* Tests the {@link Object#equals} and {@link Object#hashCode} of {@code cls}. In details:
*
* <ul>
* <li>The non-private constructor or non-private static factory method with the most parameters
* is used to construct the sample instances. In case of tie, the candidate constructors or
* factories are tried one after another until one can be used to construct sample
* instances.
* <li>For the constructor or static factory method used to construct instances, it's checked
* that when equal parameters are passed, the result instance should also be equal; and vice
* versa.
* <li>If a non-private constructor or non-private static factory method exists:
* <ul>
* <li>Test will fail if default value for a parameter cannot be determined.
* <li>Test will fail if the factory method returns null so testing instance methods is
* impossible.
* <li>Test will fail if the constructor or factory method throws exception.
* </ul>
* <li>If there is no non-private constructor or non-private static factory method declared by
* {@code cls}, no test is performed.
* <li>Equality test is not performed on method return values unless the method is a non-private
* static factory method whose return type is {@code cls} or {@code cls}'s subtype.
* <li>Inequality check is not performed against state mutation methods such as {@link
* List#add}, or functional update methods such as {@link
* com.google.common.base.Joiner#skipNulls}.
* </ul>
*
* <p>Note that constructors taking a builder object cannot be tested effectively because
* semantics of builder can be arbitrarily complex. Still, a factory class can be created in the
* test to facilitate equality testing. For example:
*
* <pre>
* public class FooTest {
*
* private static class FooFactoryForTest {
* public static Foo create(String a, String b, int c, boolean d) {
* return Foo.builder()
* .setA(a)
* .setB(b)
* .setC(c)
* .setD(d)
* .build();
* }
* }
*
* public void testEquals() {
* new ClassSanityTester()
* .forAllPublicStaticMethods(FooFactoryForTest.class)
* .thatReturn(Foo.class)
* .testEquals();
* }
* }
* </pre>
*
* <p>It will test that Foo objects created by the {@code create(a, b, c, d)} factory method with
* equal parameters are equal and vice versa, thus indirectly tests the builder equality.
*/
public void testEquals(Class<?> cls) {
try {
doTestEquals(cls);
} catch (Exception e) {
throwIfUnchecked(e);
throw new RuntimeException(e);
}
}
void doTestEquals(Class<?> cls)
throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
if (cls.isEnum()) {
return;
}
List<? extends Invokable<?, ?>> factories = Lists.reverse(getFactories(TypeToken.of(cls)));
if (factories.isEmpty()) {
return;
}
int numberOfParameters = factories.get(0).getParameters().size();
List<ParameterNotInstantiableException> paramErrors = Lists.newArrayList();
List<ParameterHasNoDistinctValueException> distinctValueErrors = Lists.newArrayList();
List<InvocationTargetException> instantiationExceptions = Lists.newArrayList();
List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList();
// Try factories with the greatest number of parameters.
for (Invokable<?, ?> factory : factories) {
if (factory.getParameters().size() == numberOfParameters) {
try {
testEqualsUsing(factory);
return;
} catch (ParameterNotInstantiableException e) {
paramErrors.add(e);
} catch (ParameterHasNoDistinctValueException e) {
distinctValueErrors.add(e);
} catch (InvocationTargetException e) {
instantiationExceptions.add(e);
} catch (FactoryMethodReturnsNullException e) {
nullErrors.add(e);
}
}
}
throwFirst(paramErrors);
throwFirst(distinctValueErrors);
throwFirst(instantiationExceptions);
throwFirst(nullErrors);
}
/**
* Instantiates {@code cls} by invoking one of its non-private constructors or non-private static
* factory methods with the parameters automatically provided using dummy values.
*
* @return The instantiated instance, or {@code null} if the class has no non-private constructor
* or factory method to be constructed.
*/
<T> @Nullable T instantiate(Class<T> cls)
throws ParameterNotInstantiableException, IllegalAccessException, InvocationTargetException,
FactoryMethodReturnsNullException {
if (cls.isEnum()) {
T[] constants = cls.getEnumConstants();
if (constants.length > 0) {
return constants[0];
} else {
return null;
}
}
TypeToken<T> type = TypeToken.of(cls);
List<ParameterNotInstantiableException> paramErrors = Lists.newArrayList();
List<InvocationTargetException> instantiationExceptions = Lists.newArrayList();
List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList();
for (Invokable<?, ? extends T> factory : getFactories(type)) {
T instance;
try {
instance = instantiate(factory);
} catch (ParameterNotInstantiableException e) {
paramErrors.add(e);
continue;
} catch (InvocationTargetException e) {
instantiationExceptions.add(e);
continue;
}
if (instance == null) {
nullErrors.add(new FactoryMethodReturnsNullException(factory));
} else {
return instance;
}
}
throwFirst(paramErrors);
throwFirst(instantiationExceptions);
throwFirst(nullErrors);
return null;
}
/**
* Instantiates using {@code factory}. If {@code factory} is annotated nullable and returns null,
* null will be returned.
*
* @throws ParameterNotInstantiableException if the static methods cannot be invoked because the
* default value of a parameter cannot be determined.
* @throws IllegalAccessException if the class isn't public or is nested inside a non-public
* class, preventing its methods from being accessible.
* @throws InvocationTargetException if a static method threw exception.
*/
private <T> @Nullable T instantiate(Invokable<?, ? extends T> factory)
throws ParameterNotInstantiableException, InvocationTargetException, IllegalAccessException {
return invoke(factory, getDummyArguments(factory));
}
/**
* Returns an object responsible for performing sanity tests against the return values of all
* public static methods declared by {@code cls}, excluding superclasses.
*/
public FactoryMethodReturnValueTester forAllPublicStaticMethods(Class<?> cls) {
ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
for (Method method : cls.getDeclaredMethods()) {
Invokable<?, ?> invokable = Invokable.from(method);
invokable.setAccessible(true);
if (invokable.isPublic() && invokable.isStatic() && !invokable.isSynthetic()) {
builder.add(invokable);
}
}
return new FactoryMethodReturnValueTester(cls, builder.build(), "public static methods");
}
/** Runs sanity tests against return values of static factory methods declared by a class. */
public final class FactoryMethodReturnValueTester {
private final Set<String> packagesToTest = Sets.newHashSet();
private final Class<?> declaringClass;
private final ImmutableList<Invokable<?, ?>> factories;
private final String factoryMethodsDescription;
private Class<?> returnTypeToTest = Object.class;
private FactoryMethodReturnValueTester(
Class<?> declaringClass,
ImmutableList<Invokable<?, ?>> factories,
String factoryMethodsDescription) {
this.declaringClass = declaringClass;
this.factories = factories;
this.factoryMethodsDescription = factoryMethodsDescription;
packagesToTest.add(Reflection.getPackageName(declaringClass));
}
/**
* Specifies that only the methods that are declared to return {@code returnType} or its subtype
* are tested.
*
* @return this tester object
*/
public FactoryMethodReturnValueTester thatReturn(Class<?> returnType) {
this.returnTypeToTest = returnType;
return this;
}
/**
* Tests null checks against the instance methods of the return values, if any.
*
* <p>Test fails if default value cannot be determined for a constructor or factory method
* parameter, or if the constructor or factory method throws exception.
*
* @return this tester
*/
public FactoryMethodReturnValueTester testNulls() throws Exception {
for (Invokable<?, ?> factory : getFactoriesToTest()) {
Object instance = instantiate(factory);
if (instance != null
&& packagesToTest.contains(Reflection.getPackageName(instance.getClass()))) {
try {
nullPointerTester.testAllPublicInstanceMethods(instance);
} catch (AssertionError e) {
AssertionError error =
new AssertionFailedError("Null check failed on return value of " + factory);
error.initCause(e);
throw error;
}
}
}
return this;
}
/**
* Tests {@link Object#equals} and {@link Object#hashCode} against the return values of the
* static methods, by asserting that when equal parameters are passed to the same static method,
* the return value should also be equal; and vice versa.
*
* <p>Test fails if default value cannot be determined for a constructor or factory method
* parameter, or if the constructor or factory method throws exception.
*
* @return this tester
*/
public FactoryMethodReturnValueTester testEquals() throws Exception {
for (Invokable<?, ?> factory : getFactoriesToTest()) {
try {
testEqualsUsing(factory);
} catch (FactoryMethodReturnsNullException e) {
// If the factory returns null, we just skip it.
}
}
return this;
}
/**
* Runs serialization test on the return values of the static methods.
*
* <p>Test fails if default value cannot be determined for a constructor or factory method
* parameter, or if the constructor or factory method throws exception.
*
* @return this tester
*/
public FactoryMethodReturnValueTester testSerializable() throws Exception {
for (Invokable<?, ?> factory : getFactoriesToTest()) {
Object instance = instantiate(factory);
if (instance != null) {
try {
SerializableTester.reserialize(instance);
} catch (RuntimeException e) {
AssertionError error =
new AssertionFailedError("Serialization failed on return value of " + factory);
error.initCause(e.getCause());
throw error;
}
}
}
return this;
}
/**
* Runs equals and serialization test on the return values.
*
* <p>Test fails if default value cannot be determined for a constructor or factory method
* parameter, or if the constructor or factory method throws exception.
*
* @return this tester
*/
public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Exception {
for (Invokable<?, ?> factory : getFactoriesToTest()) {
try {
testEqualsUsing(factory);
} catch (FactoryMethodReturnsNullException e) {
// If the factory returns null, we just skip it.
}
Object instance = instantiate(factory);
if (instance != null) {
try {
SerializableTester.reserializeAndAssert(instance);
} catch (RuntimeException e) {
AssertionError error =
new AssertionFailedError("Serialization failed on return value of " + factory);
error.initCause(e.getCause());
throw error;
} catch (AssertionFailedError e) {
AssertionError error =
new AssertionFailedError(
"Return value of " + factory + " reserialized to an unequal value");
error.initCause(e);
throw error;
}
}
}
return this;
}
private ImmutableList<Invokable<?, ?>> getFactoriesToTest() {
ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
for (Invokable<?, ?> factory : factories) {
if (returnTypeToTest.isAssignableFrom(factory.getReturnType().getRawType())) {
builder.add(factory);
}
}
ImmutableList<Invokable<?, ?>> factoriesToTest = builder.build();
Assert.assertFalse(
"No "
+ factoryMethodsDescription
+ " that return "
+ returnTypeToTest.getName()
+ " or subtype are found in "
+ declaringClass
+ ".",
factoriesToTest.isEmpty());
return factoriesToTest;
}
}
private void testEqualsUsing(final Invokable<?, ?> factory)
throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
List<Parameter> params = factory.getParameters();
List<FreshValueGenerator> argGenerators = Lists.newArrayListWithCapacity(params.size());
List<Object> args = Lists.newArrayListWithCapacity(params.size());
for (Parameter param : params) {
FreshValueGenerator generator = newFreshValueGenerator();
argGenerators.add(generator);
args.add(generateDummyArg(param, generator));
}
Object instance = createInstance(factory, args);
List<Object> equalArgs = generateEqualFactoryArguments(factory, params, args);
// Each group is a List of items, each item has a list of factory args.
final List<List<List<Object>>> argGroups = Lists.newArrayList();
argGroups.add(ImmutableList.of(args, equalArgs));
EqualsTester tester =
new EqualsTester(
new ItemReporter() {
@Override
String reportItem(Item<?> item) {
List<Object> factoryArgs = argGroups.get(item.groupNumber).get(item.itemNumber);
return factory.getName()
+ "("
+ Joiner.on(", ").useForNull("null").join(factoryArgs)
+ ")";
}
});
tester.addEqualityGroup(instance, createInstance(factory, equalArgs));
for (int i = 0; i < params.size(); i++) {
List<Object> newArgs = Lists.newArrayList(args);
Object newArg = argGenerators.get(i).generateFresh(params.get(i).getType());
if (newArg == null || Objects.equal(args.get(i), newArg)) {
if (params.get(i).getType().getRawType().isEnum()) {
continue; // Nothing better we can do if it's single-value enum
}
throw new ParameterHasNoDistinctValueException(params.get(i));
}
newArgs.set(i, newArg);
tester.addEqualityGroup(createInstance(factory, newArgs));
argGroups.add(ImmutableList.of(newArgs));
}
tester.testEquals();
}
/**
* Returns dummy factory arguments that are equal to {@code args} but may be different instances,
* to be used to construct a second instance of the same equality group.
*/
private List<Object> generateEqualFactoryArguments(
Invokable<?, ?> factory, List<Parameter> params, List<Object> args)
throws ParameterNotInstantiableException, FactoryMethodReturnsNullException,
InvocationTargetException, IllegalAccessException {
List<Object> equalArgs = Lists.newArrayList(args);
for (int i = 0; i < args.size(); i++) {
Parameter param = params.get(i);
Object arg = args.get(i);
// Use new fresh value generator because 'args' were populated with new fresh generator each.
// Two newFreshValueGenerator() instances should normally generate equal value sequence.
Object shouldBeEqualArg = generateDummyArg(param, newFreshValueGenerator());
if (arg != shouldBeEqualArg
&& Objects.equal(arg, shouldBeEqualArg)
&& hashCodeInsensitiveToArgReference(factory, args, i, shouldBeEqualArg)
&& hashCodeInsensitiveToArgReference(
factory, args, i, generateDummyArg(param, newFreshValueGenerator()))) {
// If the implementation uses identityHashCode(), referential equality is
// probably intended. So no point in using an equal-but-different factory argument.
// We check twice to avoid confusion caused by accidental hash collision.
equalArgs.set(i, shouldBeEqualArg);
}
}
return equalArgs;
}
private static boolean hashCodeInsensitiveToArgReference(
Invokable<?, ?> factory, List<Object> args, int i, Object alternateArg)
throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
List<Object> tentativeArgs = Lists.newArrayList(args);
tentativeArgs.set(i, alternateArg);
return createInstance(factory, tentativeArgs).hashCode()
== createInstance(factory, args).hashCode();
}
// distinctValues is a type-safe class-values mapping, but we don't have a type-safe data
// structure to hold the mappings.
@SuppressWarnings({"unchecked", "rawtypes"})
private FreshValueGenerator newFreshValueGenerator() {
FreshValueGenerator generator =
new FreshValueGenerator() {
@Override
Object interfaceMethodCalled(Class<?> interfaceType, Method method) {
return getDummyValue(TypeToken.of(interfaceType).method(method).getReturnType());
}
};
for (Entry<Class<?>, Collection<Object>> entry : distinctValues.asMap().entrySet()) {
generator.addSampleInstances((Class) entry.getKey(), entry.getValue());
}
return generator;
}
private static @Nullable Object generateDummyArg(Parameter param, FreshValueGenerator generator)
throws ParameterNotInstantiableException {
if (isNullable(param)) {
return null;
}
Object arg = generator.generateFresh(param.getType());
if (arg == null) {
throw new ParameterNotInstantiableException(param);
}
return arg;
}
private static <X extends Throwable> void throwFirst(List<X> exceptions) throws X {
if (!exceptions.isEmpty()) {
throw exceptions.get(0);
}
}
/** Factories with the least number of parameters are listed first. */
private static <T> ImmutableList<Invokable<?, ? extends T>> getFactories(TypeToken<T> type) {
List<Invokable<?, ? extends T>> factories = Lists.newArrayList();
for (Method method : type.getRawType().getDeclaredMethods()) {
Invokable<?, ?> invokable = type.method(method);
if (!invokable.isPrivate()
&& !invokable.isSynthetic()
&& invokable.isStatic()
&& type.isSupertypeOf(invokable.getReturnType())) {
@SuppressWarnings("unchecked") // guarded by isAssignableFrom()
Invokable<?, ? extends T> factory = (Invokable<?, ? extends T>) invokable;
factories.add(factory);
}
}
if (!Modifier.isAbstract(type.getRawType().getModifiers())) {
for (Constructor<?> constructor : type.getRawType().getDeclaredConstructors()) {
Invokable<T, T> invokable = type.constructor(constructor);
if (!invokable.isPrivate() && !invokable.isSynthetic()) {
factories.add(invokable);
}
}
}
for (Invokable<?, ?> factory : factories) {
factory.setAccessible(true);
}
// Sorts methods/constructors with least number of parameters first since it's likely easier to
// fill dummy parameter values for them. Ties are broken by name then by the string form of the
// parameter list.
return BY_NUMBER_OF_PARAMETERS
.compound(BY_METHOD_NAME)
.compound(BY_PARAMETERS)
.immutableSortedCopy(factories);
}
private List<Object> getDummyArguments(Invokable<?, ?> invokable)
throws ParameterNotInstantiableException {
List<Object> args = Lists.newArrayList();
for (Parameter param : invokable.getParameters()) {
if (isNullable(param)) {
args.add(null);
continue;
}
Object defaultValue = getDummyValue(param.getType());
if (defaultValue == null) {
throw new ParameterNotInstantiableException(param);
}
args.add(defaultValue);
}
return args;
}
private <T> T getDummyValue(TypeToken<T> type) {
Class<? super T> rawType = type.getRawType();
@SuppressWarnings("unchecked") // Assume all default values are generics safe.
T defaultValue = (T) defaultValues.getInstance(rawType);
if (defaultValue != null) {
return defaultValue;
}
@SuppressWarnings("unchecked") // ArbitraryInstances always returns generics-safe dummies.
T value = (T) ArbitraryInstances.get(rawType);
if (value != null) {
return value;
}
if (rawType.isInterface()) {
return new SerializableDummyProxy(this).newProxy(type);
}
return null;
}
private static <T> T createInstance(Invokable<?, ? extends T> factory, List<?> args)
throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
T instance = invoke(factory, args);
if (instance == null) {
throw new FactoryMethodReturnsNullException(factory);
}
return instance;
}
private static <T> @Nullable T invoke(Invokable<?, ? extends T> factory, List<?> args)
throws InvocationTargetException, IllegalAccessException {
T returnValue = factory.invoke(null, args.toArray());
if (returnValue == null) {
Assert.assertTrue(
factory + " returns null but it's not annotated with @Nullable", isNullable(factory));
}
return returnValue;
}
/**
* Thrown if the test tries to invoke a constructor or static factory method but failed because
* the dummy value of a constructor or method parameter is unknown.
*/
@VisibleForTesting
static class ParameterNotInstantiableException extends Exception {
public ParameterNotInstantiableException(Parameter parameter) {
super(
"Cannot determine value for parameter "
+ parameter
+ " of "
+ parameter.getDeclaringInvokable());
}
}
/**
* Thrown if the test fails to generate two distinct non-null values of a constructor or factory
* parameter in order to test {@link Object#equals} and {@link Object#hashCode} of the declaring
* class.
*/
@VisibleForTesting
static class ParameterHasNoDistinctValueException extends Exception {
ParameterHasNoDistinctValueException(Parameter parameter) {
super(
"Cannot generate distinct value for parameter "
+ parameter
+ " of "
+ parameter.getDeclaringInvokable());
}
}
/**
* Thrown if the test tries to invoke a static factory method to test instance methods but the
* factory returned null.
*/
@VisibleForTesting
static class FactoryMethodReturnsNullException extends Exception {
public FactoryMethodReturnsNullException(Invokable<?, ?> factory) {
super(factory + " returns null and cannot be used to test instance methods.");
}
}
private static final class SerializableDummyProxy extends DummyProxy implements Serializable {
private final transient ClassSanityTester tester;
SerializableDummyProxy(ClassSanityTester tester) {
this.tester = tester;
}
@Override
<R> R dummyReturnValue(TypeToken<R> returnType) {
return tester.getDummyValue(returnType);
}
@Override
public boolean equals(Object obj) {
return obj instanceof SerializableDummyProxy;
}
@Override
public int hashCode() {
return 0;
}
}
}