/* * Copyright (C) 2005 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.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Converter; import com.google.common.base.Function; import com.google.common.base.Supplier; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.ImmutableMultiset; import com.google.common.collect.ImmutableSet; import com.google.common.collect.ImmutableSortedSet; import com.google.common.collect.ImmutableTable; import com.google.common.collect.Maps; import com.google.common.collect.Multimap; import com.google.common.collect.Multiset; import com.google.common.collect.Table; import com.google.common.reflect.TypeToken; import com.google.common.testing.NullPointerTester.Visibility; import com.google.common.testing.anotherpackage.SomeClassThatDoesNotUseNullable; import java.lang.reflect.Constructor; import java.lang.reflect.Method; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Set; import java.util.SortedSet; import junit.framework.AssertionFailedError; import junit.framework.TestCase; import org.checkerframework.checker.nullness.qual.Nullable; /** * Unit test for {@link NullPointerTester}. * * @author Kevin Bourrillion * @author Mick Killianey */ @SuppressWarnings("CheckReturnValue") public class NullPointerTesterTest extends TestCase { /** Non-NPE RuntimeException. */ public static class FooException extends RuntimeException { private static final long serialVersionUID = 1L; } /** * Class for testing all permutations of static/non-static one-argument methods using * methodParameter(). */ @SuppressWarnings("unused") // used by reflection public static class OneArg { public static void staticOneArgCorrectlyThrowsNpe(String s) { checkNotNull(s); // expect NPE here on null } public static void staticOneArgThrowsOtherThanNpe(String s) { throw new FooException(); // should catch as failure } public static void staticOneArgShouldThrowNpeButDoesnt(String s) { // should catch as failure } public static void staticOneArgCheckForNullCorrectlyDoesNotThrowNPE( @javax.annotation.CheckForNull String s) { // null? no problem } public static void staticOneArgJsr305NullableCorrectlyDoesNotThrowNPE( @javax.annotation.Nullable String s) { // null? no problem } public static void staticOneArgNullableCorrectlyDoesNotThrowNPE(@Nullable String s) { // null? no problem } public static void staticOneArgCheckForNullCorrectlyThrowsOtherThanNPE( @javax.annotation.CheckForNull String s) { throw new FooException(); // ok, as long as it's not NullPointerException } public static void staticOneArgNullableCorrectlyThrowsOtherThanNPE(@Nullable String s) { throw new FooException(); // ok, as long as it's not NullPointerException } public static void staticOneArgCheckForNullThrowsNPE(@javax.annotation.CheckForNull String s) { checkNotNull(s); // doesn't check if you said you'd accept null, but you don't } public static void staticOneArgNullableThrowsNPE(@Nullable String s) { checkNotNull(s); // doesn't check if you said you'd accept null, but you don't } public void oneArgCorrectlyThrowsNpe(String s) { checkNotNull(s); // expect NPE here on null } public void oneArgThrowsOtherThanNpe(String s) { throw new FooException(); // should catch as failure } public void oneArgShouldThrowNpeButDoesnt(String s) { // should catch as failure } public void oneArgCheckForNullCorrectlyDoesNotThrowNPE( @javax.annotation.CheckForNull String s) { // null? no problem } public void oneArgNullableCorrectlyDoesNotThrowNPE(@Nullable String s) { // null? no problem } public void oneArgCheckForNullCorrectlyThrowsOtherThanNPE( @javax.annotation.CheckForNull String s) { throw new FooException(); // ok, as long as it's not NullPointerException } public void oneArgNullableCorrectlyThrowsOtherThanNPE(@Nullable String s) { throw new FooException(); // ok, as long as it's not NullPointerException } public void oneArgCheckForNullThrowsNPE(@javax.annotation.CheckForNull String s) { checkNotNull(s); // doesn't check if you said you'd accept null, but you don't } public void oneArgNullableThrowsNPE(@Nullable String s) { checkNotNull(s); // doesn't check if you said you'd accept null, but you don't } } private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_PASS = { "staticOneArgCorrectlyThrowsNpe", "staticOneArgCheckForNullCorrectlyDoesNotThrowNPE", "staticOneArgCheckForNullCorrectlyThrowsOtherThanNPE", "staticOneArgCheckForNullThrowsNPE", "staticOneArgNullableCorrectlyDoesNotThrowNPE", "staticOneArgNullableCorrectlyThrowsOtherThanNPE", "staticOneArgNullableThrowsNPE", }; private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_FAIL = { "staticOneArgThrowsOtherThanNpe", "staticOneArgShouldThrowNpeButDoesnt", }; private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS = { "oneArgCorrectlyThrowsNpe", "oneArgCheckForNullCorrectlyDoesNotThrowNPE", "oneArgCheckForNullCorrectlyThrowsOtherThanNPE", "oneArgCheckForNullThrowsNPE", "oneArgNullableCorrectlyDoesNotThrowNPE", "oneArgNullableCorrectlyThrowsOtherThanNPE", "oneArgNullableThrowsNPE", }; private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL = { "oneArgThrowsOtherThanNpe", "oneArgShouldThrowNpeButDoesnt", }; private static class ThrowsIae { public static void christenPoodle(String name) { checkArgument(name != null); } } private static class ThrowsNpe { public static void christenPoodle(String name) { checkNotNull(name); } } private static class ThrowsUoe { public static void christenPoodle(String name) { throw new UnsupportedOperationException(); } } private static class ThrowsSomethingElse { public static void christenPoodle(String name) { throw new RuntimeException(); } } private interface InterfaceStaticMethodFailsToCheckNull { static String create(String s) { return "I don't check"; } } private interface InterfaceStaticMethodChecksNull { static String create(String s) { return checkNotNull(s); } } private interface InterfaceDefaultMethodFailsToCheckNull { static InterfaceDefaultMethodFailsToCheckNull create() { return new InterfaceDefaultMethodFailsToCheckNull() {}; } default void doNotCheckNull(String s) {} } private interface InterfaceDefaultMethodChecksNull { static InterfaceDefaultMethodChecksNull create() { return new InterfaceDefaultMethodChecksNull() {}; } default void checksNull(String s) { checkNotNull(s); } } public void testInterfaceStaticMethod() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(InterfaceStaticMethodChecksNull.class); try { tester.testAllPublicStaticMethods(InterfaceStaticMethodFailsToCheckNull.class); } catch (AssertionError expected) { return; } fail(); } public void testInterfaceDefaultMethod() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicInstanceMethods(InterfaceDefaultMethodChecksNull.create()); try { tester.testAllPublicInstanceMethods(InterfaceDefaultMethodFailsToCheckNull.create()); } catch (AssertionError expected) { return; } fail(); } public void testDontAcceptIae() { NullPointerTester tester = new NullPointerTester(); tester.testAllPublicStaticMethods(ThrowsNpe.class); tester.testAllPublicStaticMethods(ThrowsUoe.class); try { tester.testAllPublicStaticMethods(ThrowsIae.class); } catch (AssertionFailedError expected) { return; } fail(); } public void testStaticOneArgMethodsThatShouldPass() throws Exception { for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_PASS) { Method method = OneArg.class.getMethod(methodName, String.class); try { new NullPointerTester().testMethodParameter(new OneArg(), method, 0); } catch (AssertionFailedError unexpected) { fail("Should not have flagged method " + methodName); } } } public void testStaticOneArgMethodsThatShouldFail() throws Exception { for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_FAIL) { Method method = OneArg.class.getMethod(methodName, String.class); boolean foundProblem = false; try { new NullPointerTester().testMethodParameter(new OneArg(), method, 0); } catch (AssertionFailedError expected) { foundProblem = true; } assertTrue("Should report error in method " + methodName, foundProblem); } } public void testNonStaticOneArgMethodsThatShouldPass() throws Exception { OneArg foo = new OneArg(); for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS) { Method method = OneArg.class.getMethod(methodName, String.class); try { new NullPointerTester().testMethodParameter(foo, method, 0); } catch (AssertionFailedError unexpected) { fail("Should not have flagged method " + methodName); } } } public void testNonStaticOneArgMethodsThatShouldFail() throws Exception { OneArg foo = new OneArg(); for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL) { Method method = OneArg.class.getMethod(methodName, String.class); boolean foundProblem = false; try { new NullPointerTester().testMethodParameter(foo, method, 0); } catch (AssertionFailedError expected) { foundProblem = true; } assertTrue("Should report error in method " + methodName, foundProblem); } } public void testMessageOtherException() throws Exception { Method method = OneArg.class.getMethod("staticOneArgThrowsOtherThanNpe", String.class); boolean foundProblem = false; try { new NullPointerTester().testMethodParameter(new OneArg(), method, 0); } catch (AssertionFailedError expected) { assertThat(expected.getMessage()).contains("index 0"); assertThat(expected.getMessage()).contains("[null]"); foundProblem = true; } assertTrue("Should report error when different exception is thrown", foundProblem); } public void testMessageNoException() throws Exception { Method method = OneArg.class.getMethod("staticOneArgShouldThrowNpeButDoesnt", String.class); boolean foundProblem = false; try { new NullPointerTester().testMethodParameter(new OneArg(), method, 0); } catch (AssertionFailedError expected) { assertThat(expected.getMessage()).contains("index 0"); assertThat(expected.getMessage()).contains("[null]"); foundProblem = true; } assertTrue("Should report error when no exception is thrown", foundProblem); } /** * Class for testing all permutations of nullable/non-nullable two-argument methods using * testMethod(). * * */ public static class TwoArg { /** Action to take on a null param. */ public enum Action { THROW_A_NPE { @Override public void act() { throw new NullPointerException(); } }, THROW_OTHER { @Override public void act() { throw new FooException(); } }, JUST_RETURN { @Override public void act() {} }; public abstract void act(); } Action actionWhenFirstParamIsNull; Action actionWhenSecondParamIsNull; public TwoArg(Action actionWhenFirstParamIsNull, Action actionWhenSecondParamIsNull) { this.actionWhenFirstParamIsNull = actionWhenFirstParamIsNull; this.actionWhenSecondParamIsNull = actionWhenSecondParamIsNull; } /** Method that decides how to react to parameters. */ public void reactToNullParameters(Object first, Object second) { if (first == null) { actionWhenFirstParamIsNull.act(); } if (second == null) { actionWhenSecondParamIsNull.act(); } } /** Two-arg method with no Nullable params. */ @SuppressWarnings("GoodTime") // false positive; b/122617528 public void normalNormal(String first, Integer second) { reactToNullParameters(first, second); } /** Two-arg method with the second param Nullable. */ @SuppressWarnings("GoodTime") // false positive; b/122617528 public void normalNullable(String first, @Nullable Integer second) { reactToNullParameters(first, second); } /** Two-arg method with the first param Nullable. */ @SuppressWarnings("GoodTime") // false positive; b/122617528 public void nullableNormal(@Nullable String first, Integer second) { reactToNullParameters(first, second); } /** Two-arg method with the both params Nullable. */ @SuppressWarnings("GoodTime") // false positive; b/122617528 public void nullableNullable(@Nullable String first, @Nullable Integer second) { reactToNullParameters(first, second); } /** To provide sanity during debugging. */ @Override public String toString() { return rootLocaleFormat( "Bar(%s, %s)", actionWhenFirstParamIsNull, actionWhenSecondParamIsNull); } } public void verifyBarPass(Method method, TwoArg bar) { try { new NullPointerTester().testMethod(bar, method); } catch (AssertionFailedError incorrectError) { String errorMessage = rootLocaleFormat("Should not have flagged method %s for %s", method.getName(), bar); assertNull(errorMessage, incorrectError); } } public void verifyBarFail(Method method, TwoArg bar) { try { new NullPointerTester().testMethod(bar, method); } catch (AssertionFailedError expected) { return; // good...we wanted a failure } String errorMessage = rootLocaleFormat("Should have flagged method %s for %s", method.getName(), bar); fail(errorMessage); } public void testTwoArgNormalNormal() throws Exception { Method method = TwoArg.class.getMethod("normalNormal", String.class, Integer.class); for (TwoArg.Action first : TwoArg.Action.values()) { for (TwoArg.Action second : TwoArg.Action.values()) { TwoArg bar = new TwoArg(first, second); if (first.equals(TwoArg.Action.THROW_A_NPE) && second.equals(TwoArg.Action.THROW_A_NPE)) { verifyBarPass(method, bar); // require both params to throw NPE } else { verifyBarFail(method, bar); } } } } public void testTwoArgNormalNullable() throws Exception { Method method = TwoArg.class.getMethod("normalNullable", String.class, Integer.class); for (TwoArg.Action first : TwoArg.Action.values()) { for (TwoArg.Action second : TwoArg.Action.values()) { TwoArg bar = new TwoArg(first, second); if (first.equals(TwoArg.Action.THROW_A_NPE)) { verifyBarPass(method, bar); // only pass if 1st param throws NPE } else { verifyBarFail(method, bar); } } } } public void testTwoArgNullableNormal() throws Exception { Method method = TwoArg.class.getMethod("nullableNormal", String.class, Integer.class); for (TwoArg.Action first : TwoArg.Action.values()) { for (TwoArg.Action second : TwoArg.Action.values()) { TwoArg bar = new TwoArg(first, second); if (second.equals(TwoArg.Action.THROW_A_NPE)) { verifyBarPass(method, bar); // only pass if 2nd param throws NPE } else { verifyBarFail(method, bar); } } } } public void testTwoArgNullableNullable() throws Exception { Method method = TwoArg.class.getMethod("nullableNullable", String.class, Integer.class); for (TwoArg.Action first : TwoArg.Action.values()) { for (TwoArg.Action second : TwoArg.Action.values()) { TwoArg bar = new TwoArg(first, second); verifyBarPass(method, bar); // All args nullable: anything goes! } } } /* * This next part consists of several sample classes that provide * demonstrations of conditions that cause NullPointerTester * to succeed/fail. */ /** Lots of well-behaved methods. */ @SuppressWarnings("unused") // used by reflection private static class PassObject extends SomeClassThatDoesNotUseNullable { public static void doThrow(Object arg) { if (arg == null) { throw new FooException(); } } public void noArg() {} public void oneArg(String s) { checkNotNull(s); } void packagePrivateOneArg(String s) { checkNotNull(s); } protected void protectedOneArg(String s) { checkNotNull(s); } public void oneNullableArg(@Nullable String s) {} public void oneNullableArgThrows(@Nullable String s) { doThrow(s); } public void twoArg(String s, Integer i) { checkNotNull(s); i.intValue(); } public void twoMixedArgs(String s, @Nullable Integer i) { checkNotNull(s); } public void twoMixedArgs(@Nullable Integer i, String s) { checkNotNull(s); } public void twoMixedArgsThrows(String s, @Nullable Integer i) { checkNotNull(s); doThrow(i); } public void twoMixedArgsThrows(@Nullable Integer i, String s) { checkNotNull(s); doThrow(i); } public void twoNullableArgs(@Nullable String s, @javax.annotation.Nullable Integer i) {} public void twoNullableArgsThrowsFirstArg(@Nullable String s, @Nullable Integer i) { doThrow(s); } public void twoNullableArgsThrowsSecondArg(@Nullable String s, @Nullable Integer i) { doThrow(i); } public static void staticOneArg(String s) { checkNotNull(s); } public static void staticOneNullableArg(@Nullable String s) {} public static void staticOneNullableArgThrows(@Nullable String s) { doThrow(s); } } public void testGoodClass() { shouldPass(new PassObject()); } private static class FailOneArgDoesntThrowNPE extends PassObject { @Override public void oneArg(String s) { // Fail: missing NPE for s } } public void testFailOneArgDoesntThrowNpe() { shouldFail(new FailOneArgDoesntThrowNPE()); } private static class FailOneArgThrowsWrongType extends PassObject { @Override public void oneArg(String s) { doThrow(s); // Fail: throwing non-NPE exception for null s } } public void testFailOneArgThrowsWrongType() { shouldFail(new FailOneArgThrowsWrongType()); } private static class PassOneNullableArgThrowsNPE extends PassObject { @Override public void oneNullableArg(@Nullable String s) { checkNotNull(s); // ok to throw NPE } } public void testPassOneNullableArgThrowsNPE() { shouldPass(new PassOneNullableArgThrowsNPE()); } private static class FailTwoArgsFirstArgDoesntThrowNPE extends PassObject { @Override public void twoArg(String s, Integer i) { // Fail: missing NPE for s i.intValue(); } } public void testFailTwoArgsFirstArgDoesntThrowNPE() { shouldFail(new FailTwoArgsFirstArgDoesntThrowNPE()); } private static class FailTwoArgsFirstArgThrowsWrongType extends PassObject { @Override public void twoArg(String s, Integer i) { doThrow(s); // Fail: throwing non-NPE exception for null s i.intValue(); } } public void testFailTwoArgsFirstArgThrowsWrongType() { shouldFail(new FailTwoArgsFirstArgThrowsWrongType()); } private static class FailTwoArgsSecondArgDoesntThrowNPE extends PassObject { @Override public void twoArg(String s, Integer i) { checkNotNull(s); // Fail: missing NPE for i } } public void testFailTwoArgsSecondArgDoesntThrowNPE() { shouldFail(new FailTwoArgsSecondArgDoesntThrowNPE()); } private static class FailTwoArgsSecondArgThrowsWrongType extends PassObject { @Override public void twoArg(String s, Integer i) { checkNotNull(s); doThrow(i); // Fail: throwing non-NPE exception for null i } } public void testFailTwoArgsSecondArgThrowsWrongType() { shouldFail(new FailTwoArgsSecondArgThrowsWrongType()); } private static class FailTwoMixedArgsFirstArgDoesntThrowNPE extends PassObject { @Override public void twoMixedArgs(String s, @Nullable Integer i) { // Fail: missing NPE for s } } public void testFailTwoMixedArgsFirstArgDoesntThrowNPE() { shouldFail(new FailTwoMixedArgsFirstArgDoesntThrowNPE()); } private static class FailTwoMixedArgsFirstArgThrowsWrongType extends PassObject { @Override public void twoMixedArgs(String s, @Nullable Integer i) { doThrow(s); // Fail: throwing non-NPE exception for null s } } public void testFailTwoMixedArgsFirstArgThrowsWrongType() { shouldFail(new FailTwoMixedArgsFirstArgThrowsWrongType()); } private static class PassTwoMixedArgsNullableArgThrowsNPE extends PassObject { @Override public void twoMixedArgs(String s, @Nullable Integer i) { checkNotNull(s); i.intValue(); // ok to throw NPE? } } public void testPassTwoMixedArgsNullableArgThrowsNPE() { shouldPass(new PassTwoMixedArgsNullableArgThrowsNPE()); } private static class PassTwoMixedArgSecondNullableArgThrowsOther extends PassObject { @Override public void twoMixedArgs(String s, @Nullable Integer i) { checkNotNull(s); doThrow(i); // ok to throw non-NPE exception for null i } } public void testPassTwoMixedArgSecondNullableArgThrowsOther() { shouldPass(new PassTwoMixedArgSecondNullableArgThrowsOther()); } private static class FailTwoMixedArgsSecondArgDoesntThrowNPE extends PassObject { @Override public void twoMixedArgs(@Nullable Integer i, String s) { // Fail: missing NPE for null s } } public void testFailTwoMixedArgsSecondArgDoesntThrowNPE() { shouldFail(new FailTwoMixedArgsSecondArgDoesntThrowNPE()); } private static class FailTwoMixedArgsSecondArgThrowsWrongType extends PassObject { @Override public void twoMixedArgs(@Nullable Integer i, String s) { doThrow(s); // Fail: throwing non-NPE exception for null s } } public void testFailTwoMixedArgsSecondArgThrowsWrongType() { shouldFail(new FailTwoMixedArgsSecondArgThrowsWrongType()); } private static class PassTwoNullableArgsFirstThrowsNPE extends PassObject { @Override public void twoNullableArgs(@Nullable String s, @Nullable Integer i) { checkNotNull(s); // ok to throw NPE? } } public void testPassTwoNullableArgsFirstThrowsNPE() { shouldPass(new PassTwoNullableArgsFirstThrowsNPE()); } private static class PassTwoNullableArgsFirstThrowsOther extends PassObject { @Override public void twoNullableArgs(@Nullable String s, @Nullable Integer i) { doThrow(s); // ok to throw non-NPE exception for null s } } public void testPassTwoNullableArgsFirstThrowsOther() { shouldPass(new PassTwoNullableArgsFirstThrowsOther()); } private static class PassTwoNullableArgsSecondThrowsNPE extends PassObject { @Override public void twoNullableArgs(@Nullable String s, @Nullable Integer i) { i.intValue(); // ok to throw NPE? } } public void testPassTwoNullableArgsSecondThrowsNPE() { shouldPass(new PassTwoNullableArgsSecondThrowsNPE()); } private static class PassTwoNullableArgsSecondThrowsOther extends PassObject { @Override public void twoNullableArgs(@Nullable String s, @Nullable Integer i) { doThrow(i); // ok to throw non-NPE exception for null i } } public void testPassTwoNullableArgsSecondThrowsOther() { shouldPass(new PassTwoNullableArgsSecondThrowsOther()); } private static class PassTwoNullableArgsNeitherThrowsAnything extends PassObject { @Override public void twoNullableArgs(@Nullable String s, @Nullable Integer i) { // ok to do nothing } } public void testPassTwoNullableArgsNeitherThrowsAnything() { shouldPass(new PassTwoNullableArgsNeitherThrowsAnything()); } @SuppressWarnings("unused") // for NullPointerTester private abstract static class BaseClassThatFailsToThrow { public void oneArg(String s) {} } private static class SubclassWithBadSuperclass extends BaseClassThatFailsToThrow {} public void testSubclassWithBadSuperclass() { shouldFail(new SubclassWithBadSuperclass()); } @SuppressWarnings("unused") // for NullPointerTester private abstract static class BaseClassThatFailsToThrowForPackagePrivate { void packagePrivateOneArg(String s) {} } private static class SubclassWithBadSuperclassForPackagePrivate extends BaseClassThatFailsToThrowForPackagePrivate {} public void testSubclassWithBadSuperclassForPackagePrivateMethod() { shouldFail(new SubclassWithBadSuperclassForPackagePrivate(), Visibility.PACKAGE); } @SuppressWarnings("unused") // for NullPointerTester private abstract static class BaseClassThatFailsToThrowForProtected { protected void protectedOneArg(String s) {} } private static class SubclassWithBadSuperclassForProtected extends BaseClassThatFailsToThrowForProtected {} public void testSubclassWithBadSuperclassForPackageProtectedMethod() { shouldFail(new SubclassWithBadSuperclassForProtected(), Visibility.PROTECTED); } private static class SubclassThatOverridesBadSuperclassMethod extends BaseClassThatFailsToThrow { @Override public void oneArg(@Nullable String s) {} } public void testSubclassThatOverridesBadSuperclassMethod() { shouldPass(new SubclassThatOverridesBadSuperclassMethod()); } @SuppressWarnings("unused") // for NullPointerTester private static class SubclassOverridesTheWrongMethod extends BaseClassThatFailsToThrow { public void oneArg(@Nullable CharSequence s) {} } public void testSubclassOverridesTheWrongMethod() { shouldFail(new SubclassOverridesTheWrongMethod()); } @SuppressWarnings("unused") // for NullPointerTester private static class ClassThatFailsToThrowForStatic { static void staticOneArg(String s) {} } public void testClassThatFailsToThrowForStatic() { shouldFail(ClassThatFailsToThrowForStatic.class); } private static class SubclassThatFailsToThrowForStatic extends ClassThatFailsToThrowForStatic {} public void testSubclassThatFailsToThrowForStatic() { shouldFail(SubclassThatFailsToThrowForStatic.class); } private static class SubclassThatTriesToOverrideBadStaticMethod extends ClassThatFailsToThrowForStatic { static void staticOneArg(@Nullable String s) {} } public void testSubclassThatTriesToOverrideBadStaticMethod() { shouldFail(SubclassThatTriesToOverrideBadStaticMethod.class); } private static final class HardToCreate { private HardToCreate(HardToCreate x) {} } @SuppressWarnings("unused") // used by reflection private static class CanCreateDefault { public void foo(@Nullable HardToCreate ignored, String required) { checkNotNull(required); } } public void testCanCreateDefault() { shouldPass(new CanCreateDefault()); } @SuppressWarnings("unused") // used by reflection private static class CannotCreateDefault { public void foo(HardToCreate ignored, String required) { checkNotNull(ignored); checkNotNull(required); } } public void testCannotCreateDefault() { shouldFail(new CannotCreateDefault()); } private static void shouldPass(Object instance, Visibility visibility) { new NullPointerTester().testInstanceMethods(instance, visibility); } private static void shouldPass(Object instance) { shouldPass(instance, Visibility.PACKAGE); shouldPass(instance, Visibility.PROTECTED); shouldPass(instance, Visibility.PUBLIC); } // TODO(cpovirk): eliminate surprising Object/Class overloading of shouldFail private static void shouldFail(Object instance, Visibility visibility) { try { new NullPointerTester().testInstanceMethods(instance, visibility); } catch (AssertionFailedError expected) { return; } fail("Should detect problem in " + instance.getClass().getSimpleName()); } private static void shouldFail(Object instance) { shouldFail(instance, Visibility.PACKAGE); shouldFail(instance, Visibility.PROTECTED); shouldFail(instance, Visibility.PUBLIC); } private static void shouldFail(Class cls, Visibility visibility) { try { new NullPointerTester().testStaticMethods(cls, visibility); } catch (AssertionFailedError expected) { return; } fail("Should detect problem in " + cls.getSimpleName()); } private static void shouldFail(Class cls) { shouldFail(cls, Visibility.PACKAGE); } @SuppressWarnings("unused") // used by reflection private static class PrivateClassWithPrivateConstructor { private PrivateClassWithPrivateConstructor(@Nullable Integer argument) {} } public void testPrivateClass() { NullPointerTester tester = new NullPointerTester(); for (Constructor constructor : PrivateClassWithPrivateConstructor.class.getDeclaredConstructors()) { tester.testConstructor(constructor); } } private interface Foo { void doSomething(T bar, Integer baz); } private static class StringFoo implements Foo { @Override public void doSomething(String bar, Integer baz) { checkNotNull(bar); checkNotNull(baz); } } public void testBridgeMethodIgnored() { new NullPointerTester().testAllPublicInstanceMethods(new StringFoo()); } private abstract static class DefaultValueChecker { private final Map arguments = Maps.newHashMap(); final DefaultValueChecker runTester() { new NullPointerTester().testInstanceMethods(this, Visibility.PACKAGE); return this; } final void assertNonNullValues(Object... expectedValues) { assertEquals(expectedValues.length, arguments.size()); for (int i = 0; i < expectedValues.length; i++) { assertEquals("Default value for parameter #" + i, expectedValues[i], arguments.get(i)); } } final Object getDefaultParameterValue(int position) { return arguments.get(position); } final void calledWith(Object... args) { for (int i = 0; i < args.length; i++) { if (args[i] != null) { arguments.put(i, args[i]); } } for (Object arg : args) { checkNotNull(arg); // to fulfill null check } } } private enum Gender { MALE, FEMALE } private static class AllDefaultValuesChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkDefaultValuesForTheseTypes( Gender gender, Integer integer, int i, String string, CharSequence charSequence, List list, ImmutableList immutableList, Map map, ImmutableMap immutableMap, Set set, ImmutableSet immutableSet, SortedSet sortedSet, ImmutableSortedSet immutableSortedSet, Multiset multiset, ImmutableMultiset immutableMultiset, Multimap multimap, ImmutableMultimap immutableMultimap, Table table, ImmutableTable immutableTable) { calledWith( gender, integer, i, string, charSequence, list, immutableList, map, immutableMap, set, immutableSet, sortedSet, immutableSortedSet, multiset, immutableMultiset, multimap, immutableMultimap, table, immutableTable); } final void check() { runTester() .assertNonNullValues( Gender.MALE, Integer.valueOf(0), 0, "", "", ImmutableList.of(), ImmutableList.of(), ImmutableMap.of(), ImmutableMap.of(), ImmutableSet.of(), ImmutableSet.of(), ImmutableSortedSet.of(), ImmutableSortedSet.of(), ImmutableMultiset.of(), ImmutableMultiset.of(), ImmutableMultimap.of(), ImmutableMultimap.of(), ImmutableTable.of(), ImmutableTable.of()); } } public void testDefaultValues() { new AllDefaultValuesChecker().check(); } private static class ObjectArrayDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(Object[] array, String s) { calledWith(array, s); } void check() { runTester(); Object[] defaultArray = (Object[]) getDefaultParameterValue(0); assertThat(defaultArray).isEmpty(); } } public void testObjectArrayDefaultValue() { new ObjectArrayDefaultValueChecker().check(); } private static class StringArrayDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(String[] array, String s) { calledWith(array, s); } void check() { runTester(); String[] defaultArray = (String[]) getDefaultParameterValue(0); assertThat(defaultArray).isEmpty(); } } public void testStringArrayDefaultValue() { new StringArrayDefaultValueChecker().check(); } private static class IntArrayDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(int[] array, String s) { calledWith(array, s); } void check() { runTester(); int[] defaultArray = (int[]) getDefaultParameterValue(0); assertEquals(0, defaultArray.length); } } public void testIntArrayDefaultValue() { new IntArrayDefaultValueChecker().check(); } private enum EmptyEnum {} private static class EmptyEnumDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(EmptyEnum object, String s) { calledWith(object, s); } void check() { try { runTester(); } catch (AssertionFailedError expected) { return; } fail("Should have failed because enum has no constant"); } } public void testEmptyEnumDefaultValue() { new EmptyEnumDefaultValueChecker().check(); } private static class GenericClassTypeDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(Class> cls, String s) { calledWith(cls, s); } void check() { runTester(); Class defaultClass = (Class) getDefaultParameterValue(0); assertEquals(List.class, defaultClass); } } public void testGenericClassDefaultValue() { new GenericClassTypeDefaultValueChecker().check(); } private static class NonGenericClassTypeDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(@SuppressWarnings("rawtypes") Class cls, String s) { calledWith(cls, s); } void check() { runTester(); Class defaultClass = (Class) getDefaultParameterValue(0); assertEquals(Object.class, defaultClass); } } public void testNonGenericClassDefaultValue() { new NonGenericClassTypeDefaultValueChecker().check(); } private static class GenericTypeTokenDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(TypeToken> type, String s) { calledWith(type, s); } void check() { runTester(); TypeToken defaultType = (TypeToken) getDefaultParameterValue(0); assertTrue(new TypeToken>() {}.isSupertypeOf(defaultType)); } } public void testGenericTypeTokenDefaultValue() { new GenericTypeTokenDefaultValueChecker().check(); } private static class NonGenericTypeTokenDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(@SuppressWarnings("rawtypes") TypeToken type, String s) { calledWith(type, s); } void check() { runTester(); TypeToken defaultType = (TypeToken) getDefaultParameterValue(0); assertEquals(new TypeToken() {}, defaultType); } } public void testNonGenericTypeTokenDefaultValue() { new NonGenericTypeTokenDefaultValueChecker().check(); } private interface FromTo extends Function {} private static class GenericInterfaceDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(FromTo f, String s) { calledWith(f, s); } void check() { runTester(); FromTo defaultFunction = (FromTo) getDefaultParameterValue(0); assertEquals(0, defaultFunction.apply(null)); } } public void testGenericInterfaceDefaultValue() { new GenericInterfaceDefaultValueChecker().check(); } private interface NullRejectingFromTo extends Function { @Override public abstract T apply(F from); } private static class NullRejectingInterfaceDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(NullRejectingFromTo f, String s) { calledWith(f, s); } void check() { runTester(); NullRejectingFromTo defaultFunction = (NullRejectingFromTo) getDefaultParameterValue(0); assertNotNull(defaultFunction); try { defaultFunction.apply(null); fail("Proxy Should have rejected null"); } catch (NullPointerException expected) { } } } public void testNullRejectingInterfaceDefaultValue() { new NullRejectingInterfaceDefaultValueChecker().check(); } private static class MultipleInterfacesDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public & Supplier> void checkArray(T f, String s) { calledWith(f, s); } void check() { runTester(); FromTo defaultFunction = (FromTo) getDefaultParameterValue(0); assertEquals(0, defaultFunction.apply(null)); Supplier defaultSupplier = (Supplier) defaultFunction; assertEquals(Long.valueOf(0), defaultSupplier.get()); } } public void testMultipleInterfacesDefaultValue() { new MultipleInterfacesDefaultValueChecker().check(); } private static class GenericInterface2DefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(FromTo> f, String s) { calledWith(f, s); } void check() { runTester(); FromTo defaultFunction = (FromTo) getDefaultParameterValue(0); FromTo returnValue = (FromTo) defaultFunction.apply(null); assertEquals("", returnValue.apply(null)); } } public void testGenericInterfaceReturnedByGenericMethod() { new GenericInterface2DefaultValueChecker().check(); } private abstract static class AbstractGenericDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkGeneric(T value, String s) { calledWith(value, s); } } private static class GenericDefaultValueResolvedToStringChecker extends AbstractGenericDefaultValueChecker { void check() { runTester(); assertEquals("", getDefaultParameterValue(0)); } } public void testGenericTypeResolvedForDefaultValue() { new GenericDefaultValueResolvedToStringChecker().check(); } private abstract static class AbstractGenericDefaultValueForPackagePrivateMethodChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester void checkGeneric(T value, String s) { calledWith(value, s); } } private static class DefaultValueForPackagePrivateMethodResolvedToStringChecker extends AbstractGenericDefaultValueForPackagePrivateMethodChecker { void check() { runTester(); assertEquals("", getDefaultParameterValue(0)); } } public void testDefaultValueResolvedForPackagePrivateMethod() { new DefaultValueForPackagePrivateMethodResolvedToStringChecker().check(); } private static class ConverterDefaultValueChecker extends DefaultValueChecker { @SuppressWarnings("unused") // called by NullPointerTester public void checkArray(Converter c, String s) { calledWith(c, s); } void check() { runTester(); @SuppressWarnings("unchecked") // We are checking it anyway Converter defaultConverter = (Converter) getDefaultParameterValue(0); assertEquals(Integer.valueOf(0), defaultConverter.convert("anything")); assertEquals("", defaultConverter.reverse().convert(123)); assertNull(defaultConverter.convert(null)); assertNull(defaultConverter.reverse().convert(null)); } } public void testConverterDefaultValue() { new ConverterDefaultValueChecker().check(); } private static class VisibilityMethods { @SuppressWarnings("unused") // Called by reflection private void privateMethod() {} @SuppressWarnings("unused") // Called by reflection void packagePrivateMethod() {} @SuppressWarnings("unused") // Called by reflection protected void protectedMethod() {} @SuppressWarnings("unused") // Called by reflection public void publicMethod() {} } public void testVisibility_public() throws Exception { assertFalse( Visibility.PUBLIC.isVisible(VisibilityMethods.class.getDeclaredMethod("privateMethod"))); assertFalse( Visibility.PUBLIC.isVisible( VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod"))); assertFalse( Visibility.PUBLIC.isVisible(VisibilityMethods.class.getDeclaredMethod("protectedMethod"))); assertTrue( Visibility.PUBLIC.isVisible(VisibilityMethods.class.getDeclaredMethod("publicMethod"))); } public void testVisibility_protected() throws Exception { assertFalse( Visibility.PROTECTED.isVisible(VisibilityMethods.class.getDeclaredMethod("privateMethod"))); assertFalse( Visibility.PROTECTED.isVisible( VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod"))); assertTrue( Visibility.PROTECTED.isVisible( VisibilityMethods.class.getDeclaredMethod("protectedMethod"))); assertTrue( Visibility.PROTECTED.isVisible(VisibilityMethods.class.getDeclaredMethod("publicMethod"))); } public void testVisibility_package() throws Exception { assertFalse( Visibility.PACKAGE.isVisible(VisibilityMethods.class.getDeclaredMethod("privateMethod"))); assertTrue( Visibility.PACKAGE.isVisible( VisibilityMethods.class.getDeclaredMethod("packagePrivateMethod"))); assertTrue( Visibility.PACKAGE.isVisible(VisibilityMethods.class.getDeclaredMethod("protectedMethod"))); assertTrue( Visibility.PACKAGE.isVisible(VisibilityMethods.class.getDeclaredMethod("publicMethod"))); } private class Inner { public Inner(String s) { checkNotNull(s); } } public void testNonStaticInnerClass() { try { new NullPointerTester().testAllPublicConstructors(Inner.class); fail(); } catch (IllegalArgumentException expected) { assertThat(expected.getMessage()).contains("inner class"); } } private static String rootLocaleFormat(String format, Object... args) { return String.format(Locale.ROOT, format, args); } static class OverridesEquals { @SuppressWarnings("EqualsHashCode") @Override public boolean equals(Object o) { return true; } } static class DoesNotOverrideEquals { public boolean equals(Object a, Object b) { return true; } } public void testEqualsMethod() { shouldPass(new OverridesEquals()); shouldFail(new DoesNotOverrideEquals()); } private static final class FailOnOneOfTwoConstructors { @SuppressWarnings("unused") // Called by reflection public FailOnOneOfTwoConstructors(String s) {} @SuppressWarnings("unused") // Called by reflection public FailOnOneOfTwoConstructors(Object o) { checkNotNull(o); } } public void testConstructor_Ignored_ShouldPass() throws Exception { new NullPointerTester() .ignore(FailOnOneOfTwoConstructors.class.getDeclaredConstructor(String.class)) .testAllPublicConstructors(FailOnOneOfTwoConstructors.class); } public void testConstructor_ShouldFail() throws Exception { try { new NullPointerTester().testAllPublicConstructors(FailOnOneOfTwoConstructors.class); } catch (AssertionFailedError expected) { return; } fail("Should detect problem in " + FailOnOneOfTwoConstructors.class.getSimpleName()); } public static class NullBounds { boolean xWasCalled; @SuppressWarnings("unused") // Called by reflection public void x(X x) { xWasCalled = true; checkNotNull(x); } @SuppressWarnings("unused") // Called by reflection public void t(T t) { fail("Method with parameter should not be called"); } @SuppressWarnings("unused") // Called by reflection public void u(U u) { fail( "Method with parameter where should not be" + " called"); } @SuppressWarnings("unused") // Called by reflection public void a(A a) { fail("Method with parameter should not be called"); } @SuppressWarnings("unused") // Called by reflection public void b(A a) { fail( "Method with parameter where should not be" + " called"); } } public void testNullBounds() { // NullBounds has methods whose parameters are type variables that have // "extends @Nullable Object" as a bound. This test ensures that NullPointerTester considers // those parameters to be @Nullable, so it won't call the methods. NullBounds nullBounds = new NullBounds<>(); new NullPointerTester().testAllPublicInstanceMethods(nullBounds); assertThat(nullBounds.xWasCalled).isTrue(); } }