3 *******************************************************************************
4 * Copyright (C) 1996-2009, International Business Machines Corporation and *
5 * others. All Rights Reserved. *
6 *******************************************************************************
8 package com.ibm.icu.dev.test;
10 import com.ibm.icu.text.UTF16;
11 import com.ibm.icu.text.DecimalFormat;
12 import com.ibm.icu.text.NumberFormat;
13 import com.ibm.icu.util.TimeZone;
14 import com.ibm.icu.util.ULocale;
15 import java.io.ByteArrayOutputStream;
16 import java.io.IOException;
17 import java.io.OutputStream;
18 import java.io.PrintStream;
19 import java.io.PrintWriter;
20 import java.io.Writer;
21 import java.lang.reflect.Field;
22 import java.lang.reflect.InvocationTargetException;
23 import java.lang.reflect.Method;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Comparator;
27 import java.util.HashMap;
28 import java.util.Locale;
29 import java.util.MissingResourceException;
30 import java.util.Random;
31 //#if defined(FOUNDATION10) || defined(J2SE13)
32 //## import com.ibm.icu.impl.Utility;
35 * TestFmwk is a base class for tests that can be run conveniently from the
36 * command line as well as under the Java test harness.
38 * Sub-classes implement a set of methods named Test <something>. Each of these
39 * methods performs some test. Test methods should indicate errors by calling
40 * either err or errln. This will increment the errorCount field and may
41 * optionally print a message to the log. Debugging information may also be
42 * added to the log via the log and logln methods. These methods will add their
43 * arguments to the log only if the test is being run in verbose mode.
45 public class TestFmwk extends AbstractTestLog {
47 * The default time zone for all of our tests. Used in Target.run();
49 private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("PST");
52 * The default locale used for all of our tests. Used in Target.run();
54 private final static Locale defaultLocale = Locale.US;
56 public static final class TestFmwkException extends Exception {
60 private static final long serialVersionUID = -3051148210247229194L;
62 TestFmwkException(String msg) {
66 protected void handleException(Throwable e){
67 //#if defined(FOUNDATION10) || defined(J2SE13)
68 //## Throwable ex = null;
70 Throwable ex = e.getCause();
75 if(ex instanceof ExceptionInInitializerError){
76 ex = ((ExceptionInInitializerError)ex).getException();
78 String msg = ex.getMessage();
82 //System.err.println("TF handleException msg: " + msg);
83 if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError || msg.indexOf("java.util.MissingResourceException")>=0) {
84 if (params.warnings || params.nodata) {
86 } else if (params.nothrow) {
91 throw new RuntimeException(msg);
100 throw new RuntimeException(msg);
104 // use this instead of new random so we get a consistent seed
106 protected Random createRandom() {
107 return new Random(params.seed);
111 * A test that has no test methods itself, but instead runs other tests.
113 * This overrides methods are getTargets and getSubtest from TestFmwk.
115 * If you want the default behavior, pass an array of class names and an
116 * optional description to the constructor. The named classes must extend
117 * TestFmwk. If a provided name doesn't include a ".", package name is
118 * prefixed to it (the package of the current test is used if none was
119 * provided in the constructor). The resulting full name is used to
120 * instantiate an instance of the class using the default constructor.
122 * Class names are resolved to classes when getTargets or getSubtest is
123 * called. This allows instances of TestGroup to be compiled and run without
124 * all the targets they would normally invoke being available.
126 public static abstract class TestGroup extends TestFmwk {
127 private String defaultPackage;
128 private String[] names;
129 private String description;
131 private Class[] tests; // deferred init
134 * Constructor that takes a default package name and a list of class
135 * names. Adopts and modifies the classname list
137 protected TestGroup(String defaultPackage, String[] classnames,
138 String description) {
139 if (classnames == null) {
140 throw new IllegalStateException("classnames must not be null");
143 if (defaultPackage == null) {
144 defaultPackage = getClass().getPackage().getName();
146 defaultPackage = defaultPackage + ".";
148 this.defaultPackage = defaultPackage;
149 this.names = classnames;
150 this.description = description;
154 * Constructor that takes a list of class names and a description, and
155 * uses the package for this class as the default package.
157 protected TestGroup(String[] classnames, String description) {
158 this(null, classnames, description);
162 * Constructor that takes a list of class names, and uses the package
163 * for this class as the default package.
165 protected TestGroup(String[] classnames) {
166 this(null, classnames, null);
169 protected String getDescription() {
173 protected Target getTargets(String targetName) {
174 Target target = null;
175 if (targetName != null) {
176 finishInit(); // hmmm, want to get subtest without initializing
180 TestFmwk test = getSubtest(targetName);
182 target = test.new ClassTarget();
184 target = this.new Target(targetName);
186 } catch (TestFmwkException e) {
187 target = this.new Target(targetName);
189 } else if (params.doRecurse()) {
191 boolean groupOnly = params.doRecurseGroupsOnly();
192 for (int i = names.length; --i >= 0;) {
193 Target newTarget = null;
194 Class cls = tests[i];
195 if (cls == null) { // hack no warning for missing tests
196 if (params.warnings) {
199 newTarget = this.new Target(names[i]);
201 TestFmwk test = getSubtest(i, groupOnly);
203 newTarget = test.new ClassTarget();
206 newTarget = this.new EmptyTarget(names[i]);
208 newTarget = this.new Target(names[i]);
212 if (newTarget != null) {
213 newTarget.setNext(target);
221 protected TestFmwk getSubtest(String testName) throws TestFmwkException {
224 for (int i = 0; i < names.length; ++i) {
225 if (names[i].equalsIgnoreCase(testName)) { // allow
228 return getSubtest(i, false);
231 throw new TestFmwkException(testName);
234 private TestFmwk getSubtest(int i, boolean groupOnly) {
235 Class cls = tests[i];
237 if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) {
242 TestFmwk subtest = (TestFmwk) cls.newInstance();
243 subtest.params = params;
245 } catch (InstantiationException e) {
246 throw new IllegalStateException(e.getMessage());
247 } catch (IllegalAccessException e) {
248 throw new IllegalStateException(e.getMessage());
254 private void finishInit() {
256 tests = new Class[names.length];
258 for (int i = 0; i < names.length; ++i) {
259 String name = names[i];
260 if (name.indexOf('.') == -1) {
261 name = defaultPackage + name;
264 Class cls = Class.forName(name);
265 if (!TestFmwk.class.isAssignableFrom(cls)) {
266 throw new IllegalStateException("class " + name
267 + " does not extend TestFmwk");
271 names[i] = getClassTargetName(cls);
272 } catch (ClassNotFoundException e) {
273 // leave tests[i] null and name as classname
281 * The default target is invalid.
283 public class Target {
285 public final String name;
287 public Target(String name) {
291 public Target setNext(Target next) {
296 public Target getNext() {
300 public Target append(Target targets) {
302 while(t.next != null) {
309 public void run() throws Exception {
312 ++params.invalidCount;
314 Locale.setDefault(defaultLocale);
315 TimeZone.setDefault(defaultTimeZone);
318 params.writeTestInvalid(name, params.nodata);
320 params.push(name, getDescription(), f == 1);
327 protected int filter() {
328 return params.filter(name);
331 protected boolean validate() {
335 protected String getDescription() {
339 protected void execute() throws Exception{
343 public class EmptyTarget extends Target {
344 public EmptyTarget(String name) {
348 protected boolean validate() {
353 public class MethodTarget extends Target {
354 private Method testMethod;
356 public MethodTarget(String name, Method method) {
361 protected boolean validate() {
362 return testMethod != null && validateMethod(name);
365 protected String getDescription() {
366 return getMethodDescription(name);
369 protected void execute() throws Exception{
370 if (params.inDocMode()) {
371 // nothing to execute
372 } else if (!params.stack.included) {
373 ++params.invalidCount;
375 final Object[] NO_ARGS = new Object[0];
379 testMethod.invoke(TestFmwk.this, NO_ARGS);
380 } catch (IllegalAccessException e) {
381 errln("Can't access test method " + testMethod.getName());
382 }catch (ExceptionInInitializerError e){
384 } catch (InvocationTargetException e) {
385 //e.printStackTrace();
387 }catch (MissingResourceException e) {
389 }catch (NoClassDefFoundError e) {
391 }catch (Exception e){
392 /*errln("Encountered: "+ e.toString());
393 e.printStackTrace(System.err);
398 // If non-exhaustive, check if the method target
399 // takes excessive time.
400 if (params.inclusion <= 5) {
401 double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000;
402 if (deltaSec > params.maxTargetSec) {
403 if (params.timeLog == null) {
404 params.timeLog = new StringBuffer();
406 params.stack.appendPath(params.timeLog);
407 params.timeLog.append(" (" + deltaSec + "s" + ")\n");
412 protected String getStackTrace(InvocationTargetException e) {
413 ByteArrayOutputStream bs = new ByteArrayOutputStream();
414 PrintStream ps = new PrintStream(bs);
415 e.getTargetException().printStackTrace(ps);
416 return bs.toString();
420 public class ClassTarget extends Target {
423 public ClassTarget() {
427 public ClassTarget(String targetName) {
428 super(getClassTargetName(TestFmwk.this.getClass()));
429 this.targetName = targetName;
432 protected boolean validate() {
433 return TestFmwk.this.validate();
436 protected String getDescription() {
437 return TestFmwk.this.getDescription();
440 protected void execute() throws Exception {
441 params.indentLevel++;
442 Target target = randomize(getTargets(targetName));
443 while (target != null) {
445 target = target.next;
447 params.indentLevel--;
450 private Target randomize(Target t) {
451 if (t != null && t.getNext() != null) {
452 ArrayList list = new ArrayList();
458 Target[] arr = (Target[]) list.toArray(new Target[list.size()]);
460 if (true) { // todo - add to params?
461 // different jvms return class methods in different orders,
462 // so we sort them (always, and then randomize them, so that
463 // forcing a seed will also work across jvms).
464 Arrays.sort(arr, new Comparator() {
465 public int compare(Object lhs, Object rhs) {
466 // sort in reverse order, later we link up in
468 return ((Target) rhs).name
469 .compareTo(((Target) lhs).name);
473 // t is null to start, ends up as first element
474 // (arr[arr.length-1])
475 for (int i = 0; i < arr.length; ++i) {
476 t = arr[i].setNext(t); // relink in forward order
480 if (params.random != null) {
481 t = null; // reset t to null
482 Random r = params.random;
483 for (int i = arr.length; --i >= 1;) {
484 int x = r.nextInt(i + 1);
485 t = arr[x].setNext(t);
489 t = arr[0].setNext(t); // new first element
497 //------------------------------------------------------------------------
498 // Everything below here is boilerplate code that makes it possible
499 // to add a new test by simply adding a function to an existing class
500 //------------------------------------------------------------------------
502 protected TestFmwk() {
505 protected void init() throws Exception{
509 * Parse arguments into a TestParams object and a collection of target
510 * paths. If there was an error parsing the TestParams, print usage and exit
511 * with -1. Otherwise, call resolveTarget(TestParams, String) for each path,
512 * and run the returned target. After the last test returns, if prompt is
513 * set, prompt and wait for input from stdin. Finally, exit with number of
516 * This method never returns, since it always exits with System.exit();
518 public void run(String[] args) {
519 System.exit(run(args, new PrintWriter(System.out)));
523 * Like run(String[]) except this allows you to specify the error log.
524 * Unlike run(String[]) this returns the error code as a result instead of
525 * calling System.exit().
527 public int run(String[] args, PrintWriter log) {
528 boolean prompt = false;
530 for (int i = 0; i < args.length; ++i) {
531 String arg = args[i];
532 if (arg.equals("-p") || arg.equals("-prompt")) {
541 while (wx < args.length) {
545 TestParams localParams = TestParams.create(args, log);
546 if (localParams == null) {
550 int errorCount = runTests(localParams, args);
552 if (localParams.seed != 0) {
553 localParams.log.println("-random:" + localParams.seed);
554 localParams.log.flush();
557 if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) {
558 localParams.log.println("\nError summary:");
559 localParams.log.println(localParams.errorSummary.toString());
562 if (localParams.timeLog != null && localParams.timeLog.length() > 0) {
563 localParams.log.println("\nTest cases taking excessive time (>" +
564 localParams.maxTargetSec + "s):");
565 localParams.log.println(localParams.timeLog.toString());
569 System.out.println("Hit RETURN to exit...");
573 } catch (IOException e) {
574 localParams.log.println("Exception: " + e.toString() + e.getMessage());
581 public int runTests(TestParams _params, String[] tests) {
584 StringBuffer summary = null;
586 if (tests.length == 0 || tests[0] == null) { // no args
588 resolveTarget(_params).run();
589 ec = _params.errorCount;
591 for (int i = 0; i < tests.length ; ++i) {
592 if (tests[i] == null) continue;
595 _params.log.println();
599 resolveTarget(_params, tests[i]).run();
600 ec += _params.errorCount;
602 if (_params.errorSummary != null && _params.errorSummary.length() > 0) {
603 if (summary == null) {
604 summary = new StringBuffer();
606 summary.append("\nTest Root: " + tests[i] + "\n");
607 summary.append(_params.errorSummary());
610 _params.errorSummary = summary;
612 } catch (Exception e) {
613 e.printStackTrace(_params.log);
614 _params.log.println(e.getMessage());
615 _params.log.println("encountered exception, exiting");
622 * Return a ClassTarget for this test. Params is set on this test.
624 public Target resolveTarget(TestParams paramsArg) {
625 this.params = paramsArg;
626 return new ClassTarget();
630 * Resolve a path from this test to a target. If this test has subtests, and
631 * the path contains '/', the portion before the '/' is resolved to a
632 * subtest, until the path is consumed or the test has no subtests. Returns
633 * a ClassTarget created using the resolved test and remaining path (which
634 * ought to be null or a method name). Params is set on the target's test.
636 public Target resolveTarget(TestParams paramsArg, String targetPath) {
637 TestFmwk test = this;
638 test.params = paramsArg;
640 if (targetPath != null) {
641 if (targetPath.length() == 0) {
645 int e = targetPath.length();
647 // trim all leading and trailing '/'
648 while (targetPath.charAt(p) == '/') {
651 while (e > p && targetPath.charAt(e - 1) == '/') {
654 if (p > 0 || e < targetPath.length()) {
655 targetPath = targetPath.substring(p, e - p);
657 e = targetPath.length();
662 int n = targetPath.indexOf('/');
663 String prefix = n == -1 ? targetPath : targetPath
665 TestFmwk subtest = test.getSubtest(prefix);
667 if (subtest == null) {
678 targetPath = targetPath.substring(n + 1);
680 } catch (TestFmwkException ex) {
681 return test.new Target(targetPath);
686 return test.new ClassTarget(targetPath);
690 * Return true if we can run this test (allows test to inspect jvm,
691 * environment, params before running)
693 protected boolean validate() {
698 * Return the targets for this test. If targetName is null, return all
699 * targets, otherwise return a target for just that name. The returned
700 * target can be null.
702 * The default implementation returns a MethodTarget for each public method
703 * of the object's class whose name starts with "Test" or "test".
705 protected Target getTargets(String targetName) {
706 return getClassTargets(getClass(), targetName);
709 protected Target getClassTargets(Class cls, String targetName) {
714 Target target = null;
715 if (targetName != null) {
717 Method method = cls.getMethod(targetName, (Class[])null);
718 target = new MethodTarget(targetName, method);
719 } catch (NoSuchMethodException e) {
720 if (!inheritTargets()) {
721 return new Target(targetName); // invalid target
723 } catch (SecurityException e) {
727 if (params.doMethods()) {
728 Method[] methods = cls.getDeclaredMethods();
729 for (int i = methods.length; --i >= 0;) {
730 String name = methods[i].getName();
731 if (name.startsWith("Test") || name.startsWith("test")) {
732 target = new MethodTarget(name, methods[i])
739 if (inheritTargets()) {
740 Target parentTarget = getClassTargets(cls.getSuperclass(), targetName);
741 if (parentTarget == null) {
744 if (target == null) {
747 return parentTarget.append(target);
753 protected boolean inheritTargets() {
757 protected String getDescription() {
761 protected boolean validateMethod(String name) {
765 protected String getMethodDescription(String name) {
769 // method tests have no subtests, group tests override
770 protected TestFmwk getSubtest(String prefix) throws TestFmwkException {
774 public boolean isVerbose() {
775 return params.verbose;
778 public boolean noData() {
779 return params.nodata;
782 public boolean isTiming() {
783 return params.timing < Long.MAX_VALUE;
786 public boolean isMemTracking() {
787 return params.memusage;
791 * 0 = fewest tests, 5 is normal build, 10 is most tests
793 public int getInclusion() {
794 return params.inclusion;
797 public boolean isModularBuild() {
798 return params.warnings;
801 public boolean isQuick() {
802 return params.inclusion == 0;
805 public void msg(String message, int level, boolean incCount, boolean newln) {
806 params.msg(message, level, incCount, newln);
809 protected int getErrorCount() {
810 return params.errorCount;
813 public String getProperty(String key) {
815 if (key != null && key.length() > 0 && params.props != null) {
816 val = (String)params.props.get(key.toLowerCase());
821 protected TimeZone safeGetTimeZone(String id) {
822 TimeZone tz = TimeZone.getTimeZone(id);
824 // should never happen
825 errln("FAIL: TimeZone.getTimeZone(" + id + ") => null");
827 if (!tz.getID().equals(id)) {
828 warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID());
834 * Print a usage message for this test class.
836 public void usage() {
837 usage(new PrintWriter(System.out), getClass().getName());
840 public static void usage(PrintWriter pw, String className) {
841 pw.println("Usage: " + className + " option* target*");
843 pw.println("Options:");
844 pw.println(" -d[escribe] Print a short descriptive string for this test and all");
845 pw.println(" listed targets.");
846 pw.println(" -e<n> Set exhaustiveness from 0..10. Default is 0, fewest tests.\n"
847 + " To run all tests, specify -e10. Giving -e with no <n> is\n"
848 + " the same as -e5.");
849 pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n"
850 + " <str> is of the form ['^']text[','['^']text].\n"
851 + " Each string delimited by ',' is a separate filter argument.\n"
852 + " If '^' is prepended to an argument, its matches are excluded.\n"
853 + " Filtering operates on test groups as well as tests, if a test\n"
854 + " group is included, all its subtests that are not excluded will\n"
855 + " be run. Examples:\n"
856 + " -filter:A -- only tests matching A are run. If A matches a group,\n"
857 + " all subtests of this group are run.\n"
858 + " -filter:^A -- all tests except those matching A are run. If A matches\n"
859 + " a group, no subtest of that group will be run.\n"
860 + " -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n"
861 + " Note: Filters are case insensitive.");
862 pw.println(" -h[elp] Print this help text and exit.");
863 pw.println(" -l[ist] List immediate targets of this test");
864 pw.println(" -la, -listAll List immediate targets of this test, and all subtests");
865 pw.println(" -le, -listExaustive List all subtests and targets");
866 // don't know how to get useful numbers for memory usage using java API
868 // pw.println(" -m[emory] print memory usage and force gc for
870 pw.println(" -n[othrow] Message on test failure rather than exception");
871 pw.println(" -p[rompt] Prompt before exiting");
872 pw.println(" -prop:<key>=<value> Set optional property used by this test");
873 pw.println(" -q[uiet] Do not show warnings");
874 pw.println(" -r[andom][:<n>] If present, randomize targets. If n is present,\n"
875 + " use it as the seed. If random is not set, targets will\n"
876 + " be in alphabetical order to ensure cross-platform consistency.");
877 pw.println(" -s[ilent] No output except error summary or exceptions.");
878 pw.println(" -tfilter:<str> Transliterator Test filter of ids.");
879 pw.println(" -t[ime][:<n>] Print elapsed time for each test. if n is present\n"
880 + " only print times >= n milliseconds.");
881 pw.println(" -v[erbose] Show log messages");
882 pw.println(" -u[nicode] Don't escape error or log messages");
883 pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings.");
884 pw.println(" -nodata | -nd Do not warn if resource data is not present.");
886 pw.println(" If a list or describe option is provided, no tests are run.");
888 pw.println("Targets:");
889 pw.println(" If no target is specified, all targets for this test are run.");
890 pw.println(" If a target contains no '/' characters, and matches a target");
891 pw.println(" of this test, the target is run. Otherwise, the part before the");
892 pw.println(" '/' is used to match a subtest, which then evaluates the");
893 pw.println(" remainder of the target as above. Target matching is case-insensitive.");
895 pw.println(" If multiple targets are provided, each is executed in order.");
898 public static String hex(char[] s){
899 StringBuffer result = new StringBuffer();
900 for (int i = 0; i < s.length; ++i) {
901 if (i != 0) result.append(',');
902 result.append(hex(s[i]));
904 return result.toString();
906 public static String hex(byte[] s){
907 StringBuffer result = new StringBuffer();
908 for (int i = 0; i < s.length; ++i) {
909 if (i != 0) result.append(',');
910 result.append(hex(s[i]));
912 return result.toString();
914 public static String hex(char ch) {
915 StringBuffer result = new StringBuffer();
916 String foo = Integer.toString(ch, 16).toUpperCase();
917 for (int i = foo.length(); i < 4; ++i) {
923 public static String hex(int ch) {
924 StringBuffer result = new StringBuffer();
925 String foo = Integer.toString(ch, 16).toUpperCase();
926 for (int i = foo.length(); i < 4; ++i) {
932 public static String hex(String s) {
933 StringBuffer result = new StringBuffer();
934 for (int i = 0; i < s.length(); ++i) {
937 result.append(hex(s.charAt(i)));
939 return result.toString();
942 public static String hex(StringBuffer s) {
943 return hex(s.toString());
945 public static String prettify(String s) {
946 StringBuffer result = new StringBuffer();
948 for (int i = 0; i < s.length(); i += UTF16.getCharCount(ch)) {
949 ch = UTF16.charAt(s, i);
951 result.append("\\U00");
952 result.append(hex(ch));
953 } else if (ch > 0xffff) {
954 result.append("\\U000");
955 result.append(hex(ch));
956 } else if (ch > 0x7f) {
957 result.append("\\u");
958 result.append(hex(ch));
960 result.append((char) ch);
964 return result.toString();
966 public static String prettify(StringBuffer s) {
967 return prettify(s.toString());
970 private static java.util.GregorianCalendar cal;
973 * Return a Date given a year, month, and day of month. This is similar to
974 * new Date(y-1900, m, d). It uses the default time zone at the time this
975 * method is first called.
978 * use 2000 for 2000, unlike new Date()
980 * use Calendar.JANUARY etc.
982 * day of month, 1-based
983 * @return a Date object for the given y/m/d
985 protected static synchronized java.util.Date getDate(int year, int month,
988 cal = new java.util.GregorianCalendar();
991 cal.set(year, month, dom);
992 return cal.getTime();
995 public static class NullWriter extends PrintWriter {
996 public NullWriter() {
997 super(System.out, false);
999 public void write(int c) {
1001 public void write(char[] buf, int off, int len) {
1003 public void write(String s, int off, int len) {
1005 public void println() {
1009 public static class ASCIIWriter extends PrintWriter {
1010 private StringBuffer buffer = new StringBuffer();
1012 // Characters that we think are printable but that escapeUnprintable
1014 private static final String PRINTABLES = "\t\n\r";
1016 public ASCIIWriter(Writer w, boolean autoFlush) {
1017 super(w, autoFlush);
1020 public ASCIIWriter(OutputStream os, boolean autoFlush) {
1021 super(os, autoFlush);
1024 public void write(int c) {
1025 synchronized (lock) {
1026 buffer.setLength(0);
1027 if (PRINTABLES.indexOf(c) < 0
1028 && TestUtil.escapeUnprintable(buffer, c)) {
1029 super.write(buffer.toString());
1036 public void write(char[] buf, int off, int len) {
1037 synchronized (lock) {
1038 buffer.setLength(0);
1039 int limit = off + len;
1040 while (off < limit) {
1041 int c = UTF16Util.charAt(buf, 0, buf.length, off);
1042 off += UTF16Util.getCharCount(c);
1043 if (PRINTABLES.indexOf(c) < 0
1044 && TestUtil.escapeUnprintable(buffer, c)) {
1045 super.write(buffer.toString());
1046 buffer.setLength(0);
1054 public void write(String s, int off, int len) {
1055 write(s.substring(off, off + len).toCharArray(), 0, len);
1060 // match against the entire hierarchy
1061 // A;B;!C;!D --> (A ||B) && (!C && !D)
1062 // positive, negative, unknown matches
1063 // positive -- known to be included, negative- known to be excluded
1064 // positive only if no excludes, and matches at least one include, if any
1065 // negative only if matches at least one exclude
1066 // otherwise, we wait
1068 public static class TestParams {
1069 public boolean prompt;
1070 public boolean nothrow;
1071 public boolean verbose;
1072 public boolean quiet;
1073 public int listlevel;
1074 public boolean describe;
1075 public boolean warnings;
1076 public boolean nodata;
1077 public long timing = Long.MAX_VALUE;
1078 public boolean memusage;
1079 public int inclusion;
1080 public String filter;
1082 public String tfilter; // for transliterator tests
1086 public StringBuffer errorSummary;
1087 private StringBuffer timeLog;
1089 public PrintWriter log;
1090 public int indentLevel;
1091 private boolean needLineFeed;
1092 private boolean suppressIndent;
1093 public int errorCount;
1094 public int warnCount;
1095 public int invalidCount;
1096 public int testCount;
1097 private NumberFormat tformat;
1098 public Random random;
1099 public int maxTargetSec = 10;
1100 public HashMap props;
1102 private TestParams() {
1105 public static TestParams create(String arglist, PrintWriter log) {
1106 String[] args = null;
1107 if (arglist != null && arglist.length() > 0) {
1108 //#if defined(FOUNDATION10) || defined(J2SE13)
1109 //## args = Utility.split(arglist, '\u0020');
1111 args = arglist.split("\\s");
1114 return create(args, log);
1118 * Create a TestParams from a list of arguments. If successful, return the params object,
1119 * else return null. Error messages will be reported on errlog if it is not null.
1120 * Arguments and values understood by this method will be removed from the args array
1121 * and existing args will be shifted down, to be filled by nulls at the end.
1122 * @param args the list of arguments
1123 * @param log the error log, or null if no error log is desired
1124 * @return the new TestParams object, or null if error
1126 public static TestParams create(String[] args, PrintWriter log) {
1127 TestParams params = new TestParams();
1130 params.log = new NullWriter();
1132 params.log = new ASCIIWriter(log, true);
1135 boolean usageError = false;
1136 String filter = null;
1137 int wx = 0; // write argets.
1139 for (int i = 0; i < args.length; i++) {
1140 String arg = args[i];
1141 if (arg == null || arg.length() == 0) {
1144 if (arg.charAt(0) == '-') {
1145 arg = arg.toLowerCase();
1146 if (arg.equals("-verbose") || arg.equals("-v")) {
1147 params.verbose = true;
1148 params.quiet = false;
1149 } else if (arg.equals("-quiet") || arg.equals("-q")) {
1150 params.quiet = true;
1151 params.verbose = false;
1152 } else if (arg.equals("-help") || arg.equals("-h")) {
1154 } else if (arg.equals("-warning") || arg.equals("-w")) {
1155 params.warnings = true;
1156 } else if (arg.equals("-nodata") || arg.equals("-nd")) {
1157 params.nodata = true;
1158 } else if (arg.equals("-list") || arg.equals("-l")) {
1159 params.listlevel = 1;
1160 } else if (arg.equals("-listall") || arg.equals("-la")) {
1161 params.listlevel = 2;
1162 } else if (arg.equals("-listexaustive") || arg.equals("-le")) {
1163 params.listlevel = 3;
1164 } else if (arg.equals("-memory") || arg.equals("-m")) {
1165 params.memusage = true;
1166 } else if (arg.equals("-nothrow") || arg.equals("-n")) {
1167 params.nothrow = true;
1168 params.errorSummary = new StringBuffer();
1169 } else if (arg.equals("-describe") || arg.equals("-d")) {
1170 params.describe = true;
1171 } else if (arg.startsWith("-r")) {
1173 int n = arg.indexOf(':');
1175 s = arg.substring(n + 1);
1176 arg = arg.substring(0, n);
1179 if (arg.equals("-r") || arg.equals("-random")) {
1181 params.seed = System.currentTimeMillis();
1183 params.seed = Long.parseLong(s);
1186 log.println("*** Error: unrecognized argument: " + arg);
1190 } else if (arg.startsWith("-e")) {
1192 params.inclusion = (arg.length() == 2)
1194 : Integer.parseInt(arg.substring(2));
1195 if (params.inclusion < 0 || params.inclusion > 10) {
1199 } else if (arg.startsWith("-tfilter:")) {
1200 params.tfilter = arg.substring(8);
1201 } else if (arg.startsWith("-time") || arg.startsWith("-t")) {
1203 int inx = arg.indexOf(':');
1205 String num = arg.substring(inx + 1);
1207 val = Long.parseLong(num);
1208 } catch (Exception e) {
1209 log.println("*** Error: could not parse time threshold '"
1215 params.timing = val;
1216 String fmt = "#,00s";
1219 } else if (val <= 100) {
1221 } else if (val <= 1000) {
1224 params.tformat = new DecimalFormat(fmt);
1225 } else if (arg.startsWith("-filter:")) {
1226 String temp = arg.substring(8).toLowerCase();
1227 filter = filter == null ? temp : filter + "," + temp;
1228 } else if (arg.startsWith("-f:")) {
1229 String temp = arg.substring(3).toLowerCase();
1230 filter = filter == null ? temp : filter + "," + temp;
1231 } else if (arg.startsWith("-s")) {
1232 params.log = new NullWriter();
1233 } else if (arg.startsWith("-u")) {
1234 if (params.log instanceof ASCIIWriter) {
1237 } else if (arg.startsWith("-prop:")) {
1238 String temp = arg.substring(6);
1239 int eql = temp.indexOf('=');
1241 log.println("*** Error: could not parse custom property '" + arg + "'");
1245 if (params.props == null) {
1246 params.props = new HashMap();
1248 params.props.put(temp.substring(0, eql), temp.substring(eql+1));
1250 log.println("*** Error: unrecognized argument: "
1256 args[wx++] = arg; // shift down
1260 while (wx < args.length) {
1266 usage(log, "TestAll");
1270 if (filter != null) {
1271 params.filter = filter.toLowerCase();
1279 public String errorSummary() {
1280 return errorSummary == null ? "" : errorSummary.toString();
1283 public void init() {
1285 needLineFeed = false;
1286 suppressIndent = false;
1291 random = seed == 0 ? null : new Random(seed);
1294 public class State {
1297 StringBuffer buffer;
1304 public boolean included;
1308 public State(State link, String name, boolean included) {
1313 this.included = included;
1315 this.level = link.level + 1;
1316 this.included = included || link.included;
1318 this.ec = errorCount;
1319 this.wc = warnCount;
1320 this.ic = invalidCount;
1321 this.tc = testCount;
1323 if (link == null || this.included) {
1328 millis = System.currentTimeMillis();
1343 needLineFeed = true;
1347 void appendPath(StringBuffer buf) {
1348 if (this.link != null) {
1349 this.link.appendPath(buf);
1356 public void push(String name, String description, boolean included) {
1357 if (inDocMode() && describe && description != null) {
1358 name += ": " + description;
1360 stack = new State(stack, name, included);
1364 if (stack != null) {
1370 public boolean inDocMode() {
1371 return describe || listlevel != 0;
1374 public boolean doMethods() {
1375 return !inDocMode() || listlevel == 3
1376 || (indentLevel == 1 && listlevel > 0);
1379 public boolean doRecurse() {
1380 return !inDocMode() || listlevel > 1
1381 || (indentLevel == 1 && listlevel > 0);
1384 public boolean doRecurseGroupsOnly() {
1386 && (listlevel == 2 || (indentLevel == 1 && listlevel > 0));
1389 // return 0, -1, or 1
1391 // 0: might run this test, no positive include or exclude on this group
1392 // -1: exclude this test
1393 public int filter(String testName) {
1395 if (filter == null) {
1398 boolean noIncludes = true;
1399 boolean noExcludes = filter.indexOf('^') == -1;
1400 testName = testName.toLowerCase();
1402 while (ix < filter.length()) {
1403 int nix = filter.indexOf(',', ix);
1405 nix = filter.length();
1407 if (filter.charAt(ix) == '^') {
1408 if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) {
1414 if (testName.indexOf(filter.substring(ix, nix)) != -1) {
1424 if (result == 0 && noIncludes) {
1428 // System.out.println("filter: " + testName + " returns: " +
1435 * @param msg The string message to write
1437 public void write(String msg) {
1441 public void writeln(String msg) {
1445 private void write(String msg, boolean newln) {
1446 if (!suppressIndent) {
1449 needLineFeed = false;
1451 log.print(spaces.substring(0, indentLevel * 2));
1458 suppressIndent = !newln;
1461 private void msg(String message, int level, boolean incCount,
1463 if (level == WARN && (!warnings && !nodata)){
1468 if (level == WARN) {
1471 } else if (level == ERR) {
1476 // should roll indentation stuff into log ???
1477 if (verbose || level > (quiet ? WARN : LOG)) {
1478 if (!suppressIndent) {
1479 indent(indentLevel + 1);
1480 final String[] MSGNAMES = {"", "Warning: ", "Error: "};
1481 log.print(MSGNAMES[level]);
1493 throw new RuntimeException(message);
1495 if (!suppressIndent && errorSummary != null && stack !=null
1496 && (errorCount == stack.ec + 1)) {
1497 stack.appendPath(errorSummary);
1498 errorSummary.append("\n");
1502 suppressIndent = !newln;
1505 private void writeTestInvalid(String name, boolean nodataArg) {
1506 // msg("***" + name + "*** not found or not valid.", WARN, true,
1510 if (stack != null) {
1513 log.println(" *** Target not found or not valid.");
1515 needLineFeed = false;
1519 msg("Test " + name + " not found or not valid.", WARN, true,
1528 Runtime rt = Runtime.getRuntime();
1529 long lastmem = Long.MAX_VALUE;
1535 } catch (Exception e) {
1539 newmem = rt.totalMemory() - rt.freeMemory();
1540 } while (newmem < lastmem);
1545 private void writeTestResult() {
1551 needLineFeed = false;
1555 long dmem = getmem() - stack.mem;
1556 long dtime = System.currentTimeMillis() - stack.millis;
1558 int testDelta = testCount - stack.tc;
1559 if (testDelta == 0) {
1563 int errorDelta = errorCount - stack.ec;
1564 int invalidDelta = invalidCount - stack.ic;
1568 if (!needLineFeed) {
1569 indent(indentLevel);
1572 needLineFeed = false;
1574 if (memusage || dtime >= timing) {
1577 log.print("dmem: " + dmem);
1579 if (dtime >= timing) {
1583 log.print(tformat.format(dtime / 1000f));
1588 if (errorDelta != 0) {
1589 log.println(" FAILED ("
1592 + ((invalidDelta != 0) ? ", " + invalidDelta
1593 + " tests skipped)" : ")"));
1594 } else if (invalidDelta != 0) {
1595 log.println(" Qualified (" + invalidDelta + " tests skipped)");
1597 log.println(" Passed");
1601 private final void indent(int distance) {
1602 boolean idm = inDocMode();
1609 needLineFeed = false;
1612 log.print(spaces.substring(0, distance * (idm ? 3 : 2)));
1620 public String getTranslitTestFilter() {
1621 return params.tfilter;
1625 * Return the target name for a test class. This is either the end of the
1626 * class name, or if the class declares a public static field
1627 * CLASS_TARGET_NAME, the value of that field.
1629 private static String getClassTargetName(Class testClass) {
1630 String name = testClass.getName();
1632 Field f = testClass.getField("CLASS_TARGET_NAME");
1633 name = (String) f.get(null);
1634 } catch (IllegalAccessException e) {
1635 throw new IllegalStateException(
1636 "static field CLASS_TARGET_NAME must be accessible");
1637 } catch (NoSuchFieldException e) {
1638 int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
1640 name = name.substring(n + 1);
1647 * Check the given array to see that all the strings in the expected array
1651 * string message, for log output
1653 * array of strings to check
1655 * array of strings we expect to see, or null
1656 * @return the length of 'array', or -1 on error
1658 protected int checkArray(String msg, String array[], String expected[]) {
1659 int explen = (expected != null) ? expected.length : 0;
1660 if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32
1661 errln("Internal error");
1665 StringBuffer buf = new StringBuffer();
1667 for (; i < array.length; ++i) {
1668 String s = array[i];
1672 // check expected list
1673 for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) {
1674 if ((seenMask & bit) == 0) {
1675 if (s.equals(expected[j])) {
1677 logln("Ok: \"" + s + "\" seen");
1682 logln(msg + " = [" + buf + "] (" + i + ")");
1683 // did we see all expected strings?
1684 if (((1 << explen) - 1) != seenMask) {
1685 for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) {
1686 if ((seenMask & bit) == 0) {
1687 errln("\"" + expected[j] + "\" not seen");
1691 return array.length;
1695 * Check the given array to see that all the locales in the expected array
1699 * string message, for log output
1701 * array of locales to check
1703 * array of locales names we expect to see, or null
1704 * @return the length of 'array'
1706 protected int checkArray(String msg, Locale array[], String expected[]) {
1707 String strs[] = new String[array.length];
1708 for (int i = 0; i < array.length; ++i)
1709 strs[i] = array[i].toString();
1710 return checkArray(msg, strs, expected);
1714 * Check the given array to see that all the locales in the expected array
1718 * string message, for log output
1720 * array of locales to check
1722 * array of locales names we expect to see, or null
1723 * @return the length of 'array'
1725 protected int checkArray(String msg, ULocale array[], String expected[]) {
1726 String strs[] = new String[array.length];
1727 for (int i = 0; i < array.length; ++i)
1728 strs[i] = array[i].toString();
1729 return checkArray(msg, strs, expected);
1732 // JUnit-like assertions.
1734 protected boolean assertTrue(String message, boolean condition) {
1735 return handleAssert(condition, message, "true", null);
1738 protected boolean assertFalse(String message, boolean condition) {
1739 return handleAssert(!condition, message, "false", null);
1742 protected boolean assertEquals(String message, boolean expected,
1744 return handleAssert(expected == actual, message, String
1745 .valueOf(expected), String.valueOf(actual));
1748 protected boolean assertEquals(String message, long expected, long actual) {
1749 return handleAssert(expected == actual, message, String
1750 .valueOf(expected), String.valueOf(actual));
1753 // do NaN and range calculations to precision of float, don't rely on
1754 // promotion to double
1755 protected boolean assertEquals(String message, float expected,
1756 float actual, double error) {
1757 boolean result = Float.isInfinite(expected)
1758 ? expected == actual
1759 : !(Math.abs(expected - actual) > error); // handles NaN
1760 return handleAssert(result, message, String.valueOf(expected)
1761 + (error == 0 ? "" : " (within " + error + ")"), String
1765 protected boolean assertEquals(String message, double expected,
1766 double actual, double error) {
1767 boolean result = Double.isInfinite(expected)
1768 ? expected == actual
1769 : !(Math.abs(expected - actual) > error); // handles NaN
1770 return handleAssert(result, message, String.valueOf(expected)
1771 + (error == 0 ? "" : " (within " + error + ")"), String
1775 protected boolean assertEquals(String message, Object expected,
1777 boolean result = expected == null ? actual == null : expected
1779 return handleAssert(result, message, stringFor(expected),
1783 protected boolean assertNotEquals(String message, Object expected,
1785 boolean result = !(expected == null ? actual == null : expected
1787 return handleAssert(result, message, stringFor(expected),
1788 stringFor(actual), "not equal to", true);
1791 protected boolean assertSame(String message, Object expected, Object actual) {
1792 return handleAssert(expected == actual, message, stringFor(expected),
1793 stringFor(actual), "==", false);
1796 protected boolean assertNotSame(String message, Object expected,
1798 return handleAssert(expected != actual, message, stringFor(expected),
1799 stringFor(actual), "!=", true);
1802 protected boolean assertNull(String message, Object actual) {
1803 return handleAssert(actual == null, message, null, stringFor(actual));
1806 protected boolean assertNotNull(String message, Object actual) {
1807 return handleAssert(actual != null, message, null, stringFor(actual),
1811 protected void fail(String message) {
1815 private boolean handleAssert(boolean result, String message,
1816 String expected, String actual) {
1817 return handleAssert(result, message, expected, actual, null, false);
1820 public boolean handleAssert(boolean result, String message,
1821 Object expected, Object actual, String relation, boolean flip) {
1822 if (!result || isVerbose()) {
1823 message = message == null ? "" : " " + message;
1824 relation = relation == null ? ", got " : " " + relation + " ";
1826 logln("OK" + message + ": "
1827 + (flip ? expected + relation + actual : expected));
1829 // assert must assume errors are true errors and not just warnings
1830 // so cannot warnln here
1833 + (flip ? relation + expected : " " + expected
1834 + (actual != null ? relation + actual : "")));
1840 private final String stringFor(Object obj) {
1843 if (obj instanceof String)
1844 return "\"" + obj + '"';
1845 return obj.getClass().getName() + "<" + obj + ">";
1848 // End JUnit-like assertions
1850 // PrintWriter support
1852 public PrintWriter getErrorLogPrintWriter() {
1853 return new PrintWriter(new TestLogWriter(this, TestLog.ERR));
1856 public PrintWriter getLogPrintWriter() {
1857 return new PrintWriter(new TestLogWriter(this, TestLog.LOG));
1860 // end PrintWriter support
1862 protected TestParams params = null;
1864 private final static String spaces = " ";