diff options
Diffstat (limited to 'guava-testlib/src/com/google/common/testing/ClassSanityTester.java')
-rw-r--r-- | guava-testlib/src/com/google/common/testing/ClassSanityTester.java | 765 |
1 files changed, 0 insertions, 765 deletions
diff --git a/guava-testlib/src/com/google/common/testing/ClassSanityTester.java b/guava-testlib/src/com/google/common/testing/ClassSanityTester.java deleted file mode 100644 index 4b273fb..0000000 --- a/guava-testlib/src/com/google/common/testing/ClassSanityTester.java +++ /dev/null @@ -1,765 +0,0 @@ -/* - * 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.checkNotNull; - -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Joiner; -import com.google.common.base.Objects; -import com.google.common.base.Throwables; -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 junit.framework.Assert; -import junit.framework.AssertionFailedError; - -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; -import java.util.Set; - -import javax.annotation.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> - * 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 -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> sampleInstances = 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 sample instances for equality testing, use {@link #setSampleInstances} instead. - */ - public <T> ClassSanityTester setDefault(Class<T> type, T value) { - nullPointerTester.setDefault(type, value); - defaultValues.putInstance(type, value); - return this; - } - - /** - * Sets sample instances for {@code type} for purpose of {@code equals} testing, where different - * values are needed to test inequality. - * - * <p>Used for types that {@link ClassSanityTester} doesn't already know how to sample. - * It's usually necessary to add two unequal instances for each type, with the exception that if - * the sample instance is to be passed to a {@link Nullable} parameter, one non-null sample is - * sufficient. Setting an empty list will clear sample instances for {@code type}. - */ - public <T> ClassSanityTester setSampleInstances(Class<T> type, Iterable<? extends T> instances) { - ImmutableList<? extends T> samples = ImmutableList.copyOf(instances); - sampleInstances.putAll(checkNotNull(type), samples); - if (!samples.isEmpty()) { - setDefault(type, samples.get(0)); - } - return this; - } - - /** - * Tests that {@code cls} properly checks null on all constructor and method parameters that - * aren't annotated with {@link Nullable}. In details: - * <ul> - * <li>All non-private static methods are checked such that passing null for any parameter that's - * not annotated with {@link javax.annotation.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) { - throw Throwables.propagate(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> - * 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) { - throw Throwables.propagate(e); - } - } - - void doTestEquals(Class<?> cls) - throws ParameterNotInstantiableException, 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<InvocationTargetException> instantiationExceptions = Lists.newArrayList(); - List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList(); - // Try factories with the greatest number of parameters first. - for (Invokable<?, ?> factory : factories) { - if (factory.getParameters().size() == numberOfParameters) { - try { - testEqualsUsing(factory); - return; - } catch (ParameterNotInstantiableException e) { - paramErrors.add(e); - } catch (InvocationTargetException e) { - instantiationExceptions.add(e); - } catch (FactoryMethodReturnsNullException e) { - nullErrors.add(e); - } - } - } - throwFirst(paramErrors); - 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. - */ - @Nullable <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; - } - - /** - * 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; - } - } - - /** - * Instantiates using {@code factory}. If {@code factory} is annotated with {@link 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. - */ - @Nullable private <T> T instantiate(Invokable<?, ? extends T> factory) - throws ParameterNotInstantiableException, InvocationTargetException, - IllegalAccessException { - return invoke(factory, getDummyArguments(factory)); - } - - private void testEqualsUsing(final Invokable<?, ?> factory) - throws ParameterNotInstantiableException, 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().setItemReporter(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).generate(params.get(i).getType().getRawType()); - if (Objects.equal(args.get(i), newArg)) { - // no value variance, no equality group - continue; - } - 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(); - } - - // sampleInstances is a type-safe class-values mapping, but we don't have a type-safe data - // 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 (Map.Entry<Class<?>, Collection<Object>> entry : sampleInstances.asMap().entrySet()) { - generator.addSampleInstances((Class) entry.getKey(), entry.getValue()); - } - return generator; - } - - private static @Nullable Object generateDummyArg(Parameter param, FreshValueGenerator generator) - throws ParameterNotInstantiableException { - if (param.isAnnotationPresent(Nullable.class)) { - return null; - } - Object arg = generator.generate(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.isAssignableFrom(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 (param.isAnnotationPresent(Nullable.class)) { - 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; - } - - @Nullable 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", - factory.isAnnotationPresent(Nullable.class)); - } - 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 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 transient final 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; - } - } -} |