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