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