alvinalexander.com | career | drupal | java | mac | mysql | perl | scala | uml | unix  

Java example source code file (AbstractPackageSanityTests.java)

This example Java source code file (AbstractPackageSanityTests.java) is included in the alvinalexander.com "Java Source Code Warehouse" project. The intent of this project is to help you "Learn Java by Example" TM.

Learn more about this Java project at its project page.

Java - Java tags/keywords

assertionfailederror, chopper, class, equals_test_method_names, exception, iterable, list, log, logging, optional, override, predicate, string, test, throwable, util

The AbstractPackageSanityTests.java Java example source code

/*
 * 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.GwtIncompatible;
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 com.google.j2objc.annotations.J2ObjCIncompatible;

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.Locale;
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>
 * <p>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 
 * interface Book {...}
 * public class Books {
 *   public static Book hardcover(String title) {...}
 *   public static Book paperback(String title) {...}
 * }
 * </pre>
 *
 * <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}.
 *
 * <p>If not all classes on the classpath should be covered, {@link
 * #ignoreClasses} can be used to exclude certain classes. As a special case, classes with an
 * underscore in the name (like {@code AutoValue_Foo}) can be excluded using
 * <code>ignoreClasses({@link #UNDERSCORE_IN_NAME}).
 *
 * <p>{@link #setDefault} allows subclasses to specify default values for types.
 *
 * <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
// Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4).
@GwtIncompatible
@J2ObjCIncompatible // com.google.common.reflect.ClassPath
public abstract class AbstractPackageSanityTests extends TestCase {

  /**
   * A predicate that matches classes with an underscore in the class name. This can be used with
   * {@link #ignoreClasses} to exclude generated classes, such as the {@code AutoValue_Foo} classes
   * generated by <a href="https://github.com/google/auto/tree/master/value">AutoValue.
   *
   * @since 19.0
   */
  public static final Predicate<Class UNDERSCORE_IN_NAME = new Predicate>() {
    @Override public boolean apply(Class<?> c) {
      return c.getSimpleName().contains("_");
    }
  };

  /* 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>() {
    @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 {@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 visible constructor or visible 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 visible
   *     static factory method whose return type is {@code C} or {@code C}'s subtype.
   * </ul>
   *
   * <p>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 classes in the package. For a class
   * {@code C}
   * <ul>
   * <li>All visible 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 visible constructor or visible static factory method declared by the class,
   *     all visible 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 visible constructor or visible 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 visible static
   *     factory method whose return type is {@code C} or {@code C}'s subtype.
   * </ul>
   *
   * <p>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 class in the
   * package, that explicitly implements {@link Object#equals}. For a class {@code C}:
   * <ul>
   * <li>The visible constructor or visible 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 visible constructor or visible 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 visible
   *     static factory method whose return type is {@code C} or {@code C}'s subtype.
   * </ul>
   *
   * <p>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 type, T value) {
    tester.setDefault(type, value);
  }

  /**
   * Sets two distinct values for {@code type}. These values can be used for both null pointer
   * testing and equals testing.
   *
   * @since 17.0
   */
  protected final <T> void setDistinctValues(Class type, T value1, T value2) {
    tester.setDistinctValues(type, value1, value2);
  }

  /** 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 explicitTestNames, String description, Throwable e) {
    String message = String.format(Locale.ROOT,
        "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 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> 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 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;
    }
  }

  abstract static 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();
          }
        }
      };
    }
  }
}

Other Java examples (source code examples)

Here is a short list of links related to this Java AbstractPackageSanityTests.java source code file:

... this post is sponsored by my books ...

#1 New Release!

FP Best Seller

 

new blog posts

 

Copyright 1998-2024 Alvin Alexander, alvinalexander.com
All Rights Reserved.

A percentage of advertising revenue from
pages under the /java/jwarehouse URI on this website is
paid back to open source projects.