Java example source code file (ClassSanityTester.java)
This example Java source code file (ClassSanityTester.java) is included in the alvinalexander.com
"Java Source Code
Warehouse" project. The intent of this project is to help you "Learn
Java by Example" TM.
The ClassSanityTester.java Java example source code
/*
* 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 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.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>
* <p>And all the created {@code Book} instances can be tested with:
* 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 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 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 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:
* <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) {
throw Throwables.propagate(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.
*/
@Nullable <T> T instantiate(Class 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, 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> 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 params, List
Other Java examples (source code examples)
Here is a short list of links related to this Java ClassSanityTester.java source code file: