Java

Форк
0
/
ClassSanityTester.java 
840 строк · 32.6 Кб
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.Preconditions.checkArgument;
20
import static com.google.common.base.Preconditions.checkNotNull;
21
import static com.google.common.base.Throwables.throwIfUnchecked;
22
import static com.google.common.testing.NullPointerTester.isNullable;
23

24
import com.google.common.annotations.GwtIncompatible;
25
import com.google.common.annotations.J2ktIncompatible;
26
import com.google.common.annotations.VisibleForTesting;
27
import com.google.common.base.Joiner;
28
import com.google.common.base.Objects;
29
import com.google.common.collect.ArrayListMultimap;
30
import com.google.common.collect.ImmutableList;
31
import com.google.common.collect.ListMultimap;
32
import com.google.common.collect.Lists;
33
import com.google.common.collect.MutableClassToInstanceMap;
34
import com.google.common.collect.Ordering;
35
import com.google.common.collect.Sets;
36
import com.google.common.reflect.Invokable;
37
import com.google.common.reflect.Parameter;
38
import com.google.common.reflect.Reflection;
39
import com.google.common.reflect.TypeToken;
40
import com.google.common.testing.NullPointerTester.Visibility;
41
import com.google.common.testing.RelationshipTester.Item;
42
import com.google.common.testing.RelationshipTester.ItemReporter;
43
import com.google.errorprone.annotations.CanIgnoreReturnValue;
44
import java.io.Serializable;
45
import java.lang.reflect.Constructor;
46
import java.lang.reflect.InvocationTargetException;
47
import java.lang.reflect.Method;
48
import java.lang.reflect.Modifier;
49
import java.util.Collection;
50
import java.util.List;
51
import java.util.Map.Entry;
52
import java.util.Set;
53
import javax.annotation.CheckForNull;
54
import junit.framework.Assert;
55
import junit.framework.AssertionFailedError;
56
import org.checkerframework.checker.nullness.qual.Nullable;
57

58
/**
59
 * Tester that runs automated sanity tests for any given class. A typical use case is to test static
60
 * factory classes like:
61
 *
62
 * <pre>
63
 * interface Book {...}
64
 * public class Books {
65
 *   public static Book hardcover(String title) {...}
66
 *   public static Book paperback(String title) {...}
67
 * }
68
 * </pre>
69
 *
70
 * <p>And all the created {@code Book} instances can be tested with:
71
 *
72
 * <pre>
73
 * new ClassSanityTester()
74
 *     .forAllPublicStaticMethods(Books.class)
75
 *     .thatReturn(Book.class)
76
 *     .testEquals(); // or testNulls(), testSerializable() etc.
77
 * </pre>
78
 *
79
 * @author Ben Yu
80
 * @since 14.0
81
 */
82
@GwtIncompatible
83
@J2ktIncompatible
84
public final class ClassSanityTester {
85

86
  private static final Ordering<Invokable<?, ?>> BY_METHOD_NAME =
87
      new Ordering<Invokable<?, ?>>() {
88
        @Override
89
        public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
90
          return left.getName().compareTo(right.getName());
91
        }
92
      };
93

94
  private static final Ordering<Invokable<?, ?>> BY_PARAMETERS =
95
      new Ordering<Invokable<?, ?>>() {
96
        @Override
97
        public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
98
          return Ordering.usingToString().compare(left.getParameters(), right.getParameters());
99
        }
100
      };
101

102
  private static final Ordering<Invokable<?, ?>> BY_NUMBER_OF_PARAMETERS =
103
      new Ordering<Invokable<?, ?>>() {
104
        @Override
105
        public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
106
          return Integer.compare(left.getParameters().size(), right.getParameters().size());
107
        }
108
      };
109

110
  private final MutableClassToInstanceMap<Object> defaultValues =
111
      MutableClassToInstanceMap.create();
112
  private final ListMultimap<Class<?>, Object> distinctValues = ArrayListMultimap.create();
113
  private final NullPointerTester nullPointerTester = new NullPointerTester();
114

115
  public ClassSanityTester() {
116
    // TODO(benyu): bake these into ArbitraryInstances.
117
    setDefault(byte.class, (byte) 1);
118
    setDefault(Byte.class, (byte) 1);
119
    setDefault(short.class, (short) 1);
120
    setDefault(Short.class, (short) 1);
121
    setDefault(int.class, 1);
122
    setDefault(Integer.class, 1);
123
    setDefault(long.class, 1L);
124
    setDefault(Long.class, 1L);
125
    setDefault(float.class, 1F);
126
    setDefault(Float.class, 1F);
127
    setDefault(double.class, 1D);
128
    setDefault(Double.class, 1D);
129
    setDefault(Class.class, Class.class);
130
  }
131

132
  /**
133
   * Sets the default value for {@code type}. The default value isn't used in testing {@link
134
   * Object#equals} because more than one sample instances are needed for testing inequality. To set
135
   * distinct values for equality testing, use {@link #setDistinctValues} instead.
136
   */
137
  @CanIgnoreReturnValue
138
  public <T> ClassSanityTester setDefault(Class<T> type, T value) {
139
    nullPointerTester.setDefault(type, value);
140
    defaultValues.putInstance(type, value);
141
    return this;
142
  }
143

144
  /**
145
   * Sets distinct values for {@code type}, so that when a class {@code Foo} is tested for {@link
146
   * Object#equals} and {@link Object#hashCode}, and its construction requires a parameter of {@code
147
   * type}, the distinct values of {@code type} can be passed as parameters to create {@code Foo}
148
   * instances that are unequal.
149
   *
150
   * <p>Calling {@code setDistinctValues(type, v1, v2)} also sets the default value for {@code type}
151
   * that's used for {@link #testNulls}.
152
   *
153
   * <p>Only necessary for types where {@link ClassSanityTester} doesn't already know how to create
154
   * distinct values.
155
   *
156
   * @return this tester instance
157
   * @since 17.0
158
   */
159
  @CanIgnoreReturnValue
160
  public <T> ClassSanityTester setDistinctValues(Class<T> type, T value1, T value2) {
161
    checkNotNull(type);
162
    checkNotNull(value1);
163
    checkNotNull(value2);
164
    checkArgument(!Objects.equal(value1, value2), "Duplicate value provided.");
165
    distinctValues.replaceValues(type, ImmutableList.of(value1, value2));
166
    setDefault(type, value1);
167
    return this;
168
  }
169

170
  /**
171
   * Tests that {@code cls} properly checks null on all constructor and method parameters that
172
   * aren't annotated nullable (according to the rules of {@link NullPointerTester}). In details:
173
   *
174
   * <ul>
175
   *   <li>All non-private static methods are checked such that passing null for any parameter
176
   *       that's not annotated nullable should throw {@link NullPointerException}.
177
   *   <li>If there is any non-private constructor or non-private static factory method declared by
178
   *       {@code cls}, all non-private instance methods will be checked too using the instance
179
   *       created by invoking the constructor or static factory method.
180
   *   <li>If there is any non-private constructor or non-private static factory method declared by
181
   *       {@code cls}:
182
   *       <ul>
183
   *         <li>Test will fail if default value for a parameter cannot be determined.
184
   *         <li>Test will fail if the factory method returns null so testing instance methods is
185
   *             impossible.
186
   *         <li>Test will fail if the constructor or factory method throws exception.
187
   *       </ul>
188
   *   <li>If there is no non-private constructor or non-private static factory method declared by
189
   *       {@code cls}, instance methods are skipped for nulls test.
190
   *   <li>Nulls test is not performed on method return values unless the method is a non-private
191
   *       static factory method whose return type is {@code cls} or {@code cls}'s subtype.
192
   * </ul>
193
   */
194
  public void testNulls(Class<?> cls) {
195
    try {
196
      doTestNulls(cls, Visibility.PACKAGE);
197
    } catch (Exception e) {
198
      throwIfUnchecked(e);
199
      throw new RuntimeException(e);
200
    }
201
  }
202

203
  void doTestNulls(Class<?> cls, Visibility visibility)
204
      throws ParameterNotInstantiableException, IllegalAccessException, InvocationTargetException,
205
          FactoryMethodReturnsNullException {
206
    if (!Modifier.isAbstract(cls.getModifiers())) {
207
      nullPointerTester.testConstructors(cls, visibility);
208
    }
209
    nullPointerTester.testStaticMethods(cls, visibility);
210
    if (hasInstanceMethodToTestNulls(cls, visibility)) {
211
      Object instance = instantiate(cls);
212
      if (instance != null) {
213
        nullPointerTester.testInstanceMethods(instance, visibility);
214
      }
215
    }
216
  }
217

218
  private boolean hasInstanceMethodToTestNulls(Class<?> c, Visibility visibility) {
219
    for (Method method : nullPointerTester.getInstanceMethodsToTest(c, visibility)) {
220
      for (Parameter param : Invokable.from(method).getParameters()) {
221
        if (!NullPointerTester.isPrimitiveOrNullable(param)) {
222
          return true;
223
        }
224
      }
225
    }
226
    return false;
227
  }
228

229
  /**
230
   * Tests the {@link Object#equals} and {@link Object#hashCode} of {@code cls}. In details:
231
   *
232
   * <ul>
233
   *   <li>The non-private constructor or non-private static factory method with the most parameters
234
   *       is used to construct the sample instances. In case of tie, the candidate constructors or
235
   *       factories are tried one after another until one can be used to construct sample
236
   *       instances.
237
   *   <li>For the constructor or static factory method used to construct instances, it's checked
238
   *       that when equal parameters are passed, the result instance should also be equal; and vice
239
   *       versa.
240
   *   <li>If a non-private constructor or non-private static factory method exists:
241
   *       <ul>
242
   *         <li>Test will fail if default value for a parameter cannot be determined.
243
   *         <li>Test will fail if the factory method returns null so testing instance methods is
244
   *             impossible.
245
   *         <li>Test will fail if the constructor or factory method throws exception.
246
   *       </ul>
247
   *   <li>If there is no non-private constructor or non-private static factory method declared by
248
   *       {@code cls}, no test is performed.
249
   *   <li>Equality test is not performed on method return values unless the method is a non-private
250
   *       static factory method whose return type is {@code cls} or {@code cls}'s subtype.
251
   *   <li>Inequality check is not performed against state mutation methods such as {@link
252
   *       List#add}, or functional update methods such as {@link
253
   *       com.google.common.base.Joiner#skipNulls}.
254
   * </ul>
255
   *
256
   * <p>Note that constructors taking a builder object cannot be tested effectively because
257
   * semantics of builder can be arbitrarily complex. Still, a factory class can be created in the
258
   * test to facilitate equality testing. For example:
259
   *
260
   * <pre>
261
   * public class FooTest {
262
   *
263
   *   private static class FooFactoryForTest {
264
   *     public static Foo create(String a, String b, int c, boolean d) {
265
   *       return Foo.builder()
266
   *           .setA(a)
267
   *           .setB(b)
268
   *           .setC(c)
269
   *           .setD(d)
270
   *           .build();
271
   *     }
272
   *   }
273
   *
274
   *   public void testEquals() {
275
   *     new ClassSanityTester()
276
   *       .forAllPublicStaticMethods(FooFactoryForTest.class)
277
   *       .thatReturn(Foo.class)
278
   *       .testEquals();
279
   *   }
280
   * }
281
   * </pre>
282
   *
283
   * <p>It will test that Foo objects created by the {@code create(a, b, c, d)} factory method with
284
   * equal parameters are equal and vice versa, thus indirectly tests the builder equality.
285
   */
286
  public void testEquals(Class<?> cls) {
287
    try {
288
      doTestEquals(cls);
289
    } catch (Exception e) {
290
      throwIfUnchecked(e);
291
      throw new RuntimeException(e);
292
    }
293
  }
294

295
  void doTestEquals(Class<?> cls)
296
      throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
297
          IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
298
    if (cls.isEnum()) {
299
      return;
300
    }
301
    List<? extends Invokable<?, ?>> factories = Lists.reverse(getFactories(TypeToken.of(cls)));
302
    if (factories.isEmpty()) {
303
      return;
304
    }
305
    int numberOfParameters = factories.get(0).getParameters().size();
306
    List<ParameterNotInstantiableException> paramErrors = Lists.newArrayList();
307
    List<ParameterHasNoDistinctValueException> distinctValueErrors = Lists.newArrayList();
308
    List<InvocationTargetException> instantiationExceptions = Lists.newArrayList();
309
    List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList();
310
    // Try factories with the greatest number of parameters.
311
    for (Invokable<?, ?> factory : factories) {
312
      if (factory.getParameters().size() == numberOfParameters) {
313
        try {
314
          testEqualsUsing(factory);
315
          return;
316
        } catch (ParameterNotInstantiableException e) {
317
          paramErrors.add(e);
318
        } catch (ParameterHasNoDistinctValueException e) {
319
          distinctValueErrors.add(e);
320
        } catch (InvocationTargetException e) {
321
          instantiationExceptions.add(e);
322
        } catch (FactoryMethodReturnsNullException e) {
323
          nullErrors.add(e);
324
        }
325
      }
326
    }
327
    throwFirst(paramErrors);
328
    throwFirst(distinctValueErrors);
329
    throwFirst(instantiationExceptions);
330
    throwFirst(nullErrors);
331
  }
332

333
  /**
334
   * Instantiates {@code cls} by invoking one of its non-private constructors or non-private static
335
   * factory methods with the parameters automatically provided using dummy values.
336
   *
337
   * @return The instantiated instance, or {@code null} if the class has no non-private constructor
338
   *     or factory method to be constructed.
339
   */
340
  <T> @Nullable T instantiate(Class<T> cls)
341
      throws ParameterNotInstantiableException,
342
          IllegalAccessException,
343
          InvocationTargetException,
344
          FactoryMethodReturnsNullException {
345
    if (cls.isEnum()) {
346
      T[] constants = cls.getEnumConstants();
347
      if (constants != null && constants.length > 0) {
348
        return constants[0];
349
      } else {
350
        return null;
351
      }
352
    }
353
    TypeToken<T> type = TypeToken.of(cls);
354
    List<ParameterNotInstantiableException> paramErrors = Lists.newArrayList();
355
    List<InvocationTargetException> instantiationExceptions = Lists.newArrayList();
356
    List<FactoryMethodReturnsNullException> nullErrors = Lists.newArrayList();
357
    for (Invokable<?, ? extends T> factory : getFactories(type)) {
358
      T instance;
359
      try {
360
        instance = instantiate(factory);
361
      } catch (ParameterNotInstantiableException e) {
362
        paramErrors.add(e);
363
        continue;
364
      } catch (InvocationTargetException e) {
365
        instantiationExceptions.add(e);
366
        continue;
367
      }
368
      if (instance == null) {
369
        nullErrors.add(new FactoryMethodReturnsNullException(factory));
370
      } else {
371
        return instance;
372
      }
373
    }
374
    throwFirst(paramErrors);
375
    throwFirst(instantiationExceptions);
376
    throwFirst(nullErrors);
377
    return null;
378
  }
379

380
  /**
381
   * Instantiates using {@code factory}. If {@code factory} is annotated nullable and returns null,
382
   * null will be returned.
383
   *
384
   * @throws ParameterNotInstantiableException if the static methods cannot be invoked because the
385
   *     default value of a parameter cannot be determined.
386
   * @throws IllegalAccessException if the class isn't public or is nested inside a non-public
387
   *     class, preventing its methods from being accessible.
388
   * @throws InvocationTargetException if a static method threw exception.
389
   */
390
  private <T> @Nullable T instantiate(Invokable<?, ? extends T> factory)
391
      throws ParameterNotInstantiableException, InvocationTargetException, IllegalAccessException {
392
    return invoke(factory, getDummyArguments(factory));
393
  }
394

395
  /**
396
   * Returns an object responsible for performing sanity tests against the return values of all
397
   * public static methods declared by {@code cls}, excluding superclasses.
398
   */
399
  public FactoryMethodReturnValueTester forAllPublicStaticMethods(Class<?> cls) {
400
    ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
401
    for (Method method : cls.getDeclaredMethods()) {
402
      Invokable<?, ?> invokable = Invokable.from(method);
403
      invokable.setAccessible(true);
404
      if (invokable.isPublic() && invokable.isStatic() && !invokable.isSynthetic()) {
405
        builder.add(invokable);
406
      }
407
    }
408
    return new FactoryMethodReturnValueTester(cls, builder.build(), "public static methods");
409
  }
410

411
  /** Runs sanity tests against return values of static factory methods declared by a class. */
412
  public final class FactoryMethodReturnValueTester {
413
    private final Set<String> packagesToTest = Sets.newHashSet();
414
    private final Class<?> declaringClass;
415
    private final ImmutableList<Invokable<?, ?>> factories;
416
    private final String factoryMethodsDescription;
417
    private Class<?> returnTypeToTest = Object.class;
418

419
    private FactoryMethodReturnValueTester(
420
        Class<?> declaringClass,
421
        ImmutableList<Invokable<?, ?>> factories,
422
        String factoryMethodsDescription) {
423
      this.declaringClass = declaringClass;
424
      this.factories = factories;
425
      this.factoryMethodsDescription = factoryMethodsDescription;
426
      packagesToTest.add(Reflection.getPackageName(declaringClass));
427
    }
428

429
    /**
430
     * Specifies that only the methods that are declared to return {@code returnType} or its subtype
431
     * are tested.
432
     *
433
     * @return this tester object
434
     */
435
    @CanIgnoreReturnValue
436
    public FactoryMethodReturnValueTester thatReturn(Class<?> returnType) {
437
      this.returnTypeToTest = returnType;
438
      return this;
439
    }
440

441
    /**
442
     * Tests null checks against the instance methods of the return values, if any.
443
     *
444
     * <p>Test fails if default value cannot be determined for a constructor or factory method
445
     * parameter, or if the constructor or factory method throws exception.
446
     *
447
     * @return this tester
448
     */
449
    @CanIgnoreReturnValue
450
    public FactoryMethodReturnValueTester testNulls() throws Exception {
451
      for (Invokable<?, ?> factory : getFactoriesToTest()) {
452
        Object instance = instantiate(factory);
453
        if (instance != null
454
            && packagesToTest.contains(Reflection.getPackageName(instance.getClass()))) {
455
          try {
456
            nullPointerTester.testAllPublicInstanceMethods(instance);
457
          } catch (AssertionError e) {
458
            throw new AssertionError("Null check failed on return value of " + factory, e);
459
          }
460
        }
461
      }
462
      return this;
463
    }
464

465
    /**
466
     * Tests {@link Object#equals} and {@link Object#hashCode} against the return values of the
467
     * static methods, by asserting that when equal parameters are passed to the same static method,
468
     * the return value should also be equal; and vice versa.
469
     *
470
     * <p>Test fails if default value cannot be determined for a constructor or factory method
471
     * parameter, or if the constructor or factory method throws exception.
472
     *
473
     * @return this tester
474
     */
475
    @CanIgnoreReturnValue
476
    public FactoryMethodReturnValueTester testEquals() throws Exception {
477
      for (Invokable<?, ?> factory : getFactoriesToTest()) {
478
        try {
479
          testEqualsUsing(factory);
480
        } catch (FactoryMethodReturnsNullException e) {
481
          // If the factory returns null, we just skip it.
482
        }
483
      }
484
      return this;
485
    }
486

487
    /**
488
     * Runs serialization test on the return values of the static methods.
489
     *
490
     * <p>Test fails if default value cannot be determined for a constructor or factory method
491
     * parameter, or if the constructor or factory method throws exception.
492
     *
493
     * @return this tester
494
     */
495
    @CanIgnoreReturnValue
496
    @SuppressWarnings("CatchingUnchecked") // sneaky checked exception
497
    public FactoryMethodReturnValueTester testSerializable() throws Exception {
498
      for (Invokable<?, ?> factory : getFactoriesToTest()) {
499
        Object instance = instantiate(factory);
500
        if (instance != null) {
501
          try {
502
            SerializableTester.reserialize(instance);
503
          } catch (Exception e) { // sneaky checked exception
504
            throw new AssertionError(
505
                "Serialization failed on return value of " + factory, e.getCause());
506
          }
507
        }
508
      }
509
      return this;
510
    }
511

512
    /**
513
     * Runs equals and serialization test on the return values.
514
     *
515
     * <p>Test fails if default value cannot be determined for a constructor or factory method
516
     * parameter, or if the constructor or factory method throws exception.
517
     *
518
     * @return this tester
519
     */
520
    @CanIgnoreReturnValue
521
    @SuppressWarnings("CatchingUnchecked") // sneaky checked exception
522
    public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Exception {
523
      for (Invokable<?, ?> factory : getFactoriesToTest()) {
524
        try {
525
          testEqualsUsing(factory);
526
        } catch (FactoryMethodReturnsNullException e) {
527
          // If the factory returns null, we just skip it.
528
        }
529
        Object instance = instantiate(factory);
530
        if (instance != null) {
531
          try {
532
            SerializableTester.reserializeAndAssert(instance);
533
          } catch (Exception e) { // sneaky checked exception
534
            throw new AssertionError(
535
                "Serialization failed on return value of " + factory, e.getCause());
536
          } catch (AssertionFailedError e) {
537
            throw new AssertionError(
538
                "Return value of " + factory + " reserialized to an unequal value", e);
539
          }
540
        }
541
      }
542
      return this;
543
    }
544

545
    private ImmutableList<Invokable<?, ?>> getFactoriesToTest() {
546
      ImmutableList.Builder<Invokable<?, ?>> builder = ImmutableList.builder();
547
      for (Invokable<?, ?> factory : factories) {
548
        if (returnTypeToTest.isAssignableFrom(factory.getReturnType().getRawType())) {
549
          builder.add(factory);
550
        }
551
      }
552
      ImmutableList<Invokable<?, ?>> factoriesToTest = builder.build();
553
      Assert.assertFalse(
554
          "No "
555
              + factoryMethodsDescription
556
              + " that return "
557
              + returnTypeToTest.getName()
558
              + " or subtype are found in "
559
              + declaringClass
560
              + ".",
561
          factoriesToTest.isEmpty());
562
      return factoriesToTest;
563
    }
564
  }
565

566
  private void testEqualsUsing(final Invokable<?, ?> factory)
567
      throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
568
          IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
569
    List<Parameter> params = factory.getParameters();
570
    List<FreshValueGenerator> argGenerators = Lists.newArrayListWithCapacity(params.size());
571
    List<@Nullable Object> args = Lists.newArrayListWithCapacity(params.size());
572
    for (Parameter param : params) {
573
      FreshValueGenerator generator = newFreshValueGenerator();
574
      argGenerators.add(generator);
575
      args.add(generateDummyArg(param, generator));
576
    }
577
    Object instance = createInstance(factory, args);
578
    List<Object> equalArgs = generateEqualFactoryArguments(factory, params, args);
579
    // Each group is a List of items, each item has a list of factory args.
580
    final List<List<List<Object>>> argGroups = Lists.newArrayList();
581
    argGroups.add(ImmutableList.of(args, equalArgs));
582
    EqualsTester tester =
583
        new EqualsTester(
584
            new ItemReporter() {
585
              @Override
586
              String reportItem(Item<?> item) {
587
                List<Object> factoryArgs = argGroups.get(item.groupNumber).get(item.itemNumber);
588
                return factory.getName()
589
                    + "("
590
                    + Joiner.on(", ").useForNull("null").join(factoryArgs)
591
                    + ")";
592
              }
593
            });
594
    tester.addEqualityGroup(instance, createInstance(factory, equalArgs));
595
    for (int i = 0; i < params.size(); i++) {
596
      List<Object> newArgs = Lists.newArrayList(args);
597
      Object newArg = argGenerators.get(i).generateFresh(params.get(i).getType());
598

599
      if (newArg == null || Objects.equal(args.get(i), newArg)) {
600
        if (params.get(i).getType().getRawType().isEnum()) {
601
          continue; // Nothing better we can do if it's single-value enum
602
        }
603
        throw new ParameterHasNoDistinctValueException(params.get(i));
604
      }
605
      newArgs.set(i, newArg);
606
      tester.addEqualityGroup(createInstance(factory, newArgs));
607
      argGroups.add(ImmutableList.of(newArgs));
608
    }
609
    tester.testEquals();
610
  }
611

612
  /**
613
   * Returns dummy factory arguments that are equal to {@code args} but may be different instances,
614
   * to be used to construct a second instance of the same equality group.
615
   */
616
  private List<Object> generateEqualFactoryArguments(
617
      Invokable<?, ?> factory, List<Parameter> params, List<Object> args)
618
      throws ParameterNotInstantiableException, FactoryMethodReturnsNullException,
619
          InvocationTargetException, IllegalAccessException {
620
    List<Object> equalArgs = Lists.newArrayList(args);
621
    for (int i = 0; i < args.size(); i++) {
622
      Parameter param = params.get(i);
623
      Object arg = args.get(i);
624
      // Use new fresh value generator because 'args' were populated with new fresh generator each.
625
      // Two newFreshValueGenerator() instances should normally generate equal value sequence.
626
      Object shouldBeEqualArg = generateDummyArg(param, newFreshValueGenerator());
627
      if (arg != shouldBeEqualArg
628
          && Objects.equal(arg, shouldBeEqualArg)
629
          && hashCodeInsensitiveToArgReference(factory, args, i, shouldBeEqualArg)
630
          && hashCodeInsensitiveToArgReference(
631
              factory, args, i, generateDummyArg(param, newFreshValueGenerator()))) {
632
        // If the implementation uses identityHashCode(), referential equality is
633
        // probably intended. So no point in using an equal-but-different factory argument.
634
        // We check twice to avoid confusion caused by accidental hash collision.
635
        equalArgs.set(i, shouldBeEqualArg);
636
      }
637
    }
638
    return equalArgs;
639
  }
640

641
  private static boolean hashCodeInsensitiveToArgReference(
642
      Invokable<?, ?> factory, List<Object> args, int i, Object alternateArg)
643
      throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
644
    List<Object> tentativeArgs = Lists.newArrayList(args);
645
    tentativeArgs.set(i, alternateArg);
646
    return createInstance(factory, tentativeArgs).hashCode()
647
        == createInstance(factory, args).hashCode();
648
  }
649

650
  // distinctValues is a type-safe class-values mapping, but we don't have a type-safe data
651
  // structure to hold the mappings.
652
  @SuppressWarnings({"unchecked", "rawtypes"})
653
  private FreshValueGenerator newFreshValueGenerator() {
654
    FreshValueGenerator generator =
655
        new FreshValueGenerator() {
656
          @Override
657
          @CheckForNull
658
          Object interfaceMethodCalled(Class<?> interfaceType, Method method) {
659
            return getDummyValue(TypeToken.of(interfaceType).method(method).getReturnType());
660
          }
661
        };
662
    for (Entry<Class<?>, Collection<Object>> entry : distinctValues.asMap().entrySet()) {
663
      generator.addSampleInstances((Class) entry.getKey(), entry.getValue());
664
    }
665
    return generator;
666
  }
667

668
  private static @Nullable Object generateDummyArg(Parameter param, FreshValueGenerator generator)
669
      throws ParameterNotInstantiableException {
670
    if (isNullable(param)) {
671
      return null;
672
    }
673
    Object arg = generator.generateFresh(param.getType());
674
    if (arg == null) {
675
      throw new ParameterNotInstantiableException(param);
676
    }
677
    return arg;
678
  }
679

680
  private static <X extends Throwable> void throwFirst(List<X> exceptions) throws X {
681
    if (!exceptions.isEmpty()) {
682
      throw exceptions.get(0);
683
    }
684
  }
685

686
  /** Factories with the least number of parameters are listed first. */
687
  private static <T> ImmutableList<Invokable<?, ? extends T>> getFactories(TypeToken<T> type) {
688
    List<Invokable<?, ? extends T>> factories = Lists.newArrayList();
689
    for (Method method : type.getRawType().getDeclaredMethods()) {
690
      Invokable<?, ?> invokable = type.method(method);
691
      if (!invokable.isPrivate()
692
          && !invokable.isSynthetic()
693
          && invokable.isStatic()
694
          && type.isSupertypeOf(invokable.getReturnType())) {
695
        @SuppressWarnings("unchecked") // guarded by isAssignableFrom()
696
        Invokable<?, ? extends T> factory = (Invokable<?, ? extends T>) invokable;
697
        factories.add(factory);
698
      }
699
    }
700
    if (!Modifier.isAbstract(type.getRawType().getModifiers())) {
701
      for (Constructor<?> constructor : type.getRawType().getDeclaredConstructors()) {
702
        Invokable<T, T> invokable = type.constructor(constructor);
703
        if (!invokable.isPrivate() && !invokable.isSynthetic()) {
704
          factories.add(invokable);
705
        }
706
      }
707
    }
708
    for (Invokable<?, ?> factory : factories) {
709
      factory.setAccessible(true);
710
    }
711
    // Sorts methods/constructors with the least number of parameters first since it's likely easier
712
    // to fill dummy parameter values for them. Ties are broken by name then by the string form of
713
    // the parameter list.
714
    return BY_NUMBER_OF_PARAMETERS
715
        .compound(BY_METHOD_NAME)
716
        .compound(BY_PARAMETERS)
717
        .immutableSortedCopy(factories);
718
  }
719

720
  private List<Object> getDummyArguments(Invokable<?, ?> invokable)
721
      throws ParameterNotInstantiableException {
722
    List<Object> args = Lists.newArrayList();
723
    for (Parameter param : invokable.getParameters()) {
724
      if (isNullable(param)) {
725
        args.add(null);
726
        continue;
727
      }
728
      Object defaultValue = getDummyValue(param.getType());
729
      if (defaultValue == null) {
730
        throw new ParameterNotInstantiableException(param);
731
      }
732
      args.add(defaultValue);
733
    }
734
    return args;
735
  }
736

737
  @CheckForNull
738
  private <T> T getDummyValue(TypeToken<T> type) {
739
    Class<? super T> rawType = type.getRawType();
740
    @SuppressWarnings("unchecked") // Assume all default values are generics safe.
741
    T defaultValue = (T) defaultValues.getInstance(rawType);
742
    if (defaultValue != null) {
743
      return defaultValue;
744
    }
745
    @SuppressWarnings("unchecked") // ArbitraryInstances always returns generics-safe dummies.
746
    T value = (T) ArbitraryInstances.get(rawType);
747
    if (value != null) {
748
      return value;
749
    }
750
    if (rawType.isInterface()) {
751
      return new SerializableDummyProxy(this).newProxy(type);
752
    }
753
    return null;
754
  }
755

756
  private static <T> T createInstance(Invokable<?, ? extends T> factory, List<?> args)
757
      throws FactoryMethodReturnsNullException, InvocationTargetException, IllegalAccessException {
758
    T instance = invoke(factory, args);
759
    if (instance == null) {
760
      throw new FactoryMethodReturnsNullException(factory);
761
    }
762
    return instance;
763
  }
764

765
  private static <T> @Nullable T invoke(Invokable<?, ? extends T> factory, List<?> args)
766
      throws InvocationTargetException, IllegalAccessException {
767
    T returnValue = factory.invoke(null, args.toArray());
768
    if (returnValue == null) {
769
      Assert.assertTrue(
770
          factory + " returns null but it's not annotated with @Nullable", isNullable(factory));
771
    }
772
    return returnValue;
773
  }
774

775
  /**
776
   * Thrown if the test tries to invoke a constructor or static factory method but failed because
777
   * the dummy value of a constructor or method parameter is unknown.
778
   */
779
  @VisibleForTesting
780
  static class ParameterNotInstantiableException extends Exception {
781
    public ParameterNotInstantiableException(Parameter parameter) {
782
      super(
783
          "Cannot determine value for parameter "
784
              + parameter
785
              + " of "
786
              + parameter.getDeclaringInvokable());
787
    }
788
  }
789

790
  /**
791
   * Thrown if the test fails to generate two distinct non-null values of a constructor or factory
792
   * parameter in order to test {@link Object#equals} and {@link Object#hashCode} of the declaring
793
   * class.
794
   */
795
  @VisibleForTesting
796
  static class ParameterHasNoDistinctValueException extends Exception {
797
    ParameterHasNoDistinctValueException(Parameter parameter) {
798
      super(
799
          "Cannot generate distinct value for parameter "
800
              + parameter
801
              + " of "
802
              + parameter.getDeclaringInvokable());
803
    }
804
  }
805

806
  /**
807
   * Thrown if the test tries to invoke a static factory method to test instance methods but the
808
   * factory returned null.
809
   */
810
  @VisibleForTesting
811
  static class FactoryMethodReturnsNullException extends Exception {
812
    public FactoryMethodReturnsNullException(Invokable<?, ?> factory) {
813
      super(factory + " returns null and cannot be used to test instance methods.");
814
    }
815
  }
816

817
  private static final class SerializableDummyProxy extends DummyProxy implements Serializable {
818

819
    private final transient ClassSanityTester tester;
820

821
    SerializableDummyProxy(ClassSanityTester tester) {
822
      this.tester = tester;
823
    }
824

825
    @Override
826
    <R> R dummyReturnValue(TypeToken<R> returnType) {
827
      return tester.getDummyValue(returnType);
828
    }
829

830
    @Override
831
    public boolean equals(@Nullable Object obj) {
832
      return obj instanceof SerializableDummyProxy;
833
    }
834

835
    @Override
836
    public int hashCode() {
837
      return 0;
838
    }
839
  }
840
}
841

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

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

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

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