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