Home | History | Annotate | Download | only in testing
      1 /*
      2  * Copyright (C) 2012 The Guava Authors
      3  *
      4  * Licensed under the Apache License, Version 2.0 (the "License");
      5  * you may not use this file except in compliance with the License.
      6  * You may obtain a copy of the License at
      7  *
      8  * http://www.apache.org/licenses/LICENSE-2.0
      9  *
     10  * Unless required by applicable law or agreed to in writing, software
     11  * distributed under the License is distributed on an "AS IS" BASIS,
     12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     13  * See the License for the specific language governing permissions and
     14  * limitations under the License.
     15  */
     16 
     17 package com.google.common.testing;
     18 
     19 import static com.google.common.base.Predicates.and;
     20 import static com.google.common.base.Predicates.not;
     21 import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix;
     22 
     23 import com.google.common.annotations.Beta;
     24 import com.google.common.annotations.VisibleForTesting;
     25 import com.google.common.base.Optional;
     26 import com.google.common.base.Predicate;
     27 import com.google.common.collect.HashMultimap;
     28 import com.google.common.collect.ImmutableList;
     29 import com.google.common.collect.Iterables;
     30 import com.google.common.collect.Lists;
     31 import com.google.common.collect.Maps;
     32 import com.google.common.collect.Multimap;
     33 import com.google.common.collect.Sets;
     34 import com.google.common.reflect.ClassPath;
     35 import com.google.common.testing.NullPointerTester.Visibility;
     36 
     37 import junit.framework.AssertionFailedError;
     38 import junit.framework.TestCase;
     39 
     40 import org.junit.Test;
     41 
     42 import java.io.IOException;
     43 import java.io.Serializable;
     44 import java.util.LinkedHashSet;
     45 import java.util.List;
     46 import java.util.TreeMap;
     47 import java.util.logging.Level;
     48 import java.util.logging.Logger;
     49 
     50 /**
     51  * Automatically runs sanity checks against top level classes in the same package of the test that
     52  * extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link
     53  * NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example: <pre>
     54  * public class PackageSanityTests extends AbstractPackageSanityTests {}
     55  * </pre>
     56  *
     57  * <p>Note that only top-level classes with either a non-private constructor or a non-private static
     58  * factory method to construct instances can have their instance methods checked. For example: <pre>
     59  * public class Address {
     60  *   private final String city;
     61  *   private final String state;
     62  *   private final String zipcode;
     63  *
     64  *   public Address(String city, String state, String zipcode) {...}
     65  *
     66  *   {@literal @Override} public boolean equals(Object obj) {...}
     67  *   {@literal @Override} public int hashCode() {...}
     68  *   ...
     69  * }
     70  * </pre>
     71  * <p>No cascading checks are performed against the return values of methods unless the method is a
     72  * static factory method. Neither are semantics of mutation methods such as {@code
     73  * someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see
     74  * {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}.
     75  *
     76  * <p>For testing against the returned instances from a static factory class, such as <pre>
     77  * interface Book {...}
     78  * public class Books {
     79  *   public static Book hardcover(String title) {...}
     80  *   public static Book paperback(String title) {...}
     81  * }
     82  * </pre>
     83  *
     84  * <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}.
     85  *
     86  * <p>If not all classes on the classpath should be covered, {@link
     87  * #ignoreClasses} can be used to exclude certain classes.
     88  *
     89  * <p>{@link #setDefault} allows subclasses to specify default values for types.
     90  *
     91  * <p>This class incurs IO because it scans the classpath and reads classpath resources.
     92  *
     93  * @author Ben Yu
     94  * @since 14.0
     95  */
     96 @Beta
     97 // TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass
     98 public abstract class AbstractPackageSanityTests extends TestCase {
     99 
    100   /* The names of the expected method that tests null checks. */
    101   private static final ImmutableList<String> NULL_TEST_METHOD_NAMES = ImmutableList.of(
    102       "testNulls", "testNull",
    103       "testNullPointers", "testNullPointer",
    104       "testNullPointerExceptions", "testNullPointerException");
    105 
    106   /* The names of the expected method that tests serializable. */
    107   private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES = ImmutableList.of(
    108       "testSerializable", "testSerialization",
    109       "testEqualsAndSerializable", "testEqualsAndSerialization");
    110 
    111   /* The names of the expected method that tests equals. */
    112   private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES = ImmutableList.of(
    113       "testEquals", "testEqualsAndHashCode",
    114       "testEqualsAndSerializable", "testEqualsAndSerialization",
    115       "testEquality");
    116 
    117   private static final Chopper TEST_SUFFIX =
    118       suffix("Test")
    119           .or(suffix("Tests"))
    120           .or(suffix("TestCase"))
    121           .or(suffix("TestSuite"));
    122 
    123   private final Logger logger = Logger.getLogger(getClass().getName());
    124   private final ClassSanityTester tester = new ClassSanityTester();
    125   private Visibility visibility = Visibility.PACKAGE;
    126   private Predicate<Class<?>> classFilter = new Predicate<Class<?>>() {
    127     @Override public boolean apply(Class<?> cls) {
    128       return visibility.isVisible(cls.getModifiers());
    129     }
    130   };
    131 
    132   /**
    133    * Restricts the sanity tests for public API only. By default, package-private API are also
    134    * covered.
    135    */
    136   protected final void publicApiOnly() {
    137     visibility = Visibility.PUBLIC;
    138   }
    139 
    140   /**
    141    * Tests all top-level {@link Serializable} classes in the package. For a serializable Class
    142    * {@code C}:
    143    * <ul>
    144    * <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will be
    145    *     checked to be equal to the instance before serialization.
    146    * <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a
    147    *     superclass, no equality check is done on the deserialized instance because it's not clear
    148    *     whether the author intended for the class to be a value type.
    149    * <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic
    150    *     proxy will be passed to the method. It's possible that the method body expects an instance
    151    *     method of the passed-in proxy to be of a certain value yet the proxy isn't aware of the
    152    *     assumption, in which case the equality check before and after serialization will fail.
    153    * <li>If the constructor or factory method takes a parameter that {@link
    154    *     AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
    155    * <li>If there is no visible constructor or visible static factory method declared by {@code C},
    156    *     {@code C} is skipped for serialization test, even if it implements {@link Serializable}.
    157    * <li>Serialization test is not performed on method return values unless the method is a visible
    158    *     static factory method whose return type is {@code C} or {@code C}'s subtype.
    159    * </ul>
    160    *
    161    * <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an
    162    * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code
    163    * C} will be excluded from automated serialization test performed by this method.
    164    */
    165   @Test
    166   public void testSerializable() throws Exception {
    167     // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once.
    168     for (Class<?> classToTest
    169         : findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) {
    170       if (Serializable.class.isAssignableFrom(classToTest)) {
    171         try {
    172           Object instance = tester.instantiate(classToTest);
    173           if (instance != null) {
    174             if (isEqualsDefined(classToTest)) {
    175               SerializableTester.reserializeAndAssert(instance);
    176             } else {
    177               SerializableTester.reserialize(instance);
    178             }
    179           }
    180         } catch (Throwable e) {
    181           throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e);
    182         }
    183       }
    184     }
    185   }
    186 
    187   /**
    188    * Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class
    189    * {@code C}
    190    * <ul>
    191    * <li>All visible static methods are checked such that passing null for any parameter that's not
    192    *     annotated with {@link javax.annotation.Nullable} should throw {@link NullPointerException}.
    193    * <li>If there is any visible constructor or visible static factory method declared by the class,
    194    *     all visible instance methods will be checked too using the instance created by invoking the
    195    *     constructor or static factory method.
    196    * <li>If the constructor or factory method used to construct instance takes a parameter that
    197    *     {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
    198    * <li>If there is no visible constructor or visible static factory method declared by {@code C},
    199    *     instance methods are skipped for nulls test.
    200    * <li>Nulls test is not performed on method return values unless the method is a visible static
    201    *     factory method whose return type is {@code C} or {@code C}'s subtype.
    202    * </ul>
    203    *
    204    * <p>In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit
    205    * {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be
    206    * excluded from the automated null tests performed by this method.
    207    */
    208   @Test
    209   public void testNulls() throws Exception {
    210     for (Class<?> classToTest
    211         : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
    212       try {
    213         tester.doTestNulls(classToTest, visibility);
    214       } catch (Throwable e) {
    215         throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e);
    216       }
    217     }
    218   }
    219 
    220   /**
    221    * Tests {@code equals()} and {@code hashCode()} implementations for every top-level class in the
    222    * package, that explicitly implements {@link Object#equals}. For a class {@code C}:
    223    * <ul>
    224    * <li>The visible constructor or visible static factory method with the most parameters is used
    225    *     to construct the sample instances. In case of tie, the candidate constructors or factories
    226    *     are tried one after another until one can be used to construct sample instances.
    227    * <li>For the constructor or static factory method used to construct instances, it's checked that
    228    *     when equal parameters are passed, the result instance should also be equal; and vice versa.
    229    * <li>Inequality check is not performed against state mutation methods such as {@link List#add},
    230    *     or functional update methods such as {@link com.google.common.base.Joiner#skipNulls}.
    231    * <li>If the constructor or factory method used to construct instance takes a parameter that
    232    *     {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
    233    * <li>If there is no visible constructor or visible static factory method declared by {@code C},
    234    *     {@code C} is skipped for equality test.
    235    * <li>Equality test is not performed on method return values unless the method is a visible
    236    *     static factory method whose return type is {@code C} or {@code C}'s subtype.
    237    * </ul>
    238    *
    239    * <p>In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an
    240    * explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will
    241    * be excluded from the automated {@code equals} test performed by this method.
    242    */
    243   @Test
    244   public void testEquals() throws Exception {
    245     for (Class<?> classToTest
    246         : findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) {
    247       if (!classToTest.isEnum() && isEqualsDefined(classToTest)) {
    248         try {
    249           tester.doTestEquals(classToTest);
    250         } catch (Throwable e) {
    251           throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e);
    252         }
    253       }
    254     }
    255   }
    256 
    257   /**
    258    * Sets the default value for {@code type}, when dummy value for a parameter of the same type
    259    * needs to be created in order to invoke a method or constructor. The default value isn't used in
    260    * testing {@link Object#equals} because more than one sample instances are needed for testing
    261    * inequality.
    262    */
    263   protected final <T> void setDefault(Class<T> type, T value) {
    264     tester.setDefault(type, value);
    265   }
    266 
    267   /**
    268    * Sets two distinct values for {@code type}. These values can be used for both null pointer
    269    * testing and equals testing.
    270    *
    271    * @since 17.0
    272    */
    273   protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) {
    274     tester.setDistinctValues(type, value1, value2);
    275   }
    276 
    277   /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */
    278   protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
    279     this.classFilter = and(this.classFilter, not(condition));
    280   }
    281 
    282   private static AssertionFailedError sanityError(
    283       Class<?> cls, List<String> explicitTestNames, String description, Throwable e) {
    284     String message = String.format(
    285         "Error in automated %s of %s\n"
    286             + "If the class is better tested explicitly, you can add %s() to %sTest",
    287         description, cls, explicitTestNames.get(0), cls.getName());
    288     AssertionFailedError error = new AssertionFailedError(message);
    289     error.initCause(e);
    290     return error;
    291   }
    292 
    293   /**
    294    * Finds the classes not ending with a test suffix and not covered by an explicit test
    295    * whose name is {@code explicitTestName}.
    296    */
    297   @VisibleForTesting List<Class<?>> findClassesToTest(
    298       Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) {
    299     // "a.b.Foo" -> a.b.Foo.class
    300     TreeMap<String, Class<?>> classMap = Maps.newTreeMap();
    301     for (Class<?> cls : classes) {
    302       classMap.put(cls.getName(), cls);
    303     }
    304     // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...]
    305     Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create();
    306     LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet();
    307     for (Class<?> cls : classes) {
    308       Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName());
    309       if (testedClassName.isPresent()) {
    310         Class<?> testedClass = classMap.get(testedClassName.get());
    311         if (testedClass != null) {
    312           testClasses.put(testedClass, cls);
    313         }
    314       } else {
    315         candidateClasses.add(cls);
    316       }
    317     }
    318     List<Class<?>> result = Lists.newArrayList();
    319     NEXT_CANDIDATE: for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) {
    320       for (Class<?> testClass : testClasses.get(candidate)) {
    321         if (hasTest(testClass, explicitTestNames)) {
    322           // covered by explicit test
    323           continue NEXT_CANDIDATE;
    324         }
    325       }
    326       result.add(candidate);
    327     }
    328     return result;
    329   }
    330 
    331   private List<Class<?>> loadClassesInPackage() throws IOException {
    332     List<Class<?>> classes = Lists.newArrayList();
    333     String packageName = getClass().getPackage().getName();
    334     for (ClassPath.ClassInfo classInfo
    335         : ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) {
    336       Class<?> cls;
    337       try {
    338         cls = classInfo.load();
    339       } catch (NoClassDefFoundError e) {
    340         // In case there were linking problems, this is probably not a class we care to test anyway.
    341         logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e);
    342         continue;
    343       }
    344       if (!cls.isInterface()) {
    345         classes.add(cls);
    346       }
    347     }
    348     return classes;
    349   }
    350 
    351   private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
    352     for (String testName : testNames) {
    353       try {
    354         testClass.getMethod(testName);
    355         return true;
    356       } catch (NoSuchMethodException e) {
    357         continue;
    358       }
    359     }
    360     return false;
    361   }
    362 
    363   private static boolean isEqualsDefined(Class<?> cls) {
    364     try {
    365       return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
    366     } catch (NoSuchMethodException e) {
    367       return false;
    368     }
    369   }
    370 
    371   static abstract class Chopper {
    372 
    373     final Chopper or(final Chopper you) {
    374       final Chopper i = this;
    375       return new Chopper() {
    376         @Override Optional<String> chop(String str) {
    377           return i.chop(str).or(you.chop(str));
    378         }
    379       };
    380     }
    381 
    382     abstract Optional<String> chop(String str);
    383 
    384     static Chopper suffix(final String suffix) {
    385       return new Chopper() {
    386         @Override Optional<String> chop(String str) {
    387           if (str.endsWith(suffix)) {
    388             return Optional.of(str.substring(0, str.length() - suffix.length()));
    389           } else {
    390             return Optional.absent();
    391           }
    392         }
    393       };
    394     }
    395   }
    396 }
    397