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