Home | History | Annotate | Download | only in testing
      1 /*
      2  * Copyright (C) 2005 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.checkNotNull;
     20 
     21 import com.google.common.collect.Lists;
     22 import com.google.common.collect.Sets;
     23 
     24 import junit.framework.AssertionFailedError;
     25 import junit.framework.TestCase;
     26 
     27 import java.lang.reflect.Constructor;
     28 import java.lang.reflect.Method;
     29 import java.util.List;
     30 import java.util.Set;
     31 
     32 import javax.annotation.Nullable;
     33 
     34 /**
     35  * Unit test for {@link NullPointerTester}.
     36  *
     37  * @author Kevin Bourrillion
     38  * @author Mick Killianey
     39  */
     40 public class NullPointerTesterTest extends TestCase {
     41 
     42   private NullPointerTester tester;
     43 
     44   @Override protected void setUp() throws Exception {
     45     super.setUp();
     46     tester = new NullPointerTester();
     47   }
     48 
     49   /** Non-NPE RuntimeException. */
     50   public static class FooException extends RuntimeException {
     51     private static final long serialVersionUID = 1L;
     52   }
     53 
     54   /**
     55    * Class for testing all permutations of static/non-static one-argument
     56    * methods using methodParameter().
     57    */
     58   public static class OneArg {
     59 
     60     public static void staticOneArgCorrectlyThrowsNpe(String s) {
     61       checkNotNull(s); // expect NPE here on null
     62     }
     63     public static void staticOneArgThrowsOtherThanNpe(String s) {
     64       throw new FooException();  // should catch as failure
     65     }
     66     public static void staticOneArgShouldThrowNpeButDoesnt(String s) {
     67       // should catch as failure
     68     }
     69     public static void
     70     staticOneArgNullableCorrectlyDoesNotThrowNPE(@Nullable String s) {
     71       // null?  no problem
     72     }
     73     public static void
     74     staticOneArgNullableCorrectlyThrowsOtherThanNPE(@Nullable String s) {
     75       throw new FooException(); // ok, as long as it's not NullPointerException
     76     }
     77     public static void
     78     staticOneArgNullableThrowsNPE(@Nullable String s) {
     79       checkNotNull(s); // doesn't check if you said you'd accept null, but you don't
     80     }
     81 
     82     public void oneArgCorrectlyThrowsNpe(String s) {
     83       checkNotNull(s); // expect NPE here on null
     84     }
     85     public void oneArgThrowsOtherThanNpe(String s) {
     86       throw new FooException();  // should catch as failure
     87     }
     88     public void oneArgShouldThrowNpeButDoesnt(String s) {
     89       // should catch as failure
     90     }
     91     public void oneArgNullableCorrectlyDoesNotThrowNPE(@Nullable String s) {
     92       // null?  no problem
     93     }
     94     public void oneArgNullableCorrectlyThrowsOtherThanNPE(@Nullable String s) {
     95       throw new FooException(); // ok, as long as it's not NullPointerException
     96     }
     97     public void oneArgNullableThrowsNPE(@Nullable String s) {
     98       checkNotNull(s); // doesn't check if you said you'd accept null, but you don't
     99     }
    100   }
    101 
    102   private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_PASS = {
    103     "staticOneArgCorrectlyThrowsNpe",
    104     "staticOneArgNullableCorrectlyDoesNotThrowNPE",
    105     "staticOneArgNullableCorrectlyThrowsOtherThanNPE",
    106     "staticOneArgNullableThrowsNPE",
    107   };
    108   private static final String[] STATIC_ONE_ARG_METHODS_SHOULD_FAIL = {
    109     "staticOneArgThrowsOtherThanNpe",
    110     "staticOneArgShouldThrowNpeButDoesnt",
    111   };
    112   private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS = {
    113     "oneArgCorrectlyThrowsNpe",
    114     "oneArgNullableCorrectlyDoesNotThrowNPE",
    115     "oneArgNullableCorrectlyThrowsOtherThanNPE",
    116     "oneArgNullableThrowsNPE",
    117   };
    118   private static final String[] NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL = {
    119     "oneArgThrowsOtherThanNpe",
    120     "oneArgShouldThrowNpeButDoesnt",
    121   };
    122 
    123   public void testStaticOneArgMethodsThatShouldPass() throws Exception {
    124     for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_PASS) {
    125       Method method = OneArg.class.getMethod(methodName, String.class);
    126       try {
    127         tester.testMethodParameter(OneArg.class, method, 0);
    128       } catch (AssertionFailedError unexpected) {
    129         fail("Should not have flagged method " + methodName);
    130       }
    131     }
    132   }
    133 
    134   public void testStaticOneArgMethodsThatShouldFail() throws Exception {
    135     for (String methodName : STATIC_ONE_ARG_METHODS_SHOULD_FAIL) {
    136       Method method = OneArg.class.getMethod(methodName, String.class);
    137       boolean foundProblem = false;
    138       try {
    139         tester.testMethodParameter(OneArg.class, method, 0);
    140       } catch (AssertionFailedError expected) {
    141         foundProblem = true;
    142       }
    143       assertTrue("Should report error in method " + methodName, foundProblem);
    144     }
    145   }
    146 
    147   public void testNonStaticOneArgMethodsThatShouldPass() throws Exception {
    148     OneArg foo = new OneArg();
    149     for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_PASS) {
    150       Method method = OneArg.class.getMethod(methodName, String.class);
    151       try {
    152         tester.testMethodParameter(foo, method, 0);
    153       } catch (AssertionFailedError unexpected) {
    154         fail("Should not have flagged method " + methodName);
    155       }
    156     }
    157   }
    158 
    159   public void testNonStaticOneArgMethodsThatShouldFail() throws Exception {
    160     OneArg foo = new OneArg();
    161     for (String methodName : NONSTATIC_ONE_ARG_METHODS_SHOULD_FAIL) {
    162       Method method = OneArg.class.getMethod(methodName, String.class);
    163       boolean foundProblem = false;
    164       try {
    165         tester.testMethodParameter(foo, method, 0);
    166       } catch (AssertionFailedError expected) {
    167         foundProblem = true;
    168       }
    169       assertTrue("Should report error in method " + methodName, foundProblem);
    170     }
    171   }
    172 
    173   /**
    174    * Class for testing all permutations of nullable/non-nullable two-argument
    175    * methods using testMethod().
    176    *
    177    *   normalNormal:  two params, neither is Nullable
    178    *   nullableNormal:  only first param is Nullable
    179    *   normalNullable:  only second param is Nullable
    180    *   nullableNullable:  both params are Nullable
    181    */
    182   public static class TwoArg {
    183     /** Action to take on a null param. */
    184     public enum Action {
    185       THROW_A_NPE {
    186         @Override public void act() {
    187           throw new NullPointerException();
    188         }
    189       },
    190       THROW_OTHER {
    191         @Override public void act() {
    192           throw new FooException();
    193         }
    194       },
    195       JUST_RETURN {
    196         @Override public void act() {}
    197       };
    198 
    199       public abstract void act();
    200     }
    201     Action actionWhenFirstParamIsNull;
    202     Action actionWhenSecondParamIsNull;
    203 
    204     public TwoArg(
    205         Action actionWhenFirstParamIsNull,
    206         Action actionWhenSecondParamIsNull) {
    207       this.actionWhenFirstParamIsNull = actionWhenFirstParamIsNull;
    208       this.actionWhenSecondParamIsNull = actionWhenSecondParamIsNull;
    209     }
    210 
    211     /** Method that decides how to react to parameters. */
    212     public void reactToNullParameters(Object first, Object second) {
    213       if (first == null) {
    214         actionWhenFirstParamIsNull.act();
    215       }
    216       if (second == null) {
    217         actionWhenSecondParamIsNull.act();
    218       }
    219     }
    220 
    221     /** Two-arg method with no Nullable params. */
    222     public void normalNormal(String first, Integer second) {
    223       reactToNullParameters(first, second);
    224     }
    225 
    226     /** Two-arg method with the second param Nullable. */
    227     public void normalNullable(String first, @Nullable Integer second) {
    228       reactToNullParameters(first, second);
    229     }
    230 
    231     /** Two-arg method with the first param Nullable. */
    232     public void nullableNormal(@Nullable String first, Integer second) {
    233       reactToNullParameters(first, second);
    234     }
    235 
    236     /** Two-arg method with the both params Nullable. */
    237     public void nullableNullable(
    238         @Nullable String first, @Nullable Integer second) {
    239       reactToNullParameters(first, second);
    240     }
    241 
    242     /** To provide sanity during debugging. */
    243     @Override public String toString() {
    244       return String.format("Bar(%s, %s)",
    245           actionWhenFirstParamIsNull, actionWhenSecondParamIsNull);
    246     }
    247   }
    248 
    249   public void verifyBarPass(Method method, TwoArg bar) throws Exception {
    250     try {
    251       tester.testMethod(bar, method);
    252     } catch (AssertionFailedError incorrectError) {
    253       String errorMessage = String.format(
    254           "Should not have flagged method %s for %s", method.getName(), bar);
    255       assertNull(errorMessage, incorrectError);
    256     }
    257   }
    258 
    259   public void verifyBarFail(Method method, TwoArg bar) throws Exception {
    260     try {
    261       tester.testMethod(bar, method);
    262     } catch (AssertionFailedError expected) {
    263       return; // good...we wanted a failure
    264     }
    265     String errorMessage = String.format(
    266         "Should have flagged method %s for %s", method.getName(), bar);
    267     fail(errorMessage);
    268   }
    269 
    270   public void testTwoArgNormalNormal() throws Exception {
    271     Method method = TwoArg.class.getMethod(
    272         "normalNormal", String.class, Integer.class);
    273     for (TwoArg.Action first : TwoArg.Action.values()) {
    274       for (TwoArg.Action second : TwoArg.Action.values()) {
    275         TwoArg bar = new TwoArg(first, second);
    276         if (first.equals(TwoArg.Action.THROW_A_NPE)
    277             && second.equals(TwoArg.Action.THROW_A_NPE)) {
    278           verifyBarPass(method, bar); // require both params to throw NPE
    279         } else {
    280           verifyBarFail(method, bar);
    281         }
    282       }
    283     }
    284   }
    285 
    286   public void testTwoArgNormalNullable() throws Exception {
    287     Method method = TwoArg.class.getMethod(
    288         "normalNullable", String.class, Integer.class);
    289     for (TwoArg.Action first : TwoArg.Action.values()) {
    290       for (TwoArg.Action second : TwoArg.Action.values()) {
    291         TwoArg bar = new TwoArg(first, second);
    292         if (first.equals(TwoArg.Action.THROW_A_NPE)) {
    293           verifyBarPass(method, bar); // only pass if 1st param throws NPE
    294         } else {
    295           verifyBarFail(method, bar);
    296         }
    297       }
    298     }
    299   }
    300 
    301   public void testTwoArgNullableNormal() throws Exception {
    302     Method method = TwoArg.class.getMethod(
    303         "nullableNormal", String.class, Integer.class);
    304     for (TwoArg.Action first : TwoArg.Action.values()) {
    305       for (TwoArg.Action second : TwoArg.Action.values()) {
    306         TwoArg bar = new TwoArg(first, second);
    307         if (second.equals(TwoArg.Action.THROW_A_NPE)) {
    308           verifyBarPass(method, bar); // only pass if 2nd param throws NPE
    309         } else {
    310           verifyBarFail(method, bar);
    311         }
    312       }
    313     }
    314   }
    315 
    316   public void testTwoArgNullableNullable() throws Exception {
    317     Method method = TwoArg.class.getMethod(
    318         "nullableNullable", String.class, Integer.class);
    319     for (TwoArg.Action first : TwoArg.Action.values()) {
    320       for (TwoArg.Action second : TwoArg.Action.values()) {
    321         TwoArg bar = new TwoArg(first, second);
    322         verifyBarPass(method, bar); // All args nullable:  anything goes!
    323       }
    324     }
    325   }
    326 
    327   /*
    328    * This next part consists of several sample classes that provide
    329    * demonstrations of conditions that cause NullPointerTester
    330    * to succeed/fail.
    331    *
    332    * Add naughty classes to failClasses to verify that NullPointerTest
    333    * raises an AssertionFailedError.
    334    *
    335    * Add acceptable classes to passClasses to verify that NullPointerTest
    336    * doesn't complain.
    337    */
    338 
    339   /** List of classes that NullPointerTester should pass as acceptable. */
    340   static List<Class<?>> failClasses = Lists.newArrayList();
    341 
    342   /** List of classes that NullPointerTester should flag as problematic. */
    343   static List<Class<?>> passClasses = Lists.newArrayList();
    344 
    345   /** Lots of well-behaved methods. */
    346   public static class PassObject {
    347     public static void doThrow(Object arg) {
    348       if (arg == null) {
    349         throw new FooException();
    350       }
    351     }
    352     public void noArg() {}
    353     public void oneArg(String s) { checkNotNull(s); }
    354     public void oneNullableArg(@Nullable String s) {}
    355     public void oneNullableArgThrows(@Nullable String s) { doThrow(s); }
    356 
    357     public void twoArg(String s, Integer i) { checkNotNull(s); i.intValue(); }
    358     public void twoMixedArgs(String s, @Nullable Integer i) { checkNotNull(s); }
    359     public void twoMixedArgsThrows(String s, @Nullable Integer i) {
    360       checkNotNull(s); doThrow(i);
    361     }
    362     public void twoMixedArgs(@Nullable Integer i, String s) { checkNotNull(s); }
    363     public void twoMixedArgsThrows(@Nullable Integer i, String s) {
    364       checkNotNull(s); doThrow(i);
    365     }
    366     public void twoNullableArgs(@Nullable String s,
    367         @javax.annotation.Nullable Integer i) { }
    368     public void twoNullableArgsThrowsFirstArg(
    369         @Nullable String s, @Nullable Integer i) {
    370       doThrow(s);
    371     }
    372     public void twoNullableArgsThrowsSecondArg(
    373         @Nullable String s, @Nullable Integer i) {
    374       doThrow(i);
    375     }
    376     public static void staticOneArg(String s) { checkNotNull(s); }
    377     public static void staticOneNullableArg(@Nullable String s) { }
    378     public static void staticOneNullableArgThrows(@Nullable String s) {
    379       doThrow(s);
    380     }
    381   }
    382   static { passClasses.add(PassObject.class); }
    383 
    384   static class FailOneArgDoesntThrowNPE extends PassObject {
    385     @Override public void oneArg(String s) {
    386       // Fail:  missing NPE for s
    387     }
    388   }
    389   static { failClasses.add(FailOneArgDoesntThrowNPE.class); }
    390 
    391   static class FailOneArgThrowsWrongType extends PassObject {
    392     @Override public void oneArg(String s) {
    393       doThrow(s); // Fail:  throwing non-NPE exception for null s
    394     }
    395   }
    396   static { failClasses.add(FailOneArgThrowsWrongType.class); }
    397 
    398   static class PassOneNullableArgThrowsNPE extends PassObject {
    399     @Override public void oneNullableArg(@Nullable String s) {
    400       checkNotNull(s); // ok to throw NPE
    401     }
    402   }
    403   static { passClasses.add(PassOneNullableArgThrowsNPE.class); }
    404 
    405   static class FailTwoArgsFirstArgDoesntThrowNPE extends PassObject {
    406     @Override public void twoArg(String s, Integer i) {
    407       // Fail: missing NPE for s
    408       i.intValue();
    409     }
    410   }
    411   static { failClasses.add(FailTwoArgsFirstArgDoesntThrowNPE.class); }
    412 
    413   static class FailTwoArgsFirstArgThrowsWrongType extends PassObject {
    414     @Override public void twoArg(String s, Integer i) {
    415       doThrow(s); // Fail:  throwing non-NPE exception for null s
    416       i.intValue();
    417     }
    418   }
    419   static { failClasses.add(FailTwoArgsFirstArgThrowsWrongType.class); }
    420 
    421   static class FailTwoArgsSecondArgDoesntThrowNPE extends PassObject {
    422     @Override public void twoArg(String s, Integer i) {
    423       checkNotNull(s);
    424       // Fail: missing NPE for i
    425     }
    426   }
    427   static { failClasses.add(FailTwoArgsSecondArgDoesntThrowNPE.class); }
    428 
    429   static class FailTwoArgsSecondArgThrowsWrongType extends PassObject {
    430     @Override public void twoArg(String s, Integer i) {
    431       checkNotNull(s);
    432       doThrow(i); // Fail:  throwing non-NPE exception for null i
    433     }
    434   }
    435   static { failClasses.add(FailTwoArgsSecondArgThrowsWrongType.class); }
    436 
    437   static class FailTwoMixedArgsFirstArgDoesntThrowNPE extends PassObject {
    438     @Override public void twoMixedArgs(String s, @Nullable Integer i) {
    439       // Fail: missing NPE for s
    440     }
    441   }
    442   static { failClasses.add(FailTwoMixedArgsFirstArgDoesntThrowNPE.class); }
    443 
    444   static class FailTwoMixedArgsFirstArgThrowsWrongType extends PassObject {
    445     @Override public void twoMixedArgs(String s, @Nullable Integer i) {
    446       doThrow(s); // Fail:  throwing non-NPE exception for null s
    447     }
    448   }
    449   static { failClasses.add(FailTwoMixedArgsFirstArgThrowsWrongType.class); }
    450 
    451   static class PassTwoMixedArgsNullableArgThrowsNPE extends PassObject {
    452     @Override public void twoMixedArgs(String s, @Nullable Integer i) {
    453       checkNotNull(s);
    454       i.intValue(); // ok to throw NPE?
    455     }
    456   }
    457   static { passClasses.add(PassTwoMixedArgsNullableArgThrowsNPE.class); }
    458 
    459   static class PassTwoMixedArgSecondNullableArgThrowsOther extends PassObject {
    460     @Override public void twoMixedArgs(String s, @Nullable Integer i) {
    461       checkNotNull(s);
    462       doThrow(i); // ok to throw non-NPE exception for null i
    463     }
    464   }
    465   static { passClasses.add(PassTwoMixedArgSecondNullableArgThrowsOther.class); }
    466 
    467   static class FailTwoMixedArgsSecondArgDoesntThrowNPE extends PassObject {
    468     @Override public void twoMixedArgs(@Nullable Integer i, String s) {
    469       // Fail: missing NPE for null s
    470     }
    471   }
    472   static { failClasses.add(FailTwoMixedArgsSecondArgDoesntThrowNPE.class); }
    473 
    474   static class FailTwoMixedArgsSecondArgThrowsWrongType extends PassObject {
    475     @Override public void twoMixedArgs(@Nullable Integer i, String s) {
    476       doThrow(s); // Fail:  throwing non-NPE exception for null s
    477     }
    478   }
    479   static { failClasses.add(FailTwoMixedArgsSecondArgThrowsWrongType.class); }
    480 
    481   static class PassTwoNullableArgsFirstThrowsNPE extends PassObject {
    482     @Override public void twoNullableArgs(
    483         @Nullable String s, @Nullable Integer i) {
    484       checkNotNull(s); // ok to throw NPE?
    485     }
    486   }
    487   static { passClasses.add(PassTwoNullableArgsFirstThrowsNPE.class); }
    488 
    489   static class PassTwoNullableArgsFirstThrowsOther extends PassObject {
    490     @Override public void twoNullableArgs(
    491         @Nullable String s, @Nullable Integer i) {
    492       doThrow(s); // ok to throw non-NPE exception for null s
    493     }
    494   }
    495   static { passClasses.add(PassTwoNullableArgsFirstThrowsOther.class); }
    496 
    497   static class PassTwoNullableArgsSecondThrowsNPE extends PassObject {
    498     @Override public void twoNullableArgs(
    499         @Nullable String s, @Nullable Integer i) {
    500       i.intValue(); // ok to throw NPE?
    501     }
    502   }
    503   static { passClasses.add(PassTwoNullableArgsSecondThrowsNPE.class); }
    504 
    505   static class PassTwoNullableArgsSecondThrowsOther extends PassObject {
    506     @Override public void twoNullableArgs(
    507         @Nullable String s, @Nullable Integer i) {
    508       doThrow(i); // ok to throw non-NPE exception for null i
    509     }
    510   }
    511   static { passClasses.add(PassTwoNullableArgsSecondThrowsOther.class); }
    512 
    513   static class PassTwoNullableArgsNeitherThrowsAnything extends PassObject {
    514     @Override public void twoNullableArgs(
    515         @Nullable String s, @Nullable Integer i) {
    516       // ok to do nothing
    517     }
    518   }
    519   static { passClasses.add(PassTwoNullableArgsNeitherThrowsAnything.class); }
    520 
    521   /** Sanity check:  it's easy to make typos. */
    522   private void checkClasses(String message, List<Class<?>> classes) {
    523     Set<Class<?>> set = Sets.newHashSet(classes);
    524     for (Class<?> clazz : classes) {
    525       if (!set.remove(clazz)) {
    526         fail(String.format("%s: %s appears twice. Typo?",
    527             message, clazz.getSimpleName()));
    528       }
    529     }
    530   }
    531 
    532   public void testDidntMakeTypoInTestCases() {
    533     checkClasses("passClass", passClasses);
    534     checkClasses("failClasses", failClasses);
    535     List<Class<?>> allClasses = Lists.newArrayList(passClasses);
    536     allClasses.addAll(failClasses);
    537     checkClasses("allClasses", allClasses);
    538   }
    539 
    540   public void testShouldNotFindProblemInPassClass() throws Exception {
    541     for (Class<?> passClass : passClasses) {
    542       Object instance = passClass.newInstance();
    543       try {
    544         tester.testAllPublicInstanceMethods(instance);
    545       } catch (AssertionFailedError e) {
    546         assertNull("Should not detect problem in " + passClass.getSimpleName(),
    547             e);
    548       }
    549     }
    550   }
    551 
    552   public void testShouldFindProblemInFailClass() throws Exception {
    553     for (Class<?> failClass : failClasses) {
    554       Object instance = failClass.newInstance();
    555       boolean foundProblem = false;
    556       try {
    557         tester.testAllPublicInstanceMethods(instance);
    558       } catch (AssertionFailedError e) {
    559         foundProblem = true;
    560       }
    561       assertTrue("Should detect problem in " + failClass.getSimpleName(),
    562           foundProblem);
    563     }
    564   }
    565 
    566   private static class PrivateClassWithPrivateConstructor {
    567     private PrivateClassWithPrivateConstructor(@Nullable Integer argument) {}
    568   }
    569 
    570   public void testPrivateClass() throws Exception {
    571     NullPointerTester tester = new NullPointerTester();
    572     for (Constructor<?> constructor
    573         : PrivateClassWithPrivateConstructor.class.getDeclaredConstructors()) {
    574       tester.testConstructor(constructor);
    575     }
    576   }
    577 
    578   private interface Foo<T> {
    579     void doSomething(T bar, Integer baz);
    580   }
    581 
    582   private static class StringFoo implements Foo<String> {
    583 
    584     @Override public void doSomething(String bar, Integer baz) {
    585       checkNotNull(bar);
    586       checkNotNull(baz);
    587     }
    588   }
    589 
    590   public void testBidgeMethodIgnored() throws Exception {
    591     new NullPointerTester().testAllPublicInstanceMethods(new StringFoo());
    592   }
    593 
    594   /*
    595    *
    596    * TODO(kevinb): This is only a very small start.
    597    * Must come back and finish.
    598    *
    599    */
    600 
    601 }
    602