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