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.VisibleForTesting; 25 import com.google.common.base.Optional; 26 import com.google.common.base.Predicate; 27 import com.google.common.collect.HashMultimap; 28 import com.google.common.collect.ImmutableList; 29 import com.google.common.collect.Iterables; 30 import com.google.common.collect.Lists; 31 import com.google.common.collect.Maps; 32 import com.google.common.collect.Multimap; 33 import com.google.common.collect.Sets; 34 import com.google.common.reflect.ClassPath; 35 import com.google.common.testing.NullPointerTester.Visibility; 36 37 import junit.framework.AssertionFailedError; 38 import junit.framework.TestCase; 39 40 import org.junit.Test; 41 42 import java.io.IOException; 43 import java.io.Serializable; 44 import java.util.LinkedHashSet; 45 import java.util.List; 46 import java.util.TreeMap; 47 import java.util.logging.Level; 48 import 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 98 public 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