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