1 // Copied from ICU4J 57.1 2 /* 3 ******************************************************************************* 4 * Copyright (C) 1996-2015, International Business Machines Corporation and * 5 * others. All Rights Reserved. * 6 ******************************************************************************* 7 */ 8 package com.ibm.icu.dev.test; 9 10 import java.io.ByteArrayOutputStream; 11 import java.io.CharArrayWriter; 12 import java.io.IOException; 13 import java.io.OutputStream; 14 import java.io.PrintStream; 15 import java.io.PrintWriter; 16 import java.io.Writer; 17 import java.lang.reflect.Field; 18 import java.lang.reflect.InvocationTargetException; 19 import java.lang.reflect.Method; 20 import java.text.DecimalFormat; 21 import java.text.NumberFormat; 22 import java.util.ArrayList; 23 import java.util.Arrays; 24 import java.util.Comparator; 25 import java.util.HashMap; 26 import java.util.List; 27 import java.util.Locale; 28 import java.util.Map; 29 import java.util.Map.Entry; 30 import java.util.MissingResourceException; 31 import java.util.Random; 32 import java.util.TreeMap; 33 34 import com.ibm.icu.util.TimeZone; 35 import com.ibm.icu.util.ULocale; 36 37 /** 38 * TestFmwk is a base class for tests that can be run conveniently from the 39 * command line as well as under the Java test harness. 40 * <p> 41 * Sub-classes implement a set of methods named Test <something>. Each of these 42 * methods performs some test. Test methods should indicate errors by calling 43 * either err or errln. This will increment the errorCount field and may 44 * optionally print a message to the log. Debugging information may also be 45 * added to the log via the log and logln methods. These methods will add their 46 * arguments to the log only if the test is being run in verbose mode. 47 */ 48 public class TestFmwk extends AbstractTestLog { 49 /** 50 * The default time zone for all of our tests. Used in Target.run(); 51 */ 52 private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("America/Los_Angeles"); 53 54 /** 55 * The default locale used for all of our tests. Used in Target.run(); 56 */ 57 private final static Locale defaultLocale = Locale.US; 58 59 public static final class TestFmwkException extends Exception { 60 /** 61 * For serialization 62 */ 63 private static final long serialVersionUID = -3051148210247229194L; 64 TestFmwkException(String msg)65 TestFmwkException(String msg) { 66 super(msg); 67 } 68 } 69 70 static final class ICUTestError extends RuntimeException { 71 /** 72 * For serialization 73 */ 74 private static final long serialVersionUID = 6170003850185143046L; 75 ICUTestError(String msg)76 ICUTestError(String msg) { 77 super(msg); 78 } 79 } 80 81 // Handling exception thrown during text execution (not including 82 // RuntimeException thrown by errln). handleException(Throwable e)83 protected void handleException(Throwable e){ 84 Throwable ex = e.getCause(); 85 if(ex == null){ 86 ex = e; 87 } 88 if (ex instanceof OutOfMemoryError) { 89 // Once OOM happens, it does not make sense to run 90 // the rest of test cases. 91 throw new RuntimeException(ex); 92 } 93 if (ex instanceof ICUTestError) { 94 // ICUTestError is one produced by errln. 95 // We don't need to include useless stack trace information for 96 // such case. 97 return; 98 } 99 if (ex instanceof ExceptionInInitializerError){ 100 ex = ((ExceptionInInitializerError)ex).getException(); 101 } 102 103 //Stack trace 104 CharArrayWriter caw = new CharArrayWriter(); 105 PrintWriter pw = new PrintWriter(caw); 106 ex.printStackTrace(pw); 107 pw.close(); 108 String msg = caw.toString(); 109 110 //System.err.println("TF handleException msg: " + msg); 111 if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError || 112 msg.indexOf("java.util.MissingResourceException") >= 0) { 113 if (params.warnings || params.nodata) { 114 warnln(ex.toString() + '\n' + msg); 115 } else { 116 errln(ex.toString() + '\n' + msg); 117 } 118 } else { 119 errln(ex.toString() + '\n' + msg); 120 } 121 } 122 // use this instead of new random so we get a consistent seed 123 // for our tests createRandom()124 protected Random createRandom() { 125 return new Random(params.seed); 126 } 127 128 /** 129 * A test that has no test methods itself, but instead runs other tests. 130 * 131 * This overrides methods are getTargets and getSubtest from TestFmwk. 132 * 133 * If you want the default behavior, pass an array of class names and an 134 * optional description to the constructor. The named classes must extend 135 * TestFmwk. If a provided name doesn't include a ".", package name is 136 * prefixed to it (the package of the current test is used if none was 137 * provided in the constructor). The resulting full name is used to 138 * instantiate an instance of the class using the default constructor. 139 * 140 * Class names are resolved to classes when getTargets or getSubtest is 141 * called. This allows instances of TestGroup to be compiled and run without 142 * all the targets they would normally invoke being available. 143 */ 144 public static abstract class TestGroup extends TestFmwk { 145 private String defaultPackage; 146 private String[] names; 147 private String description; 148 149 private Class[] tests; // deferred init 150 151 /** 152 * Constructor that takes a default package name and a list of class 153 * names. Adopts and modifies the classname list 154 */ TestGroup(String defaultPackage, String[] classnames, String description)155 protected TestGroup(String defaultPackage, String[] classnames, 156 String description) { 157 if (classnames == null) { 158 throw new IllegalStateException("classnames must not be null"); 159 } 160 161 if (defaultPackage == null) { 162 defaultPackage = getClass().getPackage().getName(); 163 } 164 defaultPackage = defaultPackage + "."; 165 166 this.defaultPackage = defaultPackage; 167 this.names = classnames; 168 this.description = description; 169 } 170 171 /** 172 * Constructor that takes a list of class names and a description, and 173 * uses the package for this class as the default package. 174 */ TestGroup(String[] classnames, String description)175 protected TestGroup(String[] classnames, String description) { 176 this(null, classnames, description); 177 } 178 179 /** 180 * Constructor that takes a list of class names, and uses the package 181 * for this class as the default package. 182 */ TestGroup(String[] classnames)183 protected TestGroup(String[] classnames) { 184 this(null, classnames, null); 185 } 186 getDescription()187 protected String getDescription() { 188 return description; 189 } 190 getTargets(String targetName)191 protected Target getTargets(String targetName) { 192 Target target = null; 193 if (targetName != null) { 194 finishInit(); // hmmm, want to get subtest without initializing 195 // all tests 196 197 try { 198 TestFmwk test = getSubtest(targetName); 199 if (test != null) { 200 target = test.new ClassTarget(); 201 } else { 202 target = this.new Target(targetName); 203 } 204 } catch (TestFmwkException e) { 205 target = this.new Target(targetName); 206 } 207 } else if (params.doRecurse()) { 208 finishInit(); 209 boolean groupOnly = params.doRecurseGroupsOnly(); 210 for (int i = names.length; --i >= 0;) { 211 Target newTarget = null; 212 Class cls = tests[i]; 213 if (cls == null) { // hack no warning for missing tests 214 if (params.warnings) { 215 continue; 216 } 217 newTarget = this.new Target(names[i]); 218 } else { 219 TestFmwk test = getSubtest(i, groupOnly); 220 if (test != null) { 221 newTarget = test.new ClassTarget(); 222 } else { 223 if (groupOnly) { 224 newTarget = this.new EmptyTarget(names[i]); 225 } else { 226 newTarget = this.new Target(names[i]); 227 } 228 } 229 } 230 if (newTarget != null) { 231 newTarget.setNext(target); 232 target = newTarget; 233 } 234 } 235 } 236 237 return target; 238 } getSubtest(String testName)239 protected TestFmwk getSubtest(String testName) throws TestFmwkException { 240 finishInit(); 241 242 for (int i = 0; i < names.length; ++i) { 243 if (names[i].equalsIgnoreCase(testName)) { // allow 244 // case-insensitive 245 // matching 246 return getSubtest(i, false); 247 } 248 } 249 throw new TestFmwkException(testName); 250 } 251 getSubtest(int i, boolean groupOnly)252 private TestFmwk getSubtest(int i, boolean groupOnly) { 253 Class cls = tests[i]; 254 if (cls != null) { 255 if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) { 256 return null; 257 } 258 259 try { 260 TestFmwk subtest = (TestFmwk) cls.newInstance(); 261 subtest.params = params; 262 return subtest; 263 } catch (InstantiationException e) { 264 throw new IllegalStateException(e.getMessage()); 265 } catch (IllegalAccessException e) { 266 throw new IllegalStateException(e.getMessage()); 267 } 268 } 269 return null; 270 } 271 finishInit()272 private void finishInit() { 273 if (tests == null) { 274 tests = new Class[names.length]; 275 276 for (int i = 0; i < names.length; ++i) { 277 String name = names[i]; 278 if (name.indexOf('.') == -1) { 279 name = defaultPackage + name; 280 } 281 try { 282 Class cls = Class.forName(name); 283 if (!TestFmwk.class.isAssignableFrom(cls)) { 284 throw new IllegalStateException("class " + name 285 + " does not extend TestFmwk"); 286 } 287 288 tests[i] = cls; 289 names[i] = getClassTargetName(cls); 290 } catch (ClassNotFoundException e) { 291 // leave tests[i] null and name as classname 292 } 293 } 294 } 295 } 296 } 297 298 /** 299 * The default target is invalid. 300 */ 301 public class Target { 302 private Target next; 303 public final String name; 304 Target(String name)305 public Target(String name) { 306 this.name = name; 307 } 308 setNext(Target next)309 public Target setNext(Target next) { 310 this.next = next; 311 return this; 312 } 313 getNext()314 public Target getNext() { 315 return next; 316 } 317 append(Target targets)318 public Target append(Target targets) { 319 Target t = this; 320 while(t.next != null) { 321 t = t.next; 322 } 323 t.next = targets; 324 return this; 325 } 326 run()327 public void run() throws Exception { 328 int f = filter(); 329 if (f == -1) { 330 ++params.invalidCount; 331 } else { 332 Locale.setDefault(defaultLocale); 333 TimeZone.setDefault(defaultTimeZone); 334 335 if (!validate()) { 336 params.writeTestInvalid(name, params.nodata); 337 } else { 338 params.push(name, getDescription(), f == 1); 339 execute(); 340 params.pop(); 341 } 342 } 343 } 344 filter()345 protected int filter() { 346 return params.filter(name); 347 } 348 validate()349 protected boolean validate() { 350 return false; 351 } 352 getDescription()353 protected String getDescription() { 354 return null; 355 } 356 execute()357 protected void execute() throws Exception{ 358 } 359 } 360 361 public class EmptyTarget extends Target { EmptyTarget(String name)362 public EmptyTarget(String name) { 363 super(name); 364 } 365 validate()366 protected boolean validate() { 367 return true; 368 } 369 } 370 371 public class MethodTarget extends Target { 372 private Method testMethod; 373 MethodTarget(String name, Method method)374 public MethodTarget(String name, Method method) { 375 super(name); 376 testMethod = method; 377 } 378 validate()379 protected boolean validate() { 380 return testMethod != null && validateMethod(name); 381 } 382 getDescription()383 protected String getDescription() { 384 return getMethodDescription(name); 385 } 386 execute()387 protected void execute() throws Exception{ 388 if (params.inDocMode()) { 389 // nothing to execute 390 } else if (!params.stack.included) { 391 ++params.invalidCount; 392 } else { 393 final Object[] NO_ARGS = new Object[0]; 394 try { 395 ++params.testCount; 396 init(); 397 testMethod.invoke(TestFmwk.this, NO_ARGS); 398 } catch (IllegalAccessException e) { 399 errln("Can't access test method " + testMethod.getName()); 400 } catch (Exception e) { 401 handleException(e); 402 } 403 404 } 405 // If non-exhaustive, check if the method target 406 // takes excessive time. 407 if (params.inclusion <= 5) { 408 double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000; 409 if (deltaSec > params.maxTargetSec) { 410 if (params.timeLog == null) { 411 params.timeLog = new StringBuffer(); 412 } 413 params.stack.appendPath(params.timeLog); 414 params.timeLog.append(" (" + deltaSec + "s" + ")\n"); 415 } 416 } 417 } 418 getStackTrace(InvocationTargetException e)419 protected String getStackTrace(InvocationTargetException e) { 420 ByteArrayOutputStream bs = new ByteArrayOutputStream(); 421 PrintStream ps = new PrintStream(bs); 422 e.getTargetException().printStackTrace(ps); 423 return bs.toString(); 424 } 425 } 426 427 public class ClassTarget extends Target { 428 String targetName; 429 ClassTarget()430 public ClassTarget() { 431 this(null); 432 } 433 ClassTarget(String targetName)434 public ClassTarget(String targetName) { 435 super(getClassTargetName(TestFmwk.this.getClass())); 436 this.targetName = targetName; 437 } 438 validate()439 protected boolean validate() { 440 return TestFmwk.this.validate(); 441 } 442 getDescription()443 protected String getDescription() { 444 return TestFmwk.this.getDescription(); 445 } 446 execute()447 protected void execute() throws Exception { 448 params.indentLevel++; 449 Target target = randomize(getTargets(targetName)); 450 while (target != null) { 451 target.run(); 452 target = target.next; 453 } 454 params.indentLevel--; 455 } 456 randomize(Target t)457 private Target randomize(Target t) { 458 if (t != null && t.getNext() != null) { 459 ArrayList list = new ArrayList(); 460 while (t != null) { 461 list.add(t); 462 t = t.getNext(); 463 } 464 465 Target[] arr = (Target[]) list.toArray(new Target[list.size()]); 466 467 if (true) { // todo - add to params? 468 // different jvms return class methods in different orders, 469 // so we sort them (always, and then randomize them, so that 470 // forcing a seed will also work across jvms). 471 Arrays.sort(arr, new Comparator() { 472 public int compare(Object lhs, Object rhs) { 473 // sort in reverse order, later we link up in 474 // forward order 475 return ((Target) rhs).name 476 .compareTo(((Target) lhs).name); 477 } 478 }); 479 480 // t is null to start, ends up as first element 481 // (arr[arr.length-1]) 482 for (int i = 0; i < arr.length; ++i) { 483 t = arr[i].setNext(t); // relink in forward order 484 } 485 } 486 487 if (params.random != null) { 488 t = null; // reset t to null 489 Random r = params.random; 490 for (int i = arr.length; --i >= 1;) { 491 int x = r.nextInt(i + 1); 492 t = arr[x].setNext(t); 493 arr[x] = arr[i]; 494 } 495 496 t = arr[0].setNext(t); // new first element 497 } 498 } 499 500 return t; 501 } 502 } 503 504 //------------------------------------------------------------------------ 505 // Everything below here is boilerplate code that makes it possible 506 // to add a new test by simply adding a function to an existing class 507 //------------------------------------------------------------------------ 508 TestFmwk()509 protected TestFmwk() { 510 } 511 init()512 protected void init() throws Exception{ 513 } 514 515 /** 516 * Parse arguments into a TestParams object and a collection of target 517 * paths. If there was an error parsing the TestParams, print usage and exit 518 * with -1. Otherwise, call resolveTarget(TestParams, String) for each path, 519 * and run the returned target. After the last test returns, if prompt is 520 * set, prompt and wait for input from stdin. Finally, exit with number of 521 * errors. 522 * 523 * This method never returns, since it always exits with System.exit(); 524 */ run(String[] args)525 public void run(String[] args) { 526 System.exit(run(args, new PrintWriter(System.out))); 527 } 528 529 /** 530 * Like run(String[]) except this allows you to specify the error log. 531 * Unlike run(String[]) this returns the error code as a result instead of 532 * calling System.exit(). 533 */ run(String[] args, PrintWriter log)534 public int run(String[] args, PrintWriter log) { 535 boolean prompt = false; 536 int wx = 0; 537 for (int i = 0; i < args.length; ++i) { 538 String arg = args[i]; 539 if (arg.equals("-p") || arg.equals("-prompt")) { 540 prompt = true; 541 } else { 542 if (wx < i) { 543 args[wx] = arg; 544 } 545 wx++; 546 } 547 } 548 while (wx < args.length) { 549 args[wx++] = null; 550 } 551 552 TestParams localParams = TestParams.create(args, log); 553 if (localParams == null) { 554 return -1; 555 } 556 557 int errorCount = runTests(localParams, args); 558 559 if (localParams.seed != 0) { 560 localParams.log.println("-random:" + localParams.seed); 561 localParams.log.flush(); 562 } 563 564 if (localParams.timeLog != null && localParams.timeLog.length() > 0) { 565 localParams.log.println("\nTest cases taking excessive time (>" + 566 localParams.maxTargetSec + "s):"); 567 localParams.log.println(localParams.timeLog.toString()); 568 } 569 570 if (localParams.knownIssues != null) { 571 localParams.log.println("\nKnown Issues:"); 572 for (Entry<String, List<String>> entry : localParams.knownIssues.entrySet()) { 573 String ticketLink = entry.getKey(); 574 localParams.log.println("[" + ticketLink + "]"); 575 for (String line : entry.getValue()) { 576 localParams.log.println(" - " + line); 577 } 578 } 579 } 580 581 if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) { 582 localParams.log.println("\nError summary:"); 583 localParams.log.println(localParams.errorSummary.toString()); 584 } 585 586 if (errorCount > 0) { 587 localParams.log.println("\n<< " + errorCount+ " TEST(S) FAILED >>"); 588 } else { 589 localParams.log.println("\n<< ALL TESTS PASSED >>"); 590 } 591 592 if (prompt) { 593 System.out.println("Hit RETURN to exit..."); 594 System.out.flush(); 595 try { 596 System.in.read(); 597 } catch (IOException e) { 598 localParams.log.println("Exception: " + e.toString() + e.getMessage()); 599 } 600 } 601 602 localParams.log.flush(); 603 604 return errorCount; 605 } 606 runTests(TestParams _params, String[] tests)607 public int runTests(TestParams _params, String[] tests) { 608 int ec = 0; 609 610 StringBuffer summary = null; 611 try { 612 if (tests.length == 0 || tests[0] == null) { // no args 613 _params.init(); 614 resolveTarget(_params).run(); 615 ec = _params.errorCount; 616 } else { 617 for (int i = 0; i < tests.length ; ++i) { 618 if (tests[i] == null) continue; 619 620 if (i > 0) { 621 _params.log.println(); 622 } 623 624 _params.init(); 625 resolveTarget(_params, tests[i]).run(); 626 ec += _params.errorCount; 627 628 if (_params.errorSummary != null && _params.errorSummary.length() > 0) { 629 if (summary == null) { 630 summary = new StringBuffer(); 631 } 632 summary.append("\nTest Root: " + tests[i] + "\n"); 633 summary.append(_params.errorSummary()); 634 } 635 } 636 _params.errorSummary = summary; 637 } 638 } catch (Exception e) { 639 // We should normally not get here because 640 // MethodTarget.execute() calls handleException(). 641 ec++; 642 _params.log.println("\nencountered a test failure, exiting\n" + e); 643 e.printStackTrace(_params.log); 644 } 645 646 return ec; 647 } 648 649 /** 650 * Return a ClassTarget for this test. Params is set on this test. 651 */ resolveTarget(TestParams paramsArg)652 public Target resolveTarget(TestParams paramsArg) { 653 this.params = paramsArg; 654 return new ClassTarget(); 655 } 656 657 /** 658 * Resolve a path from this test to a target. If this test has subtests, and 659 * the path contains '/', the portion before the '/' is resolved to a 660 * subtest, until the path is consumed or the test has no subtests. Returns 661 * a ClassTarget created using the resolved test and remaining path (which 662 * ought to be null or a method name). Params is set on the target's test. 663 */ resolveTarget(TestParams paramsArg, String targetPath)664 public Target resolveTarget(TestParams paramsArg, String targetPath) { 665 TestFmwk test = this; 666 test.params = paramsArg; 667 668 if (targetPath != null) { 669 if (targetPath.length() == 0) { 670 targetPath = null; 671 } else { 672 int p = 0; 673 int e = targetPath.length(); 674 675 // trim all leading and trailing '/' 676 while (targetPath.charAt(p) == '/') { 677 ++p; 678 } 679 while (e > p && targetPath.charAt(e - 1) == '/') { 680 --e; 681 } 682 if (p > 0 || e < targetPath.length()) { 683 targetPath = targetPath.substring(p, e - p); 684 p = 0; 685 e = targetPath.length(); 686 } 687 688 try { 689 for (;;) { 690 int n = targetPath.indexOf('/'); 691 String prefix = n == -1 ? targetPath : targetPath 692 .substring(0, n); 693 TestFmwk subtest = test.getSubtest(prefix); 694 695 if (subtest == null) { 696 break; 697 } 698 699 test = subtest; 700 701 if (n == -1) { 702 targetPath = null; 703 break; 704 } 705 706 targetPath = targetPath.substring(n + 1); 707 } 708 } catch (TestFmwkException ex) { 709 return test.new Target(targetPath); 710 } 711 } 712 } 713 714 return test.new ClassTarget(targetPath); 715 } 716 717 /** 718 * Return true if we can run this test (allows test to inspect jvm, 719 * environment, params before running) 720 */ validate()721 protected boolean validate() { 722 return true; 723 } 724 725 /** 726 * Return the targets for this test. If targetName is null, return all 727 * targets, otherwise return a target for just that name. The returned 728 * target can be null. 729 * 730 * The default implementation returns a MethodTarget for each public method 731 * of the object's class whose name starts with "Test" or "test". 732 */ getTargets(String targetName)733 protected Target getTargets(String targetName) { 734 return getClassTargets(getClass(), targetName); 735 } 736 getClassTargets(Class cls, String targetName)737 protected Target getClassTargets(Class cls, String targetName) { 738 if (cls == null) { 739 return null; 740 } 741 742 Target target = null; 743 if (targetName != null) { 744 try { 745 Method method = cls.getMethod(targetName, (Class[])null); 746 target = new MethodTarget(targetName, method); 747 } catch (NoSuchMethodException e) { 748 if (!inheritTargets()) { 749 return new Target(targetName); // invalid target 750 } 751 } catch (SecurityException e) { 752 return null; 753 } 754 } else { 755 if (params.doMethods()) { 756 Method[] methods = cls.getDeclaredMethods(); 757 for (int i = methods.length; --i >= 0;) { 758 String name = methods[i].getName(); 759 if (name.startsWith("Test") || name.startsWith("test")) { 760 target = new MethodTarget(name, methods[i]) 761 .setNext(target); 762 } 763 } 764 } 765 } 766 767 if (inheritTargets()) { 768 Target parentTarget = getClassTargets(cls.getSuperclass(), targetName); 769 if (parentTarget == null) { 770 return target; 771 } 772 if (target == null) { 773 return parentTarget; 774 } 775 return parentTarget.append(target); 776 } 777 778 return target; 779 } 780 inheritTargets()781 protected boolean inheritTargets() { 782 return false; 783 } 784 getDescription()785 protected String getDescription() { 786 return null; 787 } 788 validateMethod(String name)789 protected boolean validateMethod(String name) { 790 return true; 791 } 792 getMethodDescription(String name)793 protected String getMethodDescription(String name) { 794 return null; 795 } 796 797 // method tests have no subtests, group tests override getSubtest(String prefix)798 protected TestFmwk getSubtest(String prefix) throws TestFmwkException { 799 return null; 800 } 801 isVerbose()802 public boolean isVerbose() { 803 return params.verbose; 804 } 805 noData()806 public boolean noData() { 807 return params.nodata; 808 } 809 isTiming()810 public boolean isTiming() { 811 return params.timing < Long.MAX_VALUE; 812 } 813 isMemTracking()814 public boolean isMemTracking() { 815 return params.memusage; 816 } 817 818 /** 819 * 0 = fewest tests, 5 is normal build, 10 is most tests 820 */ getInclusion()821 public int getInclusion() { 822 return params.inclusion; 823 } 824 isModularBuild()825 public boolean isModularBuild() { 826 return params.warnings; 827 } 828 isQuick()829 public boolean isQuick() { 830 return params.inclusion == 0; 831 } 832 msg(String message, int level, boolean incCount, boolean newln)833 public void msg(String message, int level, boolean incCount, boolean newln) { 834 params.msg(message, level, incCount, newln); 835 } 836 837 static final String ICU_TRAC_URL = "http://bugs.icu-project.org/trac/ticket/"; 838 static final String CLDR_TRAC_URL = "http://unicode.org/cldr/trac/ticket/"; 839 static final String CLDR_TICKET_PREFIX = "cldrbug:"; 840 841 /** 842 * Log the known issue. 843 * This method returns true unless -prop:logKnownIssue=no is specified 844 * in the argument list. 845 * 846 * @param ticket A ticket number string. For an ICU ticket, use numeric characters only, 847 * such as "10245". For a CLDR ticket, use prefix "cldrbug:" followed by ticket number, 848 * such as "cldrbug:5013". 849 * @param comment Additional comment, or null 850 * @return true unless -prop:logKnownIssue=no is specified in the test command line argument. 851 */ logKnownIssue(String ticket, String comment)852 public boolean logKnownIssue(String ticket, String comment) { 853 if (!getBooleanProperty("logKnownIssue", true)) { 854 return false; 855 } 856 857 StringBuffer descBuf = new StringBuffer(); 858 params.stack.appendPath(descBuf); 859 if (comment != null && comment.length() > 0) { 860 descBuf.append(" (" + comment + ")"); 861 } 862 String description = descBuf.toString(); 863 864 String ticketLink = "Unknown Ticket"; 865 if (ticket != null && ticket.length() > 0) { 866 boolean isCldr = false; 867 ticket = ticket.toLowerCase(Locale.ENGLISH); 868 if (ticket.startsWith(CLDR_TICKET_PREFIX)) { 869 isCldr = true; 870 ticket = ticket.substring(CLDR_TICKET_PREFIX.length()); 871 } 872 ticketLink = (isCldr ? CLDR_TRAC_URL : ICU_TRAC_URL) + ticket; 873 } 874 875 if (params.knownIssues == null) { 876 params.knownIssues = new TreeMap<String, List<String>>(); 877 } 878 List<String> lines = params.knownIssues.get(ticketLink); 879 if (lines == null) { 880 lines = new ArrayList<String>(); 881 params.knownIssues.put(ticketLink, lines); 882 } 883 if (!lines.contains(description)) { 884 lines.add(description); 885 } 886 887 return true; 888 } 889 getErrorCount()890 protected int getErrorCount() { 891 return params.errorCount; 892 } 893 getProperty(String key)894 public String getProperty(String key) { 895 String val = null; 896 if (key != null && key.length() > 0 && params.props != null) { 897 val = (String)params.props.get(key.toLowerCase()); 898 } 899 return val; 900 } 901 getBooleanProperty(String key, boolean defVal)902 public boolean getBooleanProperty(String key, boolean defVal) { 903 String s = getProperty(key); 904 if (s != null) { 905 if (s.equalsIgnoreCase("yes") || s.equals("true")) { 906 return true; 907 } 908 if (s.equalsIgnoreCase("no") || s.equalsIgnoreCase("false")) { 909 return false; 910 } 911 } 912 return defVal; 913 } 914 safeGetTimeZone(String id)915 protected TimeZone safeGetTimeZone(String id) { 916 TimeZone tz = TimeZone.getTimeZone(id); 917 if (tz == null) { 918 // should never happen 919 errln("FAIL: TimeZone.getTimeZone(" + id + ") => null"); 920 } 921 if (!tz.getID().equals(id)) { 922 warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID()); 923 } 924 return tz; 925 } 926 927 /** 928 * Print a usage message for this test class. 929 */ usage()930 public void usage() { 931 usage(new PrintWriter(System.out), getClass().getName()); 932 } 933 usage(PrintWriter pw, String className)934 public static void usage(PrintWriter pw, String className) { 935 pw.println("Usage: " + className + " option* target*"); 936 pw.println(); 937 pw.println("Options:"); 938 pw.println(" -d[escribe] Print a short descriptive string for this test and all"); 939 pw.println(" listed targets."); 940 pw.println(" -e<n> Set exhaustiveness from 0..10. Default is 0, fewest tests.\n" 941 + " To run all tests, specify -e10. Giving -e with no <n> is\n" 942 + " the same as -e5."); 943 pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n" 944 + " <str> is of the form ['^']text[','['^']text].\n" 945 + " Each string delimited by ',' is a separate filter argument.\n" 946 + " If '^' is prepended to an argument, its matches are excluded.\n" 947 + " Filtering operates on test groups as well as tests, if a test\n" 948 + " group is included, all its subtests that are not excluded will\n" 949 + " be run. Examples:\n" 950 + " -filter:A -- only tests matching A are run. If A matches a group,\n" 951 + " all subtests of this group are run.\n" 952 + " -filter:^A -- all tests except those matching A are run. If A matches\n" 953 + " a group, no subtest of that group will be run.\n" 954 + " -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n" 955 + " Note: Filters are case insensitive."); 956 pw.println(" -h[elp] Print this help text and exit."); 957 pw.println(" -hex Display non-ASCII characters in hexadecimal format"); 958 pw.println(" -l[ist] List immediate targets of this test"); 959 pw.println(" -la, -listAll List immediate targets of this test, and all subtests"); 960 pw.println(" -le, -listExaustive List all subtests and targets"); 961 // don't know how to get useful numbers for memory usage using java API 962 // calls 963 // pw.println(" -m[emory] print memory usage and force gc for 964 // each test"); 965 pw.println(" -n[othrow] Message on test failure rather than exception.\n" 966 + " This is the default behavior and has no effects on ICU 55+."); 967 pw.println(" -p[rompt] Prompt before exiting"); 968 pw.println(" -prop:<key>=<value> Set optional property used by this test"); 969 pw.println(" -q[uiet] Do not show warnings"); 970 pw.println(" -r[andom][:<n>] If present, randomize targets. If n is present,\n" 971 + " use it as the seed. If random is not set, targets will\n" 972 + " be in alphabetical order to ensure cross-platform consistency."); 973 pw.println(" -s[ilent] No output except error summary or exceptions."); 974 pw.println(" -tfilter:<str> Transliterator Test filter of ids."); 975 pw.println(" -t[ime]:<n> Print elapsed time only for tests exceeding n milliseconds."); 976 pw.println(" -v[erbose] Show log messages"); 977 pw.println(" -u[nicode] Don't escape error or log messages (Default on ICU 55+)"); 978 pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings."); 979 pw.println(" -nodata | -nd Do not warn if resource data is not present."); 980 pw.println(); 981 pw.println(" If a list or describe option is provided, no tests are run."); 982 pw.println(); 983 pw.println("Targets:"); 984 pw.println(" If no target is specified, all targets for this test are run."); 985 pw.println(" If a target contains no '/' characters, and matches a target"); 986 pw.println(" of this test, the target is run. Otherwise, the part before the"); 987 pw.println(" '/' is used to match a subtest, which then evaluates the"); 988 pw.println(" remainder of the target as above. Target matching is case-insensitive."); 989 pw.println(); 990 pw.println(" If multiple targets are provided, each is executed in order."); 991 pw.flush(); 992 } hex(char[] s)993 public static String hex(char[] s){ 994 StringBuffer result = new StringBuffer(); 995 for (int i = 0; i < s.length; ++i) { 996 if (i != 0) result.append(','); 997 result.append(hex(s[i])); 998 } 999 return result.toString(); 1000 } hex(byte[] s)1001 public static String hex(byte[] s){ 1002 StringBuffer result = new StringBuffer(); 1003 for (int i = 0; i < s.length; ++i) { 1004 if (i != 0) result.append(','); 1005 result.append(hex(s[i])); 1006 } 1007 return result.toString(); 1008 } hex(char ch)1009 public static String hex(char ch) { 1010 StringBuffer result = new StringBuffer(); 1011 String foo = Integer.toString(ch, 16).toUpperCase(); 1012 for (int i = foo.length(); i < 4; ++i) { 1013 result.append('0'); 1014 } 1015 return result + foo; 1016 } 1017 hex(int ch)1018 public static String hex(int ch) { 1019 StringBuffer result = new StringBuffer(); 1020 String foo = Integer.toString(ch, 16).toUpperCase(); 1021 for (int i = foo.length(); i < 4; ++i) { 1022 result.append('0'); 1023 } 1024 return result + foo; 1025 } 1026 hex(CharSequence s)1027 public static String hex(CharSequence s) { 1028 StringBuilder result = new StringBuilder(); 1029 for (int i = 0; i < s.length(); ++i) { 1030 if (i != 0) 1031 result.append(','); 1032 result.append(hex(s.charAt(i))); 1033 } 1034 return result.toString(); 1035 } 1036 prettify(CharSequence s)1037 public static String prettify(CharSequence s) { 1038 StringBuilder result = new StringBuilder(); 1039 int ch; 1040 for (int i = 0; i < s.length(); i += Character.charCount(ch)) { 1041 ch = Character.codePointAt(s, i); 1042 if (ch > 0xfffff) { 1043 result.append("\\U00"); 1044 result.append(hex(ch)); 1045 } else if (ch > 0xffff) { 1046 result.append("\\U000"); 1047 result.append(hex(ch)); 1048 } else if (ch < 0x20 || 0x7e < ch) { 1049 result.append("\\u"); 1050 result.append(hex(ch)); 1051 } else { 1052 result.append((char) ch); 1053 } 1054 1055 } 1056 return result.toString(); 1057 } 1058 1059 private static java.util.GregorianCalendar cal; 1060 1061 /** 1062 * Return a Date given a year, month, and day of month. This is similar to 1063 * new Date(y-1900, m, d). It uses the default time zone at the time this 1064 * method is first called. 1065 * 1066 * @param year 1067 * use 2000 for 2000, unlike new Date() 1068 * @param month 1069 * use Calendar.JANUARY etc. 1070 * @param dom 1071 * day of month, 1-based 1072 * @return a Date object for the given y/m/d 1073 */ getDate(int year, int month, int dom)1074 protected static synchronized java.util.Date getDate(int year, int month, 1075 int dom) { 1076 if (cal == null) { 1077 cal = new java.util.GregorianCalendar(); 1078 } 1079 cal.clear(); 1080 cal.set(year, month, dom); 1081 return cal.getTime(); 1082 } 1083 1084 public static class NullWriter extends PrintWriter { NullWriter()1085 public NullWriter() { 1086 super(System.out, false); 1087 } write(int c)1088 public void write(int c) { 1089 } write(char[] buf, int off, int len)1090 public void write(char[] buf, int off, int len) { 1091 } write(String s, int off, int len)1092 public void write(String s, int off, int len) { 1093 } println()1094 public void println() { 1095 } 1096 } 1097 1098 public static class ASCIIWriter extends PrintWriter { 1099 private StringBuffer buffer = new StringBuffer(); 1100 1101 // Characters that we think are printable but that escapeUnprintable 1102 // doesn't 1103 private static final String PRINTABLES = "\t\n\r"; 1104 ASCIIWriter(Writer w, boolean autoFlush)1105 public ASCIIWriter(Writer w, boolean autoFlush) { 1106 super(w, autoFlush); 1107 } 1108 ASCIIWriter(OutputStream os, boolean autoFlush)1109 public ASCIIWriter(OutputStream os, boolean autoFlush) { 1110 super(os, autoFlush); 1111 } 1112 write(int c)1113 public void write(int c) { 1114 synchronized (lock) { 1115 buffer.setLength(0); 1116 if (PRINTABLES.indexOf(c) < 0 1117 && TestUtil.escapeUnprintable(buffer, c)) { 1118 super.write(buffer.toString()); 1119 } else { 1120 super.write(c); 1121 } 1122 } 1123 } 1124 write(char[] buf, int off, int len)1125 public void write(char[] buf, int off, int len) { 1126 synchronized (lock) { 1127 buffer.setLength(0); 1128 int limit = off + len; 1129 while (off < limit) { 1130 int c = UTF16Util.charAt(buf, 0, buf.length, off); 1131 off += UTF16Util.getCharCount(c); 1132 if (PRINTABLES.indexOf(c) < 0 1133 && TestUtil.escapeUnprintable(buffer, c)) { 1134 super.write(buffer.toString()); 1135 buffer.setLength(0); 1136 } else { 1137 super.write(c); 1138 } 1139 } 1140 } 1141 } 1142 write(String s, int off, int len)1143 public void write(String s, int off, int len) { 1144 write(s.substring(off, off + len).toCharArray(), 0, len); 1145 } 1146 } 1147 1148 // filters 1149 // match against the entire hierarchy 1150 // A;B;!C;!D --> (A ||B) && (!C && !D) 1151 // positive, negative, unknown matches 1152 // positive -- known to be included, negative- known to be excluded 1153 // positive only if no excludes, and matches at least one include, if any 1154 // negative only if matches at least one exclude 1155 // otherwise, we wait 1156 1157 public static class TestParams { 1158 public boolean prompt; 1159 public boolean verbose; 1160 public boolean quiet; 1161 public int listlevel; 1162 public boolean describe; 1163 public boolean warnings; 1164 public boolean nodata; 1165 public long timing = 0; 1166 public boolean memusage; 1167 public int inclusion; 1168 public String filter; 1169 public long seed; 1170 public String tfilter; // for transliterator tests 1171 1172 public State stack; 1173 1174 public StringBuffer errorSummary = new StringBuffer(); 1175 private StringBuffer timeLog; 1176 private Map<String, List<String>> knownIssues; 1177 1178 public PrintWriter log; 1179 public int indentLevel; 1180 private boolean needLineFeed; 1181 private boolean suppressIndent; 1182 public int errorCount; 1183 public int warnCount; 1184 public int invalidCount; 1185 public int testCount; 1186 private NumberFormat tformat; 1187 public Random random; 1188 public int maxTargetSec = 10; 1189 public HashMap props; 1190 TestParams()1191 private TestParams() { 1192 } 1193 create(String arglist, PrintWriter log)1194 public static TestParams create(String arglist, PrintWriter log) { 1195 String[] args = null; 1196 if (arglist != null && arglist.length() > 0) { 1197 args = arglist.split("\\s"); 1198 } 1199 return create(args, log); 1200 } 1201 1202 /** 1203 * Create a TestParams from a list of arguments. If successful, return the params object, 1204 * else return null. Error messages will be reported on errlog if it is not null. 1205 * Arguments and values understood by this method will be removed from the args array 1206 * and existing args will be shifted down, to be filled by nulls at the end. 1207 * @param args the list of arguments 1208 * @param log the error log, or null if no error log is desired 1209 * @return the new TestParams object, or null if error 1210 */ create(String[] args, PrintWriter log)1211 public static TestParams create(String[] args, PrintWriter log) { 1212 TestParams params = new TestParams(); 1213 1214 if (log == null) { 1215 params.log = new NullWriter(); 1216 } else { 1217 params.log = log; 1218 } 1219 1220 boolean usageError = false; 1221 String filter = null; 1222 String fmt = "#,##0.000s"; 1223 int wx = 0; // write argets. 1224 if (args != null) { 1225 for (int i = 0; i < args.length; i++) { 1226 String arg = args[i]; 1227 if (arg == null || arg.length() == 0) { 1228 continue; 1229 } 1230 if (arg.charAt(0) == '-') { 1231 arg = arg.toLowerCase(); 1232 if (arg.equals("-verbose") || arg.equals("-v")) { 1233 params.verbose = true; 1234 params.quiet = false; 1235 } else if (arg.equals("-quiet") || arg.equals("-q")) { 1236 params.quiet = true; 1237 params.verbose = false; 1238 } else if (arg.equals("-hex")) { 1239 params.log = new ASCIIWriter(log, true); 1240 } else if (arg.equals("-help") || arg.equals("-h")) { 1241 usageError = true; 1242 } else if (arg.equals("-warning") || arg.equals("-w")) { 1243 params.warnings = true; 1244 } else if (arg.equals("-nodata") || arg.equals("-nd")) { 1245 params.nodata = true; 1246 } else if (arg.equals("-list") || arg.equals("-l")) { 1247 params.listlevel = 1; 1248 } else if (arg.equals("-listall") || arg.equals("-la")) { 1249 params.listlevel = 2; 1250 } else if (arg.equals("-listexaustive") || arg.equals("-le")) { 1251 params.listlevel = 3; 1252 } else if (arg.equals("-memory") || arg.equals("-m")) { 1253 params.memusage = true; 1254 } else if (arg.equals("-nothrow") || arg.equals("-n")) { 1255 // Default since ICU 55. This option has no effects. 1256 } else if (arg.equals("-describe") || arg.equals("-d")) { 1257 params.describe = true; 1258 } else if (arg.startsWith("-r")) { 1259 String s = null; 1260 int n = arg.indexOf(':'); 1261 if (n != -1) { 1262 s = arg.substring(n + 1); 1263 arg = arg.substring(0, n); 1264 } 1265 1266 if (arg.equals("-r") || arg.equals("-random")) { 1267 if (s == null) { 1268 params.seed = System.currentTimeMillis(); 1269 } else { 1270 params.seed = Long.parseLong(s); 1271 } 1272 } else { 1273 log.println("*** Error: unrecognized argument: " + arg); 1274 usageError = true; 1275 break; 1276 } 1277 } else if (arg.startsWith("-e")) { 1278 // see above 1279 params.inclusion = (arg.length() == 2) 1280 ? 5 1281 : Integer.parseInt(arg.substring(2)); 1282 if (params.inclusion < 0 || params.inclusion > 10) { 1283 usageError = true; 1284 break; 1285 } 1286 } else if (arg.startsWith("-tfilter:")) { 1287 params.tfilter = arg.substring(8); 1288 } else if (arg.startsWith("-time") || arg.startsWith("-t")) { 1289 long val = 0; 1290 int inx = arg.indexOf(':'); 1291 if (inx > 0) { 1292 String num = arg.substring(inx + 1); 1293 try { 1294 val = Long.parseLong(num); 1295 } catch (Exception e) { 1296 log.println("*** Error: could not parse time threshold '" 1297 + num + "'"); 1298 usageError = true; 1299 break; 1300 } 1301 } 1302 params.timing = val; 1303 if (val <= 10) { 1304 fmt = "#,##0.000s"; 1305 } else if (val <= 100) { 1306 fmt = "#,##0.00s"; 1307 } else if (val <= 1000) { 1308 fmt = "#,##0.0s"; 1309 } 1310 } else if (arg.startsWith("-filter:")) { 1311 String temp = arg.substring(8).toLowerCase(); 1312 filter = filter == null ? temp : filter + "," + temp; 1313 } else if (arg.startsWith("-f:")) { 1314 String temp = arg.substring(3).toLowerCase(); 1315 filter = filter == null ? temp : filter + "," + temp; 1316 } else if (arg.startsWith("-s")) { 1317 params.log = new NullWriter(); 1318 } else if (arg.startsWith("-u")) { 1319 if (params.log instanceof ASCIIWriter) { 1320 params.log = log; 1321 } 1322 } else if (arg.startsWith("-prop:")) { 1323 String temp = arg.substring(6); 1324 int eql = temp.indexOf('='); 1325 if (eql <= 0) { 1326 log.println("*** Error: could not parse custom property '" + arg + "'"); 1327 usageError = true; 1328 break; 1329 } 1330 if (params.props == null) { 1331 params.props = new HashMap(); 1332 } 1333 params.props.put(temp.substring(0, eql), temp.substring(eql+1)); 1334 } else { 1335 log.println("*** Error: unrecognized argument: " 1336 + args[i]); 1337 usageError = true; 1338 break; 1339 } 1340 } else { 1341 args[wx++] = arg; // shift down 1342 } 1343 } 1344 1345 while (wx < args.length) { 1346 args[wx++] = null; 1347 } 1348 } 1349 1350 params.tformat = new DecimalFormat(fmt); 1351 1352 if (usageError) { 1353 usage(log, "TestAll"); 1354 return null; 1355 } 1356 1357 if (filter != null) { 1358 params.filter = filter.toLowerCase(); 1359 } 1360 1361 params.init(); 1362 1363 return params; 1364 } 1365 errorSummary()1366 public String errorSummary() { 1367 return errorSummary == null ? "" : errorSummary.toString(); 1368 } 1369 init()1370 public void init() { 1371 indentLevel = 0; 1372 needLineFeed = false; 1373 suppressIndent = false; 1374 errorCount = 0; 1375 warnCount = 0; 1376 invalidCount = 0; 1377 testCount = 0; 1378 random = seed == 0 ? null : new Random(seed); 1379 } 1380 1381 public class State { 1382 State link; 1383 String name; 1384 StringBuffer buffer; 1385 int level; 1386 int ec; 1387 int wc; 1388 int ic; 1389 int tc; 1390 boolean flushed; 1391 public boolean included; 1392 long mem; 1393 long millis; 1394 State(State link, String name, boolean included)1395 public State(State link, String name, boolean included) { 1396 this.link = link; 1397 this.name = name; 1398 if (link == null) { 1399 this.level = 0; 1400 this.included = included; 1401 } else { 1402 this.level = link.level + 1; 1403 this.included = included || link.included; 1404 } 1405 this.ec = errorCount; 1406 this.wc = warnCount; 1407 this.ic = invalidCount; 1408 this.tc = testCount; 1409 1410 if (link == null || this.included) { 1411 flush(); 1412 } 1413 1414 mem = getmem(); 1415 millis = System.currentTimeMillis(); 1416 } 1417 flush()1418 void flush() { 1419 if (!flushed) { 1420 if (link != null) { 1421 link.flush(); 1422 } 1423 1424 indent(level); 1425 log.print(name); 1426 log.flush(); 1427 1428 flushed = true; 1429 1430 needLineFeed = true; 1431 } 1432 } 1433 appendPath(StringBuffer buf)1434 void appendPath(StringBuffer buf) { 1435 if (this.link != null) { 1436 this.link.appendPath(buf); 1437 buf.append('/'); 1438 } 1439 buf.append(name); 1440 } 1441 } 1442 push(String name, String description, boolean included)1443 public void push(String name, String description, boolean included) { 1444 if (inDocMode() && describe && description != null) { 1445 name += ": " + description; 1446 } 1447 stack = new State(stack, name, included); 1448 } 1449 pop()1450 public void pop() { 1451 if (stack != null) { 1452 writeTestResult(); 1453 stack = stack.link; 1454 } 1455 } 1456 inDocMode()1457 public boolean inDocMode() { 1458 return describe || listlevel != 0; 1459 } 1460 doMethods()1461 public boolean doMethods() { 1462 return !inDocMode() || listlevel == 3 1463 || (indentLevel == 1 && listlevel > 0); 1464 } 1465 doRecurse()1466 public boolean doRecurse() { 1467 return !inDocMode() || listlevel > 1 1468 || (indentLevel == 1 && listlevel > 0); 1469 } 1470 doRecurseGroupsOnly()1471 public boolean doRecurseGroupsOnly() { 1472 return inDocMode() 1473 && (listlevel == 2 || (indentLevel == 1 && listlevel > 0)); 1474 } 1475 1476 // return 0, -1, or 1 1477 // 1: run this test 1478 // 0: might run this test, no positive include or exclude on this group 1479 // -1: exclude this test filter(String testName)1480 public int filter(String testName) { 1481 int result = 0; 1482 if (filter == null) { 1483 result = 1; 1484 } else { 1485 boolean noIncludes = true; 1486 boolean noExcludes = filter.indexOf('^') == -1; 1487 testName = testName.toLowerCase(); 1488 int ix = 0; 1489 while (ix < filter.length()) { 1490 int nix = filter.indexOf(',', ix); 1491 if (nix == -1) { 1492 nix = filter.length(); 1493 } 1494 if (filter.charAt(ix) == '^') { 1495 if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) { 1496 result = -1; 1497 break; 1498 } 1499 } else { 1500 noIncludes = false; 1501 if (testName.indexOf(filter.substring(ix, nix)) != -1) { 1502 result = 1; 1503 if (noExcludes) { 1504 break; 1505 } 1506 } 1507 } 1508 1509 ix = nix + 1; 1510 } 1511 if (result == 0 && noIncludes) { 1512 result = 1; 1513 } 1514 } 1515 // System.out.println("filter: " + testName + " returns: " + 1516 // result); 1517 return result; 1518 } 1519 1520 /** 1521 * Log access. 1522 * @param msg The string message to write 1523 */ write(String msg)1524 public void write(String msg) { 1525 write(msg, false); 1526 } 1527 writeln(String msg)1528 public void writeln(String msg) { 1529 write(msg, true); 1530 } 1531 write(String msg, boolean newln)1532 private void write(String msg, boolean newln) { 1533 if (!suppressIndent) { 1534 if (needLineFeed) { 1535 log.println(); 1536 needLineFeed = false; 1537 } 1538 log.print(spaces.substring(0, indentLevel * 2)); 1539 } 1540 log.print(msg); 1541 if (newln) { 1542 log.println(); 1543 } 1544 log.flush(); 1545 suppressIndent = !newln; 1546 } 1547 msg(String message, int level, boolean incCount, boolean newln)1548 private void msg(String message, int level, boolean incCount, 1549 boolean newln) { 1550 int oldLevel = level; 1551 // if (level == WARN && (!warnings && !nodata)){ 1552 // level = ERR; 1553 // } 1554 1555 if (incCount) { 1556 if (level == WARN) { 1557 warnCount++; 1558 // invalidCount++; 1559 } else if (level == ERR) { 1560 errorCount++; 1561 } 1562 } 1563 1564 // should roll indentation stuff into log ??? 1565 if (verbose || level > (quiet ? WARN : LOG)) { 1566 if (!suppressIndent) { 1567 indent(indentLevel + 1); 1568 final String[] MSGNAMES = {"", "Warning: ", "Error: "}; 1569 log.print(MSGNAMES[oldLevel]); 1570 } 1571 1572 String testLocation = sourceLocation(); 1573 message = testLocation + message; 1574 log.print(message); 1575 if (newln) { 1576 log.println(); 1577 } 1578 log.flush(); 1579 } 1580 1581 if (level == ERR) { 1582 if (!suppressIndent && errorSummary != null && stack !=null 1583 && (errorCount == stack.ec + 1)) { 1584 stack.appendPath(errorSummary); 1585 errorSummary.append("\n"); 1586 } 1587 } 1588 1589 suppressIndent = !newln; 1590 } 1591 writeTestInvalid(String name, boolean nodataArg)1592 private void writeTestInvalid(String name, boolean nodataArg) { 1593 // msg("***" + name + "*** not found or not valid.", WARN, true, 1594 // true); 1595 if (inDocMode()) { 1596 if (!warnings) { 1597 if (stack != null) { 1598 stack.flush(); 1599 } 1600 log.println(" *** Target not found or not valid."); 1601 log.flush(); 1602 needLineFeed = false; 1603 } 1604 } else { 1605 if(!nodataArg){ 1606 msg("Test " + name + " not found or not valid.", WARN, true, 1607 true); 1608 } 1609 } 1610 } 1611 getmem()1612 long getmem() { 1613 long newmem = 0; 1614 if (memusage) { 1615 Runtime rt = Runtime.getRuntime(); 1616 long lastmem = Long.MAX_VALUE; 1617 do { 1618 rt.gc(); 1619 rt.gc(); 1620 try { 1621 Thread.sleep(50); 1622 } catch (Exception e) { 1623 break; 1624 } 1625 lastmem = newmem; 1626 newmem = rt.totalMemory() - rt.freeMemory(); 1627 } while (newmem < lastmem); 1628 } 1629 return newmem; 1630 } 1631 writeTestResult()1632 private void writeTestResult() { 1633 if (inDocMode()) { 1634 if (needLineFeed) { 1635 log.println(); 1636 log.flush(); 1637 } 1638 needLineFeed = false; 1639 return; 1640 } 1641 1642 long dmem = getmem() - stack.mem; 1643 long dtime = System.currentTimeMillis() - stack.millis; 1644 1645 int testDelta = testCount - stack.tc; 1646 if (testDelta == 0) { 1647 if (stack.included) { 1648 stack.flush(); 1649 indent(indentLevel); 1650 log.println("} (0s) Empty"); 1651 } 1652 return; 1653 } 1654 1655 int errorDelta = errorCount - stack.ec; 1656 int warnDelta = warnCount - stack.wc; 1657 int invalidDelta = invalidCount - stack.ic; 1658 1659 stack.flush(); 1660 1661 if (!needLineFeed) { 1662 indent(indentLevel); 1663 log.print("}"); 1664 } 1665 needLineFeed = false; 1666 1667 if (memusage || dtime >= timing) { 1668 log.print(" ("); 1669 if (memusage) { 1670 log.print("dmem: " + dmem); 1671 } 1672 if (dtime >= timing) { 1673 if (memusage) { 1674 log.print(", "); 1675 } 1676 log.print(tformat.format(dtime / 1000f)); 1677 } 1678 log.print(")"); 1679 } 1680 1681 if (errorDelta != 0) { 1682 log.println(" FAILED (" 1683 + errorDelta 1684 + " failure(s)" 1685 + ((warnDelta != 0) ? ", " + warnDelta 1686 + " warning(s)" : "") 1687 + ((invalidDelta != 0) ? ", " + invalidDelta 1688 + " test(s) skipped)" : ")")); 1689 } else if (warnDelta != 0) { 1690 log.println(" ALERT (" 1691 + warnDelta 1692 + " warning(s)" 1693 + ((invalidDelta != 0) ? ", " + invalidDelta 1694 + " test(s) skipped)" : ")")); 1695 } else if (invalidDelta != 0) { 1696 log.println(" Qualified (" + invalidDelta + " test(s) skipped)"); 1697 } else { 1698 log.println(" Passed"); 1699 } 1700 } 1701 indent(int distance)1702 private final void indent(int distance) { 1703 boolean idm = inDocMode(); 1704 if (needLineFeed) { 1705 if (idm) { 1706 log.println(); 1707 } else { 1708 log.println(" {"); 1709 } 1710 needLineFeed = false; 1711 } 1712 1713 log.print(spaces.substring(0, distance * (idm ? 3 : 2))); 1714 1715 if (idm) { 1716 log.print("-- "); 1717 } 1718 } 1719 } 1720 getTranslitTestFilter()1721 public String getTranslitTestFilter() { 1722 return params.tfilter; 1723 } 1724 1725 /** 1726 * Return the target name for a test class. This is either the end of the 1727 * class name, or if the class declares a public static field 1728 * CLASS_TARGET_NAME, the value of that field. 1729 */ getClassTargetName(Class testClass)1730 private static String getClassTargetName(Class testClass) { 1731 String name = testClass.getName(); 1732 try { 1733 Field f = testClass.getField("CLASS_TARGET_NAME"); 1734 name = (String) f.get(null); 1735 } catch (IllegalAccessException e) { 1736 throw new IllegalStateException( 1737 "static field CLASS_TARGET_NAME must be accessible"); 1738 } catch (NoSuchFieldException e) { 1739 int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$')); 1740 if (n != -1) { 1741 name = name.substring(n + 1); 1742 } 1743 } 1744 return name; 1745 } 1746 1747 /** 1748 * Check the given array to see that all the strings in the expected array 1749 * are present. 1750 * 1751 * @param msg 1752 * string message, for log output 1753 * @param array 1754 * array of strings to check 1755 * @param expected 1756 * array of strings we expect to see, or null 1757 * @return the length of 'array', or -1 on error 1758 */ checkArray(String msg, String array[], String expected[])1759 protected int checkArray(String msg, String array[], String expected[]) { 1760 int explen = (expected != null) ? expected.length : 0; 1761 if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32 1762 errln("Internal error"); 1763 return -1; 1764 } 1765 int i = 0; 1766 StringBuffer buf = new StringBuffer(); 1767 int seenMask = 0; 1768 for (; i < array.length; ++i) { 1769 String s = array[i]; 1770 if (i != 0) 1771 buf.append(", "); 1772 buf.append(s); 1773 // check expected list 1774 for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) { 1775 if ((seenMask & bit) == 0) { 1776 if (s.equals(expected[j])) { 1777 seenMask |= bit; 1778 logln("Ok: \"" + s + "\" seen"); 1779 } 1780 } 1781 } 1782 } 1783 logln(msg + " = [" + buf + "] (" + i + ")"); 1784 // did we see all expected strings? 1785 if (((1 << explen) - 1) != seenMask) { 1786 for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) { 1787 if ((seenMask & bit) == 0) { 1788 errln("\"" + expected[j] + "\" not seen"); 1789 } 1790 } 1791 } 1792 return array.length; 1793 } 1794 1795 /** 1796 * Check the given array to see that all the locales in the expected array 1797 * are present. 1798 * 1799 * @param msg 1800 * string message, for log output 1801 * @param array 1802 * array of locales to check 1803 * @param expected 1804 * array of locales names we expect to see, or null 1805 * @return the length of 'array' 1806 */ checkArray(String msg, Locale array[], String expected[])1807 protected int checkArray(String msg, Locale array[], String expected[]) { 1808 String strs[] = new String[array.length]; 1809 for (int i = 0; i < array.length; ++i) 1810 strs[i] = array[i].toString(); 1811 return checkArray(msg, strs, expected); 1812 } 1813 1814 /** 1815 * Check the given array to see that all the locales in the expected array 1816 * are present. 1817 * 1818 * @param msg 1819 * string message, for log output 1820 * @param array 1821 * array of locales to check 1822 * @param expected 1823 * array of locales names we expect to see, or null 1824 * @return the length of 'array' 1825 */ checkArray(String msg, ULocale array[], String expected[])1826 protected int checkArray(String msg, ULocale array[], String expected[]) { 1827 String strs[] = new String[array.length]; 1828 for (int i = 0; i < array.length; ++i) 1829 strs[i] = array[i].toString(); 1830 return checkArray(msg, strs, expected); 1831 } 1832 1833 // JUnit-like assertions. 1834 assertTrue(String message, boolean condition)1835 protected boolean assertTrue(String message, boolean condition) { 1836 return handleAssert(condition, message, "true", null); 1837 } 1838 assertFalse(String message, boolean condition)1839 protected boolean assertFalse(String message, boolean condition) { 1840 return handleAssert(!condition, message, "false", null); 1841 } 1842 assertEquals(String message, boolean expected, boolean actual)1843 protected boolean assertEquals(String message, boolean expected, 1844 boolean actual) { 1845 return handleAssert(expected == actual, message, String 1846 .valueOf(expected), String.valueOf(actual)); 1847 } 1848 assertEquals(String message, long expected, long actual)1849 protected boolean assertEquals(String message, long expected, long actual) { 1850 return handleAssert(expected == actual, message, String 1851 .valueOf(expected), String.valueOf(actual)); 1852 } 1853 1854 // do NaN and range calculations to precision of float, don't rely on 1855 // promotion to double assertEquals(String message, float expected, float actual, double error)1856 protected boolean assertEquals(String message, float expected, 1857 float actual, double error) { 1858 boolean result = Float.isInfinite(expected) 1859 ? expected == actual 1860 : !(Math.abs(expected - actual) > error); // handles NaN 1861 return handleAssert(result, message, String.valueOf(expected) 1862 + (error == 0 ? "" : " (within " + error + ")"), String 1863 .valueOf(actual)); 1864 } 1865 assertEquals(String message, double expected, double actual, double error)1866 protected boolean assertEquals(String message, double expected, 1867 double actual, double error) { 1868 boolean result = Double.isInfinite(expected) 1869 ? expected == actual 1870 : !(Math.abs(expected - actual) > error); // handles NaN 1871 return handleAssert(result, message, String.valueOf(expected) 1872 + (error == 0 ? "" : " (within " + error + ")"), String 1873 .valueOf(actual)); 1874 } 1875 assertEquals(String message, T[] expected, T[] actual)1876 protected <T> boolean assertEquals(String message, T[] expected, T[] actual) { 1877 // Use toString on a List to get useful, readable messages 1878 String expectedString = expected == null ? "null" : Arrays.asList(expected).toString(); 1879 String actualString = actual == null ? "null" : Arrays.asList(actual).toString(); 1880 return assertEquals(message, expectedString, actualString); 1881 } 1882 assertEquals(String message, Object expected, Object actual)1883 protected boolean assertEquals(String message, Object expected, 1884 Object actual) { 1885 boolean result = expected == null ? actual == null : expected 1886 .equals(actual); 1887 return handleAssert(result, message, stringFor(expected), 1888 stringFor(actual)); 1889 } 1890 assertNotEquals(String message, Object expected, Object actual)1891 protected boolean assertNotEquals(String message, Object expected, 1892 Object actual) { 1893 boolean result = !(expected == null ? actual == null : expected 1894 .equals(actual)); 1895 return handleAssert(result, message, stringFor(expected), 1896 stringFor(actual), "not equal to", true); 1897 } 1898 assertSame(String message, Object expected, Object actual)1899 protected boolean assertSame(String message, Object expected, Object actual) { 1900 return handleAssert(expected == actual, message, stringFor(expected), 1901 stringFor(actual), "==", false); 1902 } 1903 assertNotSame(String message, Object expected, Object actual)1904 protected boolean assertNotSame(String message, Object expected, 1905 Object actual) { 1906 return handleAssert(expected != actual, message, stringFor(expected), 1907 stringFor(actual), "!=", true); 1908 } 1909 assertNull(String message, Object actual)1910 protected boolean assertNull(String message, Object actual) { 1911 return handleAssert(actual == null, message, null, stringFor(actual)); 1912 } 1913 assertNotNull(String message, Object actual)1914 protected boolean assertNotNull(String message, Object actual) { 1915 return handleAssert(actual != null, message, null, stringFor(actual), 1916 "!=", true); 1917 } 1918 fail()1919 protected void fail() { 1920 fail(""); 1921 } 1922 fail(String message)1923 protected void fail(String message) { 1924 if (message == null) { 1925 message = ""; 1926 } 1927 if (!message.equals("")) { 1928 message = ": " + message; 1929 } 1930 errln(sourceLocation() + message); 1931 } 1932 handleAssert(boolean result, String message, String expected, String actual)1933 private boolean handleAssert(boolean result, String message, 1934 String expected, String actual) { 1935 return handleAssert(result, message, expected, actual, null, false); 1936 } 1937 handleAssert(boolean result, String message, Object expected, Object actual, String relation, boolean flip)1938 public boolean handleAssert(boolean result, String message, 1939 Object expected, Object actual, String relation, boolean flip) { 1940 if (!result || isVerbose()) { 1941 if (message == null) { 1942 message = ""; 1943 } 1944 if (!message.equals("")) { 1945 message = ": " + message; 1946 } 1947 relation = relation == null ? ", got " : " " + relation + " "; 1948 if (result) { 1949 logln("OK " + message + ": " 1950 + (flip ? expected + relation + actual : expected)); 1951 } else { 1952 // assert must assume errors are true errors and not just warnings 1953 // so cannot warnln here 1954 errln( message 1955 + ": expected" 1956 + (flip ? relation + expected : " " + expected 1957 + (actual != null ? relation + actual : ""))); 1958 } 1959 } 1960 return result; 1961 } 1962 stringFor(Object obj)1963 private final String stringFor(Object obj) { 1964 if (obj == null) { 1965 return "null"; 1966 } 1967 if (obj instanceof String) { 1968 return "\"" + obj + '"'; 1969 } 1970 return obj.getClass().getName() + "<" + obj + ">"; 1971 } 1972 1973 // Return the source code location of the caller located callDepth frames up the stack. sourceLocation()1974 public static String sourceLocation() { 1975 // Walk up the stack to the first call site outside this file 1976 for (StackTraceElement st : new Throwable().getStackTrace()) { 1977 String source = st.getFileName(); 1978 if (source != null && !source.equals("TestFmwk.java") && !source.equals("AbstractTestLog.java")) { 1979 String methodName = st.getMethodName(); 1980 if (methodName != null && 1981 (methodName.startsWith("Test") || methodName.startsWith("test") || methodName.equals("main"))) { 1982 return "(" + source + ":" + st.getLineNumber() + ") "; 1983 } 1984 } 1985 } 1986 throw new InternalError(); 1987 } 1988 1989 1990 // End JUnit-like assertions 1991 1992 // PrintWriter support 1993 getErrorLogPrintWriter()1994 public PrintWriter getErrorLogPrintWriter() { 1995 return new PrintWriter(new TestLogWriter(this, TestLog.ERR)); 1996 } 1997 getLogPrintWriter()1998 public PrintWriter getLogPrintWriter() { 1999 return new PrintWriter(new TestLogWriter(this, TestLog.LOG)); 2000 } 2001 2002 // end PrintWriter support 2003 2004 protected TestParams params = null; 2005 2006 private final static String spaces = " "; 2007 2008 } 2009