| /* |
| * 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.compatqual.NullableDecl; |
| |
| /** |
| * 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. |
| */ |
| @NullableDecl |
| <T> 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. |
| */ |
| @NullableDecl |
| private <T> 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; |
| } |
| |
| @NullableDecl |
| private static 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; |
| } |
| |
| @NullableDecl |
| private static <T> 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; |
| } |
| } |
| } |