aboutsummaryrefslogtreecommitdiffstats
path: root/guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java
diff options
context:
space:
mode:
Diffstat (limited to 'guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java')
-rw-r--r--guava-testlib/src/com/google/common/testing/AbstractPackageSanityTests.java380
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();
- }
- }
- };
- }
- }
-}