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.Beta; 24 import com.google.common.annotations.GwtIncompatible; 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 @Beta 105 // TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass 106 // Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4). 107 @GwtIncompatible 108 @J2ObjCIncompatible // com.google.common.reflect.ClassPath 109 public abstract class AbstractPackageSanityTests extends TestCase { 110 111 /** 112 * A predicate that matches classes with an underscore in the class name. This can be used with 113 * {@link #ignoreClasses} to exclude generated classes, such as the {@code AutoValue_Foo} classes 114 * generated by <a href="https://github.com/google/auto/tree/master/value">AutoValue</a>. 115 * 116 * @since 19.0 117 */ 118 public static final Predicate<Class<?>> UNDERSCORE_IN_NAME = 119 new Predicate<Class<?>>() { 120 @Override 121 public boolean apply(Class<?> c) { 122 return c.getSimpleName().contains("_"); 123 } 124 }; 125 126 /* The names of the expected method that tests null checks. */ 127 private static final ImmutableList<String> NULL_TEST_METHOD_NAMES = 128 ImmutableList.of( 129 "testNulls", "testNull", 130 "testNullPointers", "testNullPointer", 131 "testNullPointerExceptions", "testNullPointerException"); 132 133 /* The names of the expected method that tests serializable. */ 134 private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES = 135 ImmutableList.of( 136 "testSerializable", "testSerialization", 137 "testEqualsAndSerializable", "testEqualsAndSerialization"); 138 139 /* The names of the expected method that tests equals. */ 140 private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES = 141 ImmutableList.of( 142 "testEquals", 143 "testEqualsAndHashCode", 144 "testEqualsAndSerializable", 145 "testEqualsAndSerialization", 146 "testEquality"); 147 148 private static final Chopper TEST_SUFFIX = 149 suffix("Test").or(suffix("Tests")).or(suffix("TestCase")).or(suffix("TestSuite")); 150 151 private final Logger logger = Logger.getLogger(getClass().getName()); 152 private final ClassSanityTester tester = new ClassSanityTester(); 153 private Visibility visibility = Visibility.PACKAGE; 154 private Predicate<Class<?>> classFilter = 155 new Predicate<Class<?>>() { 156 @Override 157 public boolean apply(Class<?> cls) { 158 return visibility.isVisible(cls.getModifiers()); 159 } 160 }; 161 162 /** 163 * Restricts the sanity tests for public API only. By default, package-private API are also 164 * covered. 165 */ publicApiOnly()166 protected final void publicApiOnly() { 167 visibility = Visibility.PUBLIC; 168 } 169 170 /** 171 * Tests all top-level {@link Serializable} classes in the package. For a serializable Class 172 * {@code C}: 173 * 174 * <ul> 175 * <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will 176 * be checked to be equal to the instance before serialization. 177 * <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a 178 * superclass, no equality check is done on the deserialized instance because it's not clear 179 * whether the author intended for the class to be a value type. 180 * <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic 181 * proxy will be passed to the method. It's possible that the method body expects an 182 * instance method of the passed-in proxy to be of a certain value yet the proxy isn't aware 183 * of the assumption, in which case the equality check before and after serialization will 184 * fail. 185 * <li>If the constructor or factory method takes a parameter that {@link 186 * AbstractPackageSanityTests} doesn't know how to construct, the test will fail. 187 * <li>If there is no visible constructor or visible static factory method declared by {@code 188 * C}, {@code C} is skipped for serialization test, even if it implements {@link 189 * Serializable}. 190 * <li>Serialization test is not performed on method return values unless the method is a 191 * visible static factory method whose return type is {@code C} or {@code C}'s subtype. 192 * </ul> 193 * 194 * <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an 195 * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code 196 * C} will be excluded from automated serialization test performed by this method. 197 */ 198 @Test testSerializable()199 public void testSerializable() throws Exception { 200 // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once. 201 for (Class<?> classToTest : 202 findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) { 203 if (Serializable.class.isAssignableFrom(classToTest)) { 204 try { 205 Object instance = tester.instantiate(classToTest); 206 if (instance != null) { 207 if (isEqualsDefined(classToTest)) { 208 SerializableTester.reserializeAndAssert(instance); 209 } else { 210 SerializableTester.reserialize(instance); 211 } 212 } 213 } catch (Throwable e) { 214 throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e); 215 } 216 } 217 } 218 } 219 220 /** 221 * Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class 222 * {@code C} 223 * 224 * <ul> 225 * <li>All visible static methods are checked such that passing null for any parameter that's 226 * not annotated nullable (according to the rules of {@link NullPointerTester}) should throw 227 * {@link NullPointerException}. 228 * <li>If there is any visible constructor or visible static factory method declared by the 229 * class, all visible instance methods will be checked too using the instance created by 230 * invoking the constructor or static factory method. 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 234 * C}, instance methods are skipped for nulls test. 235 * <li>Nulls test is not performed on method return values unless the method is a visible static 236 * 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 nulls, you can add an explicit 240 * {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be 241 * excluded from the automated null tests performed by this method. 242 */ 243 @Test testNulls()244 public void testNulls() throws Exception { 245 for (Class<?> classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) { 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 testEquals()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 */ setDefault(Class<T> type, T value)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 */ setDistinctValues(Class<T> type, T value1, T value2)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. */ ignoreClasses(Predicate<? super Class<?>> condition)316 protected final void ignoreClasses(Predicate<? super Class<?>> condition) { 317 this.classFilter = and(this.classFilter, not(condition)); 318 } 319 sanityError( Class<?> cls, List<String> explicitTestNames, String description, Throwable e)320 private static AssertionFailedError 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 AssertionFailedError error = new AssertionFailedError(message); 332 error.initCause(e); 333 return error; 334 } 335 336 /** 337 * Finds the classes not ending with a test suffix and not covered by an explicit test whose name 338 * is {@code explicitTestName}. 339 */ 340 @VisibleForTesting findClassesToTest( Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames)341 List<Class<?>> findClassesToTest( 342 Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) { 343 // "a.b.Foo" -> a.b.Foo.class 344 TreeMap<String, Class<?>> classMap = Maps.newTreeMap(); 345 for (Class<?> cls : classes) { 346 classMap.put(cls.getName(), cls); 347 } 348 // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...] 349 Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create(); 350 LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet(); 351 for (Class<?> cls : classes) { 352 Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName()); 353 if (testedClassName.isPresent()) { 354 Class<?> testedClass = classMap.get(testedClassName.get()); 355 if (testedClass != null) { 356 testClasses.put(testedClass, cls); 357 } 358 } else { 359 candidateClasses.add(cls); 360 } 361 } 362 List<Class<?>> result = Lists.newArrayList(); 363 NEXT_CANDIDATE: 364 for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) { 365 for (Class<?> testClass : testClasses.get(candidate)) { 366 if (hasTest(testClass, explicitTestNames)) { 367 // covered by explicit test 368 continue NEXT_CANDIDATE; 369 } 370 } 371 result.add(candidate); 372 } 373 return result; 374 } 375 loadClassesInPackage()376 private List<Class<?>> loadClassesInPackage() throws IOException { 377 List<Class<?>> classes = Lists.newArrayList(); 378 String packageName = getClass().getPackage().getName(); 379 for (ClassPath.ClassInfo classInfo : 380 ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) { 381 Class<?> cls; 382 try { 383 cls = classInfo.load(); 384 } catch (NoClassDefFoundError e) { 385 // In case there were linking problems, this is probably not a class we care to test anyway. 386 logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e); 387 continue; 388 } 389 if (!cls.isInterface()) { 390 classes.add(cls); 391 } 392 } 393 return classes; 394 } 395 hasTest(Class<?> testClass, Iterable<String> testNames)396 private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) { 397 for (String testName : testNames) { 398 try { 399 testClass.getMethod(testName); 400 return true; 401 } catch (NoSuchMethodException e) { 402 continue; 403 } 404 } 405 return false; 406 } 407 isEqualsDefined(Class<?> cls)408 private static boolean isEqualsDefined(Class<?> cls) { 409 try { 410 return !cls.getDeclaredMethod("equals", Object.class).isSynthetic(); 411 } catch (NoSuchMethodException e) { 412 return false; 413 } 414 } 415 416 abstract static class Chopper { 417 or(final Chopper you)418 final Chopper or(final Chopper you) { 419 final Chopper i = this; 420 return new Chopper() { 421 @Override 422 Optional<String> chop(String str) { 423 return i.chop(str).or(you.chop(str)); 424 } 425 }; 426 } 427 chop(String str)428 abstract Optional<String> chop(String str); 429 suffix(final String suffix)430 static Chopper suffix(final String suffix) { 431 return new Chopper() { 432 @Override 433 Optional<String> chop(String str) { 434 if (str.endsWith(suffix)) { 435 return Optional.of(str.substring(0, str.length() - suffix.length())); 436 } else { 437 return Optional.absent(); 438 } 439 } 440 }; 441 } 442 } 443 } 444