guava

Форк
0
/
ClassSanityTester.java 
841 строка · 32.7 Кб
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.primitives.Ints;
37
import com.google.common.reflect.Invokable;
38
import com.google.common.reflect.Parameter;
39
import com.google.common.reflect.Reflection;
40
import com.google.common.reflect.TypeToken;
41
import com.google.common.testing.NullPointerTester.Visibility;
42
import com.google.common.testing.RelationshipTester.Item;
43
import com.google.common.testing.RelationshipTester.ItemReporter;
44
import com.google.errorprone.annotations.CanIgnoreReturnValue;
45
import java.io.Serializable;
46
import java.lang.reflect.Constructor;
47
import java.lang.reflect.InvocationTargetException;
48
import java.lang.reflect.Method;
49
import java.lang.reflect.Modifier;
50
import java.util.Collection;
51
import java.util.List;
52
import java.util.Map.Entry;
53
import java.util.Set;
54
import javax.annotation.CheckForNull;
55
import junit.framework.Assert;
56
import junit.framework.AssertionFailedError;
57
import org.checkerframework.checker.nullness.qual.Nullable;
58

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

820
    private final transient ClassSanityTester tester;
821

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

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

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

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

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

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

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

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