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