2 *******************************************************************************
3 * Copyright (C) 1996-2013, International Business Machines Corporation and *
4 * others. All Rights Reserved. *
5 *******************************************************************************
7 package com.ibm.icu.dev.test;
9 import java.io.ByteArrayOutputStream;
10 import java.io.IOException;
11 import java.io.OutputStream;
12 import java.io.PrintStream;
13 import java.io.PrintWriter;
14 import java.io.Writer;
15 import java.lang.reflect.Field;
16 import java.lang.reflect.InvocationTargetException;
17 import java.lang.reflect.Method;
18 import java.text.DecimalFormat;
19 import java.text.NumberFormat;
20 import java.util.ArrayList;
21 import java.util.Arrays;
22 import java.util.Comparator;
23 import java.util.HashMap;
24 import java.util.List;
25 import java.util.Locale;
27 import java.util.Map.Entry;
28 import java.util.MissingResourceException;
29 import java.util.Random;
30 import java.util.TreeMap;
32 import com.ibm.icu.util.TimeZone;
33 import com.ibm.icu.util.ULocale;
36 * TestFmwk is a base class for tests that can be run conveniently from the
37 * command line as well as under the Java test harness.
39 * Sub-classes implement a set of methods named Test <something>. Each of these
40 * methods performs some test. Test methods should indicate errors by calling
41 * either err or errln. This will increment the errorCount field and may
42 * optionally print a message to the log. Debugging information may also be
43 * added to the log via the log and logln methods. These methods will add their
44 * arguments to the log only if the test is being run in verbose mode.
46 public class TestFmwk extends AbstractTestLog {
48 * The default time zone for all of our tests. Used in Target.run();
50 private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("PST");
53 * The default locale used for all of our tests. Used in Target.run();
55 private final static Locale defaultLocale = Locale.US;
57 public static final class TestFmwkException extends Exception {
61 private static final long serialVersionUID = -3051148210247229194L;
63 TestFmwkException(String msg) {
67 protected void handleException(Throwable e){
68 Throwable ex = e.getCause();
72 if(ex instanceof ExceptionInInitializerError){
73 ex = ((ExceptionInInitializerError)ex).getException();
75 String msg = ex.getMessage();
79 //System.err.println("TF handleException msg: " + msg);
80 if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError ||
81 msg.indexOf("java.util.MissingResourceException") >= 0) {
82 if (params.warnings || params.nodata) {
84 } else if (params.nothrow) {
89 throw new RuntimeException(msg);
98 throw new RuntimeException(msg);
102 // use this instead of new random so we get a consistent seed
104 protected Random createRandom() {
105 return new Random(params.seed);
109 * A test that has no test methods itself, but instead runs other tests.
111 * This overrides methods are getTargets and getSubtest from TestFmwk.
113 * If you want the default behavior, pass an array of class names and an
114 * optional description to the constructor. The named classes must extend
115 * TestFmwk. If a provided name doesn't include a ".", package name is
116 * prefixed to it (the package of the current test is used if none was
117 * provided in the constructor). The resulting full name is used to
118 * instantiate an instance of the class using the default constructor.
120 * Class names are resolved to classes when getTargets or getSubtest is
121 * called. This allows instances of TestGroup to be compiled and run without
122 * all the targets they would normally invoke being available.
124 public static abstract class TestGroup extends TestFmwk {
125 private String defaultPackage;
126 private String[] names;
127 private String description;
129 private Class[] tests; // deferred init
132 * Constructor that takes a default package name and a list of class
133 * names. Adopts and modifies the classname list
135 protected TestGroup(String defaultPackage, String[] classnames,
136 String description) {
137 if (classnames == null) {
138 throw new IllegalStateException("classnames must not be null");
141 if (defaultPackage == null) {
142 defaultPackage = getClass().getPackage().getName();
144 defaultPackage = defaultPackage + ".";
146 this.defaultPackage = defaultPackage;
147 this.names = classnames;
148 this.description = description;
152 * Constructor that takes a list of class names and a description, and
153 * uses the package for this class as the default package.
155 protected TestGroup(String[] classnames, String description) {
156 this(null, classnames, description);
160 * Constructor that takes a list of class names, and uses the package
161 * for this class as the default package.
163 protected TestGroup(String[] classnames) {
164 this(null, classnames, null);
167 protected String getDescription() {
171 protected Target getTargets(String targetName) {
172 Target target = null;
173 if (targetName != null) {
174 finishInit(); // hmmm, want to get subtest without initializing
178 TestFmwk test = getSubtest(targetName);
180 target = test.new ClassTarget();
182 target = this.new Target(targetName);
184 } catch (TestFmwkException e) {
185 target = this.new Target(targetName);
187 } else if (params.doRecurse()) {
189 boolean groupOnly = params.doRecurseGroupsOnly();
190 for (int i = names.length; --i >= 0;) {
191 Target newTarget = null;
192 Class cls = tests[i];
193 if (cls == null) { // hack no warning for missing tests
194 if (params.warnings) {
197 newTarget = this.new Target(names[i]);
199 TestFmwk test = getSubtest(i, groupOnly);
201 newTarget = test.new ClassTarget();
204 newTarget = this.new EmptyTarget(names[i]);
206 newTarget = this.new Target(names[i]);
210 if (newTarget != null) {
211 newTarget.setNext(target);
219 protected TestFmwk getSubtest(String testName) throws TestFmwkException {
222 for (int i = 0; i < names.length; ++i) {
223 if (names[i].equalsIgnoreCase(testName)) { // allow
226 return getSubtest(i, false);
229 throw new TestFmwkException(testName);
232 private TestFmwk getSubtest(int i, boolean groupOnly) {
233 Class cls = tests[i];
235 if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) {
240 TestFmwk subtest = (TestFmwk) cls.newInstance();
241 subtest.params = params;
243 } catch (InstantiationException e) {
244 throw new IllegalStateException(e.getMessage());
245 } catch (IllegalAccessException e) {
246 throw new IllegalStateException(e.getMessage());
252 private void finishInit() {
254 tests = new Class[names.length];
256 for (int i = 0; i < names.length; ++i) {
257 String name = names[i];
258 if (name.indexOf('.') == -1) {
259 name = defaultPackage + name;
262 Class cls = Class.forName(name);
263 if (!TestFmwk.class.isAssignableFrom(cls)) {
264 throw new IllegalStateException("class " + name
265 + " does not extend TestFmwk");
269 names[i] = getClassTargetName(cls);
270 } catch (ClassNotFoundException e) {
271 // leave tests[i] null and name as classname
279 * The default target is invalid.
281 public class Target {
283 public final String name;
285 public Target(String name) {
289 public Target setNext(Target next) {
294 public Target getNext() {
298 public Target append(Target targets) {
300 while(t.next != null) {
307 public void run() throws Exception {
310 ++params.invalidCount;
312 Locale.setDefault(defaultLocale);
313 TimeZone.setDefault(defaultTimeZone);
316 params.writeTestInvalid(name, params.nodata);
318 params.push(name, getDescription(), f == 1);
325 protected int filter() {
326 return params.filter(name);
329 protected boolean validate() {
333 protected String getDescription() {
337 protected void execute() throws Exception{
341 public class EmptyTarget extends Target {
342 public EmptyTarget(String name) {
346 protected boolean validate() {
351 public class MethodTarget extends Target {
352 private Method testMethod;
354 public MethodTarget(String name, Method method) {
359 protected boolean validate() {
360 return testMethod != null && validateMethod(name);
363 protected String getDescription() {
364 return getMethodDescription(name);
367 protected void execute() throws Exception{
368 if (params.inDocMode()) {
369 // nothing to execute
370 } else if (!params.stack.included) {
371 ++params.invalidCount;
373 final Object[] NO_ARGS = new Object[0];
377 testMethod.invoke(TestFmwk.this, NO_ARGS);
378 } catch (IllegalAccessException e) {
379 errln("Can't access test method " + testMethod.getName());
380 }catch (ExceptionInInitializerError e){
382 } catch (InvocationTargetException e) {
383 //e.printStackTrace();
385 }catch (MissingResourceException e) {
387 }catch (NoClassDefFoundError e) {
389 }catch (Exception e){
390 /*errln("Encountered: "+ e.toString());
391 e.printStackTrace(System.err);
396 // If non-exhaustive, check if the method target
397 // takes excessive time.
398 if (params.inclusion <= 5) {
399 double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000;
400 if (deltaSec > params.maxTargetSec) {
401 if (params.timeLog == null) {
402 params.timeLog = new StringBuffer();
404 params.stack.appendPath(params.timeLog);
405 params.timeLog.append(" (" + deltaSec + "s" + ")\n");
410 protected String getStackTrace(InvocationTargetException e) {
411 ByteArrayOutputStream bs = new ByteArrayOutputStream();
412 PrintStream ps = new PrintStream(bs);
413 e.getTargetException().printStackTrace(ps);
414 return bs.toString();
418 public class ClassTarget extends Target {
421 public ClassTarget() {
425 public ClassTarget(String targetName) {
426 super(getClassTargetName(TestFmwk.this.getClass()));
427 this.targetName = targetName;
430 protected boolean validate() {
431 return TestFmwk.this.validate();
434 protected String getDescription() {
435 return TestFmwk.this.getDescription();
438 protected void execute() throws Exception {
439 params.indentLevel++;
440 Target target = randomize(getTargets(targetName));
441 while (target != null) {
443 target = target.next;
445 params.indentLevel--;
448 private Target randomize(Target t) {
449 if (t != null && t.getNext() != null) {
450 ArrayList list = new ArrayList();
456 Target[] arr = (Target[]) list.toArray(new Target[list.size()]);
458 if (true) { // todo - add to params?
459 // different jvms return class methods in different orders,
460 // so we sort them (always, and then randomize them, so that
461 // forcing a seed will also work across jvms).
462 Arrays.sort(arr, new Comparator() {
463 public int compare(Object lhs, Object rhs) {
464 // sort in reverse order, later we link up in
466 return ((Target) rhs).name
467 .compareTo(((Target) lhs).name);
471 // t is null to start, ends up as first element
472 // (arr[arr.length-1])
473 for (int i = 0; i < arr.length; ++i) {
474 t = arr[i].setNext(t); // relink in forward order
478 if (params.random != null) {
479 t = null; // reset t to null
480 Random r = params.random;
481 for (int i = arr.length; --i >= 1;) {
482 int x = r.nextInt(i + 1);
483 t = arr[x].setNext(t);
487 t = arr[0].setNext(t); // new first element
495 //------------------------------------------------------------------------
496 // Everything below here is boilerplate code that makes it possible
497 // to add a new test by simply adding a function to an existing class
498 //------------------------------------------------------------------------
500 protected TestFmwk() {
503 protected void init() throws Exception{
507 * Parse arguments into a TestParams object and a collection of target
508 * paths. If there was an error parsing the TestParams, print usage and exit
509 * with -1. Otherwise, call resolveTarget(TestParams, String) for each path,
510 * and run the returned target. After the last test returns, if prompt is
511 * set, prompt and wait for input from stdin. Finally, exit with number of
514 * This method never returns, since it always exits with System.exit();
516 public void run(String[] args) {
517 System.exit(run(args, new PrintWriter(System.out)));
521 * Like run(String[]) except this allows you to specify the error log.
522 * Unlike run(String[]) this returns the error code as a result instead of
523 * calling System.exit().
525 public int run(String[] args, PrintWriter log) {
526 boolean prompt = false;
528 for (int i = 0; i < args.length; ++i) {
529 String arg = args[i];
530 if (arg.equals("-p") || arg.equals("-prompt")) {
539 while (wx < args.length) {
543 TestParams localParams = TestParams.create(args, log);
544 if (localParams == null) {
548 int errorCount = runTests(localParams, args);
550 if (localParams.seed != 0) {
551 localParams.log.println("-random:" + localParams.seed);
552 localParams.log.flush();
555 if (localParams.timeLog != null && localParams.timeLog.length() > 0) {
556 localParams.log.println("\nTest cases taking excessive time (>" +
557 localParams.maxTargetSec + "s):");
558 localParams.log.println(localParams.timeLog.toString());
561 if (localParams.knownIssues != null) {
562 localParams.log.println("\nKnown Issues:");
563 for (Entry<String, List<String>> entry : localParams.knownIssues.entrySet()) {
564 String ticketLink = entry.getKey();
565 localParams.log.println("[" + ticketLink + "]");
566 for (String line : entry.getValue()) {
567 localParams.log.println(" - " + line);
572 if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) {
573 localParams.log.println("\nError summary:");
574 localParams.log.println(localParams.errorSummary.toString());
577 if (errorCount > 0) {
578 localParams.log.println("\n<< " + errorCount+ " TEST(S) FAILED >>");
580 localParams.log.println("\n<< ALL TESTS PASSED >>");
584 System.out.println("Hit RETURN to exit...");
588 } catch (IOException e) {
589 localParams.log.println("Exception: " + e.toString() + e.getMessage());
596 public int runTests(TestParams _params, String[] tests) {
599 StringBuffer summary = null;
601 if (tests.length == 0 || tests[0] == null) { // no args
603 resolveTarget(_params).run();
604 ec = _params.errorCount;
606 for (int i = 0; i < tests.length ; ++i) {
607 if (tests[i] == null) continue;
610 _params.log.println();
614 resolveTarget(_params, tests[i]).run();
615 ec += _params.errorCount;
617 if (_params.errorSummary != null && _params.errorSummary.length() > 0) {
618 if (summary == null) {
619 summary = new StringBuffer();
621 summary.append("\nTest Root: " + tests[i] + "\n");
622 summary.append(_params.errorSummary());
625 _params.errorSummary = summary;
627 } catch (Exception e) {
629 e.printStackTrace(_params.log);
630 _params.log.println(e.getMessage());
631 _params.log.println("encountered exception, exiting");
638 * Return a ClassTarget for this test. Params is set on this test.
640 public Target resolveTarget(TestParams paramsArg) {
641 this.params = paramsArg;
642 return new ClassTarget();
646 * Resolve a path from this test to a target. If this test has subtests, and
647 * the path contains '/', the portion before the '/' is resolved to a
648 * subtest, until the path is consumed or the test has no subtests. Returns
649 * a ClassTarget created using the resolved test and remaining path (which
650 * ought to be null or a method name). Params is set on the target's test.
652 public Target resolveTarget(TestParams paramsArg, String targetPath) {
653 TestFmwk test = this;
654 test.params = paramsArg;
656 if (targetPath != null) {
657 if (targetPath.length() == 0) {
661 int e = targetPath.length();
663 // trim all leading and trailing '/'
664 while (targetPath.charAt(p) == '/') {
667 while (e > p && targetPath.charAt(e - 1) == '/') {
670 if (p > 0 || e < targetPath.length()) {
671 targetPath = targetPath.substring(p, e - p);
673 e = targetPath.length();
678 int n = targetPath.indexOf('/');
679 String prefix = n == -1 ? targetPath : targetPath
681 TestFmwk subtest = test.getSubtest(prefix);
683 if (subtest == null) {
694 targetPath = targetPath.substring(n + 1);
696 } catch (TestFmwkException ex) {
697 return test.new Target(targetPath);
702 return test.new ClassTarget(targetPath);
706 * Return true if we can run this test (allows test to inspect jvm,
707 * environment, params before running)
709 protected boolean validate() {
714 * Return the targets for this test. If targetName is null, return all
715 * targets, otherwise return a target for just that name. The returned
716 * target can be null.
718 * The default implementation returns a MethodTarget for each public method
719 * of the object's class whose name starts with "Test" or "test".
721 protected Target getTargets(String targetName) {
722 return getClassTargets(getClass(), targetName);
725 protected Target getClassTargets(Class cls, String targetName) {
730 Target target = null;
731 if (targetName != null) {
733 Method method = cls.getMethod(targetName, (Class[])null);
734 target = new MethodTarget(targetName, method);
735 } catch (NoSuchMethodException e) {
736 if (!inheritTargets()) {
737 return new Target(targetName); // invalid target
739 } catch (SecurityException e) {
743 if (params.doMethods()) {
744 Method[] methods = cls.getDeclaredMethods();
745 for (int i = methods.length; --i >= 0;) {
746 String name = methods[i].getName();
747 if (name.startsWith("Test") || name.startsWith("test")) {
748 target = new MethodTarget(name, methods[i])
755 if (inheritTargets()) {
756 Target parentTarget = getClassTargets(cls.getSuperclass(), targetName);
757 if (parentTarget == null) {
760 if (target == null) {
763 return parentTarget.append(target);
769 protected boolean inheritTargets() {
773 protected String getDescription() {
777 protected boolean validateMethod(String name) {
781 protected String getMethodDescription(String name) {
785 // method tests have no subtests, group tests override
786 protected TestFmwk getSubtest(String prefix) throws TestFmwkException {
790 public boolean isVerbose() {
791 return params.verbose;
794 public boolean noData() {
795 return params.nodata;
798 public boolean isTiming() {
799 return params.timing < Long.MAX_VALUE;
802 public boolean isMemTracking() {
803 return params.memusage;
807 * 0 = fewest tests, 5 is normal build, 10 is most tests
809 public int getInclusion() {
810 return params.inclusion;
813 public boolean isModularBuild() {
814 return params.warnings;
817 public boolean isQuick() {
818 return params.inclusion == 0;
821 public void msg(String message, int level, boolean incCount, boolean newln) {
822 params.msg(message, level, incCount, newln);
825 static final String ICU_TRAC_URL = "http://bugs.icu-project.org/trac/ticket/";
826 static final String CLDR_TRAC_URL = "http://unicode.org/cldr/trac/ticket/";
827 static final String CLDR_TICKET_PREFIX = "cldrbug:";
830 * Log the known issue.
831 * This method returns true unless -prop:logKnownIssue=no is specified
832 * in the argument list.
834 * @param ticket A ticket number string. For an ICU ticket, use numeric characters only,
835 * such as "10245". For a CLDR ticket, use prefix "cldrbug:" followed by ticket number,
836 * such as "cldrbug:5013".
837 * @param comment Additional comment, or null
838 * @return true unless -prop:logKnownIssue=no is specified in the test command line argument.
840 public boolean logKnownIssue(String ticket, String comment) {
841 if (!getBooleanProperty("logKnownIssue", true)) {
845 StringBuffer descBuf = new StringBuffer();
846 params.stack.appendPath(descBuf);
847 if (comment != null && comment.length() > 0) {
848 descBuf.append(" (" + comment + ")");
850 String description = descBuf.toString();
852 String ticketLink = "Unknown Ticket";
853 if (ticket != null && ticket.length() > 0) {
854 boolean isCldr = false;
855 ticket = ticket.toLowerCase(Locale.ENGLISH);
856 if (ticket.startsWith(CLDR_TICKET_PREFIX)) {
858 ticket = ticket.substring(CLDR_TICKET_PREFIX.length());
860 ticketLink = (isCldr ? CLDR_TRAC_URL : ICU_TRAC_URL) + ticket;
863 if (params.knownIssues == null) {
864 params.knownIssues = new TreeMap<String, List<String>>();
866 List<String> lines = params.knownIssues.get(ticketLink);
868 lines = new ArrayList<String>();
869 params.knownIssues.put(ticketLink, lines);
871 if (!lines.contains(description)) {
872 lines.add(description);
878 protected int getErrorCount() {
879 return params.errorCount;
882 public String getProperty(String key) {
884 if (key != null && key.length() > 0 && params.props != null) {
885 val = (String)params.props.get(key.toLowerCase());
890 public boolean getBooleanProperty(String key, boolean defVal) {
891 String s = getProperty(key);
893 if (s.equalsIgnoreCase("yes") || s.equals("true")) {
896 if (s.equalsIgnoreCase("no") || s.equalsIgnoreCase("false")) {
903 protected TimeZone safeGetTimeZone(String id) {
904 TimeZone tz = TimeZone.getTimeZone(id);
906 // should never happen
907 errln("FAIL: TimeZone.getTimeZone(" + id + ") => null");
909 if (!tz.getID().equals(id)) {
910 warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID());
916 * Print a usage message for this test class.
918 public void usage() {
919 usage(new PrintWriter(System.out), getClass().getName());
922 public static void usage(PrintWriter pw, String className) {
923 pw.println("Usage: " + className + " option* target*");
925 pw.println("Options:");
926 pw.println(" -d[escribe] Print a short descriptive string for this test and all");
927 pw.println(" listed targets.");
928 pw.println(" -e<n> Set exhaustiveness from 0..10. Default is 0, fewest tests.\n"
929 + " To run all tests, specify -e10. Giving -e with no <n> is\n"
930 + " the same as -e5.");
931 pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n"
932 + " <str> is of the form ['^']text[','['^']text].\n"
933 + " Each string delimited by ',' is a separate filter argument.\n"
934 + " If '^' is prepended to an argument, its matches are excluded.\n"
935 + " Filtering operates on test groups as well as tests, if a test\n"
936 + " group is included, all its subtests that are not excluded will\n"
937 + " be run. Examples:\n"
938 + " -filter:A -- only tests matching A are run. If A matches a group,\n"
939 + " all subtests of this group are run.\n"
940 + " -filter:^A -- all tests except those matching A are run. If A matches\n"
941 + " a group, no subtest of that group will be run.\n"
942 + " -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n"
943 + " Note: Filters are case insensitive.");
944 pw.println(" -h[elp] Print this help text and exit.");
945 pw.println(" -l[ist] List immediate targets of this test");
946 pw.println(" -la, -listAll List immediate targets of this test, and all subtests");
947 pw.println(" -le, -listExaustive List all subtests and targets");
948 // don't know how to get useful numbers for memory usage using java API
950 // pw.println(" -m[emory] print memory usage and force gc for
952 pw.println(" -n[othrow] Message on test failure rather than exception");
953 pw.println(" -p[rompt] Prompt before exiting");
954 pw.println(" -prop:<key>=<value> Set optional property used by this test");
955 pw.println(" -q[uiet] Do not show warnings");
956 pw.println(" -r[andom][:<n>] If present, randomize targets. If n is present,\n"
957 + " use it as the seed. If random is not set, targets will\n"
958 + " be in alphabetical order to ensure cross-platform consistency.");
959 pw.println(" -s[ilent] No output except error summary or exceptions.");
960 pw.println(" -tfilter:<str> Transliterator Test filter of ids.");
961 pw.println(" -t[ime]:<n> Print elapsed time only for tests exceeding n milliseconds.");
962 pw.println(" -v[erbose] Show log messages");
963 pw.println(" -u[nicode] Don't escape error or log messages");
964 pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings.");
965 pw.println(" -nodata | -nd Do not warn if resource data is not present.");
967 pw.println(" If a list or describe option is provided, no tests are run.");
969 pw.println("Targets:");
970 pw.println(" If no target is specified, all targets for this test are run.");
971 pw.println(" If a target contains no '/' characters, and matches a target");
972 pw.println(" of this test, the target is run. Otherwise, the part before the");
973 pw.println(" '/' is used to match a subtest, which then evaluates the");
974 pw.println(" remainder of the target as above. Target matching is case-insensitive.");
976 pw.println(" If multiple targets are provided, each is executed in order.");
979 public static String hex(char[] s){
980 StringBuffer result = new StringBuffer();
981 for (int i = 0; i < s.length; ++i) {
982 if (i != 0) result.append(',');
983 result.append(hex(s[i]));
985 return result.toString();
987 public static String hex(byte[] s){
988 StringBuffer result = new StringBuffer();
989 for (int i = 0; i < s.length; ++i) {
990 if (i != 0) result.append(',');
991 result.append(hex(s[i]));
993 return result.toString();
995 public static String hex(char ch) {
996 StringBuffer result = new StringBuffer();
997 String foo = Integer.toString(ch, 16).toUpperCase();
998 for (int i = foo.length(); i < 4; ++i) {
1001 return result + foo;
1004 public static String hex(int ch) {
1005 StringBuffer result = new StringBuffer();
1006 String foo = Integer.toString(ch, 16).toUpperCase();
1007 for (int i = foo.length(); i < 4; ++i) {
1010 return result + foo;
1013 public static String hex(CharSequence s) {
1014 StringBuilder result = new StringBuilder();
1015 for (int i = 0; i < s.length(); ++i) {
1018 result.append(hex(s.charAt(i)));
1020 return result.toString();
1023 public static String prettify(CharSequence s) {
1024 StringBuilder result = new StringBuilder();
1026 for (int i = 0; i < s.length(); i += Character.charCount(ch)) {
1027 ch = Character.codePointAt(s, i);
1029 result.append("\\U00");
1030 result.append(hex(ch));
1031 } else if (ch > 0xffff) {
1032 result.append("\\U000");
1033 result.append(hex(ch));
1034 } else if (ch < 0x20 || 0x7e < ch) {
1035 result.append("\\u");
1036 result.append(hex(ch));
1038 result.append((char) ch);
1042 return result.toString();
1045 private static java.util.GregorianCalendar cal;
1048 * Return a Date given a year, month, and day of month. This is similar to
1049 * new Date(y-1900, m, d). It uses the default time zone at the time this
1050 * method is first called.
1053 * use 2000 for 2000, unlike new Date()
1055 * use Calendar.JANUARY etc.
1057 * day of month, 1-based
1058 * @return a Date object for the given y/m/d
1060 protected static synchronized java.util.Date getDate(int year, int month,
1063 cal = new java.util.GregorianCalendar();
1066 cal.set(year, month, dom);
1067 return cal.getTime();
1070 public static class NullWriter extends PrintWriter {
1071 public NullWriter() {
1072 super(System.out, false);
1074 public void write(int c) {
1076 public void write(char[] buf, int off, int len) {
1078 public void write(String s, int off, int len) {
1080 public void println() {
1084 public static class ASCIIWriter extends PrintWriter {
1085 private StringBuffer buffer = new StringBuffer();
1087 // Characters that we think are printable but that escapeUnprintable
1089 private static final String PRINTABLES = "\t\n\r";
1091 public ASCIIWriter(Writer w, boolean autoFlush) {
1092 super(w, autoFlush);
1095 public ASCIIWriter(OutputStream os, boolean autoFlush) {
1096 super(os, autoFlush);
1099 public void write(int c) {
1100 synchronized (lock) {
1101 buffer.setLength(0);
1102 if (PRINTABLES.indexOf(c) < 0
1103 && TestUtil.escapeUnprintable(buffer, c)) {
1104 super.write(buffer.toString());
1111 public void write(char[] buf, int off, int len) {
1112 synchronized (lock) {
1113 buffer.setLength(0);
1114 int limit = off + len;
1115 while (off < limit) {
1116 int c = UTF16Util.charAt(buf, 0, buf.length, off);
1117 off += UTF16Util.getCharCount(c);
1118 if (PRINTABLES.indexOf(c) < 0
1119 && TestUtil.escapeUnprintable(buffer, c)) {
1120 super.write(buffer.toString());
1121 buffer.setLength(0);
1129 public void write(String s, int off, int len) {
1130 write(s.substring(off, off + len).toCharArray(), 0, len);
1135 // match against the entire hierarchy
1136 // A;B;!C;!D --> (A ||B) && (!C && !D)
1137 // positive, negative, unknown matches
1138 // positive -- known to be included, negative- known to be excluded
1139 // positive only if no excludes, and matches at least one include, if any
1140 // negative only if matches at least one exclude
1141 // otherwise, we wait
1143 public static class TestParams {
1144 public boolean prompt;
1145 public boolean nothrow;
1146 public boolean verbose;
1147 public boolean quiet;
1148 public int listlevel;
1149 public boolean describe;
1150 public boolean warnings;
1151 public boolean nodata;
1152 public long timing = 0;
1153 public boolean memusage;
1154 public int inclusion;
1155 public String filter;
1157 public String tfilter; // for transliterator tests
1161 public StringBuffer errorSummary;
1162 private StringBuffer timeLog;
1163 private Map<String, List<String>> knownIssues;
1165 public PrintWriter log;
1166 public int indentLevel;
1167 private boolean needLineFeed;
1168 private boolean suppressIndent;
1169 public int errorCount;
1170 public int warnCount;
1171 public int invalidCount;
1172 public int testCount;
1173 private NumberFormat tformat;
1174 public Random random;
1175 public int maxTargetSec = 10;
1176 public HashMap props;
1178 private TestParams() {
1181 public static TestParams create(String arglist, PrintWriter log) {
1182 String[] args = null;
1183 if (arglist != null && arglist.length() > 0) {
1184 args = arglist.split("\\s");
1186 return create(args, log);
1190 * Create a TestParams from a list of arguments. If successful, return the params object,
1191 * else return null. Error messages will be reported on errlog if it is not null.
1192 * Arguments and values understood by this method will be removed from the args array
1193 * and existing args will be shifted down, to be filled by nulls at the end.
1194 * @param args the list of arguments
1195 * @param log the error log, or null if no error log is desired
1196 * @return the new TestParams object, or null if error
1198 public static TestParams create(String[] args, PrintWriter log) {
1199 TestParams params = new TestParams();
1202 params.log = new NullWriter();
1204 params.log = new ASCIIWriter(log, true);
1207 boolean usageError = false;
1208 String filter = null;
1209 String fmt = "#,##0.000s";
1210 int wx = 0; // write argets.
1212 for (int i = 0; i < args.length; i++) {
1213 String arg = args[i];
1214 if (arg == null || arg.length() == 0) {
1217 if (arg.charAt(0) == '-') {
1218 arg = arg.toLowerCase();
1219 if (arg.equals("-verbose") || arg.equals("-v")) {
1220 params.verbose = true;
1221 params.quiet = false;
1222 } else if (arg.equals("-quiet") || arg.equals("-q")) {
1223 params.quiet = true;
1224 params.verbose = false;
1225 } else if (arg.equals("-help") || arg.equals("-h")) {
1227 } else if (arg.equals("-warning") || arg.equals("-w")) {
1228 params.warnings = true;
1229 } else if (arg.equals("-nodata") || arg.equals("-nd")) {
1230 params.nodata = true;
1231 } else if (arg.equals("-list") || arg.equals("-l")) {
1232 params.listlevel = 1;
1233 } else if (arg.equals("-listall") || arg.equals("-la")) {
1234 params.listlevel = 2;
1235 } else if (arg.equals("-listexaustive") || arg.equals("-le")) {
1236 params.listlevel = 3;
1237 } else if (arg.equals("-memory") || arg.equals("-m")) {
1238 params.memusage = true;
1239 } else if (arg.equals("-nothrow") || arg.equals("-n")) {
1240 params.nothrow = true;
1241 params.errorSummary = new StringBuffer();
1242 } else if (arg.equals("-describe") || arg.equals("-d")) {
1243 params.describe = true;
1244 } else if (arg.startsWith("-r")) {
1246 int n = arg.indexOf(':');
1248 s = arg.substring(n + 1);
1249 arg = arg.substring(0, n);
1252 if (arg.equals("-r") || arg.equals("-random")) {
1254 params.seed = System.currentTimeMillis();
1256 params.seed = Long.parseLong(s);
1259 log.println("*** Error: unrecognized argument: " + arg);
1263 } else if (arg.startsWith("-e")) {
1265 params.inclusion = (arg.length() == 2)
1267 : Integer.parseInt(arg.substring(2));
1268 if (params.inclusion < 0 || params.inclusion > 10) {
1272 } else if (arg.startsWith("-tfilter:")) {
1273 params.tfilter = arg.substring(8);
1274 } else if (arg.startsWith("-time") || arg.startsWith("-t")) {
1276 int inx = arg.indexOf(':');
1278 String num = arg.substring(inx + 1);
1280 val = Long.parseLong(num);
1281 } catch (Exception e) {
1282 log.println("*** Error: could not parse time threshold '"
1288 params.timing = val;
1291 } else if (val <= 100) {
1293 } else if (val <= 1000) {
1296 } else if (arg.startsWith("-filter:")) {
1297 String temp = arg.substring(8).toLowerCase();
1298 filter = filter == null ? temp : filter + "," + temp;
1299 } else if (arg.startsWith("-f:")) {
1300 String temp = arg.substring(3).toLowerCase();
1301 filter = filter == null ? temp : filter + "," + temp;
1302 } else if (arg.startsWith("-s")) {
1303 params.log = new NullWriter();
1304 } else if (arg.startsWith("-u")) {
1305 if (params.log instanceof ASCIIWriter) {
1308 } else if (arg.startsWith("-prop:")) {
1309 String temp = arg.substring(6);
1310 int eql = temp.indexOf('=');
1312 log.println("*** Error: could not parse custom property '" + arg + "'");
1316 if (params.props == null) {
1317 params.props = new HashMap();
1319 params.props.put(temp.substring(0, eql), temp.substring(eql+1));
1321 log.println("*** Error: unrecognized argument: "
1327 args[wx++] = arg; // shift down
1331 while (wx < args.length) {
1336 params.tformat = new DecimalFormat(fmt);
1339 usage(log, "TestAll");
1343 if (filter != null) {
1344 params.filter = filter.toLowerCase();
1352 public String errorSummary() {
1353 return errorSummary == null ? "" : errorSummary.toString();
1356 public void init() {
1358 needLineFeed = false;
1359 suppressIndent = false;
1364 random = seed == 0 ? null : new Random(seed);
1367 public class State {
1370 StringBuffer buffer;
1377 public boolean included;
1381 public State(State link, String name, boolean included) {
1386 this.included = included;
1388 this.level = link.level + 1;
1389 this.included = included || link.included;
1391 this.ec = errorCount;
1392 this.wc = warnCount;
1393 this.ic = invalidCount;
1394 this.tc = testCount;
1396 if (link == null || this.included) {
1401 millis = System.currentTimeMillis();
1416 needLineFeed = true;
1420 void appendPath(StringBuffer buf) {
1421 if (this.link != null) {
1422 this.link.appendPath(buf);
1429 public void push(String name, String description, boolean included) {
1430 if (inDocMode() && describe && description != null) {
1431 name += ": " + description;
1433 stack = new State(stack, name, included);
1437 if (stack != null) {
1443 public boolean inDocMode() {
1444 return describe || listlevel != 0;
1447 public boolean doMethods() {
1448 return !inDocMode() || listlevel == 3
1449 || (indentLevel == 1 && listlevel > 0);
1452 public boolean doRecurse() {
1453 return !inDocMode() || listlevel > 1
1454 || (indentLevel == 1 && listlevel > 0);
1457 public boolean doRecurseGroupsOnly() {
1459 && (listlevel == 2 || (indentLevel == 1 && listlevel > 0));
1462 // return 0, -1, or 1
1464 // 0: might run this test, no positive include or exclude on this group
1465 // -1: exclude this test
1466 public int filter(String testName) {
1468 if (filter == null) {
1471 boolean noIncludes = true;
1472 boolean noExcludes = filter.indexOf('^') == -1;
1473 testName = testName.toLowerCase();
1475 while (ix < filter.length()) {
1476 int nix = filter.indexOf(',', ix);
1478 nix = filter.length();
1480 if (filter.charAt(ix) == '^') {
1481 if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) {
1487 if (testName.indexOf(filter.substring(ix, nix)) != -1) {
1497 if (result == 0 && noIncludes) {
1501 // System.out.println("filter: " + testName + " returns: " +
1508 * @param msg The string message to write
1510 public void write(String msg) {
1514 public void writeln(String msg) {
1518 private void write(String msg, boolean newln) {
1519 if (!suppressIndent) {
1522 needLineFeed = false;
1524 log.print(spaces.substring(0, indentLevel * 2));
1531 suppressIndent = !newln;
1534 private void msg(String message, int level, boolean incCount,
1536 int oldLevel = level;
1537 if (level == WARN && (!warnings && !nodata)){
1542 if (level == WARN) {
1545 } else if (level == ERR) {
1550 // should roll indentation stuff into log ???
1551 if (verbose || level > (quiet ? WARN : LOG)) {
1552 if (!suppressIndent) {
1553 indent(indentLevel + 1);
1554 final String[] MSGNAMES = {"", "Warning: ", "Error: "};
1555 log.print(MSGNAMES[oldLevel]);
1567 throw new RuntimeException(message);
1569 if (!suppressIndent && errorSummary != null && stack !=null
1570 && (errorCount == stack.ec + 1)) {
1571 stack.appendPath(errorSummary);
1572 errorSummary.append("\n");
1576 suppressIndent = !newln;
1579 private void writeTestInvalid(String name, boolean nodataArg) {
1580 // msg("***" + name + "*** not found or not valid.", WARN, true,
1584 if (stack != null) {
1587 log.println(" *** Target not found or not valid.");
1589 needLineFeed = false;
1593 msg("Test " + name + " not found or not valid.", WARN, true,
1602 Runtime rt = Runtime.getRuntime();
1603 long lastmem = Long.MAX_VALUE;
1609 } catch (Exception e) {
1613 newmem = rt.totalMemory() - rt.freeMemory();
1614 } while (newmem < lastmem);
1619 private void writeTestResult() {
1625 needLineFeed = false;
1629 long dmem = getmem() - stack.mem;
1630 long dtime = System.currentTimeMillis() - stack.millis;
1632 int testDelta = testCount - stack.tc;
1633 if (testDelta == 0) {
1637 int errorDelta = errorCount - stack.ec;
1638 int invalidDelta = invalidCount - stack.ic;
1642 if (!needLineFeed) {
1643 indent(indentLevel);
1646 needLineFeed = false;
1648 if (memusage || dtime >= timing) {
1651 log.print("dmem: " + dmem);
1653 if (dtime >= timing) {
1657 log.print(tformat.format(dtime / 1000f));
1662 if (errorDelta != 0) {
1663 log.println(" FAILED ("
1666 + ((invalidDelta != 0) ? ", " + invalidDelta
1667 + " tests skipped)" : ")"));
1668 } else if (invalidDelta != 0) {
1669 log.println(" Qualified (" + invalidDelta + " tests skipped)");
1671 log.println(" Passed");
1675 private final void indent(int distance) {
1676 boolean idm = inDocMode();
1683 needLineFeed = false;
1686 log.print(spaces.substring(0, distance * (idm ? 3 : 2)));
1694 public String getTranslitTestFilter() {
1695 return params.tfilter;
1699 * Return the target name for a test class. This is either the end of the
1700 * class name, or if the class declares a public static field
1701 * CLASS_TARGET_NAME, the value of that field.
1703 private static String getClassTargetName(Class testClass) {
1704 String name = testClass.getName();
1706 Field f = testClass.getField("CLASS_TARGET_NAME");
1707 name = (String) f.get(null);
1708 } catch (IllegalAccessException e) {
1709 throw new IllegalStateException(
1710 "static field CLASS_TARGET_NAME must be accessible");
1711 } catch (NoSuchFieldException e) {
1712 int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
1714 name = name.substring(n + 1);
1721 * Check the given array to see that all the strings in the expected array
1725 * string message, for log output
1727 * array of strings to check
1729 * array of strings we expect to see, or null
1730 * @return the length of 'array', or -1 on error
1732 protected int checkArray(String msg, String array[], String expected[]) {
1733 int explen = (expected != null) ? expected.length : 0;
1734 if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32
1735 errln("Internal error");
1739 StringBuffer buf = new StringBuffer();
1741 for (; i < array.length; ++i) {
1742 String s = array[i];
1746 // check expected list
1747 for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) {
1748 if ((seenMask & bit) == 0) {
1749 if (s.equals(expected[j])) {
1751 logln("Ok: \"" + s + "\" seen");
1756 logln(msg + " = [" + buf + "] (" + i + ")");
1757 // did we see all expected strings?
1758 if (((1 << explen) - 1) != seenMask) {
1759 for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) {
1760 if ((seenMask & bit) == 0) {
1761 errln("\"" + expected[j] + "\" not seen");
1765 return array.length;
1769 * Check the given array to see that all the locales in the expected array
1773 * string message, for log output
1775 * array of locales to check
1777 * array of locales names we expect to see, or null
1778 * @return the length of 'array'
1780 protected int checkArray(String msg, Locale array[], String expected[]) {
1781 String strs[] = new String[array.length];
1782 for (int i = 0; i < array.length; ++i)
1783 strs[i] = array[i].toString();
1784 return checkArray(msg, strs, expected);
1788 * Check the given array to see that all the locales in the expected array
1792 * string message, for log output
1794 * array of locales to check
1796 * array of locales names we expect to see, or null
1797 * @return the length of 'array'
1799 protected int checkArray(String msg, ULocale array[], String expected[]) {
1800 String strs[] = new String[array.length];
1801 for (int i = 0; i < array.length; ++i)
1802 strs[i] = array[i].toString();
1803 return checkArray(msg, strs, expected);
1806 // JUnit-like assertions.
1808 protected boolean assertTrue(String message, boolean condition) {
1809 return handleAssert(condition, message, "true", null);
1812 protected boolean assertFalse(String message, boolean condition) {
1813 return handleAssert(!condition, message, "false", null);
1816 protected boolean assertEquals(String message, boolean expected,
1818 return handleAssert(expected == actual, message, String
1819 .valueOf(expected), String.valueOf(actual));
1822 protected boolean assertEquals(String message, long expected, long actual) {
1823 return handleAssert(expected == actual, message, String
1824 .valueOf(expected), String.valueOf(actual));
1827 // do NaN and range calculations to precision of float, don't rely on
1828 // promotion to double
1829 protected boolean assertEquals(String message, float expected,
1830 float actual, double error) {
1831 boolean result = Float.isInfinite(expected)
1832 ? expected == actual
1833 : !(Math.abs(expected - actual) > error); // handles NaN
1834 return handleAssert(result, message, String.valueOf(expected)
1835 + (error == 0 ? "" : " (within " + error + ")"), String
1839 protected boolean assertEquals(String message, double expected,
1840 double actual, double error) {
1841 boolean result = Double.isInfinite(expected)
1842 ? expected == actual
1843 : !(Math.abs(expected - actual) > error); // handles NaN
1844 return handleAssert(result, message, String.valueOf(expected)
1845 + (error == 0 ? "" : " (within " + error + ")"), String
1849 protected <T> boolean assertEquals(String message, T[] expected, T[] actual) {
1850 // Use toString on a List to get useful, readable messages
1851 String expectedString = expected == null ? "null" : Arrays.asList(expected).toString();
1852 String actualString = actual == null ? "null" : Arrays.asList(actual).toString();
1853 return assertEquals(message, expectedString, actualString);
1856 protected boolean assertEquals(String message, Object expected,
1858 boolean result = expected == null ? actual == null : expected
1860 return handleAssert(result, message, stringFor(expected),
1864 protected boolean assertNotEquals(String message, Object expected,
1866 boolean result = !(expected == null ? actual == null : expected
1868 return handleAssert(result, message, stringFor(expected),
1869 stringFor(actual), "not equal to", true);
1872 protected boolean assertSame(String message, Object expected, Object actual) {
1873 return handleAssert(expected == actual, message, stringFor(expected),
1874 stringFor(actual), "==", false);
1877 protected boolean assertNotSame(String message, Object expected,
1879 return handleAssert(expected != actual, message, stringFor(expected),
1880 stringFor(actual), "!=", true);
1883 protected boolean assertNull(String message, Object actual) {
1884 return handleAssert(actual == null, message, null, stringFor(actual));
1887 protected boolean assertNotNull(String message, Object actual) {
1888 return handleAssert(actual != null, message, null, stringFor(actual),
1892 protected void fail() {
1896 protected void fail(String message) {
1897 if (message == null) {
1900 if (!message.equals("")) {
1901 message = ": " + message;
1903 errln(sourceLocation() + message);
1906 private boolean handleAssert(boolean result, String message,
1907 String expected, String actual) {
1908 return handleAssert(result, message, expected, actual, null, false);
1911 public boolean handleAssert(boolean result, String message,
1912 Object expected, Object actual, String relation, boolean flip) {
1913 if (!result || isVerbose()) {
1914 String testLocation = sourceLocation();
1915 if (message == null) {
1918 if (!message.equals("")) {
1919 message = ": " + message;
1921 relation = relation == null ? ", got " : " " + relation + " ";
1923 logln("OK " + testLocation + message + ": "
1924 + (flip ? expected + relation + actual : expected));
1926 // assert must assume errors are true errors and not just warnings
1927 // so cannot warnln here
1928 errln(testLocation + message
1930 + (flip ? relation + expected : " " + expected
1931 + (actual != null ? relation + actual : "")));
1937 private final String stringFor(Object obj) {
1941 if (obj instanceof String) {
1942 return "\"" + obj + '"';
1944 return obj.getClass().getName() + "<" + obj + ">";
1947 // Return the source code location of the caller located callDepth frames up the stack.
1948 private String sourceLocation() {
1949 // Walk up the stack to the first call site outside this file
1950 StackTraceElement[] st = new Throwable().getStackTrace();
1951 for (int i = 0; i < st.length; ++i) {
1952 if (!"TestFmwk.java".equals(st[i].getFileName())) {
1953 return "File " + st[i].getFileName() + ", Line " + st[i].getLineNumber();
1956 throw new InternalError();
1960 // End JUnit-like assertions
1962 // PrintWriter support
1964 public PrintWriter getErrorLogPrintWriter() {
1965 return new PrintWriter(new TestLogWriter(this, TestLog.ERR));
1968 public PrintWriter getLogPrintWriter() {
1969 return new PrintWriter(new TestLogWriter(this, TestLog.LOG));
1972 // end PrintWriter support
1974 protected TestParams params = null;
1976 private final static String spaces = " ";