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
17package com.google.common.testing;
18
19import static com.google.common.base.Predicates.and;
20import static com.google.common.base.Predicates.not;
21import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix;
22
23import com.google.common.annotations.Beta;
24import com.google.common.annotations.VisibleForTesting;
25import com.google.common.base.Optional;
26import com.google.common.base.Predicate;
27import com.google.common.collect.HashMultimap;
28import com.google.common.collect.ImmutableList;
29import com.google.common.collect.Iterables;
30import com.google.common.collect.Lists;
31import com.google.common.collect.Maps;
32import com.google.common.collect.Multimap;
33import com.google.common.collect.Sets;
34import com.google.common.reflect.ClassPath;
35import com.google.common.testing.NullPointerTester.Visibility;
36
37import junit.framework.AssertionFailedError;
38import junit.framework.TestCase;
39
40import org.junit.Test;
41
42import java.io.IOException;
43import java.io.Serializable;
44import java.util.LinkedHashSet;
45import java.util.List;
46import java.util.TreeMap;
47import java.util.logging.Level;
48import 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
98public 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   */
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
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
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
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   */
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   */
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. */
278  protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
279    this.classFilter = and(this.classFilter, not(condition));
280  }
281
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   */
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
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
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
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
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
382    abstract Optional<String> chop(String str);
383
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