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