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