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}