1 /* 2 * Copyright (c) 2013, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. 8 * 9 * This code is distributed in the hope that it will be useful, but WITHOUT 10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 12 * version 2 for more details (a copy is included in the LICENSE file that 13 * accompanied this code). 14 * 15 * You should have received a copy of the GNU General Public License version 16 * 2 along with this work; if not, write to the Free Software Foundation, 17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 18 * 19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 20 * or visit www.oracle.com if you need additional information or have any 21 * questions. 22 */ 23 24 import java.security.CodeSource; 25 import java.security.Permission; 26 import java.security.PermissionCollection; 27 import java.security.Permissions; 28 import java.security.Policy; 29 import java.security.ProtectionDomain; 30 import java.util.EnumSet; 31 import java.util.HashMap; 32 import java.util.Map; 33 import java.util.logging.LogManager; 34 import java.util.logging.Logger; 35 import java.util.logging.LoggingPermission; 36 import sun.misc.JavaAWTAccess; 37 import sun.misc.SharedSecrets; 38 39 /* 40 * @test 41 * @bug 8017174 8010727 42 * @summary NPE when using Logger.getAnonymousLogger or 43 * LogManager.getLogManager().getLogger 44 * 45 * @run main/othervm -Dtest.security=off TestAppletLoggerContext LoadingApplet 46 * @run main/othervm -Dtest.security=on TestAppletLoggerContext LoadingApplet 47 * @run main/othervm -Dtest.security=off TestAppletLoggerContext LoadingMain 48 * @run main/othervm -Dtest.security=on TestAppletLoggerContext LoadingMain 49 * @run main/othervm -Dtest.security=off TestAppletLoggerContext One 50 * @run main/othervm -Dtest.security=on TestAppletLoggerContext One 51 * @run main/othervm -Dtest.security=off TestAppletLoggerContext Two 52 * @run main/othervm -Dtest.security=on TestAppletLoggerContext Two 53 * @run main/othervm -Dtest.security=off TestAppletLoggerContext Three 54 * @run main/othervm -Dtest.security=on TestAppletLoggerContext Three 55 * @run main/othervm -Dtest.security=off TestAppletLoggerContext Four 56 * @run main/othervm -Dtest.security=on TestAppletLoggerContext Four 57 * @run main/othervm -Dtest.security=off TestAppletLoggerContext Five 58 * @run main/othervm -Dtest.security=on TestAppletLoggerContext Five 59 * @run main/othervm -Dtest.security=off TestAppletLoggerContext Six 60 * @run main/othervm -Dtest.security=on TestAppletLoggerContext Six 61 * @run main/othervm -Dtest.security=off TestAppletLoggerContext Seven 62 * @run main/othervm -Dtest.security=on TestAppletLoggerContext Seven 63 * @run main/othervm -Dtest.security=off TestAppletLoggerContext 64 * @run main/othervm -Dtest.security=on TestAppletLoggerContext 65 */ 66 67 // NOTE: We run in other VM in order to 1. switch security manager and 2. cause 68 // LogManager class to be loaded anew. 69 public class TestAppletLoggerContext { 70 71 // Avoids the hassle of dealing with files and system props... 72 static class SimplePolicy extends Policy { 73 private final Permissions perms; 74 public SimplePolicy(Permission... permissions) { 75 perms = new Permissions(); 76 for (Permission permission : permissions) { 77 perms.add(permission); 78 } 79 } 80 @Override 81 public PermissionCollection getPermissions(CodeSource cs) { 82 return perms; 83 } 84 @Override 85 public PermissionCollection getPermissions(ProtectionDomain pd) { 86 return perms; 87 } 88 @Override 89 public boolean implies(ProtectionDomain pd, Permission p) { 90 return perms.implies(p); 91 } 92 } 93 94 // The bridge class initializes the logging system. 95 // It stubs the applet context in order to simulate context changes. 96 // 97 public static class Bridge { 98 99 private static class JavaAWTAccessStub implements JavaAWTAccess { 100 boolean active = true; 101 102 private static class TestExc { 103 private final Map<Object, Object> map = new HashMap<>(); 104 void put(Object key, Object v) { map.put(key, v); } 105 Object get(Object key) { return map.get(key); } 106 void remove(Object o) { map.remove(o); } 107 public static TestExc exc(Object o) { 108 return TestExc.class.cast(o); 109 } 110 } 111 112 TestExc exc; 113 TestExc global = new TestExc(); 114 115 @Override 116 public Object getContext() { return active ? global : null; } 117 @Override 118 public Object getExecutionContext() { return active ? exc : null; } 119 @Override 120 public Object get(Object o, Object o1) { return TestExc.exc(o).get(o1); } 121 @Override 122 public void put(Object o, Object o1, Object o2) { TestExc.exc(o).put(o1, o2); } 123 @Override 124 public void remove(Object o, Object o1) { TestExc.exc(o).remove(o1); } 125 @Override 126 public Object get(Object o) { return global.get(o); } 127 @Override 128 public void put(Object o, Object o1) { global.put(o, o1); } 129 @Override 130 public void remove(Object o) { global.remove(o); } 131 @Override 132 public boolean isDisposed() { return false; } 133 @Override 134 public boolean isMainAppContext() { return exc == null; } 135 } 136 137 final static JavaAWTAccessStub javaAwtAccess = new JavaAWTAccessStub(); 138 public static void init() { 139 SharedSecrets.setJavaAWTAccess(javaAwtAccess); 140 if (System.getProperty("test.security", "on").equals("on")) { 141 Policy p = new SimplePolicy(new LoggingPermission("control", null), 142 new RuntimePermission("setContextClassLoader"), 143 new RuntimePermission("shutdownHooks")); 144 Policy.setPolicy(p); 145 System.setSecurityManager(new SecurityManager()); 146 } 147 } 148 149 public static void changeContext() { 150 System.out.println("... Switching to a new applet context ..."); 151 javaAwtAccess.active = true; 152 javaAwtAccess.exc = new JavaAWTAccessStub.TestExc(); 153 } 154 155 public static void desactivate() { 156 System.out.println("... Running with no applet context ..."); 157 javaAwtAccess.exc = null; 158 javaAwtAccess.active = false; 159 } 160 161 public static class CustomAnonymousLogger extends Logger { 162 public CustomAnonymousLogger() { 163 this(""); 164 } 165 public CustomAnonymousLogger(String name) { 166 super(null, null); 167 System.out.println( " LogManager: " +LogManager.getLogManager()); 168 System.out.println( " getLogger: " +LogManager.getLogManager().getLogger(name)); 169 setParent(LogManager.getLogManager().getLogger(name)); 170 } 171 } 172 173 public static class CustomLogger extends Logger { 174 CustomLogger(String name) { 175 super(name, null); 176 } 177 } 178 } 179 180 public static enum TestCase { 181 LoadingApplet, LoadingMain, One, Two, Three, Four, Five, Six, Seven; 182 public void test() { 183 switch(this) { 184 // When run - each of these two tests must be 185 // run before any other tests and before each other. 186 case LoadingApplet: testLoadingApplet(); break; 187 case LoadingMain: testLoadingMain(); break; 188 case One: testOne(); break; 189 case Two: testTwo(); break; 190 case Three: testThree(); break; 191 case Four: testFour(); break; 192 case Five: testFive(); break; 193 case Six: testSix(); break; 194 case Seven: testSeven(); break; 195 } 196 } 197 public String describe() { 198 switch(this) { 199 case LoadingApplet: 200 return "Test that when the LogManager class is" 201 + " loaded in an applet thread first," 202 + "\n all LoggerContexts are correctly initialized"; 203 case LoadingMain: 204 return "Test that when the LogManager class is" 205 + " loaded in the main thread first," 206 + "\n all LoggerContexts are correctly initialized"; 207 case One: 208 return "Test that Logger.getAnonymousLogger()" 209 + " and new CustomAnonymousLogger() don't throw NPE"; 210 case Two: 211 return "Test that Logger.getLogger(\"\")" 212 + " does not return null nor throws NPE"; 213 case Three: 214 return "Test that LogManager.getLogManager().getLogger(\"\")" 215 + " does not return null nor throws NPE"; 216 case Four: 217 return "Test that Logger.getLogger(Logger.GLOBAL_LOGGER_NAME)" 218 + " does not return null,\n and that" 219 + " new CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME)" 220 + " does not throw NPE"; 221 case Five: 222 return "Test that LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME)" 223 + "\n does not return null nor throws NPE"; 224 case Six: 225 return "Test that manager.getLogger(Logger.GLOBAL_LOGGER_NAME)" 226 + " returns null\n when manager is not the default" 227 + " LogManager instance.\n" 228 + "Test adding a new logger named \"global\" in that" 229 + " non default instance."; 230 case Seven: return "Test that manager.getLogger(\"\")" 231 + " returns null\n when manager is not the default" 232 + " LogManager instance.\n" 233 + "Test adding a new logger named \"\" in that" 234 + " non default instance."; 235 default: return "Undefined"; 236 } 237 } 238 }; 239 240 /** 241 * @param args the command line arguments 242 */ 243 public static void main(String[] args) { 244 Bridge.init(); 245 EnumSet<TestCase> tests = EnumSet.noneOf(TestCase.class); 246 for (String arg : args) { 247 tests.add(TestCase.valueOf(arg)); 248 } 249 if (args.length == 0) { 250 tests = EnumSet.complementOf(EnumSet.of(TestCase.LoadingMain)); 251 } 252 final EnumSet<TestCase> loadingTests = 253 EnumSet.of(TestCase.LoadingApplet, TestCase.LoadingMain); 254 int testrun = 0; 255 for (TestCase test : tests) { 256 if (loadingTests.contains(test)) { 257 if (testrun > 0) { 258 throw new UnsupportedOperationException("Test case " 259 + test + " must be executed first!"); 260 } 261 } 262 System.out.println("Testing "+ test+": "); 263 System.out.println(test.describe()); 264 try { 265 test.test(); 266 } catch (Exception x) { 267 throw new Error(String.valueOf(test) 268 + (System.getSecurityManager() == null ? " without " : " with ") 269 + "security failed: "+x+"\n "+"FAILED: "+test.describe()+"\n", x); 270 } finally { 271 testrun++; 272 } 273 Bridge.changeContext(); 274 System.out.println("PASSED: "+ test); 275 } 276 } 277 278 public static void testLoadingApplet() { 279 Bridge.changeContext(); 280 281 Logger bar = new Bridge.CustomLogger("com.foo.Bar"); 282 LogManager.getLogManager().addLogger(bar); 283 assertNotNull(bar.getParent()); 284 testParent(bar); 285 testParent(LogManager.getLogManager().getLogger("global")); 286 testParent(LogManager.getLogManager().getLogger(bar.getName())); 287 288 Bridge.desactivate(); 289 290 Logger foo = new Bridge.CustomLogger("com.foo.Foo"); 291 boolean b = LogManager.getLogManager().addLogger(foo); 292 assertEquals(Boolean.TRUE, Boolean.valueOf(b)); 293 assertNotNull(foo.getParent()); 294 testParent(foo); 295 testParent(LogManager.getLogManager().getLogger("global")); 296 testParent(LogManager.getLogManager().getLogger(foo.getName())); 297 } 298 299 public static void testLoadingMain() { 300 Bridge.desactivate(); 301 302 Logger bar = new Bridge.CustomLogger("com.foo.Bar"); 303 LogManager.getLogManager().addLogger(bar); 304 assertNotNull(bar.getParent()); 305 testParent(bar); 306 testParent(LogManager.getLogManager().getLogger("global")); 307 testParent(LogManager.getLogManager().getLogger(bar.getName())); 308 309 Bridge.changeContext(); 310 311 Logger foo = new Bridge.CustomLogger("com.foo.Foo"); 312 boolean b = LogManager.getLogManager().addLogger(foo); 313 assertEquals(Boolean.TRUE, Boolean.valueOf(b)); 314 assertNotNull(foo.getParent()); 315 testParent(foo); 316 testParent(LogManager.getLogManager().getLogger("global")); 317 testParent(LogManager.getLogManager().getLogger(foo.getName())); 318 319 } 320 321 public static void testOne() { 322 for (int i=0; i<3 ; i++) { 323 Logger logger1 = Logger.getAnonymousLogger(); 324 Logger logger1b = Logger.getAnonymousLogger(); 325 Bridge.changeContext(); 326 Logger logger2 = Logger.getAnonymousLogger(); 327 Logger logger2b = Logger.getAnonymousLogger(); 328 Bridge.changeContext(); 329 Logger logger3 = new Bridge.CustomAnonymousLogger(); 330 Logger logger3b = new Bridge.CustomAnonymousLogger(); 331 Bridge.changeContext(); 332 Logger logger4 = new Bridge.CustomAnonymousLogger(); 333 Logger logger4b = new Bridge.CustomAnonymousLogger(); 334 } 335 } 336 337 338 public static void testTwo() { 339 for (int i=0; i<3 ; i++) { 340 Logger logger1 = Logger.getLogger(""); 341 Logger logger1b = Logger.getLogger(""); 342 assertNotNull(logger1); 343 assertNotNull(logger1b); 344 assertEquals(logger1, logger1b); 345 Bridge.changeContext(); 346 Logger logger2 = Logger.getLogger(""); 347 Logger logger2b = Logger.getLogger(""); 348 assertNotNull(logger2); 349 assertNotNull(logger2b); 350 assertEquals(logger2, logger2b); 351 assertEquals(logger1, logger2); 352 } 353 } 354 355 public static void testThree() { 356 for (int i=0; i<3 ; i++) { 357 Logger logger1 = LogManager.getLogManager().getLogger(""); 358 Logger logger1b = LogManager.getLogManager().getLogger(""); 359 assertNotNull(logger1); 360 assertNotNull(logger1b); 361 assertEquals(logger1, logger1b); 362 Bridge.changeContext(); 363 Logger logger2 = LogManager.getLogManager().getLogger(""); 364 Logger logger2b = LogManager.getLogManager().getLogger(""); 365 assertNotNull(logger2); 366 assertNotNull(logger2b); 367 assertEquals(logger2, logger2b); 368 assertEquals(logger1, logger2); 369 } 370 } 371 372 public static void testFour() { 373 for (int i=0; i<3 ; i++) { 374 Logger logger1 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 375 Logger logger1b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 376 assertNotNull(logger1); 377 assertNotNull(logger1b); 378 assertEquals(logger1, logger1b); 379 Bridge.changeContext(); 380 381 Logger logger2 = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 382 Logger logger2b = Logger.getLogger(Logger.GLOBAL_LOGGER_NAME); 383 assertNotNull(logger2); 384 assertNotNull(logger2b); 385 assertEquals(logger2, logger2b); 386 387 assertEquals(logger1, logger2); 388 389 Bridge.changeContext(); 390 Logger logger3 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); 391 Logger logger3b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); 392 Bridge.changeContext(); 393 Logger logger4 = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); 394 Logger logger4b = new Bridge.CustomAnonymousLogger(Logger.GLOBAL_LOGGER_NAME); 395 } 396 } 397 398 public static void testFive() { 399 for (int i=0; i<3 ; i++) { 400 Logger logger1 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); 401 Logger logger1b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); 402 assertNotNull(logger1); 403 assertNotNull(logger1b); 404 assertEquals(logger1, logger1b); 405 406 Bridge.changeContext(); 407 408 Logger logger2 = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); 409 Logger logger2b = LogManager.getLogManager().getLogger(Logger.GLOBAL_LOGGER_NAME); 410 assertNotNull(logger2); 411 assertNotNull(logger2b); 412 assertEquals(logger2, logger2b); 413 414 assertEquals(logger1, logger2); 415 } 416 } 417 418 /** 419 * This test is designed to test the behavior of additional LogManager instances. 420 * It must be noted that if the security manager is off, then calling 421 * Bridge.changeContext() has actually no effect - which explains why we have 422 * some differences between the cases security manager on & security manager 423 * off. 424 **/ 425 public static void testSix() { 426 for (int i=0; i<3 ; i++) { 427 Bridge.desactivate(); 428 LogManager manager = new LogManager() {}; 429 Logger logger1 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 430 Logger logger1b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 431 assertNull(logger1); 432 assertNull(logger1b); 433 Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); 434 manager.addLogger(global); 435 Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 436 Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 437 assertNotNull(logger2); 438 assertNotNull(logger2b); 439 assertEquals(logger2, global); 440 assertEquals(logger2b, global); 441 assertNull(manager.getLogger("")); 442 assertNull(manager.getLogger("")); 443 444 Bridge.changeContext(); 445 446 // this is not a supported configuration: 447 // We are in an applet context with several log managers. 448 // We however need to check our assumptions... 449 450 // Applet context => root logger and global logger are not null. 451 // root == LogManager.getLogManager().rootLogger 452 // global == Logger.global 453 454 Logger logger3 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 455 Logger logger3b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 456 assertNotNull(logger3); 457 assertNotNull(logger3b); 458 Logger expected = (System.getSecurityManager() != null 459 ? Logger.getGlobal() 460 : global); 461 assertEquals(logger3, expected); // in applet context, we will not see 462 // the LogManager's custom global logger added above... 463 assertEquals(logger3b, expected); // in applet context, we will not see 464 // the LogManager's custom global logger added above... 465 Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); 466 manager.addLogger(global2); // adding a global logger will not work in applet context 467 // we will always get back the global logger. 468 // this could be considered as a bug... 469 Logger logger4 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 470 Logger logger4b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 471 assertNotNull(logger4); 472 assertNotNull(logger4b); 473 assertEquals(logger4, expected); // adding a global logger will not work in applet context 474 assertEquals(logger4b, expected); // adding a global logger will not work in applet context 475 476 Logger logger5 = manager.getLogger(""); 477 Logger logger5b = manager.getLogger(""); 478 Logger expectedRoot = (System.getSecurityManager() != null 479 ? LogManager.getLogManager().getLogger("") 480 : null); 481 assertEquals(logger5, expectedRoot); 482 assertEquals(logger5b, expectedRoot); 483 484 } 485 } 486 487 /** 488 * This test is designed to test the behavior of additional LogManager instances. 489 * It must be noted that if the security manager is off, then calling 490 * Bridge.changeContext() has actually no effect - which explains why we have 491 * some differences between the cases security manager on & security manager 492 * off. 493 **/ 494 public static void testSeven() { 495 for (int i=0; i<3 ; i++) { 496 Bridge.desactivate(); 497 LogManager manager = new LogManager() {}; 498 Logger logger1 = manager.getLogger(""); 499 Logger logger1b = manager.getLogger(""); 500 assertNull(logger1); 501 assertNull(logger1b); 502 Logger global = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); 503 manager.addLogger(global); 504 Logger logger2 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 505 Logger logger2b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 506 assertNotNull(logger2); 507 assertNotNull(logger2b); 508 assertEquals(logger2, global); 509 assertEquals(logger2b, global); 510 Logger logger3 = manager.getLogger(""); 511 Logger logger3b = manager.getLogger(""); 512 assertNull(logger3); 513 assertNull(logger3b); 514 Logger root = new Bridge.CustomLogger(""); 515 manager.addLogger(root); 516 Logger logger4 = manager.getLogger(""); 517 Logger logger4b = manager.getLogger(""); 518 assertNotNull(logger4); 519 assertNotNull(logger4b); 520 assertEquals(logger4, root); 521 assertEquals(logger4b, root); 522 523 Bridge.changeContext(); 524 525 // this is not a supported configuration: 526 // We are in an applet context with several log managers. 527 // We haowever need to check our assumptions... 528 529 // Applet context => root logger and global logger are not null. 530 // root == LogManager.getLogManager().rootLogger 531 // global == Logger.global 532 533 Logger logger5 = manager.getLogger(""); 534 Logger logger5b = manager.getLogger(""); 535 Logger expectedRoot = (System.getSecurityManager() != null 536 ? LogManager.getLogManager().getLogger("") 537 : root); 538 539 assertNotNull(logger5); 540 assertNotNull(logger5b); 541 assertEquals(logger5, expectedRoot); 542 assertEquals(logger5b, expectedRoot); 543 if (System.getSecurityManager() != null) { 544 assertNotEquals(logger5, root); 545 assertNotEquals(logger5b, root); 546 } 547 548 Logger global2 = new Bridge.CustomLogger(Logger.GLOBAL_LOGGER_NAME); 549 manager.addLogger(global2); // adding a global logger will not work in applet context 550 // we will always get back the global logger. 551 // this could be considered as a bug... 552 Logger logger6 = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 553 Logger logger6b = manager.getLogger(Logger.GLOBAL_LOGGER_NAME); 554 Logger expectedGlobal = (System.getSecurityManager() != null 555 ? Logger.getGlobal() 556 : global); 557 assertNotNull(logger6); 558 assertNotNull(logger6b); 559 assertEquals(logger6, expectedGlobal); // adding a global logger will not work in applet context 560 assertEquals(logger6b, expectedGlobal); // adding a global logger will not work in applet context 561 562 Logger root2 = new Bridge.CustomLogger(""); 563 manager.addLogger(root2); // adding a root logger will not work in applet context 564 // we will always get back the default manager's root logger. 565 // this could be considered as a bug... 566 Logger logger7 = manager.getLogger(""); 567 Logger logger7b = manager.getLogger(""); 568 assertNotNull(logger7); 569 assertNotNull(logger7b); 570 assertEquals(logger7, expectedRoot); // adding a global logger will not work in applet context 571 assertEquals(logger7b, expectedRoot); // adding a global logger will not work in applet context 572 assertNotEquals(logger7, root2); 573 assertNotEquals(logger7b, root2); 574 } 575 } 576 577 public static void testParent(Logger logger) { 578 Logger l = logger; 579 while (l.getParent() != null) { 580 l = l.getParent(); 581 } 582 assertEquals("", l.getName()); 583 } 584 585 public static class TestError extends RuntimeException { 586 public TestError(String msg) { 587 super(msg); 588 } 589 } 590 591 public static void assertNotNull(Object obj) { 592 if (obj == null) throw new NullPointerException(); 593 } 594 595 public static void assertNull(Object obj) { 596 if (obj != null) throw new TestError("Null expected, got "+obj); 597 } 598 599 public static void assertEquals(Object o1, Object o2) { 600 if (o1 != o2) { 601 throw new TestError(o1 + " != " + o2); 602 } 603 } 604 605 public static void assertNotEquals(Object o1, Object o2) { 606 if (o1 == o2) { 607 throw new TestError(o1 + " == " + o2); 608 } 609 } 610 }