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    */
publicApiOnly()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
testSerializable()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
testNulls()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
testEquals()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    */
setDefault(Class<T> type, T value)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    */
setDistinctValues(Class<T> type, T value1, T value2)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. */
ignoreClasses(Predicate<? super Class<?>> condition)278   protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
279     this.classFilter = and(this.classFilter, not(condition));
280   }
281 
sanityError( Class<?> cls, List<String> explicitTestNames, String description, Throwable e)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    */
findClassesToTest( Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames)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 
loadClassesInPackage()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 
hasTest(Class<?> testClass, Iterable<String> testNames)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 
isEqualsDefined(Class<?> cls)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 
or(final Chopper you)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 
chop(String str)382     abstract Optional<String> chop(String str);
383 
suffix(final String suffix)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