diff options
Diffstat (limited to 'guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java')
-rw-r--r-- | guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java | 380 |
1 files changed, 0 insertions, 380 deletions
diff --git a/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java b/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java deleted file mode 100644 index 3e13fa0..0000000 --- a/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java +++ /dev/null @@ -1,380 +0,0 @@ -/* - * Copyright (C) 2012 The Guava Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package com.google.common.testing; - -import static com.google.common.base.Predicates.and; -import static com.google.common.base.Predicates.not; -import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix; - -import com.google.common.annotations.Beta; -import com.google.common.annotations.VisibleForTesting; -import com.google.common.base.Optional; -import com.google.common.base.Predicate; -import com.google.common.collect.HashMultimap; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Iterables; -import com.google.common.collect.Lists; -import com.google.common.collect.Maps; -import com.google.common.collect.Multimap; -import com.google.common.collect.Sets; -import com.google.common.reflect.ClassPath; -import com.google.common.testing.NullPointerTester.Visibility; - -import junit.framework.AssertionFailedError; -import junit.framework.TestCase; - -import org.junit.Test; - -import java.io.IOException; -import java.io.Serializable; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.TreeMap; -import java.util.logging.Level; -import java.util.logging.Logger; - -/** - * Automatically runs sanity checks against top level classes in the same package of the test that - * extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link - * NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example: <pre> - * public class PackageSanityTests extends AbstractPackageSanityTests {} - * </pre> - * - * <p>Note that only top-level classes with either a non-private constructor or a non-private static - * factory method to construct instances can have their instance methods checked. For example: <pre> - * public class Address { - * private final String city; - * private final String state; - * private final String zipcode; - * - * public Address(String city, String state, String zipcode) {...} - * - * {@literal @Override} public boolean equals(Object obj) {...} - * {@literal @Override} public int hashCode() {...} - * ... - * } - * </pre> - * No cascading checks are performed against the return values of methods unless the method is a - * static factory method. Neither are semantics of mutation methods such as {@code - * someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see - * {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}. - * - * <p>For testing against the returned instances from a static factory class, such as <pre> - * interface Book {...} - * public class Books { - * public static Book hardcover(String title) {...} - * public static Book paperback(String title) {...} - * } - * </pre> - * please use {@link ClassSanityTester#forAllPublicStaticMethods}. - * - * <p>This class incurs IO because it scans the classpath and reads classpath resources. - * - * @author Ben Yu - * @since 14.0 - */ -@Beta -// TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass -public abstract class AbstractPackageSanityTests extends TestCase { - - /* The names of the expected method that tests null checks. */ - private static final ImmutableList<String> NULL_TEST_METHOD_NAMES = ImmutableList.of( - "testNulls", "testNull", - "testNullPointers", "testNullPointer", - "testNullPointerExceptions", "testNullPointerException"); - - /* The names of the expected method that tests serializable. */ - private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES = ImmutableList.of( - "testSerializable", "testSerialization", - "testEqualsAndSerializable", "testEqualsAndSerialization"); - - /* The names of the expected method that tests equals. */ - private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES = ImmutableList.of( - "testEquals", "testEqualsAndHashCode", - "testEqualsAndSerializable", "testEqualsAndSerialization", - "testEquality"); - - private static final Chopper TEST_SUFFIX = - suffix("Test") - .or(suffix("Tests")) - .or(suffix("TestCase")) - .or(suffix("TestSuite")); - - private final Logger logger = Logger.getLogger(getClass().getName()); - private final ClassSanityTester tester = new ClassSanityTester(); - private Visibility visibility = Visibility.PACKAGE; - private Predicate<Class<?>> classFilter = new Predicate<Class<?>>() { - @Override public boolean apply(Class<?> cls) { - return visibility.isVisible(cls.getModifiers()); - } - }; - - /** - * Restricts the sanity tests for public API only. By default, package-private API are also - * covered. - */ - protected final void publicApiOnly() { - visibility = Visibility.PUBLIC; - } - - /** - * Tests all top-level public {@link Serializable} classes in the package. For a serializable - * Class {@code C}: - * <ul> - * <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will be - * checked to be equal to the instance before serialization. - * <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a - * superclass, no equality check is done on the deserialized instance because it's not clear - * whether the author intended for the class to be a value type. - * <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic - * proxy will be passed to the method. It's possible that the method body expects an instance - * method of the passed-in proxy to be of a certain value yet the proxy isn't aware of the - * assumption, in which case the equality check before and after serialization will fail. - * <li>If the constructor or factory method takes a parameter that {@link - * AbstractPackageSanityTests} doesn't know how to construct, the test will fail. - * <li>If there is no public constructor or public static factory method declared by {@code C}, - * {@code C} is skipped for serialization test, even if it implements {@link Serializable}. - * <li>Serialization test is not performed on method return values unless the method is a public - * static factory method whose return type is {@code C} or {@code C}'s subtype. - * </ul> - * - * In all cases, if {@code C} needs custom logic for testing serialization, you can add an - * explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code - * C} will be excluded from automated serialization test performed by this method. - */ - @Test - public void testSerializable() throws Exception { - // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once. - for (Class<?> classToTest - : findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) { - if (Serializable.class.isAssignableFrom(classToTest)) { - try { - Object instance = tester.instantiate(classToTest); - if (instance != null) { - if (isEqualsDefined(classToTest)) { - SerializableTester.reserializeAndAssert(instance); - } else { - SerializableTester.reserialize(instance); - } - } - } catch (Throwable e) { - throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e); - } - } - } - } - - /** - * Performs {@link NullPointerTester} checks for all top-level public classes in the package. For - * a class {@code C} - * <ul> - * <li>All public static methods are checked such that passing null for any parameter that's not - * annotated with {@link javax.annotation.Nullable} should throw {@link NullPointerException}. - * <li>If there is any public constructor or public static factory method declared by the class, - * all public instance methods will be checked too using the instance created by invoking the - * constructor or static factory method. - * <li>If the constructor or factory method used to construct instance takes a parameter that - * {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail. - * <li>If there is no public constructor or public static factory method declared by {@code C}, - * instance methods are skipped for nulls test. - * <li>Nulls test is not performed on method return values unless the method is a public static - * factory method whose return type is {@code C} or {@code C}'s subtype. - * </ul> - * - * In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit {@code - * testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be excluded from - * the automated null tests performed by this method. - */ - @Test - public void testNulls() throws Exception { - for (Class<?> classToTest - : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) { - try { - tester.doTestNulls(classToTest, visibility); - } catch (Throwable e) { - throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e); - } - } - } - - /** - * Tests {@code equals()} and {@code hashCode()} implementations for every top-level public class - * in the package, that explicitly implements {@link Object#equals}. For a class {@code C}: - * <ul> - * <li>The public constructor or public static factory method with the most parameters is used to - * construct the sample instances. In case of tie, the candidate constructors or factories are - * tried one after another until one can be used to construct sample instances. - * <li>For the constructor or static factory method used to construct instances, it's checked that - * when equal parameters are passed, the result instance should also be equal; and vice versa. - * <li>Inequality check is not performed against state mutation methods such as {@link List#add}, - * or functional update methods such as {@link com.google.common.base.Joiner#skipNulls}. - * <li>If the constructor or factory method used to construct instance takes a parameter that - * {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail. - * <li>If there is no public constructor or public static factory method declared by {@code C}, - * {@code C} is skipped for equality test. - * <li>Equality test is not performed on method return values unless the method is a public static - * factory method whose return type is {@code C} or {@code C}'s subtype. - * </ul> - * - * In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an - * explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will - * be excluded from the automated {@code equals} test performed by this method. - */ - @Test - public void testEquals() throws Exception { - for (Class<?> classToTest - : findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) { - if (!classToTest.isEnum() && isEqualsDefined(classToTest)) { - try { - tester.doTestEquals(classToTest); - } catch (Throwable e) { - throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e); - } - } - } - } - - /** - * Sets the default value for {@code type}, when dummy value for a parameter of the same type - * needs to be created in order to invoke a method or constructor. The default value isn't used in - * testing {@link Object#equals} because more than one sample instances are needed for testing - * inequality. - */ - protected final <T> void setDefault(Class<T> type, T value) { - tester.setDefault(type, value); - } - - /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */ - protected final void ignoreClasses(Predicate<? super Class<?>> condition) { - this.classFilter = and(this.classFilter, not(condition)); - } - - private static AssertionFailedError sanityError( - Class<?> cls, List<String> explicitTestNames, String description, Throwable e) { - String message = String.format( - "Error in automated %s of %s\n" - + "If the class is better tested explicitly, you can add %s() to %sTest", - description, cls, explicitTestNames.get(0), cls.getName()); - AssertionFailedError error = new AssertionFailedError(message); - error.initCause(e); - return error; - } - - /** - * Finds the classes not ending with a test suffix and not covered by an explicit test - * whose name is {@code explicitTestName}. - */ - @VisibleForTesting List<Class<?>> findClassesToTest( - Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) { - // "a.b.Foo" -> a.b.Foo.class - TreeMap<String, Class<?>> classMap = Maps.newTreeMap(); - for (Class<?> cls : classes) { - classMap.put(cls.getName(), cls); - } - // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...] - Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create(); - LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet(); - for (Class<?> cls : classes) { - Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName()); - if (testedClassName.isPresent()) { - Class<?> testedClass = classMap.get(testedClassName.get()); - if (testedClass != null) { - testClasses.put(testedClass, cls); - } - } else { - candidateClasses.add(cls); - } - } - List<Class<?>> result = Lists.newArrayList(); - NEXT_CANDIDATE: for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) { - for (Class<?> testClass : testClasses.get(candidate)) { - if (hasTest(testClass, explicitTestNames)) { - // covered by explicit test - continue NEXT_CANDIDATE; - } - } - result.add(candidate); - } - return result; - } - - private List<Class<?>> loadClassesInPackage() throws IOException { - List<Class<?>> classes = Lists.newArrayList(); - String packageName = getClass().getPackage().getName(); - for (ClassPath.ClassInfo classInfo - : ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) { - Class<?> cls; - try { - cls = classInfo.load(); - } catch (NoClassDefFoundError e) { - // In case there were linking problems, this is probably not a class we care to test anyway. - logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e); - continue; - } - if (!cls.isInterface()) { - classes.add(cls); - } - } - return classes; - } - - private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) { - for (String testName : testNames) { - try { - testClass.getMethod(testName); - return true; - } catch (NoSuchMethodException e) { - continue; - } - } - return false; - } - - private static boolean isEqualsDefined(Class<?> cls) { - try { - return !cls.getDeclaredMethod("equals", Object.class).isSynthetic(); - } catch (NoSuchMethodException e) { - return false; - } - } - - static abstract class Chopper { - - final Chopper or(final Chopper you) { - final Chopper i = this; - return new Chopper() { - @Override Optional<String> chop(String str) { - return i.chop(str).or(you.chop(str)); - } - }; - } - - abstract Optional<String> chop(String str); - - static Chopper suffix(final String suffix) { - return new Chopper() { - @Override Optional<String> chop(String str) { - if (str.endsWith(suffix)) { - return Optional.of(str.substring(0, str.length() - suffix.length())); - } else { - return Optional.absent(); - } - } - }; - } - } -} |