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