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