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