Java
431 строка · 17.4 Кб
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
17package com.google.common.testing;
18
19import static com.google.common.base.Predicates.and;
20import static com.google.common.base.Predicates.not;
21import static com.google.common.testing.AbstractPackageSanityTests.Chopper.suffix;
22
23import com.google.common.annotations.GwtIncompatible;
24import com.google.common.annotations.J2ktIncompatible;
25import com.google.common.annotations.VisibleForTesting;
26import com.google.common.base.Optional;
27import com.google.common.base.Predicate;
28import com.google.common.collect.HashMultimap;
29import com.google.common.collect.ImmutableList;
30import com.google.common.collect.Iterables;
31import com.google.common.collect.Lists;
32import com.google.common.collect.Maps;
33import com.google.common.collect.Multimap;
34import com.google.common.collect.Sets;
35import com.google.common.reflect.ClassPath;
36import com.google.common.testing.NullPointerTester.Visibility;
37import com.google.j2objc.annotations.J2ObjCIncompatible;
38import java.io.IOException;
39import java.io.Serializable;
40import java.util.LinkedHashSet;
41import java.util.List;
42import java.util.Locale;
43import java.util.TreeMap;
44import java.util.logging.Level;
45import java.util.logging.Logger;
46import junit.framework.TestCase;
47import org.junit.Test;
48
49/**
50* Automatically runs sanity checks against top level classes in the same package of the test that
51* extends {@code AbstractPackageSanityTests}. Currently sanity checks include {@link
52* NullPointerTester}, {@link EqualsTester} and {@link SerializableTester}. For example:
53*
54* <pre>
55* public class PackageSanityTests extends AbstractPackageSanityTests {}
56* </pre>
57*
58* <p>Note that only top-level classes with either a non-private constructor or a non-private static
59* factory method to construct instances can have their instance methods checked. For example:
60*
61* <pre>
62* public class Address {
63* private final String city;
64* private final String state;
65* private final String zipcode;
66*
67* public Address(String city, String state, String zipcode) {...}
68*
69* {@literal @Override} public boolean equals(Object obj) {...}
70* {@literal @Override} public int hashCode() {...}
71* ...
72* }
73* </pre>
74*
75* <p>No cascading checks are performed against the return values of methods unless the method is a
76* static factory method. Neither are semantics of mutation methods such as {@code
77* someList.add(obj)} checked. For more detailed discussion of supported and unsupported cases, see
78* {@link #testEquals}, {@link #testNulls} and {@link #testSerializable}.
79*
80* <p>For testing against the returned instances from a static factory class, such as
81*
82* <pre>
83* interface Book {...}
84* public class Books {
85* public static Book hardcover(String title) {...}
86* public static Book paperback(String title) {...}
87* }
88* </pre>
89*
90* <p>please use {@link ClassSanityTester#forAllPublicStaticMethods}.
91*
92* <p>If not all classes on the classpath should be covered, {@link #ignoreClasses} can be used to
93* exclude certain classes. As a special case, classes with an underscore in the name (like {@code
94* AutoValue_Foo}) can be excluded using <code>ignoreClasses({@link #UNDERSCORE_IN_NAME})</code>.
95*
96* <p>{@link #setDefault} allows subclasses to specify default values for types.
97*
98* <p>This class incurs IO because it scans the classpath and reads classpath resources.
99*
100* @author Ben Yu
101* @since 14.0
102*/
103// TODO: Switch to JUnit 4 and use @Parameterized and @BeforeClass
104// Note: @Test annotations are deliberate, as some subclasses specify @RunWith(JUnit4).
105@GwtIncompatible
106@J2ktIncompatible
107@J2ObjCIncompatible // com.google.common.reflect.ClassPath
108@ElementTypesAreNonnullByDefault
109public 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*/
118public static final Predicate<Class<?>> UNDERSCORE_IN_NAME =
119(Class<?> c) -> c.getSimpleName().contains("_");
120
121/* The names of the expected method that tests null checks. */
122private static final ImmutableList<String> NULL_TEST_METHOD_NAMES =
123ImmutableList.of(
124"testNulls", "testNull",
125"testNullPointers", "testNullPointer",
126"testNullPointerExceptions", "testNullPointerException");
127
128/* The names of the expected method that tests serializable. */
129private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES =
130ImmutableList.of(
131"testSerializable", "testSerialization",
132"testEqualsAndSerializable", "testEqualsAndSerialization");
133
134/* The names of the expected method that tests equals. */
135private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES =
136ImmutableList.of(
137"testEquals",
138"testEqualsAndHashCode",
139"testEqualsAndSerializable",
140"testEqualsAndSerialization",
141"testEquality");
142
143private static final Chopper TEST_SUFFIX =
144suffix("Test").or(suffix("Tests")).or(suffix("TestCase")).or(suffix("TestSuite"));
145
146private final Logger logger = Logger.getLogger(getClass().getName());
147private final ClassSanityTester tester = new ClassSanityTester();
148private Visibility visibility = Visibility.PACKAGE;
149private Predicate<Class<?>> classFilter =
150(Class<?> cls) -> visibility.isVisible(cls.getModifiers());
151
152/**
153* Restricts the sanity tests for public API only. By default, package-private API are also
154* covered.
155*/
156protected final void publicApiOnly() {
157visibility = Visibility.PUBLIC;
158}
159
160/**
161* Tests all top-level {@link Serializable} classes in the package. For a serializable Class
162* {@code C}:
163*
164* <ul>
165* <li>If {@code C} explicitly implements {@link Object#equals}, the deserialized instance will
166* be checked to be equal to the instance before serialization.
167* <li>If {@code C} doesn't explicitly implement {@code equals} but instead inherits it from a
168* superclass, no equality check is done on the deserialized instance because it's not clear
169* whether the author intended for the class to be a value type.
170* <li>If a constructor or factory method takes a parameter whose type is interface, a dynamic
171* proxy will be passed to the method. It's possible that the method body expects an
172* instance method of the passed-in proxy to be of a certain value yet the proxy isn't aware
173* of the assumption, in which case the equality check before and after serialization will
174* fail.
175* <li>If the constructor or factory method takes a parameter that {@link
176* AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
177* <li>If there is no visible constructor or visible static factory method declared by {@code
178* C}, {@code C} is skipped for serialization test, even if it implements {@link
179* Serializable}.
180* <li>Serialization test is not performed on method return values unless the method is a
181* visible static factory method whose return type is {@code C} or {@code C}'s subtype.
182* </ul>
183*
184* <p>In all cases, if {@code C} needs custom logic for testing serialization, you can add an
185* explicit {@code testSerializable()} test in the corresponding {@code CTest} class, and {@code
186* C} will be excluded from automated serialization test performed by this method.
187*/
188@Test
189public void testSerializable() throws Exception {
190// TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once.
191for (Class<?> classToTest :
192findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) {
193if (Serializable.class.isAssignableFrom(classToTest)) {
194try {
195Object instance = tester.instantiate(classToTest);
196if (instance != null) {
197if (isEqualsDefined(classToTest)) {
198SerializableTester.reserializeAndAssert(instance);
199} else {
200SerializableTester.reserialize(instance);
201}
202}
203} catch (Throwable e) {
204throw sanityError(classToTest, SERIALIZABLE_TEST_METHOD_NAMES, "serializable test", e);
205}
206}
207}
208}
209
210/**
211* Performs {@link NullPointerTester} checks for all top-level classes in the package. For a class
212* {@code C}
213*
214* <ul>
215* <li>All visible static methods are checked such that passing null for any parameter that's
216* not annotated nullable (according to the rules of {@link NullPointerTester}) should throw
217* {@link NullPointerException}.
218* <li>If there is any visible constructor or visible static factory method declared by the
219* class, all visible instance methods will be checked too using the instance created by
220* invoking the constructor or static factory method.
221* <li>If the constructor or factory method used to construct instance takes a parameter that
222* {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
223* <li>If there is no visible constructor or visible static factory method declared by {@code
224* C}, instance methods are skipped for nulls test.
225* <li>Nulls test is not performed on method return values unless the method is a visible static
226* factory method whose return type is {@code C} or {@code C}'s subtype.
227* </ul>
228*
229* <p>In all cases, if {@code C} needs custom logic for testing nulls, you can add an explicit
230* {@code testNulls()} test in the corresponding {@code CTest} class, and {@code C} will be
231* excluded from the automated null tests performed by this method.
232*/
233@Test
234public void testNulls() throws Exception {
235for (Class<?> classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
236try {
237tester.doTestNulls(classToTest, visibility);
238} catch (Throwable e) {
239throw sanityError(classToTest, NULL_TEST_METHOD_NAMES, "nulls test", e);
240}
241}
242}
243
244/**
245* Tests {@code equals()} and {@code hashCode()} implementations for every top-level class in the
246* package, that explicitly implements {@link Object#equals}. For a class {@code C}:
247*
248* <ul>
249* <li>The visible constructor or visible static factory method with the most parameters is used
250* to construct the sample instances. In case of tie, the candidate constructors or
251* factories are tried one after another until one can be used to construct sample
252* instances.
253* <li>For the constructor or static factory method used to construct instances, it's checked
254* that when equal parameters are passed, the result instance should also be equal; and vice
255* versa.
256* <li>Inequality check is not performed against state mutation methods such as {@link
257* List#add}, or functional update methods such as {@link
258* com.google.common.base.Joiner#skipNulls}.
259* <li>If the constructor or factory method used to construct instance takes a parameter that
260* {@link AbstractPackageSanityTests} doesn't know how to construct, the test will fail.
261* <li>If there is no visible constructor or visible static factory method declared by {@code
262* C}, {@code C} is skipped for equality test.
263* <li>Equality test is not performed on method return values unless the method is a visible
264* static factory method whose return type is {@code C} or {@code C}'s subtype.
265* </ul>
266*
267* <p>In all cases, if {@code C} needs custom logic for testing {@code equals()}, you can add an
268* explicit {@code testEquals()} test in the corresponding {@code CTest} class, and {@code C} will
269* be excluded from the automated {@code equals} test performed by this method.
270*/
271@Test
272public void testEquals() throws Exception {
273for (Class<?> classToTest :
274findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) {
275if (!classToTest.isEnum() && isEqualsDefined(classToTest)) {
276try {
277tester.doTestEquals(classToTest);
278} catch (Throwable e) {
279throw sanityError(classToTest, EQUALS_TEST_METHOD_NAMES, "equals test", e);
280}
281}
282}
283}
284
285/**
286* Sets the default value for {@code type}, when dummy value for a parameter of the same type
287* needs to be created in order to invoke a method or constructor. The default value isn't used in
288* testing {@link Object#equals} because more than one sample instances are needed for testing
289* inequality.
290*/
291protected final <T> void setDefault(Class<T> type, T value) {
292tester.setDefault(type, value);
293}
294
295/**
296* Sets two distinct values for {@code type}. These values can be used for both null pointer
297* testing and equals testing.
298*
299* @since 17.0
300*/
301protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) {
302tester.setDistinctValues(type, value1, value2);
303}
304
305/** Specifies that classes that satisfy the given predicate aren't tested for sanity. */
306protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
307this.classFilter = and(this.classFilter, not(condition));
308}
309
310private static AssertionError sanityError(
311Class<?> cls, List<String> explicitTestNames, String description, Throwable e) {
312String message =
313String.format(
314Locale.ROOT,
315"Error in automated %s of %s\n"
316+ "If the class is better tested explicitly, you can add %s() to %sTest",
317description,
318cls,
319explicitTestNames.get(0),
320cls.getName());
321return new AssertionError(message, e);
322}
323
324/**
325* Finds the classes not ending with a test suffix and not covered by an explicit test whose name
326* is {@code explicitTestNames}.
327*/
328@VisibleForTesting
329List<Class<?>> findClassesToTest(
330Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) {
331// "a.b.Foo" -> a.b.Foo.class
332TreeMap<String, Class<?>> classMap = Maps.newTreeMap();
333for (Class<?> cls : classes) {
334classMap.put(cls.getName(), cls);
335}
336// Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...]
337Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create();
338LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet();
339for (Class<?> cls : classes) {
340Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName());
341if (testedClassName.isPresent()) {
342Class<?> testedClass = classMap.get(testedClassName.get());
343if (testedClass != null) {
344testClasses.put(testedClass, cls);
345}
346} else {
347candidateClasses.add(cls);
348}
349}
350List<Class<?>> result = Lists.newArrayList();
351NEXT_CANDIDATE:
352for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) {
353for (Class<?> testClass : testClasses.get(candidate)) {
354if (hasTest(testClass, explicitTestNames)) {
355// covered by explicit test
356continue NEXT_CANDIDATE;
357}
358}
359result.add(candidate);
360}
361return result;
362}
363
364private List<Class<?>> loadClassesInPackage() throws IOException {
365List<Class<?>> classes = Lists.newArrayList();
366String packageName = getClass().getPackage().getName();
367for (ClassPath.ClassInfo classInfo :
368ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) {
369Class<?> cls;
370try {
371cls = classInfo.load();
372} catch (NoClassDefFoundError e) {
373// In case there were linking problems, this is probably not a class we care to test anyway.
374logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e);
375continue;
376}
377if (!cls.isInterface()) {
378classes.add(cls);
379}
380}
381return classes;
382}
383
384private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
385for (String testName : testNames) {
386try {
387testClass.getMethod(testName);
388return true;
389} catch (NoSuchMethodException e) {
390continue;
391}
392}
393return false;
394}
395
396private static boolean isEqualsDefined(Class<?> cls) {
397try {
398return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
399} catch (NoSuchMethodException e) {
400return false;
401}
402}
403
404abstract static class Chopper {
405
406final Chopper or(Chopper you) {
407Chopper i = this;
408return new Chopper() {
409@Override
410Optional<String> chop(String str) {
411return i.chop(str).or(you.chop(str));
412}
413};
414}
415
416abstract Optional<String> chop(String str);
417
418static Chopper suffix(String suffix) {
419return new Chopper() {
420@Override
421Optional<String> chop(String str) {
422if (str.endsWith(suffix)) {
423return Optional.of(str.substring(0, str.length() - suffix.length()));
424} else {
425return Optional.absent();
426}
427}
428};
429}
430}
431}
432