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