17
package com.google.common.testing;
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;
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;
51
import java.util.Map.Entry;
53
import javax.annotation.CheckForNull;
54
import junit.framework.Assert;
55
import junit.framework.AssertionFailedError;
56
import org.checkerframework.checker.nullness.qual.Nullable;
84
public final class ClassSanityTester {
86
private static final Ordering<Invokable<?, ?>> BY_METHOD_NAME =
87
new Ordering<Invokable<?, ?>>() {
89
public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
90
return left.getName().compareTo(right.getName());
94
private static final Ordering<Invokable<?, ?>> BY_PARAMETERS =
95
new Ordering<Invokable<?, ?>>() {
97
public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
98
return Ordering.usingToString().compare(left.getParameters(), right.getParameters());
102
private static final Ordering<Invokable<?, ?>> BY_NUMBER_OF_PARAMETERS =
103
new Ordering<Invokable<?, ?>>() {
105
public int compare(Invokable<?, ?> left, Invokable<?, ?> right) {
106
return Integer.compare(left.getParameters().size(), right.getParameters().size());
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();
115
public ClassSanityTester() {
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);
137
@CanIgnoreReturnValue
138
public <T> ClassSanityTester setDefault(Class<T> type, T value) {
139
nullPointerTester.setDefault(type, value);
140
defaultValues.putInstance(type, value);
159
@CanIgnoreReturnValue
160
public <T> ClassSanityTester setDistinctValues(Class<T> type, T value1, T value2) {
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);
194
public void testNulls(Class<?> cls) {
196
doTestNulls(cls, Visibility.PACKAGE);
197
} catch (Exception e) {
199
throw new RuntimeException(e);
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);
209
nullPointerTester.testStaticMethods(cls, visibility);
210
if (hasInstanceMethodToTestNulls(cls, visibility)) {
211
Object instance = instantiate(cls);
212
if (instance != null) {
213
nullPointerTester.testInstanceMethods(instance, visibility);
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)) {
286
public void testEquals(Class<?> cls) {
289
} catch (Exception e) {
291
throw new RuntimeException(e);
295
void doTestEquals(Class<?> cls)
296
throws ParameterNotInstantiableException, ParameterHasNoDistinctValueException,
297
IllegalAccessException, InvocationTargetException, FactoryMethodReturnsNullException {
301
List<? extends Invokable<?, ?>> factories = Lists.reverse(getFactories(TypeToken.of(cls)));
302
if (factories.isEmpty()) {
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();
311
for (Invokable<?, ?> factory : factories) {
312
if (factory.getParameters().size() == numberOfParameters) {
314
testEqualsUsing(factory);
316
} catch (ParameterNotInstantiableException e) {
318
} catch (ParameterHasNoDistinctValueException e) {
319
distinctValueErrors.add(e);
320
} catch (InvocationTargetException e) {
321
instantiationExceptions.add(e);
322
} catch (FactoryMethodReturnsNullException e) {
327
throwFirst(paramErrors);
328
throwFirst(distinctValueErrors);
329
throwFirst(instantiationExceptions);
330
throwFirst(nullErrors);
340
<T> @Nullable T instantiate(Class<T> cls)
341
throws ParameterNotInstantiableException,
342
IllegalAccessException,
343
InvocationTargetException,
344
FactoryMethodReturnsNullException {
346
T[] constants = cls.getEnumConstants();
347
if (constants != null && constants.length > 0) {
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)) {
360
instance = instantiate(factory);
361
} catch (ParameterNotInstantiableException e) {
364
} catch (InvocationTargetException e) {
365
instantiationExceptions.add(e);
368
if (instance == null) {
369
nullErrors.add(new FactoryMethodReturnsNullException(factory));
374
throwFirst(paramErrors);
375
throwFirst(instantiationExceptions);
376
throwFirst(nullErrors);
390
private <T> @Nullable T instantiate(Invokable<?, ? extends T> factory)
391
throws ParameterNotInstantiableException, InvocationTargetException, IllegalAccessException {
392
return invoke(factory, getDummyArguments(factory));
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);
408
return new FactoryMethodReturnValueTester(cls, builder.build(), "public static methods");
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;
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));
435
@CanIgnoreReturnValue
436
public FactoryMethodReturnValueTester thatReturn(Class<?> returnType) {
437
this.returnTypeToTest = returnType;
449
@CanIgnoreReturnValue
450
public FactoryMethodReturnValueTester testNulls() throws Exception {
451
for (Invokable<?, ?> factory : getFactoriesToTest()) {
452
Object instance = instantiate(factory);
454
&& packagesToTest.contains(Reflection.getPackageName(instance.getClass()))) {
456
nullPointerTester.testAllPublicInstanceMethods(instance);
457
} catch (AssertionError e) {
458
throw new AssertionError("Null check failed on return value of " + factory, e);
475
@CanIgnoreReturnValue
476
public FactoryMethodReturnValueTester testEquals() throws Exception {
477
for (Invokable<?, ?> factory : getFactoriesToTest()) {
479
testEqualsUsing(factory);
480
} catch (FactoryMethodReturnsNullException e) {
495
@CanIgnoreReturnValue
496
@SuppressWarnings("CatchingUnchecked")
497
public FactoryMethodReturnValueTester testSerializable() throws Exception {
498
for (Invokable<?, ?> factory : getFactoriesToTest()) {
499
Object instance = instantiate(factory);
500
if (instance != null) {
502
SerializableTester.reserialize(instance);
503
} catch (Exception e) {
504
throw new AssertionError(
505
"Serialization failed on return value of " + factory, e.getCause());
520
@CanIgnoreReturnValue
521
@SuppressWarnings("CatchingUnchecked")
522
public FactoryMethodReturnValueTester testEqualsAndSerializable() throws Exception {
523
for (Invokable<?, ?> factory : getFactoriesToTest()) {
525
testEqualsUsing(factory);
526
} catch (FactoryMethodReturnsNullException e) {
529
Object instance = instantiate(factory);
530
if (instance != null) {
532
SerializableTester.reserializeAndAssert(instance);
533
} catch (Exception e) {
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);
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);
552
ImmutableList<Invokable<?, ?>> factoriesToTest = builder.build();
555
+ factoryMethodsDescription
557
+ returnTypeToTest.getName()
558
+ " or subtype are found in "
561
factoriesToTest.isEmpty());
562
return factoriesToTest;
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));
577
Object instance = createInstance(factory, args);
578
List<Object> equalArgs = generateEqualFactoryArguments(factory, params, args);
580
final List<List<List<Object>>> argGroups = Lists.newArrayList();
581
argGroups.add(ImmutableList.of(args, equalArgs));
582
EqualsTester tester =
586
String reportItem(Item<?> item) {
587
List<Object> factoryArgs = argGroups.get(item.groupNumber).get(item.itemNumber);
588
return factory.getName()
590
+ Joiner.on(", ").useForNull("null").join(factoryArgs)
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());
599
if (newArg == null || Objects.equal(args.get(i), newArg)) {
600
if (params.get(i).getType().getRawType().isEnum()) {
603
throw new ParameterHasNoDistinctValueException(params.get(i));
605
newArgs.set(i, newArg);
606
tester.addEqualityGroup(createInstance(factory, newArgs));
607
argGroups.add(ImmutableList.of(newArgs));
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);
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()))) {
635
equalArgs.set(i, shouldBeEqualArg);
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();
652
@SuppressWarnings({"unchecked", "rawtypes"})
653
private FreshValueGenerator newFreshValueGenerator() {
654
FreshValueGenerator generator =
655
new FreshValueGenerator() {
658
Object interfaceMethodCalled(Class<?> interfaceType, Method method) {
659
return getDummyValue(TypeToken.of(interfaceType).method(method).getReturnType());
662
for (Entry<Class<?>, Collection<Object>> entry : distinctValues.asMap().entrySet()) {
663
generator.addSampleInstances((Class) entry.getKey(), entry.getValue());
668
private static @Nullable Object generateDummyArg(Parameter param, FreshValueGenerator generator)
669
throws ParameterNotInstantiableException {
670
if (isNullable(param)) {
673
Object arg = generator.generateFresh(param.getType());
675
throw new ParameterNotInstantiableException(param);
680
private static <X extends Throwable> void throwFirst(List<X> exceptions) throws X {
681
if (!exceptions.isEmpty()) {
682
throw exceptions.get(0);
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")
696
Invokable<?, ? extends T> factory = (Invokable<?, ? extends T>) invokable;
697
factories.add(factory);
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);
708
for (Invokable<?, ?> factory : factories) {
709
factory.setAccessible(true);
714
return BY_NUMBER_OF_PARAMETERS
715
.compound(BY_METHOD_NAME)
716
.compound(BY_PARAMETERS)
717
.immutableSortedCopy(factories);
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)) {
728
Object defaultValue = getDummyValue(param.getType());
729
if (defaultValue == null) {
730
throw new ParameterNotInstantiableException(param);
732
args.add(defaultValue);
738
private <T> T getDummyValue(TypeToken<T> type) {
739
Class<? super T> rawType = type.getRawType();
740
@SuppressWarnings("unchecked")
741
T defaultValue = (T) defaultValues.getInstance(rawType);
742
if (defaultValue != null) {
745
@SuppressWarnings("unchecked")
746
T value = (T) ArbitraryInstances.get(rawType);
750
if (rawType.isInterface()) {
751
return new SerializableDummyProxy(this).newProxy(type);
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);
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) {
770
factory + " returns null but it's not annotated with @Nullable", isNullable(factory));
780
static class ParameterNotInstantiableException extends Exception {
781
public ParameterNotInstantiableException(Parameter parameter) {
783
"Cannot determine value for parameter "
786
+ parameter.getDeclaringInvokable());
796
static class ParameterHasNoDistinctValueException extends Exception {
797
ParameterHasNoDistinctValueException(Parameter parameter) {
799
"Cannot generate distinct value for parameter "
802
+ parameter.getDeclaringInvokable());
811
static class FactoryMethodReturnsNullException extends Exception {
812
public FactoryMethodReturnsNullException(Invokable<?, ?> factory) {
813
super(factory + " returns null and cannot be used to test instance methods.");
817
private static final class SerializableDummyProxy extends DummyProxy implements Serializable {
819
private final transient ClassSanityTester tester;
821
SerializableDummyProxy(ClassSanityTester tester) {
822
this.tester = tester;
826
<R> R dummyReturnValue(TypeToken<R> returnType) {
827
return tester.getDummyValue(returnType);
831
public boolean equals(@Nullable Object obj) {
832
return obj instanceof SerializableDummyProxy;
836
public int hashCode() {