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