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