Java

Форк
0
/
AbstractPackageSanityTests.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

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.GwtIncompatible;
24
import com.google.common.annotations.J2ktIncompatible;
25
import com.google.common.annotations.VisibleForTesting;
26
import com.google.common.base.Optional;
27
import com.google.common.base.Predicate;
28
import com.google.common.collect.HashMultimap;
29
import com.google.common.collect.ImmutableList;
30
import com.google.common.collect.Iterables;
31
import com.google.common.collect.Lists;
32
import com.google.common.collect.Maps;
33
import com.google.common.collect.Multimap;
34
import com.google.common.collect.Sets;
35
import com.google.common.reflect.ClassPath;
36
import com.google.common.testing.NullPointerTester.Visibility;
37
import com.google.j2objc.annotations.J2ObjCIncompatible;
38
import java.io.IOException;
39
import java.io.Serializable;
40
import java.util.LinkedHashSet;
41
import java.util.List;
42
import java.util.Locale;
43
import java.util.TreeMap;
44
import java.util.logging.Level;
45
import java.util.logging.Logger;
46
import junit.framework.TestCase;
47
import 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
109
public 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
   */
118
  public 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. */
122
  private static final ImmutableList<String> NULL_TEST_METHOD_NAMES =
123
      ImmutableList.of(
124
          "testNulls", "testNull",
125
          "testNullPointers", "testNullPointer",
126
          "testNullPointerExceptions", "testNullPointerException");
127

128
  /* The names of the expected method that tests serializable. */
129
  private static final ImmutableList<String> SERIALIZABLE_TEST_METHOD_NAMES =
130
      ImmutableList.of(
131
          "testSerializable", "testSerialization",
132
          "testEqualsAndSerializable", "testEqualsAndSerialization");
133

134
  /* The names of the expected method that tests equals. */
135
  private static final ImmutableList<String> EQUALS_TEST_METHOD_NAMES =
136
      ImmutableList.of(
137
          "testEquals",
138
          "testEqualsAndHashCode",
139
          "testEqualsAndSerializable",
140
          "testEqualsAndSerialization",
141
          "testEquality");
142

143
  private static final Chopper TEST_SUFFIX =
144
      suffix("Test").or(suffix("Tests")).or(suffix("TestCase")).or(suffix("TestSuite"));
145

146
  private final Logger logger = Logger.getLogger(getClass().getName());
147
  private final ClassSanityTester tester = new ClassSanityTester();
148
  private Visibility visibility = Visibility.PACKAGE;
149
  private 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
   */
156
  protected final void publicApiOnly() {
157
    visibility = 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
189
  public void testSerializable() throws Exception {
190
    // TODO: when we use @BeforeClass, we can pay the cost of class path scanning only once.
191
    for (Class<?> classToTest :
192
        findClassesToTest(loadClassesInPackage(), SERIALIZABLE_TEST_METHOD_NAMES)) {
193
      if (Serializable.class.isAssignableFrom(classToTest)) {
194
        try {
195
          Object instance = tester.instantiate(classToTest);
196
          if (instance != null) {
197
            if (isEqualsDefined(classToTest)) {
198
              SerializableTester.reserializeAndAssert(instance);
199
            } else {
200
              SerializableTester.reserialize(instance);
201
            }
202
          }
203
        } catch (Throwable e) {
204
          throw 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
234
  public void testNulls() throws Exception {
235
    for (Class<?> classToTest : findClassesToTest(loadClassesInPackage(), NULL_TEST_METHOD_NAMES)) {
236
      try {
237
        tester.doTestNulls(classToTest, visibility);
238
      } catch (Throwable e) {
239
        throw 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
272
  public void testEquals() throws Exception {
273
    for (Class<?> classToTest :
274
        findClassesToTest(loadClassesInPackage(), EQUALS_TEST_METHOD_NAMES)) {
275
      if (!classToTest.isEnum() && isEqualsDefined(classToTest)) {
276
        try {
277
          tester.doTestEquals(classToTest);
278
        } catch (Throwable e) {
279
          throw 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
   */
291
  protected final <T> void setDefault(Class<T> type, T value) {
292
    tester.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
   */
301
  protected final <T> void setDistinctValues(Class<T> type, T value1, T value2) {
302
    tester.setDistinctValues(type, value1, value2);
303
  }
304

305
  /** Specifies that classes that satisfy the given predicate aren't tested for sanity. */
306
  protected final void ignoreClasses(Predicate<? super Class<?>> condition) {
307
    this.classFilter = and(this.classFilter, not(condition));
308
  }
309

310
  private static AssertionError sanityError(
311
      Class<?> cls, List<String> explicitTestNames, String description, Throwable e) {
312
    String message =
313
        String.format(
314
            Locale.ROOT,
315
            "Error in automated %s of %s\n"
316
                + "If the class is better tested explicitly, you can add %s() to %sTest",
317
            description,
318
            cls,
319
            explicitTestNames.get(0),
320
            cls.getName());
321
    return 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
329
  List<Class<?>> findClassesToTest(
330
      Iterable<? extends Class<?>> classes, Iterable<String> explicitTestNames) {
331
    // "a.b.Foo" -> a.b.Foo.class
332
    TreeMap<String, Class<?>> classMap = Maps.newTreeMap();
333
    for (Class<?> cls : classes) {
334
      classMap.put(cls.getName(), cls);
335
    }
336
    // Foo.class -> [FooTest.class, FooTests.class, FooTestSuite.class, ...]
337
    Multimap<Class<?>, Class<?>> testClasses = HashMultimap.create();
338
    LinkedHashSet<Class<?>> candidateClasses = Sets.newLinkedHashSet();
339
    for (Class<?> cls : classes) {
340
      Optional<String> testedClassName = TEST_SUFFIX.chop(cls.getName());
341
      if (testedClassName.isPresent()) {
342
        Class<?> testedClass = classMap.get(testedClassName.get());
343
        if (testedClass != null) {
344
          testClasses.put(testedClass, cls);
345
        }
346
      } else {
347
        candidateClasses.add(cls);
348
      }
349
    }
350
    List<Class<?>> result = Lists.newArrayList();
351
    NEXT_CANDIDATE:
352
    for (Class<?> candidate : Iterables.filter(candidateClasses, classFilter)) {
353
      for (Class<?> testClass : testClasses.get(candidate)) {
354
        if (hasTest(testClass, explicitTestNames)) {
355
          // covered by explicit test
356
          continue NEXT_CANDIDATE;
357
        }
358
      }
359
      result.add(candidate);
360
    }
361
    return result;
362
  }
363

364
  private List<Class<?>> loadClassesInPackage() throws IOException {
365
    List<Class<?>> classes = Lists.newArrayList();
366
    String packageName = getClass().getPackage().getName();
367
    for (ClassPath.ClassInfo classInfo :
368
        ClassPath.from(getClass().getClassLoader()).getTopLevelClasses(packageName)) {
369
      Class<?> cls;
370
      try {
371
        cls = classInfo.load();
372
      } catch (NoClassDefFoundError e) {
373
        // In case there were linking problems, this is probably not a class we care to test anyway.
374
        logger.log(Level.SEVERE, "Cannot load class " + classInfo + ", skipping...", e);
375
        continue;
376
      }
377
      if (!cls.isInterface()) {
378
        classes.add(cls);
379
      }
380
    }
381
    return classes;
382
  }
383

384
  private static boolean hasTest(Class<?> testClass, Iterable<String> testNames) {
385
    for (String testName : testNames) {
386
      try {
387
        testClass.getMethod(testName);
388
        return true;
389
      } catch (NoSuchMethodException e) {
390
        continue;
391
      }
392
    }
393
    return false;
394
  }
395

396
  private static boolean isEqualsDefined(Class<?> cls) {
397
    try {
398
      return !cls.getDeclaredMethod("equals", Object.class).isSynthetic();
399
    } catch (NoSuchMethodException e) {
400
      return false;
401
    }
402
  }
403

404
  abstract static class Chopper {
405

406
    final Chopper or(Chopper you) {
407
      Chopper i = this;
408
      return new Chopper() {
409
        @Override
410
        Optional<String> chop(String str) {
411
          return i.chop(str).or(you.chop(str));
412
        }
413
      };
414
    }
415

416
    abstract Optional<String> chop(String str);
417

418
    static Chopper suffix(String suffix) {
419
      return new Chopper() {
420
        @Override
421
        Optional<String> chop(String str) {
422
          if (str.endsWith(suffix)) {
423
            return Optional.of(str.substring(0, str.length() - suffix.length()));
424
          } else {
425
            return Optional.absent();
426
          }
427
        }
428
      };
429
    }
430
  }
431
}
432

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.