/* * Copyright (C) 2012 The Guava Authors * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.google.common.testing; import static com.google.common.base.Preconditions.checkNotNull; import static org.truth0.Truth.ASSERT; import com.google.common.base.Functions; import com.google.common.collect.ImmutableList; import com.google.common.testing.ClassSanityTester.FactoryMethodReturnsNullException; import com.google.common.testing.ClassSanityTester.ParameterNotInstantiableException; import com.google.common.testing.NullPointerTester.Visibility; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import java.io.Serializable; import java.lang.reflect.InvocationTargetException; import java.util.AbstractList; import java.util.ArrayList; import java.util.concurrent.TimeUnit; import javax.annotation.Nullable; /** * Unit tests for {@link ClassSanityTester}. * * @author Ben Yu */ public class ClassSanityTesterTest extends TestCase { private final ClassSanityTester tester = new ClassSanityTester(); public void testEqualsOnReturnValues_good() throws Exception { tester.forAllPublicStaticMethods(GoodEqualsFactory.class).testEquals(); } public static class GoodEqualsFactory { public static Object good(String a, int b, // oneConstantOnly doesn't matter since it's not nullable and can be only 1 value. @SuppressWarnings("unused") OneConstantEnum oneConstantOnly, // noConstant doesn't matter since it can only be null @SuppressWarnings("unused") @Nullable NoConstantEnum noConstant) { return new GoodEquals(a, b); } // instance method ignored public Object badIgnored() { return new BadEquals(); } // primitive ignored public int returnsInt() { throw new UnsupportedOperationException(); } // void ignored public void voidMethod() { throw new UnsupportedOperationException(); } // non-public method ignored static Object badButNotPublic() { return new BadEquals(); } } public void testEqualsOnReturnValues_bad() throws Exception { try { tester.forAllPublicStaticMethods(BadEqualsFactory.class).testEquals(); } catch (AssertionFailedError expected) { return; } fail(); } public static class BadEqualsFactory { /** oneConstantOnly matters now since it can be either null or the constant. */ public static Object bad(String a, int b, @SuppressWarnings("unused") @Nullable OneConstantEnum oneConstantOnly) { return new GoodEquals(a, b); } } public void testNullsOnReturnValues_good() throws Exception { tester.forAllPublicStaticMethods(GoodNullsFactory.class).testNulls(); } public static class GoodNullsFactory { public static Object good(String s) { return new GoodNulls(s); } } public void testNullsOnReturnValues_bad() throws Exception { try { tester .forAllPublicStaticMethods(BadNullsFactory.class) .thatReturn(Object.class) .testNulls(); } catch (AssertionFailedError expected) { return; } fail(); } public void testNullsOnReturnValues_returnTypeFiltered() throws Exception { tester .forAllPublicStaticMethods(BadNullsFactory.class) .thatReturn(Iterable.class) .testNulls(); } public static class BadNullsFactory { public static Object bad(@SuppressWarnings("unused") String a) { return new BadNulls(); } } public void testSerializableOnReturnValues_good() throws Exception { tester.forAllPublicStaticMethods(GoodSerializableFactory.class).testSerializable(); } public static class GoodSerializableFactory { public static Object good(Runnable r) { return r; } public static Object good(AnInterface i) { return i; } } public void testSerializableOnReturnValues_bad() throws Exception { try { tester.forAllPublicStaticMethods(BadSerializableFactory.class).testSerializable(); } catch (AssertionError expected) { return; } fail(); } public static class BadSerializableFactory { public static Object bad() { return new Serializable() { @SuppressWarnings("unused") private final Object notSerializable = new Object(); }; } } public void testEqualsAndSerializableOnReturnValues_equalsIsGoodButNotSerializable() throws Exception { try { tester.forAllPublicStaticMethods(GoodEqualsFactory.class).testEqualsAndSerializable(); } catch (AssertionError expected) { return; } fail("should have failed"); } public void testEqualsAndSerializableOnReturnValues_serializableButNotEquals() throws Exception { try { tester.forAllPublicStaticMethods(GoodSerializableFactory.class).testEqualsAndSerializable(); } catch (AssertionFailedError expected) { return; } fail("should have failed"); } public void testEqualsAndSerializableOnReturnValues_good() throws Exception { tester.forAllPublicStaticMethods(GoodEqualsAndSerialiableFactory.class) .testEqualsAndSerializable(); } public static class GoodEqualsAndSerialiableFactory { public static Object good(AnInterface s) { return Functions.constant(s); } } public void testEqualsForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { try { tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class) .testEquals(); } catch (AssertionFailedError expected) { return; } fail(); } public void testNullsForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { try { tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class) .testNulls(); } catch (AssertionFailedError expected) { return; } fail(); } public void testSerializableForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { try { tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class) .testSerializable(); } catch (AssertionFailedError expected) { return; } fail(); } public void testEqualsAndSerializableForReturnValues_factoryReturnsNullButNotAnnotated() throws Exception { try { tester.forAllPublicStaticMethods(FactoryThatReturnsNullButNotAnnotated.class) .testEqualsAndSerializable(); } catch (AssertionFailedError expected) { return; } fail(); } public static class FactoryThatReturnsNullButNotAnnotated { public static Object bad() { return null; } } public void testEqualsForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class) .testEquals(); } public void testNullsForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class) .testNulls(); } public void testSerializableForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class) .testSerializable(); } public void testEqualsAndSerializableForReturnValues_factoryReturnsNullAndAnnotated() throws Exception { tester.forAllPublicStaticMethods(FactoryThatReturnsNullAndAnnotated.class) .testEqualsAndSerializable(); } public static class FactoryThatReturnsNullAndAnnotated { @Nullable public static Object bad() { return null; } } public void testGoodEquals() throws Exception { tester.testEquals(GoodEquals.class); } public void testEquals_interface() { tester.testEquals(AnInterface.class); } public void testEquals_abstractClass() { tester.testEquals(AnAbstractClass.class); } public void testEquals_enum() { tester.testEquals(OneConstantEnum.class); } public void testBadEquals() throws Exception { try { tester.testEquals(BadEquals.class); } catch (AssertionFailedError expected) { ASSERT.that(expected.getMessage()).contains("create(null)"); return; } fail("should have failed"); } public void testBadEquals_withParameterizedType() throws Exception { try { tester.testEquals(BadEqualsWithParameterizedType.class); } catch (AssertionFailedError expected) { ASSERT.that(expected.getMessage()).contains("create([[1]])"); return; } fail("should have failed"); } public void testParameterNotInstantiableForEqualsTest() throws Exception { try { tester.doTestEquals(ConstructorParameterNotInstantiable.class); fail("should have failed"); } catch (ParameterNotInstantiableException expected) {} } public void testConstructorThrowsForEqualsTest() throws Exception { try { tester.doTestEquals(ConstructorThrows.class); fail("should have failed"); } catch (InvocationTargetException expected) {} } public void testFactoryMethodReturnsNullForEqualsTest() throws Exception { try { tester.doTestEquals(FactoryMethodReturnsNullAndAnnotated.class); fail("should have failed"); } catch (FactoryMethodReturnsNullException expected) {} } public void testFactoryMethodReturnsNullButNotAnnotatedInEqualsTest() throws Exception { try { tester.testEquals(FactoryMethodReturnsNullButNotAnnotated.class); } catch (AssertionFailedError expected) { return; } fail("should have failed"); } public void testNoEqualsChecksOnEnum() throws Exception { tester.testEquals(OneConstantEnum.class); tester.testEquals(NoConstantEnum.class); tester.testEquals(TimeUnit.class); } public void testNoEqualsChecksOnInterface() throws Exception { tester.testEquals(Runnable.class); } public void testNoEqualsChecksOnAnnotation() throws Exception { tester.testEquals(Nullable.class); } public void testGoodNulls() throws Exception { tester.testNulls(GoodNulls.class); } public void testNoNullCheckNeededDespitNotInstantiable() throws Exception { tester.doTestNulls(NoNullCheckNeededDespitNotInstantiable.class, Visibility.PACKAGE); } public void testNulls_interface() { tester.testNulls(AnInterface.class); } public void testNulls_abstractClass() { tester.testNulls(AnAbstractClass.class); } public void testNulls_enum() throws Exception { tester.testNulls(OneConstantEnum.class); tester.testNulls(NoConstantEnum.class); tester.testNulls(TimeUnit.class); } public void testEnumFailsToCheckNull() throws Exception { try { tester.testNulls(EnumFailsToCheckNull.class); } catch (AssertionFailedError expected) { return; } fail("should have failed"); } public void testNoNullChecksOnInterface() throws Exception { tester.testNulls(Runnable.class); } public void testNoNullChecksOnAnnotation() throws Exception { tester.testNulls(Nullable.class); } public void testBadNulls() throws Exception { try { tester.testNulls(BadNulls.class); } catch (AssertionFailedError expected) { return; } fail("should have failed"); } public void testInstantiate_factoryMethodReturnsNullButNotAnnotated() throws Exception { try { tester.instantiate(FactoryMethodReturnsNullButNotAnnotated.class); } catch (AssertionFailedError expected) { ASSERT.that(expected.getMessage()).contains("@Nullable"); return; } fail("should have failed"); } public void testInstantiate_factoryMethodReturnsNullAndAnnotated() throws Exception { try { tester.instantiate(FactoryMethodReturnsNullAndAnnotated.class); fail("should have failed"); } catch (FactoryMethodReturnsNullException expected) {} } public void testInstantiate_factoryMethodAcceptsNull() throws Exception { assertNull(tester.instantiate(FactoryMethodAcceptsNull.class).name); } public void testInstantiate_factoryMethodDoesNotAcceptNull() throws Exception { assertNotNull(tester.instantiate(FactoryMethodDoesNotAcceptNull.class).name); } public void testInstantiate_constructorAcceptsNull() throws Exception { assertNull(tester.instantiate(ConstructorAcceptsNull.class).name); } public void testInstantiate_constructorDoesNotAcceptNull() throws Exception { assertNotNull(tester.instantiate(ConstructorDoesNotAcceptNull.class).name); } public void testInstantiate_notInstantiable() throws Exception { assertNull(tester.instantiate(NotInstantiable.class)); } public void testInstantiate_noConstantEnum() throws Exception { assertNull(tester.instantiate(NoConstantEnum.class)); } public void testInstantiate_oneConstantEnum() throws Exception { assertEquals(OneConstantEnum.A, tester.instantiate(OneConstantEnum.class)); } public void testInstantiate_interface() throws Exception { assertNull(tester.instantiate(Runnable.class)); } public void testInstantiate_abstractClass() throws Exception { assertNull(tester.instantiate(AbstractList.class)); } public void testInstantiate_annotation() throws Exception { assertNull(tester.instantiate(Nullable.class)); } public void testInstantiate_setDefault() throws Exception { NotInstantiable x = new NotInstantiable(); tester.setDefault(NotInstantiable.class, x); assertNotNull(tester.instantiate(ConstructorParameterNotInstantiable.class)); } public void testInstantiate_setSampleInstances() throws Exception { NotInstantiable x = new NotInstantiable(); tester.setSampleInstances(NotInstantiable.class, ImmutableList.of(x)); assertNotNull(tester.instantiate(ConstructorParameterNotInstantiable.class)); } public void testInstantiate_setSampleInstances_empty() throws Exception { tester.setSampleInstances(NotInstantiable.class, ImmutableList.<NotInstantiable>of()); try { tester.instantiate(ConstructorParameterNotInstantiable.class); fail(); } catch (ParameterNotInstantiableException expected) {} } public void testInstantiate_constructorThrows() throws Exception { try { tester.instantiate(ConstructorThrows.class); fail(); } catch (InvocationTargetException expected) {} } public void testInstantiate_factoryMethodThrows() throws Exception { try { tester.instantiate(FactoryMethodThrows.class); fail(); } catch (InvocationTargetException expected) {} } public void testInstantiate_constructorParameterNotInstantiable() throws Exception { try { tester.instantiate(ConstructorParameterNotInstantiable.class); fail(); } catch (ParameterNotInstantiableException expected) {} } public void testInstantiate_factoryMethodParameterNotInstantiable() throws Exception { try { tester.instantiate(FactoryMethodParameterNotInstantiable.class); fail(); } catch (ParameterNotInstantiableException expected) {} } public void testInstantiate_instantiableFactoryMethodChosen() throws Exception { assertEquals("good", tester.instantiate(InstantiableFactoryMethodChosen.class).name); } public void testInterfaceProxySerializable() throws Exception { SerializableTester.reserializeAndAssert(tester.instantiate(HasAnInterface.class)); } public void testReturnValuesFromAnotherPackageIgnoredForNullTests() throws Exception { new ClassSanityTester().forAllPublicStaticMethods(JdkObjectFactory.class).testNulls(); } /** String doesn't check nulls as we expect. But the framework should ignore. */ public static class JdkObjectFactory { public static Object create() { return new ArrayList<String>(); } } static class HasAnInterface implements Serializable { private final AnInterface i; public HasAnInterface(AnInterface i) { this.i = i; } @Override public boolean equals(@Nullable Object obj) { if (obj instanceof HasAnInterface) { HasAnInterface that = (HasAnInterface) obj; return i.equals(that.i); } else { return false; } } @Override public int hashCode() { return i.hashCode(); } } static class InstantiableFactoryMethodChosen { final String name; private InstantiableFactoryMethodChosen(String name) { this.name = name; } public InstantiableFactoryMethodChosen(NotInstantiable x) { checkNotNull(x); this.name = "x1"; } public static InstantiableFactoryMethodChosen create(NotInstantiable x) { return new InstantiableFactoryMethodChosen(x); } public static InstantiableFactoryMethodChosen create(String s) { checkNotNull(s); return new InstantiableFactoryMethodChosen("good"); } } public void testInstantiate_instantiableConstructorChosen() throws Exception { assertEquals("good", tester.instantiate(InstantiableConstructorChosen.class).name); } static class InstantiableConstructorChosen { final String name; public InstantiableConstructorChosen(String name) { checkNotNull(name); this.name = "good"; } public InstantiableConstructorChosen(NotInstantiable x) { checkNotNull(x); this.name = "x1"; } public static InstantiableFactoryMethodChosen create(NotInstantiable x) { return new InstantiableFactoryMethodChosen(x); } } static class GoodEquals { private final String a; private final int b; private GoodEquals(String a, int b) { this.a = checkNotNull(a); this.b = b; } // ignored by testEquals() GoodEquals(@SuppressWarnings("unused") NotInstantiable x) { this.a = "x"; this.b = -1; } // will keep trying public GoodEquals(@SuppressWarnings("unused") NotInstantiable x, int b) { this.a = "x"; this.b = b; } // keep trying @SuppressWarnings("unused") static GoodEquals create(int a, int b) { throw new RuntimeException(); } // keep trying @SuppressWarnings("unused") @Nullable public static GoodEquals createMayReturnNull(int a, int b) { return null; } // Good! static GoodEquals create(String a, int b) { return new GoodEquals(a, b); } @Override public boolean equals(@Nullable Object obj) { if (obj instanceof GoodEquals) { GoodEquals that = (GoodEquals) obj; return a.equals(that.a) && b == that.b; } else { return false; } } @Override public int hashCode() { return 0; } } static class BadEquals { public BadEquals() {} // ignored by testEquals() since it has less parameters. public static BadEquals create(@SuppressWarnings("unused") @Nullable String s) { return new BadEquals(); } @Override public boolean equals(@Nullable Object obj) { return obj instanceof BadEquals; } @Override public int hashCode() { return 0; } } static class BadEqualsWithParameterizedType { // ignored by testEquals() since it has less parameters. public BadEqualsWithParameterizedType() {} public static BadEqualsWithParameterizedType create( @SuppressWarnings("unused") ImmutableList<Iterable<? extends String>> s) { return new BadEqualsWithParameterizedType(); } @Override public boolean equals(@Nullable Object obj) { return obj instanceof BadEqualsWithParameterizedType; } @Override public int hashCode() { return 0; } } static class GoodNulls { public GoodNulls(String s) { checkNotNull(s); } public void rejectNull(String s) { checkNotNull(s); } } public static class BadNulls { public void failsToRejectNull(@SuppressWarnings("unused") String s) {} } public static class NoNullCheckNeededDespitNotInstantiable { public NoNullCheckNeededDespitNotInstantiable(NotInstantiable x) { checkNotNull(x); } @SuppressWarnings("unused") // reflected void primitiveOnly(int i) {} @SuppressWarnings("unused") //reflected void nullableOnly(@Nullable String s) {} public void noParameter() {} @SuppressWarnings("unused") //reflected void primitiveAndNullable(@Nullable String s, int i) {} } static class FactoryMethodReturnsNullButNotAnnotated { private FactoryMethodReturnsNullButNotAnnotated() {} static FactoryMethodReturnsNullButNotAnnotated returnsNull() { return null; } } static class FactoryMethodReturnsNullAndAnnotated { private FactoryMethodReturnsNullAndAnnotated() {} @Nullable public static FactoryMethodReturnsNullAndAnnotated returnsNull() { return null; } } static class FactoryMethodAcceptsNull { final String name; private FactoryMethodAcceptsNull(String name) { this.name = name; } static FactoryMethodAcceptsNull create(@Nullable String name) { return new FactoryMethodAcceptsNull(name); } } static class FactoryMethodDoesNotAcceptNull { final String name; private FactoryMethodDoesNotAcceptNull(String name) { this.name = checkNotNull(name); } public static FactoryMethodDoesNotAcceptNull create(String name) { return new FactoryMethodDoesNotAcceptNull(name); } } static class ConstructorAcceptsNull { final String name; public ConstructorAcceptsNull(@Nullable String name) { this.name = name; } } static class ConstructorDoesNotAcceptNull { final String name; ConstructorDoesNotAcceptNull(String name) { this.name = checkNotNull(name); } } static class ConstructorParameterNotInstantiable { public ConstructorParameterNotInstantiable(@SuppressWarnings("unused") NotInstantiable x) {} } static class FactoryMethodParameterNotInstantiable { private FactoryMethodParameterNotInstantiable() {} static FactoryMethodParameterNotInstantiable create( @SuppressWarnings("unused") NotInstantiable x) { return new FactoryMethodParameterNotInstantiable(); } } static class ConstructorThrows { public ConstructorThrows() { throw new RuntimeException(); } } static class FactoryMethodThrows { private FactoryMethodThrows() {} public static FactoryMethodThrows create() { throw new RuntimeException(); } } static class NotInstantiable { private NotInstantiable() {} } private enum NoConstantEnum {} private enum OneConstantEnum { A } private enum EnumFailsToCheckNull { A; @SuppressWarnings("unused") public void failToCheckNull(String s) {} } private interface AnInterface {} private static abstract class AnAbstractClass { @SuppressWarnings("unused") public AnAbstractClass(String s) {} @SuppressWarnings("unused") public void failsToCheckNull(String s) {} } }