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