| /* |
| * 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.Predicates.and; |
| import static com.google.common.base.Predicates.not; |
| import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix; |
| |
| import com.google.common.annotations.Beta; |
| import com.google.common.annotations.GwtIncompatible; |
| import com.google.common.annotations.VisibleForTesting; |
| import com.google.common.base.Optional; |
| import com.google.common.base.Predicate; |
| import com.google.common.collect.HashMultimap; |
| import com.google.common.collect.ImmutableList; |
| import com.google.common.collect.Iterables; |
| import com.google.common.collect.Lists; |
| import com.google.common.collect.Maps; |
| import com.google.common.collect.Multimap; |
| import com.google.common.collect.Sets; |
| import com.google.common.reflect.ClassPath; |
| import com.google.common.testing.NullPointerTester.Visibility; |
| import com.google.j2objc.annotations.J2ObjCIncompatible; |
| import java.io.IOException; |
| import java.io.Serializable; |
| import java.util.LinkedHashSet; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.TreeMap; |
| import java.util.logging.Level; |
| import java.util.logging.Logger; |
| import junit.framework.AssertionFailedError; |
| import junit.framework.TestCase; |
| import org.junit.Test; |
| |
| /** |
| * Automatically runs sanity checks against top level classes in the same package of the test that |
| * extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link |
| * NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example: |
| * |
| * <pre> |
| * public class PackageSanityTests extends AbstractPackageSanityTests {} |
| * </pre> |
| * |
| * <p>Note that only top-level classes with either a non-private constructor or a non-private static |
| * factory method to construct instances can have their instance methods checked. For example: |
| * |
| * <pre> |
| * public class Address { |
| * private final String city; |
| * private final String state; |
| * private final String zipcode; |
| * |
| * public Address(String city, String state, String zipcode) {...} |
| * |
| * {@literal @Override} public boolean equals(Object obj) {...} |
| * {@literal @Override} public int hashCode() {...} |
| * ... |
| * } |
| * </pre> |
| * |
| * <p>No cascading checks are performed against the return values of methods unless the method is a |
| * static factory method. Neither are semantics of mutation methods such as {@code |
| * someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see |
| * {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}. |
| * |
| * <p>For testing against the returned instances from a static factory class, such as |
| * |
| * <pre> |
| * interface Book {...} |
| * public class Books { |
| * public static Book hardcover(String title) {...} |
| * public static Book paperback(String title) {...} |
| * } |
| * </pre> |
| * |
| * <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}. |
| * |
| * <p>If not all classes on the classpath should be covered, {@link #ignoreClasses} can be used to |
| * exclude certain classes. As a special case, classes with an underscore in the name (like {@code |
| * AutoValue_Foo}) can be excluded using <code>ignoreClasses({@link #UNDERSCORE_IN_NAME})</code>. |
| * |
| * <p>{@link #setDefault} allows subclasses to specify default values for types. |
| * |
| * <p>This class incurs IO because it scans the classpath and reads classpath resources. |
| * |
| * @author Ben Yu |
| * @since 14.0 |
| */ |
| @Beta |
| // TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass |
| // Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4). |
| @GwtIncompatible |
| @J2ObjCIncompatible // com.google.common.reflect.ClassPath |
| public abstract class AbstractPackageSanityTests extends TestCase { |
| |
| /** |
| * A predicate that matches classes with an underscore in the class name. This can be used with |
| * {@link #ignoreClasses} to exclude generated classes, such as the {@code AutoValue_Foo} classes |
| * generated by <a href="https://github.com/google/auto/tree/master/value">AutoValue</a>. |
| * |
| * @since 19.0 |
| */ |
| public static final Predicate<Class<?>> UNDERSCORE_IN_NAME = |
| new Predicate<Class<?>>() { |
| @Override |
| public boolean apply(Class<?> c) { |
| return c.getSimpleName().contains("_"); |
| } |
| }; |
| |
| /* The names of the expected method that tests null checks. */ |
| private static final ImmutableList<String> NULL_TEST_METHOD_NAMES = |
| ImmutableList.of( |
| "testNulls", "testNull", |
| "testNullPointers", "testNullPointer", |
| "testNullPointerExceptions", "testNullPointerException"); |
| |
| /* The names of the expected method that tests serializable. */ |
| private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES = |
| ImmutableList.of( |
| "testSerializable", "testSerialization", |
| "testEqualsAndSerializable", "testEqualsAndSerialization"); |
| |
| /* The names of the expected method that tests equals. */ |
| private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES = |
| ImmutableList.of( |
| "testEquals", |
| "testEqualsAndHashCode", |
| "testEqualsAndSerializable", |
| "testEqualsAndSerialization", |
| "testEquality"); |
| |
| private static final Chopper TEST_SUFFIX = |
| suffix("Test").or(suffix("Tests")).or(suffix("TestCase")).or(suffix("TestSuite")); |
| |
| private final Logger logger = Logger.getLogger(getClass().getName()); |
| private final ClassSanityTester tester = new ClassSanityTester(); |
| private Visibility visibility = Visibility.PACKAGE; |
| private Predicate<Class<?>> classFilter = |
| new Predicate<Class<?>>() { |
| @Override |
| public boolean apply(Class<?> cls) { |
| return visibility.isVisible(cls.getModifiers()); |
| } |
| }; |
| |
| /** |
| * Restricts the sanity tests for public API only. By default, package-private API are also |
| * covered. |
| */ |
| protected final void publicApiOnly() { |
| visibility = Visibility.PUBLIC; |
| } |
| |
| /** |
| * Tests all top-level {@link Serializable} classes in the package. For a serializable Class |
| * {@code C}: |
| * |
| * <ul> |
| * <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will |
| * be checked to be equal to the instance before serialization. |
| * <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a |
| * superclass, no equality check is done on the deserialized instance because it's not clear |
| * whether the author intended for the class to be a value type. |
| * <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic |
| * proxy will be passed to the method. It's possible that the method body expects an |
| * instance method of the passed-in proxy to be of a certain value yet the proxy isn't aware |
| * of the assumption, in which case the equality check before and after serialization will |
| * fail. |
| * <li>If the constructor or factory method takes a parameter that {@link |
| * AbstractPackageSanityTests} doesn't know how to construct, the test will fail. |
| * <li>If there is no visible constructor or visible static factory method declared by {@code |
| * C}, {@code C} is skipped for serialization test, even if it implements {@link |
| * Serializable}. |
| * <li>Serialization test is not performed on method return values unless the method is a |
| * visible static factory method whose return type is {@code C} or {@code C}'s subtype. |
| * </ul> |
| * |
| * <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an |
| * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code |
| * C} will be excluded from automated serialization test performed by this method. |
| */ |
| @Test |
| public void testSerializable() throws Exception { |
| // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once. |
| for (Class<?> classToTest : |
| findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) { |
| if (Serializable.class.isAssignableFrom(classToTest)) { |
| try { |
| Object instance = tester.instantiate(classToTest); |
| if (instance != null) { |
| if (isEqualsDefined(classToTest)) { |
| SerializableTester.reserializeAndAssert(instance); |
| } else { |
| SerializableTester.reserialize(instance); |
| } |
| } |
| } catch (Throwable e) { |
| throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class |
| * {@code C} |
| * |
| * <ul> |
| * <li>All visible static methods are checked such that passing null for any parameter that's |
| * not annotated nullable (according to the rules of {@link NullPointerTester}) should throw |
| * {@link NullPointerException}. |
| * <li>If there is any visible constructor or visible static factory method declared by the |
| * class, all visible instance methods will be checked too using the instance created by |
| * invoking the constructor or static factory method. |
| * <li>If the constructor or factory method used to construct instance takes a parameter that |
| * {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail. |
| * <li>If there is no visible constructor or visible static factory method declared by {@code |
| * C}, instance methods are skipped for nulls test. |
| * <li>Nulls test is not performed on method return values unless the method is a visible static |
| * factory method whose return type is {@code C} or {@code C}'s subtype. |
| * </ul> |
| * |
| * <p>In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit |
| * {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be |
| * excluded from the automated null tests performed by this method. |
| */ |
| @Test |
| public void testNulls() throws Exception { |
| for (Class<?> classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) { |
| try { |
| tester.doTestNulls(classToTest, visibility); |
| } catch (Throwable e) { |
| throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e); |
| } |
| } |
| } |
| |
| /** |
| * Tests {@code equals()} and {@code hashCode()} implementations for every top-level class in the |
| * package, that explicitly implements {@link Object#equals}. For a class {@code C}: |
| * |
| * <ul> |
| * <li>The visible constructor or visible 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>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}. |
| * <li>If the constructor or factory method used to construct instance takes a parameter that |
| * {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail. |
| * <li>If there is no visible constructor or visible static factory method declared by {@code |
| * C}, {@code C} is skipped for equality test. |
| * <li>Equality test is not performed on method return values unless the method is a visible |
| * static factory method whose return type is {@code C} or {@code C}'s subtype. |
| * </ul> |
| * |
| * <p>In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an |
| * explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will |
| * be excluded from the automated {@code equals} test performed by this method. |
| */ |
| @Test |
| public void testEquals() throws Exception { |
| for (Class<?> classToTest : |
| findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) { |
| if (!classToTest.isEnum() && isEqualsDefined(classToTest)) { |
| try { |
| tester.doTestEquals(classToTest); |
| } catch (Throwable e) { |
| throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Sets the default value for {@code type}, when dummy value for a parameter of the same type |
| * needs to be created in order to invoke a method or constructor. The default value isn't used in |
| * testing {@link Object#equals} because more than one sample instances are needed for testing |
| * inequality. |
| */ |
| protected final <T> void setDefault(Class<T> type, T value) { |
| tester.setDefault(type, value); |
| } |
| |
| /** |
| * Sets two distinct values for {@code type}. These values can be used for both null pointer |
| * testing and equals testing. |
| * |
| * @since 17.0 |
| */ |
| protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) { |
| tester.setDistinctValues(type, value1, value2); |
| } |
| |
| /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */ |
| protected final void ignoreClasses(Predicate<? super Class<?>> condition) { |
| this.classFilter = and(this.classFilter, not(condition)); |
| } |
| |
| private static AssertionFailedError sanityError( |
| Class<?> cls, List<String> explicitTestNames, String description, Throwable e) { |
| String message = |
| String.format( |
| Locale.ROOT, |
| "Error in automated %s of %s\n" |
| + "If the class is better tested explicitly, you can add %s() to %sTest", |
| description, |
| cls, |
| explicitTestNames.get(0), |
| cls.getName()); |
| AssertionFailedError error = new AssertionFailedError(message); |
| error.initCause(e); |
| return error; |
| } |
| |
| /** |
| * Finds the classes not ending with a test suffix and not covered by an explicit test whose name |
| * is {@code explicitTestName}. |
| */ |
| @VisibleForTesting |
| List<Class<?>> findClassesToTest( |
| Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) { |
| // "a.b.Foo" -> a.b.Foo.class |
| TreeMap<String, Class<?>> classMap = Maps.newTreeMap(); |
| for (Class<?> cls : classes) { |
| classMap.put(cls.getName(), cls); |
| } |
| // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...] |
| Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create(); |
| LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet(); |
| for (Class<?> cls : classes) { |
| Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName()); |
| if (testedClassName.isPresent()) { |
| Class<?> testedClass = classMap.get(testedClassName.get()); |
| if (testedClass != null) { |
| testClasses.put(testedClass, cls); |
| } |
| } else { |
| candidateClasses.add(cls); |
| } |
| } |
| List<Class<?>> result = Lists.newArrayList(); |
| NEXT_CANDIDATE: |
| for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) { |
| for (Class<?> testClass : testClasses.get(candidate)) { |
| if (hasTest(testClass, explicitTestNames)) { |
| // covered by explicit test |
| continue NEXT_CANDIDATE; |
| } |
| } |
| result.add(candidate); |
| } |
| return result; |
| } |
| |
| private List<Class<?>> loadClassesInPackage() throws IOException { |
| List<Class<?>> classes = Lists.newArrayList(); |
| String packageName = getClass().getPackage().getName(); |
| for (ClassPath.ClassInfo classInfo : |
| ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) { |
| Class<?> cls; |
| try { |
| cls = classInfo.load(); |
| } catch (NoClassDefFoundError e) { |
| // In case there were linking problems, this is probably not a class we care to test anyway. |
| logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e); |
| continue; |
| } |
| if (!cls.isInterface()) { |
| classes.add(cls); |
| } |
| } |
| return classes; |
| } |
| |
| private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) { |
| for (String testName : testNames) { |
| try { |
| testClass.getMethod(testName); |
| return true; |
| } catch (NoSuchMethodException e) { |
| continue; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean isEqualsDefined(Class<?> cls) { |
| try { |
| return !cls.getDeclaredMethod("equals", Object.class).isSynthetic(); |
| } catch (NoSuchMethodException e) { |
| return false; |
| } |
| } |
| |
| abstract static class Chopper { |
| |
| final Chopper or(final Chopper you) { |
| final Chopper i = this; |
| return new Chopper() { |
| @Override |
| Optional<String> chop(String str) { |
| return i.chop(str).or(you.chop(str)); |
| } |
| }; |
| } |
| |
| abstract Optional<String> chop(String str); |
| |
| static Chopper suffix(final String suffix) { |
| return new Chopper() { |
| @Override |
| Optional<String> chop(String str) { |
| if (str.endsWith(suffix)) { |
| return Optional.of(str.substring(0, str.length() - suffix.length())); |
| } else { |
| return Optional.absent(); |
| } |
| } |
| }; |
| } |
| } |
| } |