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.collect.Iterables.filter;
022import static com.google.common.collect.Maps.newTreeMap;
023import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix;
024import static com.google.common.testing.SerializableTester.reserialize;
025import static com.google.common.testing.SerializableTester.reserializeAndAssert;
026import static java.util.Collections.sort;
027
028import com.google.common.annotations.GwtIncompatible;
029import com.google.common.annotations.J2ktIncompatible;
030import com.google.common.annotations.VisibleForTesting;
031import com.google.common.base.Predicate;
032import com.google.common.collect.HashMultimap;
033import com.google.common.collect.ImmutableList;
034import com.google.common.collect.Multimap;
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.ArrayList;
041import java.util.LinkedHashSet;
042import java.util.List;
043import java.util.Locale;
044import java.util.TreeMap;
045import java.util.logging.Level;
046import java.util.logging.Logger;
047import junit.framework.TestCase;
048import org.jspecify.annotations.NullMarked;
049import org.jspecify.annotations.Nullable;
050import org.junit.Test;
051
052/**
053 * Automatically runs sanity checks against top level classes in the same package of the test that
054 * extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link
055 * NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example:
056 *
057 * <pre>
058 * public class PackageSanityTests extends AbstractPackageSanityTests {}
059 * </pre>
060 *
061 * <p>Note that only top-level classes with either a non-private constructor or a non-private static
062 * factory method to construct instances can have their instance methods checked. For example:
063 *
064 * <pre>
065 * public class Address {
066 *   private final String city;
067 *   private final String state;
068 *   private final String zipcode;
069 *
070 *   public Address(String city, String state, String zipcode) {...}
071 *
072 *   {@literal @Override} public boolean equals(Object obj) {...}
073 *   {@literal @Override} public int hashCode() {...}
074 *   ...
075 * }
076 * </pre>
077 *
078 * <p>No cascading checks are performed against the return values of methods unless the method is a
079 * static factory method. Neither are semantics of mutation methods such as {@code
080 * someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see
081 * {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}.
082 *
083 * <p>For testing against the returned instances from a static factory class, such as
084 *
085 * <pre>
086 * interface Book {...}
087 * public class Books {
088 *   public static Book hardcover(String title) {...}
089 *   public static Book paperback(String title) {...}
090 * }
091 * </pre>
092 *
093 * <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}.
094 *
095 * <p>If not all classes on the classpath should be covered, {@link #ignoreClasses} can be used to
096 * exclude certain classes. As a special case, classes with an underscore in the name (like {@code
097 * AutoValue_Foo}) can be excluded using <code>ignoreClasses({@link #UNDERSCORE_IN_NAME})</code>.
098 *
099 * <p>{@link #setDefault} allows subclasses to specify default values for types.
100 *
101 * <p>This class incurs IO because it scans the classpath and reads classpath resources.
102 *
103 * @author Ben Yu
104 * @since 14.0
105 */
106// TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass
107// Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4).
108@GwtIncompatible
109@J2ktIncompatible
110@J2ObjCIncompatible // com.google.common.reflect.ClassPath
111@NullMarked
112public abstract class AbstractPackageSanityTests extends TestCase {
113
114  /**
115   * A predicate that matches classes with an underscore in the class name. This can be used with
116   * {@link #ignoreClasses} to exclude generated classes, such as the {@code AutoValue_Foo} classes
117   * generated by <a href="https://github.com/google/auto/tree/master/value">AutoValue</a>.
118   *
119   * @since 19.0
120   */
121  public static final Predicate<Class<?>> UNDERSCORE_IN_NAME =
122      (Class<?> c) -> c.getSimpleName().contains("_");
123
124  /* The names of the expected method that tests null checks. */
125  private static final ImmutableList<String> NULL_TEST_METHOD_NAMES =
126      ImmutableList.of(
127          "testNulls", "testNull",
128          "testNullPointers", "testNullPointer",
129          "testNullPointerExceptions", "testNullPointerException");
130
131  /* The names of the expected method that tests serializable. */
132  private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES =
133      ImmutableList.of(
134          "testSerializable", "testSerialization",
135          "testEqualsAndSerializable", "testEqualsAndSerialization");
136
137  /* The names of the expected method that tests equals. */
138  private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES =
139      ImmutableList.of(
140          "testEquals",
141          "testEqualsAndHashCode",
142          "testEqualsAndSerializable",
143          "testEqualsAndSerialization",
144          "testEquality");
145
146  private static final Chopper TEST_SUFFIX =
147      suffix("Test").or(suffix("Tests")).or(suffix("TestCase")).or(suffix("TestSuite"));
148
149  private final Logger logger = Logger.getLogger(getClass().getName());
150  private final ClassSanityTester tester = new ClassSanityTester();
151  private Visibility visibility = Visibility.PACKAGE;
152  private Predicate<Class<?>> classFilter =
153      (Class<?> cls) -> visibility.isVisible(cls.getModifiers());
154
155  /**
156   * Restricts the sanity tests for public API only. By default, package-private API are also
157   * covered.
158   */
159  protected final void publicApiOnly() {
160    visibility = Visibility.PUBLIC;
161  }
162
163  /**
164   * Tests all top-level {@link Serializable} classes in the package. For a serializable Class
165   * {@code C}:
166   *
167   * <ul>
168   *   <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will
169   *       be checked to be equal to the instance before serialization.
170   *   <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a
171   *       superclass, no equality check is done on the deserialized instance because it's not clear
172   *       whether the author intended for the class to be a value type.
173   *   <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic
174   *       proxy will be passed to the method. It's possible that the method body expects an
175   *       instance method of the passed-in proxy to be of a certain value yet the proxy isn't aware
176   *       of the assumption, in which case the equality check before and after serialization will
177   *       fail.
178   *   <li>If the constructor or factory method takes a parameter that {@link
179   *       AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
180   *   <li>If there is no visible constructor or visible static factory method declared by {@code
181   *       C}, {@code C} is skipped for serialization test, even if it implements {@link
182   *       Serializable}.
183   *   <li>Serialization test is not performed on method return values unless the method is a
184   *       visible static factory method whose return type is {@code C} or {@code C}'s subtype.
185   * </ul>
186   *
187   * <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an
188   * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code
189   * C} will be excluded from automated serialization test performed by this method.
190   */
191  @Test
192  public void testSerializable() throws Exception {
193    // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once.
194    for (Class<?> classToTest :
195        findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) {
196      if (Serializable.class.isAssignableFrom(classToTest)) {
197        try {
198          Object instance = tester.instantiate(classToTest);
199          if (instance != null) {
200            if (isEqualsDefined(classToTest)) {
201              reserializeAndAssert(instance);
202            } else {
203              reserialize(instance);
204            }
205          }
206        } catch (Throwable e) {
207          throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e);
208        }
209      }
210    }
211  }
212
213  /**
214   * Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class
215   * {@code C}
216   *
217   * <ul>
218   *   <li>All visible static methods are checked such that passing null for any parameter that's
219   *       not annotated nullable (according to the rules of {@link NullPointerTester}) should throw
220   *       {@link NullPointerException}.
221   *   <li>If there is any visible constructor or visible static factory method declared by the
222   *       class, all visible instance methods will be checked too using the instance created by
223   *       invoking the constructor or static factory method.
224   *   <li>If the constructor or factory method used to construct instance takes a parameter that
225   *       {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
226   *   <li>If there is no visible constructor or visible static factory method declared by {@code
227   *       C}, instance methods are skipped for nulls test.
228   *   <li>Nulls test is not performed on method return values unless the method is a visible static
229   *       factory method whose return type is {@code C} or {@code C}'s subtype.
230   * </ul>
231   *
232   * <p>In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit
233   * {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be
234   * excluded from the automated null tests performed by this method.
235   */
236  @Test
237  public void testNulls() throws Exception {
238    for (Class<?> classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
239      try {
240        tester.doTestNulls(classToTest, visibility);
241      } catch (Throwable e) {
242        throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e);
243      }
244    }
245  }
246
247  /**
248   * Tests {@code equals()} and {@code hashCode()} implementations for every top-level class in the
249   * package, that explicitly implements {@link Object#equals}. For a class {@code C}:
250   *
251   * <ul>
252   *   <li>The visible constructor or visible static factory method with the most parameters is used
253   *       to construct the sample instances. In case of tie, the candidate constructors or
254   *       factories are tried one after another until one can be used to construct sample
255   *       instances.
256   *   <li>For the constructor or static factory method used to construct instances, it's checked
257   *       that when equal parameters are passed, the result instance should also be equal; and vice
258   *       versa.
259   *   <li>Inequality check is not performed against state mutation methods such as {@link
260   *       List#add}, or functional update methods such as {@link
261   *       com.google.common.base.Joiner#skipNulls}.
262   *   <li>If the constructor or factory method used to construct instance takes a parameter that
263   *       {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
264   *   <li>If there is no visible constructor or visible static factory method declared by {@code
265   *       C}, {@code C} is skipped for equality test.
266   *   <li>Equality test is not performed on method return values unless the method is a visible
267   *       static factory method whose return type is {@code C} or {@code C}'s subtype.
268   * </ul>
269   *
270   * <p>In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an
271   * explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will
272   * be excluded from the automated {@code equals} test performed by this method.
273   */
274  @Test
275  public void testEquals() throws Exception {
276    for (Class<?> classToTest :
277        findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) {
278      if (!classToTest.isEnum() && isEqualsDefined(classToTest)) {
279        try {
280          tester.doTestEquals(classToTest);
281        } catch (Throwable e) {
282          throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e);
283        }
284      }
285    }
286  }
287
288  /**
289   * Sets the default value for {@code type}, when dummy value for a parameter of the same type
290   * needs to be created in order to invoke a method or constructor. The default value isn't used in
291   * testing {@link Object#equals} because more than one sample instances are needed for testing
292   * inequality.
293   */
294  protected final <T> void setDefault(Class<T> type, T value) {
295    tester.setDefault(type, value);
296  }
297
298  /**
299   * Sets two distinct values for {@code type}. These values can be used for both null pointer
300   * testing and equals testing.
301   *
302   * @since 17.0
303   */
304  protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) {
305    tester.setDistinctValues(type, value1, value2);
306  }
307
308  /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */
309  protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
310    this.classFilter = and(this.classFilter, not(condition));
311  }
312
313  private static AssertionError sanityError(
314      Class<?> cls, List<String> explicitTestNames, String description, Throwable e) {
315    String message =
316        String.format(
317            Locale.ROOT,
318            "Error in automated %s of %s\n"
319                + "If the class is better tested explicitly, you can add %s() to %sTest",
320            description,
321            cls,
322            explicitTestNames.get(0),
323            cls.getName());
324    return new AssertionError(message, e);
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
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 = 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 = new LinkedHashSet<>();
342    for (Class<?> cls : classes) {
343      String testedClassName = TEST_SUFFIX.chop(cls.getName());
344      if (testedClassName != null) {
345        Class<?> testedClass = classMap.get(testedClassName);
346        if (testedClass != null) {
347          testClasses.put(testedClass, cls);
348        }
349      } else {
350        candidateClasses.add(cls);
351      }
352    }
353    List<Class<?>> result = new ArrayList<>();
354    NEXT_CANDIDATE:
355    for (Class<?> candidate : 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
367  private List<Class<?>> loadClassesInPackage() throws IOException {
368    List<Class<?>> classes = new ArrayList<>();
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    sort(classes, (a, b) -> a.getName().compareTo(b.getName()));
385    return classes;
386  }
387
388  private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
389    for (String testName : testNames) {
390      try {
391        testClass.getMethod(testName);
392        return true;
393      } catch (NoSuchMethodException e) {
394        continue;
395      }
396    }
397    return false;
398  }
399
400  private static boolean isEqualsDefined(Class<?> cls) {
401    try {
402      return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
403    } catch (NoSuchMethodException e) {
404      return false;
405    }
406  }
407
408  abstract static class Chopper {
409
410    final Chopper or(Chopper you) {
411      Chopper i = this;
412      return new Chopper() {
413        @Override
414        @Nullable String chop(String str) {
415          String result = i.chop(str);
416          return result != null ? result : you.chop(str);
417        }
418      };
419    }
420
421    abstract @Nullable String chop(String str);
422
423    static Chopper suffix(String suffix) {
424      return new Chopper() {
425        @Override
426        @Nullable String chop(String str) {
427          if (str.endsWith(suffix)) {
428            return str.substring(0, str.length() - suffix.length());
429          } else {
430            return null;
431          }
432        }
433      };
434    }
435  }
436}