001/*
002 * Copyright (C) 2012 The Guava Authors
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016
017package com.google.common.testing;
018
019import static com.google.common.base.Predicates.and;
020import static com.google.common.base.Predicates.not;
021import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix;
022
023import com.google.common.annotations.GwtIncompatible;
024import com.google.common.annotations.J2ktIncompatible;
025import com.google.common.annotations.VisibleForTesting;
026import com.google.common.base.Optional;
027import com.google.common.base.Predicate;
028import com.google.common.collect.HashMultimap;
029import com.google.common.collect.ImmutableList;
030import com.google.common.collect.Iterables;
031import com.google.common.collect.Lists;
032import com.google.common.collect.Maps;
033import com.google.common.collect.Multimap;
034import com.google.common.collect.Sets;
035import com.google.common.reflect.ClassPath;
036import com.google.common.testing.NullPointerTester.Visibility;
037import com.google.j2objc.annotations.J2ObjCIncompatible;
038import java.io.IOException;
039import java.io.Serializable;
040import java.util.LinkedHashSet;
041import java.util.List;
042import java.util.Locale;
043import java.util.TreeMap;
044import java.util.logging.Level;
045import java.util.logging.Logger;
046import junit.framework.TestCase;
047import org.jspecify.annotations.NullMarked;
048import org.junit.Test;
049
050/**
051 * Automatically runs sanity checks against top level classes in the same package of the test that
052 * extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link
053 * NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example:
054 *
055 * <pre>
056 * public class PackageSanityTests extends AbstractPackageSanityTests {}
057 * </pre>
058 *
059 * <p>Note that only top-level classes with either a non-private constructor or a non-private static
060 * factory method to construct instances can have their instance methods checked. For example:
061 *
062 * <pre>
063 * public class Address {
064 *   private final String city;
065 *   private final String state;
066 *   private final String zipcode;
067 *
068 *   public Address(String city, String state, String zipcode) {...}
069 *
070 *   {@literal @Override} public boolean equals(Object obj) {...}
071 *   {@literal @Override} public int hashCode() {...}
072 *   ...
073 * }
074 * </pre>
075 *
076 * <p>No cascading checks are performed against the return values of methods unless the method is a
077 * static factory method. Neither are semantics of mutation methods such as {@code
078 * someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see
079 * {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}.
080 *
081 * <p>For testing against the returned instances from a static factory class, such as
082 *
083 * <pre>
084 * interface Book {...}
085 * public class Books {
086 *   public static Book hardcover(String title) {...}
087 *   public static Book paperback(String title) {...}
088 * }
089 * </pre>
090 *
091 * <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}.
092 *
093 * <p>If not all classes on the classpath should be covered, {@link #ignoreClasses} can be used to
094 * exclude certain classes. As a special case, classes with an underscore in the name (like {@code
095 * AutoValue_Foo}) can be excluded using <code>ignoreClasses({@link #UNDERSCORE_IN_NAME})</code>.
096 *
097 * <p>{@link #setDefault} allows subclasses to specify default values for types.
098 *
099 * <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@NullMarked
110public 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   */
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
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
235  public void testNulls() throws Exception {
236    for (Class<?> classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
237      if (classToTest.getSimpleName().equals("ReflectionFreeAssertThrows")) {
238        /*
239         * These classes handle null properly but throw IllegalArgumentException for the default
240         * Class argument that this test uses. Normally we'd fix that by declaring a
241         * ReflectionFreeAssertThrowsTest with a testNulls method, but that's annoying to have to do
242         * for a package-private utility class. So we skip the class entirely instead.
243         */
244        continue;
245      }
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
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   */
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   */
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. */
316  protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
317    this.classFilter = and(this.classFilter, not(condition));
318  }
319
320  private static AssertionError 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    return new AssertionError(message, e);
332  }
333
334  /**
335   * Finds the classes not ending with a test suffix and not covered by an explicit test whose name
336   * is {@code explicitTestNames}.
337   */
338  @VisibleForTesting
339  List<Class<?>> findClassesToTest(
340      Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) {
341    // "a.b.Foo" -> a.b.Foo.class
342    TreeMap<String, Class<?>> classMap = Maps.newTreeMap();
343    for (Class<?> cls : classes) {
344      classMap.put(cls.getName(), cls);
345    }
346    // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...]
347    Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create();
348    LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet();
349    for (Class<?> cls : classes) {
350      Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName());
351      if (testedClassName.isPresent()) {
352        Class<?> testedClass = classMap.get(testedClassName.get());
353        if (testedClass != null) {
354          testClasses.put(testedClass, cls);
355        }
356      } else {
357        candidateClasses.add(cls);
358      }
359    }
360    List<Class<?>> result = Lists.newArrayList();
361    NEXT_CANDIDATE:
362    for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) {
363      for (Class<?> testClass : testClasses.get(candidate)) {
364        if (hasTest(testClass, explicitTestNames)) {
365          // covered by explicit test
366          continue NEXT_CANDIDATE;
367        }
368      }
369      result.add(candidate);
370    }
371    return result;
372  }
373
374  private List<Class<?>> loadClassesInPackage() throws IOException {
375    List<Class<?>> classes = Lists.newArrayList();
376    String packageName = getClass().getPackage().getName();
377    for (ClassPath.ClassInfo classInfo :
378        ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) {
379      Class<?> cls;
380      try {
381        cls = classInfo.load();
382      } catch (NoClassDefFoundError e) {
383        // In case there were linking problems, this is probably not a class we care to test anyway.
384        logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e);
385        continue;
386      }
387      if (!cls.isInterface()) {
388        classes.add(cls);
389      }
390    }
391    return classes;
392  }
393
394  private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
395    for (String testName : testNames) {
396      try {
397        testClass.getMethod(testName);
398        return true;
399      } catch (NoSuchMethodException e) {
400        continue;
401      }
402    }
403    return false;
404  }
405
406  private static boolean isEqualsDefined(Class<?> cls) {
407    try {
408      return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
409    } catch (NoSuchMethodException e) {
410      return false;
411    }
412  }
413
414  abstract static class Chopper {
415
416    final Chopper or(Chopper you) {
417      Chopper i = this;
418      return new Chopper() {
419        @Override
420        Optional<String> chop(String str) {
421          return i.chop(str).or(you.chop(str));
422        }
423      };
424    }
425
426    abstract Optional<String> chop(String str);
427
428    static Chopper suffix(String suffix) {
429      return new Chopper() {
430        @Override
431        Optional<String> chop(String str) {
432          if (str.endsWith(suffix)) {
433            return Optional.of(str.substring(0, str.length() - suffix.length()));
434          } else {
435            return Optional.absent();
436          }
437        }
438      };
439    }
440  }
441}