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
17package com.google.common.testing;
18
19import static com.google.common.base.Preconditions.checkNotNull;
20
21import com.google.common.collect.Lists;
22import com.google.common.collect.Sets;
23
24import junit.framework.AssertionFailedError;
25import junit.framework.TestCase;
26
27import java.lang.reflect.Constructor;
28import java.lang.reflect.Method;
29import java.util.List;
30import java.util.Set;
31
32import javax.annotation.Nullable;
33
34/**
35 * Unit test for {@link NullPointerTester}.
36 *
37 * @author Kevin Bourrillion
38 * @author Mick Killianey
39 */
40public 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