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