View Javadoc
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.GwtIncompatible;
25  import com.google.common.annotations.VisibleForTesting;
26  import com.google.common.base.Optional;
27  import com.google.common.base.Predicate;
28  import com.google.common.collect.HashMultimap;
29  import com.google.common.collect.ImmutableList;
30  import com.google.common.collect.Iterables;
31  import com.google.common.collect.Lists;
32  import com.google.common.collect.Maps;
33  import com.google.common.collect.Multimap;
34  import com.google.common.collect.Sets;
35  import com.google.common.reflect.ClassPath;
36  import com.google.common.testing.NullPointerTester.Visibility;
37  import com.google.j2objc.annotations.J2ObjCIncompatible;
38  import java.io.IOException;
39  import java.io.Serializable;
40  import java.util.LinkedHashSet;
41  import java.util.List;
42  import java.util.Locale;
43  import java.util.TreeMap;
44  import java.util.logging.Level;
45  import java.util.logging.Logger;
46  import junit.framework.AssertionFailedError;
47  import junit.framework.TestCase;
48  import org.junit.Test;
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. As a special case, classes with an
88   * underscore in the name (like {@code AutoValue_Foo}) can be excluded using
89   * <code>ignoreClasses({@link #UNDERSCORE_IN_NAME})</code>.
90   *
91   * <p>{@link #setDefault} allows subclasses to specify default values for types.
92   *
93   * <p>This class incurs IO because it scans the classpath and reads classpath resources.
94   *
95   * @author Ben Yu
96   * @since 14.0
97   */
98  @Beta
99  // TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass
100 // Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4).
101 @GwtIncompatible
102 @J2ObjCIncompatible // com.google.common.reflect.ClassPath
103 public abstract class AbstractPackageSanityTests extends TestCase {
104 
105   /**
106    * A predicate that matches classes with an underscore in the class name. This can be used with
107    * {@link #ignoreClasses} to exclude generated classes, such as the {@code AutoValue_Foo} classes
108    * generated by <a href="https://github.com/google/auto/tree/master/value">AutoValue</a>.
109    *
110    * @since 19.0
111    */
112   public static final Predicate<Class<?>> UNDERSCORE_IN_NAME = new Predicate<Class<?>>() {
113     @Override public boolean apply(Class<?> c) {
114       return c.getSimpleName().contains("_");
115     }
116   };
117 
118   /* The names of the expected method that tests null checks. */
119   private static final ImmutableList<String> NULL_TEST_METHOD_NAMES = ImmutableList.of(
120       "testNulls", "testNull",
121       "testNullPointers", "testNullPointer",
122       "testNullPointerExceptions", "testNullPointerException");
123 
124   /* The names of the expected method that tests serializable. */
125   private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES = ImmutableList.of(
126       "testSerializable", "testSerialization",
127       "testEqualsAndSerializable", "testEqualsAndSerialization");
128 
129   /* The names of the expected method that tests equals. */
130   private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES = ImmutableList.of(
131       "testEquals", "testEqualsAndHashCode",
132       "testEqualsAndSerializable", "testEqualsAndSerialization",
133       "testEquality");
134 
135   private static final Chopper TEST_SUFFIX =
136       suffix("Test")
137           .or(suffix("Tests"))
138           .or(suffix("TestCase"))
139           .or(suffix("TestSuite"));
140 
141   private final Logger logger = Logger.getLogger(getClass().getName());
142   private final ClassSanityTester tester = new ClassSanityTester();
143   private Visibility visibility = Visibility.PACKAGE;
144   private Predicate<Class<?>> classFilter = new Predicate<Class<?>>() {
145     @Override public boolean apply(Class<?> cls) {
146       return visibility.isVisible(cls.getModifiers());
147     }
148   };
149 
150   /**
151    * Restricts the sanity tests for public API only. By default, package-private API are also
152    * covered.
153    */
154   protected final void publicApiOnly() {
155     visibility = Visibility.PUBLIC;
156   }
157 
158   /**
159    * Tests all top-level {@link Serializable} classes in the package. For a serializable Class
160    * {@code C}:
161    * <ul>
162    * <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will be
163    *     checked to be equal to the instance before serialization.
164    * <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a
165    *     superclass, no equality check is done on the deserialized instance because it's not clear
166    *     whether the author intended for the class to be a value type.
167    * <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic
168    *     proxy will be passed to the method. It's possible that the method body expects an instance
169    *     method of the passed-in proxy to be of a certain value yet the proxy isn't aware of the
170    *     assumption, in which case the equality check before and after serialization will fail.
171    * <li>If the constructor or factory method takes a parameter that {@link
172    *     AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
173    * <li>If there is no visible constructor or visible static factory method declared by {@code C},
174    *     {@code C} is skipped for serialization test, even if it implements {@link Serializable}.
175    * <li>Serialization test is not performed on method return values unless the method is a visible
176    *     static factory method whose return type is {@code C} or {@code C}'s subtype.
177    * </ul>
178    *
179    * <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an
180    * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code
181    * C} will be excluded from automated serialization test performed by this method.
182    */
183   @Test
184   public void testSerializable() throws Exception {
185     // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once.
186     for (Class<?> classToTest
187         : findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) {
188       if (Serializable.class.isAssignableFrom(classToTest)) {
189         try {
190           Object instance = tester.instantiate(classToTest);
191           if (instance != null) {
192             if (isEqualsDefined(classToTest)) {
193               SerializableTester.reserializeAndAssert(instance);
194             } else {
195               SerializableTester.reserialize(instance);
196             }
197           }
198         } catch (Throwable e) {
199           throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e);
200         }
201       }
202     }
203   }
204 
205   /**
206    * Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class
207    * {@code C}
208    * <ul>
209    * <li>All visible static methods are checked such that passing null for any parameter that's not
210    *     annotated with {@link javax.annotation.Nullable} should throw {@link NullPointerException}.
211    * <li>If there is any visible constructor or visible static factory method declared by the class,
212    *     all visible instance methods will be checked too using the instance created by invoking the
213    *     constructor or static factory method.
214    * <li>If the constructor or factory method used to construct instance takes a parameter that
215    *     {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
216    * <li>If there is no visible constructor or visible static factory method declared by {@code C},
217    *     instance methods are skipped for nulls test.
218    * <li>Nulls test is not performed on method return values unless the method is a visible static
219    *     factory method whose return type is {@code C} or {@code C}'s subtype.
220    * </ul>
221    *
222    * <p>In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit
223    * {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be
224    * excluded from the automated null tests performed by this method.
225    */
226   @Test
227   public void testNulls() throws Exception {
228     for (Class<?> classToTest
229         : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
230       try {
231         tester.doTestNulls(classToTest, visibility);
232       } catch (Throwable e) {
233         throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e);
234       }
235     }
236   }
237 
238   /**
239    * Tests {@code equals()} and {@code hashCode()} implementations for every top-level class in the
240    * package, that explicitly implements {@link Object#equals}. For a class {@code C}:
241    * <ul>
242    * <li>The visible constructor or visible static factory method with the most parameters is used
243    *     to construct the sample instances. In case of tie, the candidate constructors or factories
244    *     are tried one after another until one can be used to construct sample instances.
245    * <li>For the constructor or static factory method used to construct instances, it's checked that
246    *     when equal parameters are passed, the result instance should also be equal; and vice versa.
247    * <li>Inequality check is not performed against state mutation methods such as {@link List#add},
248    *     or functional update methods such as {@link com.google.common.base.Joiner#skipNulls}.
249    * <li>If the constructor or factory method used to construct instance takes a parameter that
250    *     {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
251    * <li>If there is no visible constructor or visible static factory method declared by {@code C},
252    *     {@code C} is skipped for equality test.
253    * <li>Equality test is not performed on method return values unless the method is a visible
254    *     static factory method whose return type is {@code C} or {@code C}'s subtype.
255    * </ul>
256    *
257    * <p>In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an
258    * explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will
259    * be excluded from the automated {@code equals} test performed by this method.
260    */
261   @Test
262   public void testEquals() throws Exception {
263     for (Class<?> classToTest
264         : findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) {
265       if (!classToTest.isEnum() && isEqualsDefined(classToTest)) {
266         try {
267           tester.doTestEquals(classToTest);
268         } catch (Throwable e) {
269           throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e);
270         }
271       }
272     }
273   }
274 
275   /**
276    * Sets the default value for {@code type}, when dummy value for a parameter of the same type
277    * needs to be created in order to invoke a method or constructor. The default value isn't used in
278    * testing {@link Object#equals} because more than one sample instances are needed for testing
279    * inequality.
280    */
281   protected final <T> void setDefault(Class<T> type, T value) {
282     tester.setDefault(type, value);
283   }
284 
285   /**
286    * Sets two distinct values for {@code type}. These values can be used for both null pointer
287    * testing and equals testing.
288    *
289    * @since 17.0
290    */
291   protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) {
292     tester.setDistinctValues(type, value1, value2);
293   }
294 
295   /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */
296   protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
297     this.classFilter = and(this.classFilter, not(condition));
298   }
299 
300   private static AssertionFailedError sanityError(
301       Class<?> cls, List<String> explicitTestNames, String description, Throwable e) {
302     String message = String.format(Locale.ROOT,
303         "Error in automated %s of %s\n"
304             + "If the class is better tested explicitly, you can add %s() to %sTest",
305         description, cls, explicitTestNames.get(0), cls.getName());
306     AssertionFailedError error = new AssertionFailedError(message);
307     error.initCause(e);
308     return error;
309   }
310 
311   /**
312    * Finds the classes not ending with a test suffix and not covered by an explicit test
313    * whose name is {@code explicitTestName}.
314    */
315   @VisibleForTesting List<Class<?>> findClassesToTest(
316       Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) {
317     // "a.b.Foo" -> a.b.Foo.class
318     TreeMap<String, Class<?>> classMap = Maps.newTreeMap();
319     for (Class<?> cls : classes) {
320       classMap.put(cls.getName(), cls);
321     }
322     // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...]
323     Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create();
324     LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet();
325     for (Class<?> cls : classes) {
326       Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName());
327       if (testedClassName.isPresent()) {
328         Class<?> testedClass = classMap.get(testedClassName.get());
329         if (testedClass != null) {
330           testClasses.put(testedClass, cls);
331         }
332       } else {
333         candidateClasses.add(cls);
334       }
335     }
336     List<Class<?>> result = Lists.newArrayList();
337     NEXT_CANDIDATE: for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) {
338       for (Class<?> testClass : testClasses.get(candidate)) {
339         if (hasTest(testClass, explicitTestNames)) {
340           // covered by explicit test
341           continue NEXT_CANDIDATE;
342         }
343       }
344       result.add(candidate);
345     }
346     return result;
347   }
348 
349   private List<Class<?>> loadClassesInPackage() throws IOException {
350     List<Class<?>> classes = Lists.newArrayList();
351     String packageName = getClass().getPackage().getName();
352     for (ClassPath.ClassInfo classInfo
353         : ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) {
354       Class<?> cls;
355       try {
356         cls = classInfo.load();
357       } catch (NoClassDefFoundError e) {
358         // In case there were linking problems, this is probably not a class we care to test anyway.
359         logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e);
360         continue;
361       }
362       if (!cls.isInterface()) {
363         classes.add(cls);
364       }
365     }
366     return classes;
367   }
368 
369   private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
370     for (String testName : testNames) {
371       try {
372         testClass.getMethod(testName);
373         return true;
374       } catch (NoSuchMethodException e) {
375         continue;
376       }
377     }
378     return false;
379   }
380 
381   private static boolean isEqualsDefined(Class<?> cls) {
382     try {
383       return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
384     } catch (NoSuchMethodException e) {
385       return false;
386     }
387   }
388 
389   abstract static class Chopper {
390 
391     final Chopper or(final Chopper you) {
392       final Chopper i = this;
393       return new Chopper() {
394         @Override Optional<String> chop(String str) {
395           return i.chop(str).or(you.chop(str));
396         }
397       };
398     }
399 
400     abstract Optional<String> chop(String str);
401 
402     static Chopper suffix(final String suffix) {
403       return new Chopper() {
404         @Override Optional<String> chop(String str) {
405           if (str.endsWith(suffix)) {
406             return Optional.of(str.substring(0, str.length() - suffix.length()));
407           } else {
408             return Optional.absent();
409           }
410         }
411       };
412     }
413   }
414 }