-//##header\r
-/*\r
- *******************************************************************************\r
- * Copyright (C) 1996-2009, International Business Machines Corporation and *\r
- * others. All Rights Reserved. *\r
- *******************************************************************************\r
- */\r
-package com.ibm.icu.dev.test;\r
-\r
-import com.ibm.icu.text.UTF16;\r
-import com.ibm.icu.text.DecimalFormat;\r
-import com.ibm.icu.text.NumberFormat;\r
-import com.ibm.icu.util.TimeZone;\r
-import com.ibm.icu.util.ULocale;\r
-import java.io.ByteArrayOutputStream;\r
-import java.io.IOException;\r
-import java.io.OutputStream;\r
-import java.io.PrintStream;\r
-import java.io.PrintWriter;\r
-import java.io.Writer;\r
-import java.lang.reflect.Field;\r
-import java.lang.reflect.InvocationTargetException;\r
-import java.lang.reflect.Method;\r
-import java.util.ArrayList;\r
-import java.util.Arrays;\r
-import java.util.Comparator;\r
-import java.util.HashMap;\r
-import java.util.Locale;\r
-import java.util.MissingResourceException;\r
-import java.util.Random;\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//## import com.ibm.icu.impl.Utility;\r
-//#endif\r
-/**\r
- * TestFmwk is a base class for tests that can be run conveniently from the\r
- * command line as well as under the Java test harness.\r
- * <p>\r
- * Sub-classes implement a set of methods named Test <something>. Each of these\r
- * methods performs some test. Test methods should indicate errors by calling\r
- * either err or errln. This will increment the errorCount field and may\r
- * optionally print a message to the log. Debugging information may also be\r
- * added to the log via the log and logln methods. These methods will add their\r
- * arguments to the log only if the test is being run in verbose mode.\r
- */\r
-public class TestFmwk extends AbstractTestLog {\r
- /**\r
- * The default time zone for all of our tests. Used in Target.run();\r
- */\r
- private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("PST");\r
-\r
- /**\r
- * The default locale used for all of our tests. Used in Target.run();\r
- */\r
- private final static Locale defaultLocale = Locale.US;\r
-\r
- public static final class TestFmwkException extends Exception {\r
- /**\r
- * For serialization\r
- */\r
- private static final long serialVersionUID = -3051148210247229194L;\r
-\r
- TestFmwkException(String msg) {\r
- super(msg);\r
- }\r
- }\r
- protected void handleException(Throwable e){\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//## Throwable ex = null;\r
-//#else\r
- Throwable ex = e.getCause();\r
-//#endif\r
- if(ex==null){\r
- ex = e;\r
- }\r
- if(ex instanceof ExceptionInInitializerError){\r
- ex = ((ExceptionInInitializerError)ex).getException();\r
- }\r
- String msg = ex.getMessage();\r
- if(msg==null){\r
- msg = "";\r
- }\r
- //System.err.println("TF handleException msg: " + msg);\r
- if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError || msg.indexOf("java.util.MissingResourceException")>=0) {\r
- if (params.warnings || params.nodata) {\r
- warnln(msg);\r
- } else if (params.nothrow) {\r
- errln(msg);\r
- ex.printStackTrace();\r
- } else {\r
- ex.printStackTrace();\r
- throw new RuntimeException(msg);\r
- }\r
- } else {\r
- if (params.nothrow) {\r
- errln(msg);\r
- ex.printStackTrace();\r
- } else {\r
- errln(msg);\r
- ex.printStackTrace();\r
- throw new RuntimeException(msg);\r
- }\r
- }\r
- }\r
- // use this instead of new random so we get a consistent seed\r
- // for our tests\r
- protected Random createRandom() {\r
- return new Random(params.seed);\r
- }\r
-\r
- /**\r
- * A test that has no test methods itself, but instead runs other tests.\r
- * \r
- * This overrides methods are getTargets and getSubtest from TestFmwk.\r
- * \r
- * If you want the default behavior, pass an array of class names and an\r
- * optional description to the constructor. The named classes must extend\r
- * TestFmwk. If a provided name doesn't include a ".", package name is\r
- * prefixed to it (the package of the current test is used if none was\r
- * provided in the constructor). The resulting full name is used to\r
- * instantiate an instance of the class using the default constructor.\r
- * \r
- * Class names are resolved to classes when getTargets or getSubtest is\r
- * called. This allows instances of TestGroup to be compiled and run without\r
- * all the targets they would normally invoke being available.\r
- */\r
- public static abstract class TestGroup extends TestFmwk {\r
- private String defaultPackage;\r
- private String[] names;\r
- private String description;\r
-\r
- private Class[] tests; // deferred init\r
-\r
- /**\r
- * Constructor that takes a default package name and a list of class\r
- * names. Adopts and modifies the classname list\r
- */\r
- protected TestGroup(String defaultPackage, String[] classnames,\r
- String description) {\r
- if (classnames == null) {\r
- throw new IllegalStateException("classnames must not be null");\r
- }\r
-\r
- if (defaultPackage == null) {\r
- defaultPackage = getClass().getPackage().getName();\r
- }\r
- defaultPackage = defaultPackage + ".";\r
-\r
- this.defaultPackage = defaultPackage;\r
- this.names = classnames;\r
- this.description = description;\r
- }\r
-\r
- /**\r
- * Constructor that takes a list of class names and a description, and\r
- * uses the package for this class as the default package.\r
- */\r
- protected TestGroup(String[] classnames, String description) {\r
- this(null, classnames, description);\r
- }\r
-\r
- /**\r
- * Constructor that takes a list of class names, and uses the package\r
- * for this class as the default package.\r
- */\r
- protected TestGroup(String[] classnames) {\r
- this(null, classnames, null);\r
- }\r
-\r
- protected String getDescription() {\r
- return description;\r
- }\r
-\r
- protected Target getTargets(String targetName) {\r
- Target target = null;\r
- if (targetName != null) {\r
- finishInit(); // hmmm, want to get subtest without initializing\r
- // all tests\r
-\r
- try {\r
- TestFmwk test = getSubtest(targetName);\r
- if (test != null) {\r
- target = test.new ClassTarget();\r
- } else {\r
- target = this.new Target(targetName);\r
- }\r
- } catch (TestFmwkException e) {\r
- target = this.new Target(targetName);\r
- }\r
- } else if (params.doRecurse()) {\r
- finishInit();\r
- boolean groupOnly = params.doRecurseGroupsOnly();\r
- for (int i = names.length; --i >= 0;) {\r
- Target newTarget = null;\r
- Class cls = tests[i];\r
- if (cls == null) { // hack no warning for missing tests\r
- if (params.warnings) {\r
- continue;\r
- }\r
- newTarget = this.new Target(names[i]);\r
- } else {\r
- TestFmwk test = getSubtest(i, groupOnly);\r
- if (test != null) {\r
- newTarget = test.new ClassTarget();\r
- } else {\r
- if (groupOnly) {\r
- newTarget = this.new EmptyTarget(names[i]);\r
- } else {\r
- newTarget = this.new Target(names[i]);\r
- }\r
- }\r
- }\r
- if (newTarget != null) {\r
- newTarget.setNext(target);\r
- target = newTarget;\r
- }\r
- }\r
- }\r
-\r
- return target;\r
- }\r
- protected TestFmwk getSubtest(String testName) throws TestFmwkException {\r
- finishInit();\r
-\r
- for (int i = 0; i < names.length; ++i) {\r
- if (names[i].equalsIgnoreCase(testName)) { // allow\r
- // case-insensitive\r
- // matching\r
- return getSubtest(i, false);\r
- }\r
- }\r
- throw new TestFmwkException(testName);\r
- }\r
-\r
- private TestFmwk getSubtest(int i, boolean groupOnly) {\r
- Class cls = tests[i];\r
- if (cls != null) {\r
- if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) {\r
- return null;\r
- }\r
-\r
- try {\r
- TestFmwk subtest = (TestFmwk) cls.newInstance();\r
- subtest.params = params;\r
- return subtest;\r
- } catch (InstantiationException e) {\r
- throw new IllegalStateException(e.getMessage());\r
- } catch (IllegalAccessException e) {\r
- throw new IllegalStateException(e.getMessage());\r
- }\r
- }\r
- return null;\r
- }\r
-\r
- private void finishInit() {\r
- if (tests == null) {\r
- tests = new Class[names.length];\r
-\r
- for (int i = 0; i < names.length; ++i) {\r
- String name = names[i];\r
- if (name.indexOf('.') == -1) {\r
- name = defaultPackage + name;\r
- }\r
- try {\r
- Class cls = Class.forName(name);\r
- if (!TestFmwk.class.isAssignableFrom(cls)) {\r
- throw new IllegalStateException("class " + name\r
- + " does not extend TestFmwk");\r
- }\r
-\r
- tests[i] = cls;\r
- names[i] = getClassTargetName(cls);\r
- } catch (ClassNotFoundException e) {\r
- // leave tests[i] null and name as classname\r
- }\r
- }\r
- }\r
- }\r
- }\r
-\r
- /**\r
- * The default target is invalid.\r
- */\r
- public class Target {\r
- private Target next;\r
- public final String name;\r
-\r
- public Target(String name) {\r
- this.name = name;\r
- }\r
-\r
- public Target setNext(Target next) {\r
- this.next = next;\r
- return this;\r
- }\r
-\r
- public Target getNext() {\r
- return next;\r
- }\r
-\r
- public Target append(Target targets) {\r
- Target t = this;\r
- while(t.next != null) {\r
- t = t.next;\r
- }\r
- t.next = targets;\r
- return this;\r
- }\r
-\r
- public void run() throws Exception {\r
- int f = filter();\r
- if (f == -1) {\r
- ++params.invalidCount;\r
- } else {\r
- Locale.setDefault(defaultLocale);\r
- TimeZone.setDefault(defaultTimeZone);\r
-\r
- if (!validate()) {\r
- params.writeTestInvalid(name, params.nodata);\r
- } else {\r
- params.push(name, getDescription(), f == 1);\r
- execute();\r
- params.pop();\r
- }\r
- }\r
- }\r
-\r
- protected int filter() {\r
- return params.filter(name);\r
- }\r
-\r
- protected boolean validate() {\r
- return false;\r
- }\r
-\r
- protected String getDescription() {\r
- return null;\r
- }\r
-\r
- protected void execute() throws Exception{\r
- }\r
- }\r
-\r
- public class EmptyTarget extends Target {\r
- public EmptyTarget(String name) {\r
- super(name);\r
- }\r
-\r
- protected boolean validate() {\r
- return true;\r
- }\r
- }\r
-\r
- public class MethodTarget extends Target {\r
- private Method testMethod;\r
-\r
- public MethodTarget(String name, Method method) {\r
- super(name);\r
- testMethod = method;\r
- }\r
-\r
- protected boolean validate() {\r
- return testMethod != null && validateMethod(name);\r
- }\r
-\r
- protected String getDescription() {\r
- return getMethodDescription(name);\r
- }\r
-\r
- protected void execute() throws Exception{\r
- if (params.inDocMode()) {\r
- // nothing to execute\r
- } else if (!params.stack.included) {\r
- ++params.invalidCount;\r
- } else {\r
- final Object[] NO_ARGS = new Object[0];\r
- try {\r
- ++params.testCount;\r
- init();\r
- testMethod.invoke(TestFmwk.this, NO_ARGS);\r
- } catch (IllegalAccessException e) {\r
- errln("Can't access test method " + testMethod.getName());\r
- }catch (ExceptionInInitializerError e){\r
- handleException(e);\r
- } catch (InvocationTargetException e) {\r
- //e.printStackTrace();\r
- handleException(e);\r
- }catch (MissingResourceException e) {\r
- handleException(e);\r
- }catch (NoClassDefFoundError e) {\r
- handleException(e);\r
- }catch (Exception e){\r
- /*errln("Encountered: "+ e.toString());\r
- e.printStackTrace(System.err);\r
- */\r
- handleException(e);\r
- }\r
- }\r
- // If non-exhaustive, check if the method target\r
- // takes excessive time.\r
- if (params.inclusion <= 5) {\r
- double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000;\r
- if (deltaSec > params.maxTargetSec) {\r
- if (params.timeLog == null) {\r
- params.timeLog = new StringBuffer();\r
- }\r
- params.stack.appendPath(params.timeLog);\r
- params.timeLog.append(" (" + deltaSec + "s" + ")\n");\r
- }\r
- }\r
- }\r
-\r
- protected String getStackTrace(InvocationTargetException e) {\r
- ByteArrayOutputStream bs = new ByteArrayOutputStream();\r
- PrintStream ps = new PrintStream(bs);\r
- e.getTargetException().printStackTrace(ps);\r
- return bs.toString();\r
- }\r
- }\r
-\r
- public class ClassTarget extends Target {\r
- String targetName;\r
-\r
- public ClassTarget() {\r
- this(null);\r
- }\r
-\r
- public ClassTarget(String targetName) {\r
- super(getClassTargetName(TestFmwk.this.getClass()));\r
- this.targetName = targetName;\r
- }\r
-\r
- protected boolean validate() {\r
- return TestFmwk.this.validate();\r
- }\r
-\r
- protected String getDescription() {\r
- return TestFmwk.this.getDescription();\r
- }\r
-\r
- protected void execute() throws Exception {\r
- params.indentLevel++;\r
- Target target = randomize(getTargets(targetName));\r
- while (target != null) {\r
- target.run();\r
- target = target.next;\r
- }\r
- params.indentLevel--;\r
- }\r
-\r
- private Target randomize(Target t) {\r
- if (t != null && t.getNext() != null) {\r
- ArrayList list = new ArrayList();\r
- while (t != null) {\r
- list.add(t);\r
- t = t.getNext();\r
- }\r
-\r
- Target[] arr = (Target[]) list.toArray(new Target[list.size()]);\r
-\r
- if (true) { // todo - add to params?\r
- // different jvms return class methods in different orders,\r
- // so we sort them (always, and then randomize them, so that\r
- // forcing a seed will also work across jvms).\r
- Arrays.sort(arr, new Comparator() {\r
- public int compare(Object lhs, Object rhs) {\r
- // sort in reverse order, later we link up in\r
- // forward order\r
- return ((Target) rhs).name\r
- .compareTo(((Target) lhs).name);\r
- }\r
- });\r
-\r
- // t is null to start, ends up as first element\r
- // (arr[arr.length-1])\r
- for (int i = 0; i < arr.length; ++i) {\r
- t = arr[i].setNext(t); // relink in forward order\r
- }\r
- }\r
-\r
- if (params.random != null) {\r
- t = null; // reset t to null\r
- Random r = params.random;\r
- for (int i = arr.length; --i >= 1;) {\r
- int x = r.nextInt(i + 1);\r
- t = arr[x].setNext(t);\r
- arr[x] = arr[i];\r
- }\r
-\r
- t = arr[0].setNext(t); // new first element\r
- }\r
- }\r
-\r
- return t;\r
- }\r
- }\r
-\r
- //------------------------------------------------------------------------\r
- // Everything below here is boilerplate code that makes it possible\r
- // to add a new test by simply adding a function to an existing class\r
- //------------------------------------------------------------------------\r
-\r
- protected TestFmwk() {\r
- }\r
- \r
- protected void init() throws Exception{\r
- }\r
- \r
- /**\r
- * Parse arguments into a TestParams object and a collection of target\r
- * paths. If there was an error parsing the TestParams, print usage and exit\r
- * with -1. Otherwise, call resolveTarget(TestParams, String) for each path,\r
- * and run the returned target. After the last test returns, if prompt is\r
- * set, prompt and wait for input from stdin. Finally, exit with number of\r
- * errors.\r
- * \r
- * This method never returns, since it always exits with System.exit();\r
- */\r
- public void run(String[] args) {\r
- System.exit(run(args, new PrintWriter(System.out)));\r
- }\r
- \r
- /**\r
- * Like run(String[]) except this allows you to specify the error log.\r
- * Unlike run(String[]) this returns the error code as a result instead of\r
- * calling System.exit().\r
- */\r
- public int run(String[] args, PrintWriter log) {\r
- boolean prompt = false;\r
- int wx = 0;\r
- for (int i = 0; i < args.length; ++i) {\r
- String arg = args[i];\r
- if (arg.equals("-p") || arg.equals("-prompt")) {\r
- prompt = true;\r
- } else {\r
- if (wx < i) {\r
- args[wx] = arg;\r
- }\r
- wx++;\r
- }\r
- }\r
- while (wx < args.length) {\r
- args[wx++] = null;\r
- }\r
- \r
- TestParams localParams = TestParams.create(args, log);\r
- if (localParams == null) {\r
- return -1;\r
- }\r
- \r
- int errorCount = runTests(localParams, args);\r
- \r
- if (localParams.seed != 0) {\r
- localParams.log.println("-random:" + localParams.seed);\r
- localParams.log.flush();\r
- }\r
-\r
- if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) {\r
- localParams.log.println("\nError summary:");\r
- localParams.log.println(localParams.errorSummary.toString());\r
- }\r
-\r
- if (localParams.timeLog != null && localParams.timeLog.length() > 0) {\r
- localParams.log.println("\nTest cases taking excessive time (>" +\r
- localParams.maxTargetSec + "s):");\r
- localParams.log.println(localParams.timeLog.toString());\r
- }\r
-\r
- if (prompt) {\r
- System.out.println("Hit RETURN to exit...");\r
- System.out.flush();\r
- try {\r
- System.in.read();\r
- } catch (IOException e) {\r
- localParams.log.println("Exception: " + e.toString() + e.getMessage());\r
- }\r
- }\r
-\r
- return errorCount;\r
- }\r
-\r
- public int runTests(TestParams _params, String[] tests) {\r
- int ec = 0;\r
- \r
- StringBuffer summary = null;\r
- try {\r
- if (tests.length == 0 || tests[0] == null) { // no args\r
- _params.init();\r
- resolveTarget(_params).run();\r
- ec = _params.errorCount;\r
- } else {\r
- for (int i = 0; i < tests.length ; ++i) {\r
- if (tests[i] == null) continue;\r
- \r
- if (i > 0) {\r
- _params.log.println();\r
- }\r
-\r
- _params.init();\r
- resolveTarget(_params, tests[i]).run();\r
- ec += _params.errorCount;\r
- \r
- if (_params.errorSummary != null && _params.errorSummary.length() > 0) {\r
- if (summary == null) {\r
- summary = new StringBuffer();\r
- }\r
- summary.append("\nTest Root: " + tests[i] + "\n");\r
- summary.append(_params.errorSummary());\r
- }\r
- }\r
- _params.errorSummary = summary;\r
- }\r
- } catch (Exception e) {\r
- e.printStackTrace(_params.log);\r
- _params.log.println(e.getMessage());\r
- _params.log.println("encountered exception, exiting");\r
- }\r
- \r
- return ec;\r
- }\r
- \r
- /**\r
- * Return a ClassTarget for this test. Params is set on this test.\r
- */\r
- public Target resolveTarget(TestParams paramsArg) {\r
- this.params = paramsArg;\r
- return new ClassTarget();\r
- }\r
-\r
- /**\r
- * Resolve a path from this test to a target. If this test has subtests, and\r
- * the path contains '/', the portion before the '/' is resolved to a\r
- * subtest, until the path is consumed or the test has no subtests. Returns\r
- * a ClassTarget created using the resolved test and remaining path (which\r
- * ought to be null or a method name). Params is set on the target's test.\r
- */\r
- public Target resolveTarget(TestParams paramsArg, String targetPath) {\r
- TestFmwk test = this;\r
- test.params = paramsArg;\r
-\r
- if (targetPath != null) {\r
- if (targetPath.length() == 0) {\r
- targetPath = null;\r
- } else {\r
- int p = 0;\r
- int e = targetPath.length();\r
-\r
- // trim all leading and trailing '/'\r
- while (targetPath.charAt(p) == '/') {\r
- ++p;\r
- }\r
- while (e > p && targetPath.charAt(e - 1) == '/') {\r
- --e;\r
- }\r
- if (p > 0 || e < targetPath.length()) {\r
- targetPath = targetPath.substring(p, e - p);\r
- p = 0;\r
- e = targetPath.length();\r
- }\r
-\r
- try {\r
- for (;;) {\r
- int n = targetPath.indexOf('/');\r
- String prefix = n == -1 ? targetPath : targetPath\r
- .substring(0, n);\r
- TestFmwk subtest = test.getSubtest(prefix);\r
-\r
- if (subtest == null) {\r
- break;\r
- }\r
-\r
- test = subtest;\r
-\r
- if (n == -1) {\r
- targetPath = null;\r
- break;\r
- }\r
-\r
- targetPath = targetPath.substring(n + 1);\r
- }\r
- } catch (TestFmwkException ex) {\r
- return test.new Target(targetPath);\r
- }\r
- }\r
- }\r
-\r
- return test.new ClassTarget(targetPath);\r
- }\r
-\r
- /**\r
- * Return true if we can run this test (allows test to inspect jvm,\r
- * environment, params before running)\r
- */\r
- protected boolean validate() {\r
- return true;\r
- }\r
-\r
- /**\r
- * Return the targets for this test. If targetName is null, return all\r
- * targets, otherwise return a target for just that name. The returned\r
- * target can be null.\r
- * \r
- * The default implementation returns a MethodTarget for each public method\r
- * of the object's class whose name starts with "Test" or "test".\r
- */\r
- protected Target getTargets(String targetName) {\r
- return getClassTargets(getClass(), targetName);\r
- }\r
-\r
- protected Target getClassTargets(Class cls, String targetName) {\r
- if (cls == null) {\r
- return null;\r
- }\r
-\r
- Target target = null;\r
- if (targetName != null) {\r
- try {\r
- Method method = cls.getMethod(targetName, (Class[])null);\r
- target = new MethodTarget(targetName, method);\r
- } catch (NoSuchMethodException e) {\r
- if (!inheritTargets()) {\r
- return new Target(targetName); // invalid target\r
- }\r
- } catch (SecurityException e) {\r
- return null;\r
- }\r
- } else {\r
- if (params.doMethods()) {\r
- Method[] methods = cls.getDeclaredMethods();\r
- for (int i = methods.length; --i >= 0;) {\r
- String name = methods[i].getName();\r
- if (name.startsWith("Test") || name.startsWith("test")) {\r
- target = new MethodTarget(name, methods[i])\r
- .setNext(target);\r
- }\r
- }\r
- }\r
- }\r
-\r
- if (inheritTargets()) {\r
- Target parentTarget = getClassTargets(cls.getSuperclass(), targetName);\r
- if (parentTarget == null) {\r
- return target;\r
- }\r
- if (target == null) {\r
- return parentTarget;\r
- }\r
- return parentTarget.append(target);\r
- }\r
-\r
- return target;\r
- }\r
-\r
- protected boolean inheritTargets() {\r
- return false;\r
- }\r
-\r
- protected String getDescription() {\r
- return null;\r
- }\r
-\r
- protected boolean validateMethod(String name) {\r
- return true;\r
- }\r
-\r
- protected String getMethodDescription(String name) {\r
- return null;\r
- }\r
-\r
- // method tests have no subtests, group tests override\r
- protected TestFmwk getSubtest(String prefix) throws TestFmwkException {\r
- return null;\r
- }\r
-\r
- public boolean isVerbose() {\r
- return params.verbose;\r
- }\r
-\r
- public boolean noData() {\r
- return params.nodata;\r
- }\r
-\r
- public boolean isTiming() {\r
- return params.timing < Long.MAX_VALUE;\r
- }\r
-\r
- public boolean isMemTracking() {\r
- return params.memusage;\r
- }\r
-\r
- /**\r
- * 0 = fewest tests, 5 is normal build, 10 is most tests\r
- */\r
- public int getInclusion() {\r
- return params.inclusion;\r
- }\r
-\r
- public boolean isModularBuild() {\r
- return params.warnings;\r
- }\r
-\r
- public boolean isQuick() {\r
- return params.inclusion == 0;\r
- }\r
-\r
- public void msg(String message, int level, boolean incCount, boolean newln) {\r
- params.msg(message, level, incCount, newln);\r
- }\r
-\r
- protected int getErrorCount() {\r
- return params.errorCount;\r
- }\r
-\r
- public String getProperty(String key) {\r
- String val = null;\r
- if (key != null && key.length() > 0 && params.props != null) {\r
- val = (String)params.props.get(key.toLowerCase());\r
- }\r
- return val;\r
- }\r
-\r
- protected TimeZone safeGetTimeZone(String id) {\r
- TimeZone tz = TimeZone.getTimeZone(id);\r
- if (tz == null) {\r
- // should never happen\r
- errln("FAIL: TimeZone.getTimeZone(" + id + ") => null");\r
- }\r
- if (!tz.getID().equals(id)) {\r
- warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID());\r
- }\r
- return tz;\r
- }\r
-\r
- /**\r
- * Print a usage message for this test class.\r
- */\r
- public void usage() {\r
- usage(new PrintWriter(System.out), getClass().getName());\r
- }\r
- \r
- public static void usage(PrintWriter pw, String className) {\r
- pw.println("Usage: " + className + " option* target*");\r
- pw.println();\r
- pw.println("Options:");\r
- pw.println(" -d[escribe] Print a short descriptive string for this test and all");\r
- pw.println(" listed targets.");\r
- pw.println(" -e<n> Set exhaustiveness from 0..10. Default is 0, fewest tests.\n"\r
- + " To run all tests, specify -e10. Giving -e with no <n> is\n"\r
- + " the same as -e5.");\r
- pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n"\r
- + " <str> is of the form ['^']text[','['^']text].\n"\r
- + " Each string delimited by ',' is a separate filter argument.\n"\r
- + " If '^' is prepended to an argument, its matches are excluded.\n"\r
- + " Filtering operates on test groups as well as tests, if a test\n"\r
- + " group is included, all its subtests that are not excluded will\n"\r
- + " be run. Examples:\n"\r
- + " -filter:A -- only tests matching A are run. If A matches a group,\n"\r
- + " all subtests of this group are run.\n"\r
- + " -filter:^A -- all tests except those matching A are run. If A matches\n"\r
- + " a group, no subtest of that group will be run.\n"\r
- + " -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n"\r
- + " Note: Filters are case insensitive.");\r
- pw.println(" -h[elp] Print this help text and exit.");\r
- pw.println(" -l[ist] List immediate targets of this test");\r
- pw.println(" -la, -listAll List immediate targets of this test, and all subtests");\r
- pw.println(" -le, -listExaustive List all subtests and targets");\r
- // don't know how to get useful numbers for memory usage using java API\r
- // calls\r
- // pw.println(" -m[emory] print memory usage and force gc for\r
- // each test");\r
- pw.println(" -n[othrow] Message on test failure rather than exception");\r
- pw.println(" -p[rompt] Prompt before exiting");\r
- pw.println(" -prop:<key>=<value> Set optional property used by this test");\r
- pw.println(" -q[uiet] Do not show warnings");\r
- pw.println(" -r[andom][:<n>] If present, randomize targets. If n is present,\n"\r
- + " use it as the seed. If random is not set, targets will\n"\r
- + " be in alphabetical order to ensure cross-platform consistency.");\r
- pw.println(" -s[ilent] No output except error summary or exceptions.");\r
- pw.println(" -tfilter:<str> Transliterator Test filter of ids.");\r
- pw.println(" -t[ime][:<n>] Print elapsed time for each test. if n is present\n"\r
- + " only print times >= n milliseconds.");\r
- pw.println(" -v[erbose] Show log messages");\r
- pw.println(" -u[nicode] Don't escape error or log messages");\r
- pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings.");\r
- pw.println(" -nodata | -nd Do not warn if resource data is not present.");\r
- pw.println();\r
- pw.println(" If a list or describe option is provided, no tests are run.");\r
- pw.println();\r
- pw.println("Targets:");\r
- pw.println(" If no target is specified, all targets for this test are run.");\r
- pw.println(" If a target contains no '/' characters, and matches a target");\r
- pw.println(" of this test, the target is run. Otherwise, the part before the");\r
- pw.println(" '/' is used to match a subtest, which then evaluates the");\r
- pw.println(" remainder of the target as above. Target matching is case-insensitive.");\r
- pw.println();\r
- pw.println(" If multiple targets are provided, each is executed in order.");\r
- pw.flush();\r
- }\r
- public static String hex(char[] s){\r
- StringBuffer result = new StringBuffer();\r
- for (int i = 0; i < s.length; ++i) {\r
- if (i != 0) result.append(',');\r
- result.append(hex(s[i]));\r
- }\r
- return result.toString();\r
- }\r
- public static String hex(byte[] s){\r
- StringBuffer result = new StringBuffer();\r
- for (int i = 0; i < s.length; ++i) {\r
- if (i != 0) result.append(',');\r
- result.append(hex(s[i]));\r
- }\r
- return result.toString();\r
- }\r
- public static String hex(char ch) {\r
- StringBuffer result = new StringBuffer();\r
- String foo = Integer.toString(ch, 16).toUpperCase();\r
- for (int i = foo.length(); i < 4; ++i) {\r
- result.append('0');\r
- }\r
- return result + foo;\r
- }\r
-\r
- public static String hex(int ch) {\r
- StringBuffer result = new StringBuffer();\r
- String foo = Integer.toString(ch, 16).toUpperCase();\r
- for (int i = foo.length(); i < 4; ++i) {\r
- result.append('0');\r
- }\r
- return result + foo;\r
- }\r
-\r
- public static String hex(String s) {\r
- StringBuffer result = new StringBuffer();\r
- for (int i = 0; i < s.length(); ++i) {\r
- if (i != 0)\r
- result.append(',');\r
- result.append(hex(s.charAt(i)));\r
- }\r
- return result.toString();\r
- }\r
-\r
- public static String hex(StringBuffer s) {\r
- return hex(s.toString());\r
- }\r
- public static String prettify(String s) {\r
- StringBuffer result = new StringBuffer();\r
- int ch;\r
- for (int i = 0; i < s.length(); i += UTF16.getCharCount(ch)) {\r
- ch = UTF16.charAt(s, i);\r
- if (ch > 0xfffff) {\r
- result.append("\\U00");\r
- result.append(hex(ch));\r
- } else if (ch > 0xffff) {\r
- result.append("\\U000");\r
- result.append(hex(ch));\r
- } else if (ch > 0x7f) {\r
- result.append("\\u");\r
- result.append(hex(ch));\r
- } else {\r
- result.append((char) ch);\r
- }\r
-\r
- }\r
- return result.toString();\r
- }\r
- public static String prettify(StringBuffer s) {\r
- return prettify(s.toString());\r
- }\r
-\r
- private static java.util.GregorianCalendar cal;\r
-\r
- /**\r
- * Return a Date given a year, month, and day of month. This is similar to\r
- * new Date(y-1900, m, d). It uses the default time zone at the time this\r
- * method is first called.\r
- * \r
- * @param year\r
- * use 2000 for 2000, unlike new Date()\r
- * @param month\r
- * use Calendar.JANUARY etc.\r
- * @param dom\r
- * day of month, 1-based\r
- * @return a Date object for the given y/m/d\r
- */\r
- protected static synchronized java.util.Date getDate(int year, int month,\r
- int dom) {\r
- if (cal == null) {\r
- cal = new java.util.GregorianCalendar();\r
- }\r
- cal.clear();\r
- cal.set(year, month, dom);\r
- return cal.getTime();\r
- }\r
-\r
- public static class NullWriter extends PrintWriter {\r
- public NullWriter() {\r
- super(System.out, false);\r
- }\r
- public void write(int c) {\r
- }\r
- public void write(char[] buf, int off, int len) {\r
- }\r
- public void write(String s, int off, int len) {\r
- }\r
- public void println() {\r
- }\r
- }\r
-\r
- public static class ASCIIWriter extends PrintWriter {\r
- private StringBuffer buffer = new StringBuffer();\r
-\r
- // Characters that we think are printable but that escapeUnprintable\r
- // doesn't\r
- private static final String PRINTABLES = "\t\n\r";\r
-\r
- public ASCIIWriter(Writer w, boolean autoFlush) {\r
- super(w, autoFlush);\r
- }\r
-\r
- public ASCIIWriter(OutputStream os, boolean autoFlush) {\r
- super(os, autoFlush);\r
- }\r
-\r
- public void write(int c) {\r
- synchronized (lock) {\r
- buffer.setLength(0);\r
- if (PRINTABLES.indexOf(c) < 0\r
- && TestUtil.escapeUnprintable(buffer, c)) {\r
- super.write(buffer.toString());\r
- } else {\r
- super.write(c);\r
- }\r
- }\r
- }\r
-\r
- public void write(char[] buf, int off, int len) {\r
- synchronized (lock) {\r
- buffer.setLength(0);\r
- int limit = off + len;\r
- while (off < limit) {\r
- int c = UTF16Util.charAt(buf, 0, buf.length, off);\r
- off += UTF16Util.getCharCount(c);\r
- if (PRINTABLES.indexOf(c) < 0\r
- && TestUtil.escapeUnprintable(buffer, c)) {\r
- super.write(buffer.toString());\r
- buffer.setLength(0);\r
- } else {\r
- super.write(c);\r
- }\r
- }\r
- }\r
- }\r
-\r
- public void write(String s, int off, int len) {\r
- write(s.substring(off, off + len).toCharArray(), 0, len);\r
- }\r
- }\r
-\r
- // filters\r
- // match against the entire hierarchy\r
- // A;B;!C;!D --> (A ||B) && (!C && !D)\r
- // positive, negative, unknown matches\r
- // positive -- known to be included, negative- known to be excluded\r
- // positive only if no excludes, and matches at least one include, if any\r
- // negative only if matches at least one exclude\r
- // otherwise, we wait\r
-\r
- public static class TestParams {\r
- public boolean prompt;\r
- public boolean nothrow;\r
- public boolean verbose;\r
- public boolean quiet;\r
- public int listlevel;\r
- public boolean describe;\r
- public boolean warnings;\r
- public boolean nodata;\r
- public long timing = Long.MAX_VALUE;\r
- public boolean memusage;\r
- public int inclusion;\r
- public String filter;\r
- public long seed;\r
- public String tfilter; // for transliterator tests\r
-\r
- public State stack;\r
-\r
- public StringBuffer errorSummary;\r
- private StringBuffer timeLog;\r
-\r
- public PrintWriter log;\r
- public int indentLevel;\r
- private boolean needLineFeed;\r
- private boolean suppressIndent;\r
- public int errorCount;\r
- public int warnCount;\r
- public int invalidCount;\r
- public int testCount;\r
- private NumberFormat tformat;\r
- public Random random;\r
- public int maxTargetSec = 10;\r
- public HashMap props;\r
-\r
- private TestParams() {\r
- }\r
- \r
- public static TestParams create(String arglist, PrintWriter log) {\r
- String[] args = null;\r
- if (arglist != null && arglist.length() > 0) {\r
-//#if defined(FOUNDATION10) || defined(J2SE13)\r
-//## args = Utility.split(arglist, '\u0020');\r
-//#else\r
- args = arglist.split("\\s");\r
-//#endif\r
- }\r
- return create(args, log);\r
- }\r
- \r
- /**\r
- * Create a TestParams from a list of arguments. If successful, return the params object,\r
- * else return null. Error messages will be reported on errlog if it is not null.\r
- * Arguments and values understood by this method will be removed from the args array\r
- * and existing args will be shifted down, to be filled by nulls at the end.\r
- * @param args the list of arguments\r
- * @param log the error log, or null if no error log is desired\r
- * @return the new TestParams object, or null if error\r
- */\r
- public static TestParams create(String[] args, PrintWriter log) {\r
- TestParams params = new TestParams();\r
- \r
- if(log == null){\r
- params.log = new NullWriter();\r
- }else{\r
- params.log = new ASCIIWriter(log, true);\r
- }\r
- \r
- boolean usageError = false;\r
- String filter = null;\r
- int wx = 0; // write argets.\r
- if (args != null) {\r
- for (int i = 0; i < args.length; i++) {\r
- String arg = args[i];\r
- if (arg == null || arg.length() == 0) {\r
- continue;\r
- }\r
- if (arg.charAt(0) == '-') {\r
- arg = arg.toLowerCase();\r
- if (arg.equals("-verbose") || arg.equals("-v")) {\r
- params.verbose = true;\r
- params.quiet = false;\r
- } else if (arg.equals("-quiet") || arg.equals("-q")) {\r
- params.quiet = true;\r
- params.verbose = false;\r
- } else if (arg.equals("-help") || arg.equals("-h")) {\r
- usageError = true;\r
- } else if (arg.equals("-warning") || arg.equals("-w")) {\r
- params.warnings = true;\r
- } else if (arg.equals("-nodata") || arg.equals("-nd")) {\r
- params.nodata = true;\r
- } else if (arg.equals("-list") || arg.equals("-l")) {\r
- params.listlevel = 1;\r
- } else if (arg.equals("-listall") || arg.equals("-la")) {\r
- params.listlevel = 2;\r
- } else if (arg.equals("-listexaustive") || arg.equals("-le")) {\r
- params.listlevel = 3;\r
- } else if (arg.equals("-memory") || arg.equals("-m")) {\r
- params.memusage = true;\r
- } else if (arg.equals("-nothrow") || arg.equals("-n")) {\r
- params.nothrow = true;\r
- params.errorSummary = new StringBuffer();\r
- } else if (arg.equals("-describe") || arg.equals("-d")) {\r
- params.describe = true;\r
- } else if (arg.startsWith("-r")) {\r
- String s = null;\r
- int n = arg.indexOf(':');\r
- if (n != -1) {\r
- s = arg.substring(n + 1);\r
- arg = arg.substring(0, n);\r
- }\r
-\r
- if (arg.equals("-r") || arg.equals("-random")) {\r
- if (s == null) {\r
- params.seed = System.currentTimeMillis();\r
- } else {\r
- params.seed = Long.parseLong(s);\r
- }\r
- } else {\r
- log.println("*** Error: unrecognized argument: " + arg);\r
- usageError = true;\r
- break;\r
- }\r
- } else if (arg.startsWith("-e")) {\r
- // see above\r
- params.inclusion = (arg.length() == 2) \r
- ? 5 \r
- : Integer.parseInt(arg.substring(2));\r
- if (params.inclusion < 0 || params.inclusion > 10) {\r
- usageError = true;\r
- break;\r
- }\r
- } else if (arg.startsWith("-tfilter:")) {\r
- params.tfilter = arg.substring(8);\r
- } else if (arg.startsWith("-time") || arg.startsWith("-t")) {\r
- long val = 0;\r
- int inx = arg.indexOf(':');\r
- if (inx > 0) {\r
- String num = arg.substring(inx + 1);\r
- try {\r
- val = Long.parseLong(num);\r
- } catch (Exception e) {\r
- log.println("*** Error: could not parse time threshold '"\r
- + num + "'");\r
- usageError = true;\r
- break;\r
- }\r
- }\r
- params.timing = val;\r
- String fmt = "#,00s";\r
- if (val <= 10) {\r
- fmt = "#,##0.000s";\r
- } else if (val <= 100) {\r
- fmt = "#,##0.00s";\r
- } else if (val <= 1000) {\r
- fmt = "#,##0.0s";\r
- }\r
- params.tformat = new DecimalFormat(fmt);\r
- } else if (arg.startsWith("-filter:")) {\r
- String temp = arg.substring(8).toLowerCase();\r
- filter = filter == null ? temp : filter + "," + temp;\r
- } else if (arg.startsWith("-f:")) {\r
- String temp = arg.substring(3).toLowerCase();\r
- filter = filter == null ? temp : filter + "," + temp;\r
- } else if (arg.startsWith("-s")) {\r
- params.log = new NullWriter();\r
- } else if (arg.startsWith("-u")) {\r
- if (params.log instanceof ASCIIWriter) {\r
- params.log = log;\r
- }\r
- } else if (arg.startsWith("-prop:")) {\r
- String temp = arg.substring(6);\r
- int eql = temp.indexOf('=');\r
- if (eql <= 0) {\r
- log.println("*** Error: could not parse custom property '" + arg + "'");\r
- usageError = true;\r
- break;\r
- }\r
- if (params.props == null) {\r
- params.props = new HashMap();\r
- }\r
- params.props.put(temp.substring(0, eql), temp.substring(eql+1));\r
- } else {\r
- log.println("*** Error: unrecognized argument: "\r
- + args[i]);\r
- usageError = true;\r
- break;\r
- }\r
- } else {\r
- args[wx++] = arg; // shift down\r
- }\r
- }\r
-\r
- while (wx < args.length) {\r
- args[wx++] = null;\r
- }\r
- }\r
- \r
- if (usageError) {\r
- usage(log, "TestAll");\r
- return null;\r
- }\r
-\r
- if (filter != null) {\r
- params.filter = filter.toLowerCase();\r
- }\r
-\r
- params.init();\r
- \r
- return params;\r
- }\r
- \r
- public String errorSummary() {\r
- return errorSummary == null ? "" : errorSummary.toString();\r
- }\r
-\r
- public void init() {\r
- indentLevel = 0;\r
- needLineFeed = false;\r
- suppressIndent = false;\r
- errorCount = 0;\r
- warnCount = 0;\r
- invalidCount = 0;\r
- testCount = 0;\r
- random = seed == 0 ? null : new Random(seed);\r
- }\r
-\r
- public class State {\r
- State link;\r
- String name;\r
- StringBuffer buffer;\r
- int level;\r
- int ec;\r
- int wc;\r
- int ic;\r
- int tc;\r
- boolean flushed;\r
- public boolean included;\r
- long mem;\r
- long millis;\r
-\r
- public State(State link, String name, boolean included) {\r
- this.link = link;\r
- this.name = name;\r
- if (link == null) {\r
- this.level = 0;\r
- this.included = included;\r
- } else {\r
- this.level = link.level + 1;\r
- this.included = included || link.included;\r
- }\r
- this.ec = errorCount;\r
- this.wc = warnCount;\r
- this.ic = invalidCount;\r
- this.tc = testCount;\r
-\r
- if (link == null || this.included) {\r
- flush();\r
- }\r
-\r
- mem = getmem();\r
- millis = System.currentTimeMillis();\r
- }\r
-\r
- void flush() {\r
- if (!flushed) {\r
- if (link != null) {\r
- link.flush();\r
- }\r
-\r
- indent(level);\r
- log.print(name);\r
- log.flush();\r
-\r
- flushed = true;\r
-\r
- needLineFeed = true;\r
- }\r
- }\r
-\r
- void appendPath(StringBuffer buf) {\r
- if (this.link != null) {\r
- this.link.appendPath(buf);\r
- buf.append('/');\r
- }\r
- buf.append(name);\r
- }\r
- }\r
-\r
- public void push(String name, String description, boolean included) {\r
- if (inDocMode() && describe && description != null) {\r
- name += ": " + description;\r
- }\r
- stack = new State(stack, name, included);\r
- }\r
-\r
- public void pop() {\r
- if (stack != null) {\r
- writeTestResult();\r
- stack = stack.link;\r
- }\r
- }\r
-\r
- public boolean inDocMode() {\r
- return describe || listlevel != 0;\r
- }\r
-\r
- public boolean doMethods() {\r
- return !inDocMode() || listlevel == 3\r
- || (indentLevel == 1 && listlevel > 0);\r
- }\r
-\r
- public boolean doRecurse() {\r
- return !inDocMode() || listlevel > 1\r
- || (indentLevel == 1 && listlevel > 0);\r
- }\r
-\r
- public boolean doRecurseGroupsOnly() {\r
- return inDocMode()\r
- && (listlevel == 2 || (indentLevel == 1 && listlevel > 0));\r
- }\r
-\r
- // return 0, -1, or 1\r
- // 1: run this test\r
- // 0: might run this test, no positive include or exclude on this group\r
- // -1: exclude this test\r
- public int filter(String testName) {\r
- int result = 0;\r
- if (filter == null) {\r
- result = 1;\r
- } else {\r
- boolean noIncludes = true;\r
- boolean noExcludes = filter.indexOf('^') == -1;\r
- testName = testName.toLowerCase();\r
- int ix = 0;\r
- while (ix < filter.length()) {\r
- int nix = filter.indexOf(',', ix);\r
- if (nix == -1) {\r
- nix = filter.length();\r
- }\r
- if (filter.charAt(ix) == '^') {\r
- if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) {\r
- result = -1;\r
- break;\r
- }\r
- } else {\r
- noIncludes = false;\r
- if (testName.indexOf(filter.substring(ix, nix)) != -1) {\r
- result = 1;\r
- if (noExcludes) {\r
- break;\r
- }\r
- }\r
- }\r
-\r
- ix = nix + 1;\r
- }\r
- if (result == 0 && noIncludes) {\r
- result = 1;\r
- }\r
- }\r
- // System.out.println("filter: " + testName + " returns: " +\r
- // result);\r
- return result;\r
- }\r
-\r
- /**\r
- * Log access.\r
- * @param msg The string message to write\r
- */\r
- public void write(String msg) {\r
- write(msg, false);\r
- }\r
- \r
- public void writeln(String msg) {\r
- write(msg, true);\r
- }\r
- \r
- private void write(String msg, boolean newln) {\r
- if (!suppressIndent) {\r
- if (needLineFeed) {\r
- log.println();\r
- needLineFeed = false;\r
- }\r
- log.print(spaces.substring(0, indentLevel * 2));\r
- }\r
- log.print(msg);\r
- if (newln) {\r
- log.println(); \r
- }\r
- log.flush();\r
- suppressIndent = !newln;\r
- }\r
- \r
- private void msg(String message, int level, boolean incCount,\r
- boolean newln) {\r
- if (level == WARN && (!warnings && !nodata)){\r
- level = ERR;\r
- }\r
-\r
- if (incCount) {\r
- if (level == WARN) {\r
- warnCount++;\r
- invalidCount++;\r
- } else if (level == ERR) {\r
- errorCount++;\r
- }\r
- }\r
-\r
- // should roll indentation stuff into log ???\r
- if (verbose || level > (quiet ? WARN : LOG)) {\r
- if (!suppressIndent) {\r
- indent(indentLevel + 1);\r
- final String[] MSGNAMES = {"", "Warning: ", "Error: "};\r
- log.print(MSGNAMES[level]);\r
- }\r
-\r
- log.print(message);\r
- if (newln) {\r
- log.println();\r
- }\r
- log.flush();\r
- }\r
-\r
- if (level == ERR) {\r
- if (!nothrow) {\r
- throw new RuntimeException(message);\r
- }\r
- if (!suppressIndent && errorSummary != null && stack !=null \r
- && (errorCount == stack.ec + 1)) {\r
- stack.appendPath(errorSummary);\r
- errorSummary.append("\n");\r
- }\r
- }\r
-\r
- suppressIndent = !newln;\r
- }\r
-\r
- private void writeTestInvalid(String name, boolean nodataArg) {\r
- // msg("***" + name + "*** not found or not valid.", WARN, true,\r
- // true);\r
- if (inDocMode()) {\r
- if (!warnings) {\r
- if (stack != null) {\r
- stack.flush();\r
- }\r
- log.println(" *** Target not found or not valid.");\r
- log.flush();\r
- needLineFeed = false;\r
- }\r
- } else {\r
- if(!nodataArg){\r
- msg("Test " + name + " not found or not valid.", WARN, true,\r
- true);\r
- }\r
- }\r
- }\r
-\r
- long getmem() {\r
- long newmem = 0;\r
- if (memusage) {\r
- Runtime rt = Runtime.getRuntime();\r
- long lastmem = Long.MAX_VALUE;\r
- do {\r
- rt.gc();\r
- rt.gc();\r
- try {\r
- Thread.sleep(50);\r
- } catch (Exception e) {\r
- break;\r
- }\r
- lastmem = newmem;\r
- newmem = rt.totalMemory() - rt.freeMemory();\r
- } while (newmem < lastmem);\r
- }\r
- return newmem;\r
- }\r
-\r
- private void writeTestResult() {\r
- if (inDocMode()) {\r
- if (needLineFeed) {\r
- log.println();\r
- log.flush();\r
- }\r
- needLineFeed = false;\r
- return;\r
- }\r
-\r
- long dmem = getmem() - stack.mem;\r
- long dtime = System.currentTimeMillis() - stack.millis;\r
-\r
- int testDelta = testCount - stack.tc;\r
- if (testDelta == 0) {\r
- return;\r
- }\r
-\r
- int errorDelta = errorCount - stack.ec;\r
- int invalidDelta = invalidCount - stack.ic;\r
-\r
- stack.flush();\r
-\r
- if (!needLineFeed) {\r
- indent(indentLevel);\r
- log.print("}");\r
- }\r
- needLineFeed = false;\r
-\r
- if (memusage || dtime >= timing) {\r
- log.print(" (");\r
- if (memusage) {\r
- log.print("dmem: " + dmem);\r
- }\r
- if (dtime >= timing) {\r
- if (memusage) {\r
- log.print(", ");\r
- }\r
- log.print(tformat.format(dtime / 1000f));\r
- }\r
- log.print(")");\r
- }\r
-\r
- if (errorDelta != 0) {\r
- log.println(" FAILED ("\r
- + errorDelta\r
- + " failures"\r
- + ((invalidDelta != 0) ? ", " + invalidDelta\r
- + " tests skipped)" : ")"));\r
- } else if (invalidDelta != 0) {\r
- log.println(" Qualified (" + invalidDelta + " tests skipped)");\r
- } else {\r
- log.println(" Passed");\r
- }\r
- }\r
-\r
- private final void indent(int distance) {\r
- boolean idm = inDocMode();\r
- if (needLineFeed) {\r
- if (idm) {\r
- log.println();\r
- } else {\r
- log.println(" {");\r
- }\r
- needLineFeed = false;\r
- }\r
-\r
- log.print(spaces.substring(0, distance * (idm ? 3 : 2)));\r
-\r
- if (idm) {\r
- log.print("-- ");\r
- }\r
- }\r
- }\r
-\r
- public String getTranslitTestFilter() {\r
- return params.tfilter;\r
- }\r
-\r
- /**\r
- * Return the target name for a test class. This is either the end of the\r
- * class name, or if the class declares a public static field\r
- * CLASS_TARGET_NAME, the value of that field.\r
- */\r
- private static String getClassTargetName(Class testClass) {\r
- String name = testClass.getName();\r
- try {\r
- Field f = testClass.getField("CLASS_TARGET_NAME");\r
- name = (String) f.get(null);\r
- } catch (IllegalAccessException e) {\r
- throw new IllegalStateException(\r
- "static field CLASS_TARGET_NAME must be accessible");\r
- } catch (NoSuchFieldException e) {\r
- int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));\r
- if (n != -1) {\r
- name = name.substring(n + 1);\r
- }\r
- }\r
- return name;\r
- }\r
-\r
- /**\r
- * Check the given array to see that all the strings in the expected array\r
- * are present.\r
- * \r
- * @param msg\r
- * string message, for log output\r
- * @param array\r
- * array of strings to check\r
- * @param expected\r
- * array of strings we expect to see, or null\r
- * @return the length of 'array', or -1 on error\r
- */\r
- protected int checkArray(String msg, String array[], String expected[]) {\r
- int explen = (expected != null) ? expected.length : 0;\r
- if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32\r
- errln("Internal error");\r
- return -1;\r
- }\r
- int i = 0;\r
- StringBuffer buf = new StringBuffer();\r
- int seenMask = 0;\r
- for (; i < array.length; ++i) {\r
- String s = array[i];\r
- if (i != 0)\r
- buf.append(", ");\r
- buf.append(s);\r
- // check expected list\r
- for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) {\r
- if ((seenMask & bit) == 0) {\r
- if (s.equals(expected[j])) {\r
- seenMask |= bit;\r
- logln("Ok: \"" + s + "\" seen");\r
- }\r
- }\r
- }\r
- }\r
- logln(msg + " = [" + buf + "] (" + i + ")");\r
- // did we see all expected strings?\r
- if (((1 << explen) - 1) != seenMask) {\r
- for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) {\r
- if ((seenMask & bit) == 0) {\r
- errln("\"" + expected[j] + "\" not seen");\r
- }\r
- }\r
- }\r
- return array.length;\r
- }\r
-\r
- /**\r
- * Check the given array to see that all the locales in the expected array\r
- * are present.\r
- * \r
- * @param msg\r
- * string message, for log output\r
- * @param array\r
- * array of locales to check\r
- * @param expected\r
- * array of locales names we expect to see, or null\r
- * @return the length of 'array'\r
- */\r
- protected int checkArray(String msg, Locale array[], String expected[]) {\r
- String strs[] = new String[array.length];\r
- for (int i = 0; i < array.length; ++i)\r
- strs[i] = array[i].toString();\r
- return checkArray(msg, strs, expected);\r
- }\r
-\r
- /**\r
- * Check the given array to see that all the locales in the expected array\r
- * are present.\r
- * \r
- * @param msg\r
- * string message, for log output\r
- * @param array\r
- * array of locales to check\r
- * @param expected\r
- * array of locales names we expect to see, or null\r
- * @return the length of 'array'\r
- */\r
- protected int checkArray(String msg, ULocale array[], String expected[]) {\r
- String strs[] = new String[array.length];\r
- for (int i = 0; i < array.length; ++i)\r
- strs[i] = array[i].toString();\r
- return checkArray(msg, strs, expected);\r
- }\r
-\r
- // JUnit-like assertions.\r
-\r
- protected boolean assertTrue(String message, boolean condition) {\r
- return handleAssert(condition, message, "true", null);\r
- }\r
-\r
- protected boolean assertFalse(String message, boolean condition) {\r
- return handleAssert(!condition, message, "false", null);\r
- }\r
-\r
- protected boolean assertEquals(String message, boolean expected,\r
- boolean actual) {\r
- return handleAssert(expected == actual, message, String\r
- .valueOf(expected), String.valueOf(actual));\r
- }\r
-\r
- protected boolean assertEquals(String message, long expected, long actual) {\r
- return handleAssert(expected == actual, message, String\r
- .valueOf(expected), String.valueOf(actual));\r
- }\r
-\r
- // do NaN and range calculations to precision of float, don't rely on\r
- // promotion to double\r
- protected boolean assertEquals(String message, float expected,\r
- float actual, double error) {\r
- boolean result = Float.isInfinite(expected)\r
- ? expected == actual\r
- : !(Math.abs(expected - actual) > error); // handles NaN\r
- return handleAssert(result, message, String.valueOf(expected)\r
- + (error == 0 ? "" : " (within " + error + ")"), String\r
- .valueOf(actual));\r
- }\r
-\r
- protected boolean assertEquals(String message, double expected,\r
- double actual, double error) {\r
- boolean result = Double.isInfinite(expected)\r
- ? expected == actual\r
- : !(Math.abs(expected - actual) > error); // handles NaN\r
- return handleAssert(result, message, String.valueOf(expected)\r
- + (error == 0 ? "" : " (within " + error + ")"), String\r
- .valueOf(actual));\r
- }\r
-\r
- protected boolean assertEquals(String message, Object expected,\r
- Object actual) {\r
- boolean result = expected == null ? actual == null : expected\r
- .equals(actual);\r
- return handleAssert(result, message, stringFor(expected),\r
- stringFor(actual));\r
- }\r
-\r
- protected boolean assertNotEquals(String message, Object expected,\r
- Object actual) {\r
- boolean result = !(expected == null ? actual == null : expected\r
- .equals(actual));\r
- return handleAssert(result, message, stringFor(expected),\r
- stringFor(actual), "not equal to", true);\r
- }\r
-\r
- protected boolean assertSame(String message, Object expected, Object actual) {\r
- return handleAssert(expected == actual, message, stringFor(expected),\r
- stringFor(actual), "==", false);\r
- }\r
-\r
- protected boolean assertNotSame(String message, Object expected,\r
- Object actual) {\r
- return handleAssert(expected != actual, message, stringFor(expected),\r
- stringFor(actual), "!=", true);\r
- }\r
-\r
- protected boolean assertNull(String message, Object actual) {\r
- return handleAssert(actual == null, message, null, stringFor(actual));\r
- }\r
-\r
- protected boolean assertNotNull(String message, Object actual) {\r
- return handleAssert(actual != null, message, null, stringFor(actual),\r
- "!=", true);\r
- }\r
-\r
- protected void fail(String message) {\r
- errln(message);\r
- }\r
-\r
- private boolean handleAssert(boolean result, String message,\r
- String expected, String actual) {\r
- return handleAssert(result, message, expected, actual, null, false);\r
- }\r
-\r
- public boolean handleAssert(boolean result, String message,\r
- Object expected, Object actual, String relation, boolean flip) {\r
- if (!result || isVerbose()) {\r
- message = message == null ? "" : " " + message;\r
- relation = relation == null ? ", got " : " " + relation + " ";\r
- if (result) {\r
- logln("OK" + message + ": "\r
- + (flip ? expected + relation + actual : expected));\r
- } else {\r
- // assert must assume errors are true errors and not just warnings\r
- // so cannot warnln here\r
- errln(message\r
- + ": expected"\r
- + (flip ? relation + expected : " " + expected\r
- + (actual != null ? relation + actual : "")));\r
- }\r
- }\r
- return result;\r
- }\r
-\r
- private final String stringFor(Object obj) {\r
- if (obj == null)\r
- return "null";\r
- if (obj instanceof String)\r
- return "\"" + obj + '"';\r
- return obj.getClass().getName() + "<" + obj + ">";\r
- }\r
-\r
- // End JUnit-like assertions\r
-\r
- // PrintWriter support\r
-\r
- public PrintWriter getErrorLogPrintWriter() {\r
- return new PrintWriter(new TestLogWriter(this, TestLog.ERR));\r
- }\r
-\r
- public PrintWriter getLogPrintWriter() {\r
- return new PrintWriter(new TestLogWriter(this, TestLog.LOG));\r
- }\r
-\r
- // end PrintWriter support\r
-\r
- protected TestParams params = null;\r
-\r
- private final static String spaces = " ";\r
- \r
-}\r
+//##header J2SE15
+/*
+ *******************************************************************************
+ * Copyright (C) 1996-2009, International Business Machines Corporation and *
+ * others. All Rights Reserved. *
+ *******************************************************************************
+ */
+package com.ibm.icu.dev.test;
+
+import com.ibm.icu.text.UTF16;
+import com.ibm.icu.text.DecimalFormat;
+import com.ibm.icu.text.NumberFormat;
+import com.ibm.icu.util.TimeZone;
+import com.ibm.icu.util.ULocale;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.PrintStream;
+import java.io.PrintWriter;
+import java.io.Writer;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.MissingResourceException;
+import java.util.Random;
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//## import com.ibm.icu.impl.Utility;
+//#endif
+/**
+ * TestFmwk is a base class for tests that can be run conveniently from the
+ * command line as well as under the Java test harness.
+ * <p>
+ * Sub-classes implement a set of methods named Test <something>. Each of these
+ * methods performs some test. Test methods should indicate errors by calling
+ * either err or errln. This will increment the errorCount field and may
+ * optionally print a message to the log. Debugging information may also be
+ * added to the log via the log and logln methods. These methods will add their
+ * arguments to the log only if the test is being run in verbose mode.
+ */
+public class TestFmwk extends AbstractTestLog {
+ /**
+ * The default time zone for all of our tests. Used in Target.run();
+ */
+ private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("PST");
+
+ /**
+ * The default locale used for all of our tests. Used in Target.run();
+ */
+ private final static Locale defaultLocale = Locale.US;
+
+ public static final class TestFmwkException extends Exception {
+ /**
+ * For serialization
+ */
+ private static final long serialVersionUID = -3051148210247229194L;
+
+ TestFmwkException(String msg) {
+ super(msg);
+ }
+ }
+ protected void handleException(Throwable e){
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//## Throwable ex = null;
+//#else
+ Throwable ex = e.getCause();
+//#endif
+ if(ex==null){
+ ex = e;
+ }
+ if(ex instanceof ExceptionInInitializerError){
+ ex = ((ExceptionInInitializerError)ex).getException();
+ }
+ String msg = ex.getMessage();
+ if(msg==null){
+ msg = "";
+ }
+ //System.err.println("TF handleException msg: " + msg);
+ if (ex instanceof MissingResourceException || ex instanceof NoClassDefFoundError || msg.indexOf("java.util.MissingResourceException")>=0) {
+ if (params.warnings || params.nodata) {
+ warnln(msg);
+ } else if (params.nothrow) {
+ errln(msg);
+ ex.printStackTrace();
+ } else {
+ ex.printStackTrace();
+ throw new RuntimeException(msg);
+ }
+ } else {
+ if (params.nothrow) {
+ errln(msg);
+ ex.printStackTrace();
+ } else {
+ errln(msg);
+ ex.printStackTrace();
+ throw new RuntimeException(msg);
+ }
+ }
+ }
+ // use this instead of new random so we get a consistent seed
+ // for our tests
+ protected Random createRandom() {
+ return new Random(params.seed);
+ }
+
+ /**
+ * A test that has no test methods itself, but instead runs other tests.
+ *
+ * This overrides methods are getTargets and getSubtest from TestFmwk.
+ *
+ * If you want the default behavior, pass an array of class names and an
+ * optional description to the constructor. The named classes must extend
+ * TestFmwk. If a provided name doesn't include a ".", package name is
+ * prefixed to it (the package of the current test is used if none was
+ * provided in the constructor). The resulting full name is used to
+ * instantiate an instance of the class using the default constructor.
+ *
+ * Class names are resolved to classes when getTargets or getSubtest is
+ * called. This allows instances of TestGroup to be compiled and run without
+ * all the targets they would normally invoke being available.
+ */
+ public static abstract class TestGroup extends TestFmwk {
+ private String defaultPackage;
+ private String[] names;
+ private String description;
+
+ private Class[] tests; // deferred init
+
+ /**
+ * Constructor that takes a default package name and a list of class
+ * names. Adopts and modifies the classname list
+ */
+ protected TestGroup(String defaultPackage, String[] classnames,
+ String description) {
+ if (classnames == null) {
+ throw new IllegalStateException("classnames must not be null");
+ }
+
+ if (defaultPackage == null) {
+ defaultPackage = getClass().getPackage().getName();
+ }
+ defaultPackage = defaultPackage + ".";
+
+ this.defaultPackage = defaultPackage;
+ this.names = classnames;
+ this.description = description;
+ }
+
+ /**
+ * Constructor that takes a list of class names and a description, and
+ * uses the package for this class as the default package.
+ */
+ protected TestGroup(String[] classnames, String description) {
+ this(null, classnames, description);
+ }
+
+ /**
+ * Constructor that takes a list of class names, and uses the package
+ * for this class as the default package.
+ */
+ protected TestGroup(String[] classnames) {
+ this(null, classnames, null);
+ }
+
+ protected String getDescription() {
+ return description;
+ }
+
+ protected Target getTargets(String targetName) {
+ Target target = null;
+ if (targetName != null) {
+ finishInit(); // hmmm, want to get subtest without initializing
+ // all tests
+
+ try {
+ TestFmwk test = getSubtest(targetName);
+ if (test != null) {
+ target = test.new ClassTarget();
+ } else {
+ target = this.new Target(targetName);
+ }
+ } catch (TestFmwkException e) {
+ target = this.new Target(targetName);
+ }
+ } else if (params.doRecurse()) {
+ finishInit();
+ boolean groupOnly = params.doRecurseGroupsOnly();
+ for (int i = names.length; --i >= 0;) {
+ Target newTarget = null;
+ Class cls = tests[i];
+ if (cls == null) { // hack no warning for missing tests
+ if (params.warnings) {
+ continue;
+ }
+ newTarget = this.new Target(names[i]);
+ } else {
+ TestFmwk test = getSubtest(i, groupOnly);
+ if (test != null) {
+ newTarget = test.new ClassTarget();
+ } else {
+ if (groupOnly) {
+ newTarget = this.new EmptyTarget(names[i]);
+ } else {
+ newTarget = this.new Target(names[i]);
+ }
+ }
+ }
+ if (newTarget != null) {
+ newTarget.setNext(target);
+ target = newTarget;
+ }
+ }
+ }
+
+ return target;
+ }
+ protected TestFmwk getSubtest(String testName) throws TestFmwkException {
+ finishInit();
+
+ for (int i = 0; i < names.length; ++i) {
+ if (names[i].equalsIgnoreCase(testName)) { // allow
+ // case-insensitive
+ // matching
+ return getSubtest(i, false);
+ }
+ }
+ throw new TestFmwkException(testName);
+ }
+
+ private TestFmwk getSubtest(int i, boolean groupOnly) {
+ Class cls = tests[i];
+ if (cls != null) {
+ if (groupOnly && !TestGroup.class.isAssignableFrom(cls)) {
+ return null;
+ }
+
+ try {
+ TestFmwk subtest = (TestFmwk) cls.newInstance();
+ subtest.params = params;
+ return subtest;
+ } catch (InstantiationException e) {
+ throw new IllegalStateException(e.getMessage());
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(e.getMessage());
+ }
+ }
+ return null;
+ }
+
+ private void finishInit() {
+ if (tests == null) {
+ tests = new Class[names.length];
+
+ for (int i = 0; i < names.length; ++i) {
+ String name = names[i];
+ if (name.indexOf('.') == -1) {
+ name = defaultPackage + name;
+ }
+ try {
+ Class cls = Class.forName(name);
+ if (!TestFmwk.class.isAssignableFrom(cls)) {
+ throw new IllegalStateException("class " + name
+ + " does not extend TestFmwk");
+ }
+
+ tests[i] = cls;
+ names[i] = getClassTargetName(cls);
+ } catch (ClassNotFoundException e) {
+ // leave tests[i] null and name as classname
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * The default target is invalid.
+ */
+ public class Target {
+ private Target next;
+ public final String name;
+
+ public Target(String name) {
+ this.name = name;
+ }
+
+ public Target setNext(Target next) {
+ this.next = next;
+ return this;
+ }
+
+ public Target getNext() {
+ return next;
+ }
+
+ public Target append(Target targets) {
+ Target t = this;
+ while(t.next != null) {
+ t = t.next;
+ }
+ t.next = targets;
+ return this;
+ }
+
+ public void run() throws Exception {
+ int f = filter();
+ if (f == -1) {
+ ++params.invalidCount;
+ } else {
+ Locale.setDefault(defaultLocale);
+ TimeZone.setDefault(defaultTimeZone);
+
+ if (!validate()) {
+ params.writeTestInvalid(name, params.nodata);
+ } else {
+ params.push(name, getDescription(), f == 1);
+ execute();
+ params.pop();
+ }
+ }
+ }
+
+ protected int filter() {
+ return params.filter(name);
+ }
+
+ protected boolean validate() {
+ return false;
+ }
+
+ protected String getDescription() {
+ return null;
+ }
+
+ protected void execute() throws Exception{
+ }
+ }
+
+ public class EmptyTarget extends Target {
+ public EmptyTarget(String name) {
+ super(name);
+ }
+
+ protected boolean validate() {
+ return true;
+ }
+ }
+
+ public class MethodTarget extends Target {
+ private Method testMethod;
+
+ public MethodTarget(String name, Method method) {
+ super(name);
+ testMethod = method;
+ }
+
+ protected boolean validate() {
+ return testMethod != null && validateMethod(name);
+ }
+
+ protected String getDescription() {
+ return getMethodDescription(name);
+ }
+
+ protected void execute() throws Exception{
+ if (params.inDocMode()) {
+ // nothing to execute
+ } else if (!params.stack.included) {
+ ++params.invalidCount;
+ } else {
+ final Object[] NO_ARGS = new Object[0];
+ try {
+ ++params.testCount;
+ init();
+ testMethod.invoke(TestFmwk.this, NO_ARGS);
+ } catch (IllegalAccessException e) {
+ errln("Can't access test method " + testMethod.getName());
+ }catch (ExceptionInInitializerError e){
+ handleException(e);
+ } catch (InvocationTargetException e) {
+ //e.printStackTrace();
+ handleException(e);
+ }catch (MissingResourceException e) {
+ handleException(e);
+ }catch (NoClassDefFoundError e) {
+ handleException(e);
+ }catch (Exception e){
+ /*errln("Encountered: "+ e.toString());
+ e.printStackTrace(System.err);
+ */
+ handleException(e);
+ }
+ }
+ // If non-exhaustive, check if the method target
+ // takes excessive time.
+ if (params.inclusion <= 5) {
+ double deltaSec = (double)(System.currentTimeMillis() - params.stack.millis)/1000;
+ if (deltaSec > params.maxTargetSec) {
+ if (params.timeLog == null) {
+ params.timeLog = new StringBuffer();
+ }
+ params.stack.appendPath(params.timeLog);
+ params.timeLog.append(" (" + deltaSec + "s" + ")\n");
+ }
+ }
+ }
+
+ protected String getStackTrace(InvocationTargetException e) {
+ ByteArrayOutputStream bs = new ByteArrayOutputStream();
+ PrintStream ps = new PrintStream(bs);
+ e.getTargetException().printStackTrace(ps);
+ return bs.toString();
+ }
+ }
+
+ public class ClassTarget extends Target {
+ String targetName;
+
+ public ClassTarget() {
+ this(null);
+ }
+
+ public ClassTarget(String targetName) {
+ super(getClassTargetName(TestFmwk.this.getClass()));
+ this.targetName = targetName;
+ }
+
+ protected boolean validate() {
+ return TestFmwk.this.validate();
+ }
+
+ protected String getDescription() {
+ return TestFmwk.this.getDescription();
+ }
+
+ protected void execute() throws Exception {
+ params.indentLevel++;
+ Target target = randomize(getTargets(targetName));
+ while (target != null) {
+ target.run();
+ target = target.next;
+ }
+ params.indentLevel--;
+ }
+
+ private Target randomize(Target t) {
+ if (t != null && t.getNext() != null) {
+ ArrayList list = new ArrayList();
+ while (t != null) {
+ list.add(t);
+ t = t.getNext();
+ }
+
+ Target[] arr = (Target[]) list.toArray(new Target[list.size()]);
+
+ if (true) { // todo - add to params?
+ // different jvms return class methods in different orders,
+ // so we sort them (always, and then randomize them, so that
+ // forcing a seed will also work across jvms).
+ Arrays.sort(arr, new Comparator() {
+ public int compare(Object lhs, Object rhs) {
+ // sort in reverse order, later we link up in
+ // forward order
+ return ((Target) rhs).name
+ .compareTo(((Target) lhs).name);
+ }
+ });
+
+ // t is null to start, ends up as first element
+ // (arr[arr.length-1])
+ for (int i = 0; i < arr.length; ++i) {
+ t = arr[i].setNext(t); // relink in forward order
+ }
+ }
+
+ if (params.random != null) {
+ t = null; // reset t to null
+ Random r = params.random;
+ for (int i = arr.length; --i >= 1;) {
+ int x = r.nextInt(i + 1);
+ t = arr[x].setNext(t);
+ arr[x] = arr[i];
+ }
+
+ t = arr[0].setNext(t); // new first element
+ }
+ }
+
+ return t;
+ }
+ }
+
+ //------------------------------------------------------------------------
+ // Everything below here is boilerplate code that makes it possible
+ // to add a new test by simply adding a function to an existing class
+ //------------------------------------------------------------------------
+
+ protected TestFmwk() {
+ }
+
+ protected void init() throws Exception{
+ }
+
+ /**
+ * Parse arguments into a TestParams object and a collection of target
+ * paths. If there was an error parsing the TestParams, print usage and exit
+ * with -1. Otherwise, call resolveTarget(TestParams, String) for each path,
+ * and run the returned target. After the last test returns, if prompt is
+ * set, prompt and wait for input from stdin. Finally, exit with number of
+ * errors.
+ *
+ * This method never returns, since it always exits with System.exit();
+ */
+ public void run(String[] args) {
+ System.exit(run(args, new PrintWriter(System.out)));
+ }
+
+ /**
+ * Like run(String[]) except this allows you to specify the error log.
+ * Unlike run(String[]) this returns the error code as a result instead of
+ * calling System.exit().
+ */
+ public int run(String[] args, PrintWriter log) {
+ boolean prompt = false;
+ int wx = 0;
+ for (int i = 0; i < args.length; ++i) {
+ String arg = args[i];
+ if (arg.equals("-p") || arg.equals("-prompt")) {
+ prompt = true;
+ } else {
+ if (wx < i) {
+ args[wx] = arg;
+ }
+ wx++;
+ }
+ }
+ while (wx < args.length) {
+ args[wx++] = null;
+ }
+
+ TestParams localParams = TestParams.create(args, log);
+ if (localParams == null) {
+ return -1;
+ }
+
+ int errorCount = runTests(localParams, args);
+
+ if (localParams.seed != 0) {
+ localParams.log.println("-random:" + localParams.seed);
+ localParams.log.flush();
+ }
+
+ if (localParams.errorSummary != null && localParams.errorSummary.length() > 0) {
+ localParams.log.println("\nError summary:");
+ localParams.log.println(localParams.errorSummary.toString());
+ }
+
+ if (localParams.timeLog != null && localParams.timeLog.length() > 0) {
+ localParams.log.println("\nTest cases taking excessive time (>" +
+ localParams.maxTargetSec + "s):");
+ localParams.log.println(localParams.timeLog.toString());
+ }
+
+ if (prompt) {
+ System.out.println("Hit RETURN to exit...");
+ System.out.flush();
+ try {
+ System.in.read();
+ } catch (IOException e) {
+ localParams.log.println("Exception: " + e.toString() + e.getMessage());
+ }
+ }
+
+ return errorCount;
+ }
+
+ public int runTests(TestParams _params, String[] tests) {
+ int ec = 0;
+
+ StringBuffer summary = null;
+ try {
+ if (tests.length == 0 || tests[0] == null) { // no args
+ _params.init();
+ resolveTarget(_params).run();
+ ec = _params.errorCount;
+ } else {
+ for (int i = 0; i < tests.length ; ++i) {
+ if (tests[i] == null) continue;
+
+ if (i > 0) {
+ _params.log.println();
+ }
+
+ _params.init();
+ resolveTarget(_params, tests[i]).run();
+ ec += _params.errorCount;
+
+ if (_params.errorSummary != null && _params.errorSummary.length() > 0) {
+ if (summary == null) {
+ summary = new StringBuffer();
+ }
+ summary.append("\nTest Root: " + tests[i] + "\n");
+ summary.append(_params.errorSummary());
+ }
+ }
+ _params.errorSummary = summary;
+ }
+ } catch (Exception e) {
+ e.printStackTrace(_params.log);
+ _params.log.println(e.getMessage());
+ _params.log.println("encountered exception, exiting");
+ }
+
+ return ec;
+ }
+
+ /**
+ * Return a ClassTarget for this test. Params is set on this test.
+ */
+ public Target resolveTarget(TestParams paramsArg) {
+ this.params = paramsArg;
+ return new ClassTarget();
+ }
+
+ /**
+ * Resolve a path from this test to a target. If this test has subtests, and
+ * the path contains '/', the portion before the '/' is resolved to a
+ * subtest, until the path is consumed or the test has no subtests. Returns
+ * a ClassTarget created using the resolved test and remaining path (which
+ * ought to be null or a method name). Params is set on the target's test.
+ */
+ public Target resolveTarget(TestParams paramsArg, String targetPath) {
+ TestFmwk test = this;
+ test.params = paramsArg;
+
+ if (targetPath != null) {
+ if (targetPath.length() == 0) {
+ targetPath = null;
+ } else {
+ int p = 0;
+ int e = targetPath.length();
+
+ // trim all leading and trailing '/'
+ while (targetPath.charAt(p) == '/') {
+ ++p;
+ }
+ while (e > p && targetPath.charAt(e - 1) == '/') {
+ --e;
+ }
+ if (p > 0 || e < targetPath.length()) {
+ targetPath = targetPath.substring(p, e - p);
+ p = 0;
+ e = targetPath.length();
+ }
+
+ try {
+ for (;;) {
+ int n = targetPath.indexOf('/');
+ String prefix = n == -1 ? targetPath : targetPath
+ .substring(0, n);
+ TestFmwk subtest = test.getSubtest(prefix);
+
+ if (subtest == null) {
+ break;
+ }
+
+ test = subtest;
+
+ if (n == -1) {
+ targetPath = null;
+ break;
+ }
+
+ targetPath = targetPath.substring(n + 1);
+ }
+ } catch (TestFmwkException ex) {
+ return test.new Target(targetPath);
+ }
+ }
+ }
+
+ return test.new ClassTarget(targetPath);
+ }
+
+ /**
+ * Return true if we can run this test (allows test to inspect jvm,
+ * environment, params before running)
+ */
+ protected boolean validate() {
+ return true;
+ }
+
+ /**
+ * Return the targets for this test. If targetName is null, return all
+ * targets, otherwise return a target for just that name. The returned
+ * target can be null.
+ *
+ * The default implementation returns a MethodTarget for each public method
+ * of the object's class whose name starts with "Test" or "test".
+ */
+ protected Target getTargets(String targetName) {
+ return getClassTargets(getClass(), targetName);
+ }
+
+ protected Target getClassTargets(Class cls, String targetName) {
+ if (cls == null) {
+ return null;
+ }
+
+ Target target = null;
+ if (targetName != null) {
+ try {
+ Method method = cls.getMethod(targetName, (Class[])null);
+ target = new MethodTarget(targetName, method);
+ } catch (NoSuchMethodException e) {
+ if (!inheritTargets()) {
+ return new Target(targetName); // invalid target
+ }
+ } catch (SecurityException e) {
+ return null;
+ }
+ } else {
+ if (params.doMethods()) {
+ Method[] methods = cls.getDeclaredMethods();
+ for (int i = methods.length; --i >= 0;) {
+ String name = methods[i].getName();
+ if (name.startsWith("Test") || name.startsWith("test")) {
+ target = new MethodTarget(name, methods[i])
+ .setNext(target);
+ }
+ }
+ }
+ }
+
+ if (inheritTargets()) {
+ Target parentTarget = getClassTargets(cls.getSuperclass(), targetName);
+ if (parentTarget == null) {
+ return target;
+ }
+ if (target == null) {
+ return parentTarget;
+ }
+ return parentTarget.append(target);
+ }
+
+ return target;
+ }
+
+ protected boolean inheritTargets() {
+ return false;
+ }
+
+ protected String getDescription() {
+ return null;
+ }
+
+ protected boolean validateMethod(String name) {
+ return true;
+ }
+
+ protected String getMethodDescription(String name) {
+ return null;
+ }
+
+ // method tests have no subtests, group tests override
+ protected TestFmwk getSubtest(String prefix) throws TestFmwkException {
+ return null;
+ }
+
+ public boolean isVerbose() {
+ return params.verbose;
+ }
+
+ public boolean noData() {
+ return params.nodata;
+ }
+
+ public boolean isTiming() {
+ return params.timing < Long.MAX_VALUE;
+ }
+
+ public boolean isMemTracking() {
+ return params.memusage;
+ }
+
+ /**
+ * 0 = fewest tests, 5 is normal build, 10 is most tests
+ */
+ public int getInclusion() {
+ return params.inclusion;
+ }
+
+ public boolean isModularBuild() {
+ return params.warnings;
+ }
+
+ public boolean isQuick() {
+ return params.inclusion == 0;
+ }
+
+ public void msg(String message, int level, boolean incCount, boolean newln) {
+ params.msg(message, level, incCount, newln);
+ }
+
+ protected int getErrorCount() {
+ return params.errorCount;
+ }
+
+ public String getProperty(String key) {
+ String val = null;
+ if (key != null && key.length() > 0 && params.props != null) {
+ val = (String)params.props.get(key.toLowerCase());
+ }
+ return val;
+ }
+
+ protected TimeZone safeGetTimeZone(String id) {
+ TimeZone tz = TimeZone.getTimeZone(id);
+ if (tz == null) {
+ // should never happen
+ errln("FAIL: TimeZone.getTimeZone(" + id + ") => null");
+ }
+ if (!tz.getID().equals(id)) {
+ warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID());
+ }
+ return tz;
+ }
+
+ /**
+ * Print a usage message for this test class.
+ */
+ public void usage() {
+ usage(new PrintWriter(System.out), getClass().getName());
+ }
+
+ public static void usage(PrintWriter pw, String className) {
+ pw.println("Usage: " + className + " option* target*");
+ pw.println();
+ pw.println("Options:");
+ pw.println(" -d[escribe] Print a short descriptive string for this test and all");
+ pw.println(" listed targets.");
+ pw.println(" -e<n> Set exhaustiveness from 0..10. Default is 0, fewest tests.\n"
+ + " To run all tests, specify -e10. Giving -e with no <n> is\n"
+ + " the same as -e5.");
+ pw.println(" -filter:<str> Only tests matching filter will be run or listed.\n"
+ + " <str> is of the form ['^']text[','['^']text].\n"
+ + " Each string delimited by ',' is a separate filter argument.\n"
+ + " If '^' is prepended to an argument, its matches are excluded.\n"
+ + " Filtering operates on test groups as well as tests, if a test\n"
+ + " group is included, all its subtests that are not excluded will\n"
+ + " be run. Examples:\n"
+ + " -filter:A -- only tests matching A are run. If A matches a group,\n"
+ + " all subtests of this group are run.\n"
+ + " -filter:^A -- all tests except those matching A are run. If A matches\n"
+ + " a group, no subtest of that group will be run.\n"
+ + " -filter:A,B,^C,^D -- tests matching A or B and not C and not D are run\n"
+ + " Note: Filters are case insensitive.");
+ pw.println(" -h[elp] Print this help text and exit.");
+ pw.println(" -l[ist] List immediate targets of this test");
+ pw.println(" -la, -listAll List immediate targets of this test, and all subtests");
+ pw.println(" -le, -listExaustive List all subtests and targets");
+ // don't know how to get useful numbers for memory usage using java API
+ // calls
+ // pw.println(" -m[emory] print memory usage and force gc for
+ // each test");
+ pw.println(" -n[othrow] Message on test failure rather than exception");
+ pw.println(" -p[rompt] Prompt before exiting");
+ pw.println(" -prop:<key>=<value> Set optional property used by this test");
+ pw.println(" -q[uiet] Do not show warnings");
+ pw.println(" -r[andom][:<n>] If present, randomize targets. If n is present,\n"
+ + " use it as the seed. If random is not set, targets will\n"
+ + " be in alphabetical order to ensure cross-platform consistency.");
+ pw.println(" -s[ilent] No output except error summary or exceptions.");
+ pw.println(" -tfilter:<str> Transliterator Test filter of ids.");
+ pw.println(" -t[ime][:<n>] Print elapsed time for each test. if n is present\n"
+ + " only print times >= n milliseconds.");
+ pw.println(" -v[erbose] Show log messages");
+ pw.println(" -u[nicode] Don't escape error or log messages");
+ pw.println(" -w[arning] Continue in presence of warnings, and disable missing test warnings.");
+ pw.println(" -nodata | -nd Do not warn if resource data is not present.");
+ pw.println();
+ pw.println(" If a list or describe option is provided, no tests are run.");
+ pw.println();
+ pw.println("Targets:");
+ pw.println(" If no target is specified, all targets for this test are run.");
+ pw.println(" If a target contains no '/' characters, and matches a target");
+ pw.println(" of this test, the target is run. Otherwise, the part before the");
+ pw.println(" '/' is used to match a subtest, which then evaluates the");
+ pw.println(" remainder of the target as above. Target matching is case-insensitive.");
+ pw.println();
+ pw.println(" If multiple targets are provided, each is executed in order.");
+ pw.flush();
+ }
+ public static String hex(char[] s){
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < s.length; ++i) {
+ if (i != 0) result.append(',');
+ result.append(hex(s[i]));
+ }
+ return result.toString();
+ }
+ public static String hex(byte[] s){
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < s.length; ++i) {
+ if (i != 0) result.append(',');
+ result.append(hex(s[i]));
+ }
+ return result.toString();
+ }
+ public static String hex(char ch) {
+ StringBuffer result = new StringBuffer();
+ String foo = Integer.toString(ch, 16).toUpperCase();
+ for (int i = foo.length(); i < 4; ++i) {
+ result.append('0');
+ }
+ return result + foo;
+ }
+
+ public static String hex(int ch) {
+ StringBuffer result = new StringBuffer();
+ String foo = Integer.toString(ch, 16).toUpperCase();
+ for (int i = foo.length(); i < 4; ++i) {
+ result.append('0');
+ }
+ return result + foo;
+ }
+
+ public static String hex(String s) {
+ StringBuffer result = new StringBuffer();
+ for (int i = 0; i < s.length(); ++i) {
+ if (i != 0)
+ result.append(',');
+ result.append(hex(s.charAt(i)));
+ }
+ return result.toString();
+ }
+
+ public static String hex(StringBuffer s) {
+ return hex(s.toString());
+ }
+ public static String prettify(String s) {
+ StringBuffer result = new StringBuffer();
+ int ch;
+ for (int i = 0; i < s.length(); i += UTF16.getCharCount(ch)) {
+ ch = UTF16.charAt(s, i);
+ if (ch > 0xfffff) {
+ result.append("\\U00");
+ result.append(hex(ch));
+ } else if (ch > 0xffff) {
+ result.append("\\U000");
+ result.append(hex(ch));
+ } else if (ch > 0x7f) {
+ result.append("\\u");
+ result.append(hex(ch));
+ } else {
+ result.append((char) ch);
+ }
+
+ }
+ return result.toString();
+ }
+ public static String prettify(StringBuffer s) {
+ return prettify(s.toString());
+ }
+
+ private static java.util.GregorianCalendar cal;
+
+ /**
+ * Return a Date given a year, month, and day of month. This is similar to
+ * new Date(y-1900, m, d). It uses the default time zone at the time this
+ * method is first called.
+ *
+ * @param year
+ * use 2000 for 2000, unlike new Date()
+ * @param month
+ * use Calendar.JANUARY etc.
+ * @param dom
+ * day of month, 1-based
+ * @return a Date object for the given y/m/d
+ */
+ protected static synchronized java.util.Date getDate(int year, int month,
+ int dom) {
+ if (cal == null) {
+ cal = new java.util.GregorianCalendar();
+ }
+ cal.clear();
+ cal.set(year, month, dom);
+ return cal.getTime();
+ }
+
+ public static class NullWriter extends PrintWriter {
+ public NullWriter() {
+ super(System.out, false);
+ }
+ public void write(int c) {
+ }
+ public void write(char[] buf, int off, int len) {
+ }
+ public void write(String s, int off, int len) {
+ }
+ public void println() {
+ }
+ }
+
+ public static class ASCIIWriter extends PrintWriter {
+ private StringBuffer buffer = new StringBuffer();
+
+ // Characters that we think are printable but that escapeUnprintable
+ // doesn't
+ private static final String PRINTABLES = "\t\n\r";
+
+ public ASCIIWriter(Writer w, boolean autoFlush) {
+ super(w, autoFlush);
+ }
+
+ public ASCIIWriter(OutputStream os, boolean autoFlush) {
+ super(os, autoFlush);
+ }
+
+ public void write(int c) {
+ synchronized (lock) {
+ buffer.setLength(0);
+ if (PRINTABLES.indexOf(c) < 0
+ && TestUtil.escapeUnprintable(buffer, c)) {
+ super.write(buffer.toString());
+ } else {
+ super.write(c);
+ }
+ }
+ }
+
+ public void write(char[] buf, int off, int len) {
+ synchronized (lock) {
+ buffer.setLength(0);
+ int limit = off + len;
+ while (off < limit) {
+ int c = UTF16Util.charAt(buf, 0, buf.length, off);
+ off += UTF16Util.getCharCount(c);
+ if (PRINTABLES.indexOf(c) < 0
+ && TestUtil.escapeUnprintable(buffer, c)) {
+ super.write(buffer.toString());
+ buffer.setLength(0);
+ } else {
+ super.write(c);
+ }
+ }
+ }
+ }
+
+ public void write(String s, int off, int len) {
+ write(s.substring(off, off + len).toCharArray(), 0, len);
+ }
+ }
+
+ // filters
+ // match against the entire hierarchy
+ // A;B;!C;!D --> (A ||B) && (!C && !D)
+ // positive, negative, unknown matches
+ // positive -- known to be included, negative- known to be excluded
+ // positive only if no excludes, and matches at least one include, if any
+ // negative only if matches at least one exclude
+ // otherwise, we wait
+
+ public static class TestParams {
+ public boolean prompt;
+ public boolean nothrow;
+ public boolean verbose;
+ public boolean quiet;
+ public int listlevel;
+ public boolean describe;
+ public boolean warnings;
+ public boolean nodata;
+ public long timing = Long.MAX_VALUE;
+ public boolean memusage;
+ public int inclusion;
+ public String filter;
+ public long seed;
+ public String tfilter; // for transliterator tests
+
+ public State stack;
+
+ public StringBuffer errorSummary;
+ private StringBuffer timeLog;
+
+ public PrintWriter log;
+ public int indentLevel;
+ private boolean needLineFeed;
+ private boolean suppressIndent;
+ public int errorCount;
+ public int warnCount;
+ public int invalidCount;
+ public int testCount;
+ private NumberFormat tformat;
+ public Random random;
+ public int maxTargetSec = 10;
+ public HashMap props;
+
+ private TestParams() {
+ }
+
+ public static TestParams create(String arglist, PrintWriter log) {
+ String[] args = null;
+ if (arglist != null && arglist.length() > 0) {
+//#if defined(FOUNDATION10) || defined(J2SE13)
+//## args = Utility.split(arglist, '\u0020');
+//#else
+ args = arglist.split("\\s");
+//#endif
+ }
+ return create(args, log);
+ }
+
+ /**
+ * Create a TestParams from a list of arguments. If successful, return the params object,
+ * else return null. Error messages will be reported on errlog if it is not null.
+ * Arguments and values understood by this method will be removed from the args array
+ * and existing args will be shifted down, to be filled by nulls at the end.
+ * @param args the list of arguments
+ * @param log the error log, or null if no error log is desired
+ * @return the new TestParams object, or null if error
+ */
+ public static TestParams create(String[] args, PrintWriter log) {
+ TestParams params = new TestParams();
+
+ if(log == null){
+ params.log = new NullWriter();
+ }else{
+ params.log = new ASCIIWriter(log, true);
+ }
+
+ boolean usageError = false;
+ String filter = null;
+ int wx = 0; // write argets.
+ if (args != null) {
+ for (int i = 0; i < args.length; i++) {
+ String arg = args[i];
+ if (arg == null || arg.length() == 0) {
+ continue;
+ }
+ if (arg.charAt(0) == '-') {
+ arg = arg.toLowerCase();
+ if (arg.equals("-verbose") || arg.equals("-v")) {
+ params.verbose = true;
+ params.quiet = false;
+ } else if (arg.equals("-quiet") || arg.equals("-q")) {
+ params.quiet = true;
+ params.verbose = false;
+ } else if (arg.equals("-help") || arg.equals("-h")) {
+ usageError = true;
+ } else if (arg.equals("-warning") || arg.equals("-w")) {
+ params.warnings = true;
+ } else if (arg.equals("-nodata") || arg.equals("-nd")) {
+ params.nodata = true;
+ } else if (arg.equals("-list") || arg.equals("-l")) {
+ params.listlevel = 1;
+ } else if (arg.equals("-listall") || arg.equals("-la")) {
+ params.listlevel = 2;
+ } else if (arg.equals("-listexaustive") || arg.equals("-le")) {
+ params.listlevel = 3;
+ } else if (arg.equals("-memory") || arg.equals("-m")) {
+ params.memusage = true;
+ } else if (arg.equals("-nothrow") || arg.equals("-n")) {
+ params.nothrow = true;
+ params.errorSummary = new StringBuffer();
+ } else if (arg.equals("-describe") || arg.equals("-d")) {
+ params.describe = true;
+ } else if (arg.startsWith("-r")) {
+ String s = null;
+ int n = arg.indexOf(':');
+ if (n != -1) {
+ s = arg.substring(n + 1);
+ arg = arg.substring(0, n);
+ }
+
+ if (arg.equals("-r") || arg.equals("-random")) {
+ if (s == null) {
+ params.seed = System.currentTimeMillis();
+ } else {
+ params.seed = Long.parseLong(s);
+ }
+ } else {
+ log.println("*** Error: unrecognized argument: " + arg);
+ usageError = true;
+ break;
+ }
+ } else if (arg.startsWith("-e")) {
+ // see above
+ params.inclusion = (arg.length() == 2)
+ ? 5
+ : Integer.parseInt(arg.substring(2));
+ if (params.inclusion < 0 || params.inclusion > 10) {
+ usageError = true;
+ break;
+ }
+ } else if (arg.startsWith("-tfilter:")) {
+ params.tfilter = arg.substring(8);
+ } else if (arg.startsWith("-time") || arg.startsWith("-t")) {
+ long val = 0;
+ int inx = arg.indexOf(':');
+ if (inx > 0) {
+ String num = arg.substring(inx + 1);
+ try {
+ val = Long.parseLong(num);
+ } catch (Exception e) {
+ log.println("*** Error: could not parse time threshold '"
+ + num + "'");
+ usageError = true;
+ break;
+ }
+ }
+ params.timing = val;
+ String fmt = "#,00s";
+ if (val <= 10) {
+ fmt = "#,##0.000s";
+ } else if (val <= 100) {
+ fmt = "#,##0.00s";
+ } else if (val <= 1000) {
+ fmt = "#,##0.0s";
+ }
+ params.tformat = new DecimalFormat(fmt);
+ } else if (arg.startsWith("-filter:")) {
+ String temp = arg.substring(8).toLowerCase();
+ filter = filter == null ? temp : filter + "," + temp;
+ } else if (arg.startsWith("-f:")) {
+ String temp = arg.substring(3).toLowerCase();
+ filter = filter == null ? temp : filter + "," + temp;
+ } else if (arg.startsWith("-s")) {
+ params.log = new NullWriter();
+ } else if (arg.startsWith("-u")) {
+ if (params.log instanceof ASCIIWriter) {
+ params.log = log;
+ }
+ } else if (arg.startsWith("-prop:")) {
+ String temp = arg.substring(6);
+ int eql = temp.indexOf('=');
+ if (eql <= 0) {
+ log.println("*** Error: could not parse custom property '" + arg + "'");
+ usageError = true;
+ break;
+ }
+ if (params.props == null) {
+ params.props = new HashMap();
+ }
+ params.props.put(temp.substring(0, eql), temp.substring(eql+1));
+ } else {
+ log.println("*** Error: unrecognized argument: "
+ + args[i]);
+ usageError = true;
+ break;
+ }
+ } else {
+ args[wx++] = arg; // shift down
+ }
+ }
+
+ while (wx < args.length) {
+ args[wx++] = null;
+ }
+ }
+
+ if (usageError) {
+ usage(log, "TestAll");
+ return null;
+ }
+
+ if (filter != null) {
+ params.filter = filter.toLowerCase();
+ }
+
+ params.init();
+
+ return params;
+ }
+
+ public String errorSummary() {
+ return errorSummary == null ? "" : errorSummary.toString();
+ }
+
+ public void init() {
+ indentLevel = 0;
+ needLineFeed = false;
+ suppressIndent = false;
+ errorCount = 0;
+ warnCount = 0;
+ invalidCount = 0;
+ testCount = 0;
+ random = seed == 0 ? null : new Random(seed);
+ }
+
+ public class State {
+ State link;
+ String name;
+ StringBuffer buffer;
+ int level;
+ int ec;
+ int wc;
+ int ic;
+ int tc;
+ boolean flushed;
+ public boolean included;
+ long mem;
+ long millis;
+
+ public State(State link, String name, boolean included) {
+ this.link = link;
+ this.name = name;
+ if (link == null) {
+ this.level = 0;
+ this.included = included;
+ } else {
+ this.level = link.level + 1;
+ this.included = included || link.included;
+ }
+ this.ec = errorCount;
+ this.wc = warnCount;
+ this.ic = invalidCount;
+ this.tc = testCount;
+
+ if (link == null || this.included) {
+ flush();
+ }
+
+ mem = getmem();
+ millis = System.currentTimeMillis();
+ }
+
+ void flush() {
+ if (!flushed) {
+ if (link != null) {
+ link.flush();
+ }
+
+ indent(level);
+ log.print(name);
+ log.flush();
+
+ flushed = true;
+
+ needLineFeed = true;
+ }
+ }
+
+ void appendPath(StringBuffer buf) {
+ if (this.link != null) {
+ this.link.appendPath(buf);
+ buf.append('/');
+ }
+ buf.append(name);
+ }
+ }
+
+ public void push(String name, String description, boolean included) {
+ if (inDocMode() && describe && description != null) {
+ name += ": " + description;
+ }
+ stack = new State(stack, name, included);
+ }
+
+ public void pop() {
+ if (stack != null) {
+ writeTestResult();
+ stack = stack.link;
+ }
+ }
+
+ public boolean inDocMode() {
+ return describe || listlevel != 0;
+ }
+
+ public boolean doMethods() {
+ return !inDocMode() || listlevel == 3
+ || (indentLevel == 1 && listlevel > 0);
+ }
+
+ public boolean doRecurse() {
+ return !inDocMode() || listlevel > 1
+ || (indentLevel == 1 && listlevel > 0);
+ }
+
+ public boolean doRecurseGroupsOnly() {
+ return inDocMode()
+ && (listlevel == 2 || (indentLevel == 1 && listlevel > 0));
+ }
+
+ // return 0, -1, or 1
+ // 1: run this test
+ // 0: might run this test, no positive include or exclude on this group
+ // -1: exclude this test
+ public int filter(String testName) {
+ int result = 0;
+ if (filter == null) {
+ result = 1;
+ } else {
+ boolean noIncludes = true;
+ boolean noExcludes = filter.indexOf('^') == -1;
+ testName = testName.toLowerCase();
+ int ix = 0;
+ while (ix < filter.length()) {
+ int nix = filter.indexOf(',', ix);
+ if (nix == -1) {
+ nix = filter.length();
+ }
+ if (filter.charAt(ix) == '^') {
+ if (testName.indexOf(filter.substring(ix + 1, nix)) != -1) {
+ result = -1;
+ break;
+ }
+ } else {
+ noIncludes = false;
+ if (testName.indexOf(filter.substring(ix, nix)) != -1) {
+ result = 1;
+ if (noExcludes) {
+ break;
+ }
+ }
+ }
+
+ ix = nix + 1;
+ }
+ if (result == 0 && noIncludes) {
+ result = 1;
+ }
+ }
+ // System.out.println("filter: " + testName + " returns: " +
+ // result);
+ return result;
+ }
+
+ /**
+ * Log access.
+ * @param msg The string message to write
+ */
+ public void write(String msg) {
+ write(msg, false);
+ }
+
+ public void writeln(String msg) {
+ write(msg, true);
+ }
+
+ private void write(String msg, boolean newln) {
+ if (!suppressIndent) {
+ if (needLineFeed) {
+ log.println();
+ needLineFeed = false;
+ }
+ log.print(spaces.substring(0, indentLevel * 2));
+ }
+ log.print(msg);
+ if (newln) {
+ log.println();
+ }
+ log.flush();
+ suppressIndent = !newln;
+ }
+
+ private void msg(String message, int level, boolean incCount,
+ boolean newln) {
+ if (level == WARN && (!warnings && !nodata)){
+ level = ERR;
+ }
+
+ if (incCount) {
+ if (level == WARN) {
+ warnCount++;
+ invalidCount++;
+ } else if (level == ERR) {
+ errorCount++;
+ }
+ }
+
+ // should roll indentation stuff into log ???
+ if (verbose || level > (quiet ? WARN : LOG)) {
+ if (!suppressIndent) {
+ indent(indentLevel + 1);
+ final String[] MSGNAMES = {"", "Warning: ", "Error: "};
+ log.print(MSGNAMES[level]);
+ }
+
+ log.print(message);
+ if (newln) {
+ log.println();
+ }
+ log.flush();
+ }
+
+ if (level == ERR) {
+ if (!nothrow) {
+ throw new RuntimeException(message);
+ }
+ if (!suppressIndent && errorSummary != null && stack !=null
+ && (errorCount == stack.ec + 1)) {
+ stack.appendPath(errorSummary);
+ errorSummary.append("\n");
+ }
+ }
+
+ suppressIndent = !newln;
+ }
+
+ private void writeTestInvalid(String name, boolean nodataArg) {
+ // msg("***" + name + "*** not found or not valid.", WARN, true,
+ // true);
+ if (inDocMode()) {
+ if (!warnings) {
+ if (stack != null) {
+ stack.flush();
+ }
+ log.println(" *** Target not found or not valid.");
+ log.flush();
+ needLineFeed = false;
+ }
+ } else {
+ if(!nodataArg){
+ msg("Test " + name + " not found or not valid.", WARN, true,
+ true);
+ }
+ }
+ }
+
+ long getmem() {
+ long newmem = 0;
+ if (memusage) {
+ Runtime rt = Runtime.getRuntime();
+ long lastmem = Long.MAX_VALUE;
+ do {
+ rt.gc();
+ rt.gc();
+ try {
+ Thread.sleep(50);
+ } catch (Exception e) {
+ break;
+ }
+ lastmem = newmem;
+ newmem = rt.totalMemory() - rt.freeMemory();
+ } while (newmem < lastmem);
+ }
+ return newmem;
+ }
+
+ private void writeTestResult() {
+ if (inDocMode()) {
+ if (needLineFeed) {
+ log.println();
+ log.flush();
+ }
+ needLineFeed = false;
+ return;
+ }
+
+ long dmem = getmem() - stack.mem;
+ long dtime = System.currentTimeMillis() - stack.millis;
+
+ int testDelta = testCount - stack.tc;
+ if (testDelta == 0) {
+ return;
+ }
+
+ int errorDelta = errorCount - stack.ec;
+ int invalidDelta = invalidCount - stack.ic;
+
+ stack.flush();
+
+ if (!needLineFeed) {
+ indent(indentLevel);
+ log.print("}");
+ }
+ needLineFeed = false;
+
+ if (memusage || dtime >= timing) {
+ log.print(" (");
+ if (memusage) {
+ log.print("dmem: " + dmem);
+ }
+ if (dtime >= timing) {
+ if (memusage) {
+ log.print(", ");
+ }
+ log.print(tformat.format(dtime / 1000f));
+ }
+ log.print(")");
+ }
+
+ if (errorDelta != 0) {
+ log.println(" FAILED ("
+ + errorDelta
+ + " failures"
+ + ((invalidDelta != 0) ? ", " + invalidDelta
+ + " tests skipped)" : ")"));
+ } else if (invalidDelta != 0) {
+ log.println(" Qualified (" + invalidDelta + " tests skipped)");
+ } else {
+ log.println(" Passed");
+ }
+ }
+
+ private final void indent(int distance) {
+ boolean idm = inDocMode();
+ if (needLineFeed) {
+ if (idm) {
+ log.println();
+ } else {
+ log.println(" {");
+ }
+ needLineFeed = false;
+ }
+
+ log.print(spaces.substring(0, distance * (idm ? 3 : 2)));
+
+ if (idm) {
+ log.print("-- ");
+ }
+ }
+ }
+
+ public String getTranslitTestFilter() {
+ return params.tfilter;
+ }
+
+ /**
+ * Return the target name for a test class. This is either the end of the
+ * class name, or if the class declares a public static field
+ * CLASS_TARGET_NAME, the value of that field.
+ */
+ private static String getClassTargetName(Class testClass) {
+ String name = testClass.getName();
+ try {
+ Field f = testClass.getField("CLASS_TARGET_NAME");
+ name = (String) f.get(null);
+ } catch (IllegalAccessException e) {
+ throw new IllegalStateException(
+ "static field CLASS_TARGET_NAME must be accessible");
+ } catch (NoSuchFieldException e) {
+ int n = Math.max(name.lastIndexOf('.'), name.lastIndexOf('$'));
+ if (n != -1) {
+ name = name.substring(n + 1);
+ }
+ }
+ return name;
+ }
+
+ /**
+ * Check the given array to see that all the strings in the expected array
+ * are present.
+ *
+ * @param msg
+ * string message, for log output
+ * @param array
+ * array of strings to check
+ * @param expected
+ * array of strings we expect to see, or null
+ * @return the length of 'array', or -1 on error
+ */
+ protected int checkArray(String msg, String array[], String expected[]) {
+ int explen = (expected != null) ? expected.length : 0;
+ if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32
+ errln("Internal error");
+ return -1;
+ }
+ int i = 0;
+ StringBuffer buf = new StringBuffer();
+ int seenMask = 0;
+ for (; i < array.length; ++i) {
+ String s = array[i];
+ if (i != 0)
+ buf.append(", ");
+ buf.append(s);
+ // check expected list
+ for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) {
+ if ((seenMask & bit) == 0) {
+ if (s.equals(expected[j])) {
+ seenMask |= bit;
+ logln("Ok: \"" + s + "\" seen");
+ }
+ }
+ }
+ }
+ logln(msg + " = [" + buf + "] (" + i + ")");
+ // did we see all expected strings?
+ if (((1 << explen) - 1) != seenMask) {
+ for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) {
+ if ((seenMask & bit) == 0) {
+ errln("\"" + expected[j] + "\" not seen");
+ }
+ }
+ }
+ return array.length;
+ }
+
+ /**
+ * Check the given array to see that all the locales in the expected array
+ * are present.
+ *
+ * @param msg
+ * string message, for log output
+ * @param array
+ * array of locales to check
+ * @param expected
+ * array of locales names we expect to see, or null
+ * @return the length of 'array'
+ */
+ protected int checkArray(String msg, Locale array[], String expected[]) {
+ String strs[] = new String[array.length];
+ for (int i = 0; i < array.length; ++i)
+ strs[i] = array[i].toString();
+ return checkArray(msg, strs, expected);
+ }
+
+ /**
+ * Check the given array to see that all the locales in the expected array
+ * are present.
+ *
+ * @param msg
+ * string message, for log output
+ * @param array
+ * array of locales to check
+ * @param expected
+ * array of locales names we expect to see, or null
+ * @return the length of 'array'
+ */
+ protected int checkArray(String msg, ULocale array[], String expected[]) {
+ String strs[] = new String[array.length];
+ for (int i = 0; i < array.length; ++i)
+ strs[i] = array[i].toString();
+ return checkArray(msg, strs, expected);
+ }
+
+ // JUnit-like assertions.
+
+ protected boolean assertTrue(String message, boolean condition) {
+ return handleAssert(condition, message, "true", null);
+ }
+
+ protected boolean assertFalse(String message, boolean condition) {
+ return handleAssert(!condition, message, "false", null);
+ }
+
+ protected boolean assertEquals(String message, boolean expected,
+ boolean actual) {
+ return handleAssert(expected == actual, message, String
+ .valueOf(expected), String.valueOf(actual));
+ }
+
+ protected boolean assertEquals(String message, long expected, long actual) {
+ return handleAssert(expected == actual, message, String
+ .valueOf(expected), String.valueOf(actual));
+ }
+
+ // do NaN and range calculations to precision of float, don't rely on
+ // promotion to double
+ protected boolean assertEquals(String message, float expected,
+ float actual, double error) {
+ boolean result = Float.isInfinite(expected)
+ ? expected == actual
+ : !(Math.abs(expected - actual) > error); // handles NaN
+ return handleAssert(result, message, String.valueOf(expected)
+ + (error == 0 ? "" : " (within " + error + ")"), String
+ .valueOf(actual));
+ }
+
+ protected boolean assertEquals(String message, double expected,
+ double actual, double error) {
+ boolean result = Double.isInfinite(expected)
+ ? expected == actual
+ : !(Math.abs(expected - actual) > error); // handles NaN
+ return handleAssert(result, message, String.valueOf(expected)
+ + (error == 0 ? "" : " (within " + error + ")"), String
+ .valueOf(actual));
+ }
+
+ protected boolean assertEquals(String message, Object expected,
+ Object actual) {
+ boolean result = expected == null ? actual == null : expected
+ .equals(actual);
+ return handleAssert(result, message, stringFor(expected),
+ stringFor(actual));
+ }
+
+ protected boolean assertNotEquals(String message, Object expected,
+ Object actual) {
+ boolean result = !(expected == null ? actual == null : expected
+ .equals(actual));
+ return handleAssert(result, message, stringFor(expected),
+ stringFor(actual), "not equal to", true);
+ }
+
+ protected boolean assertSame(String message, Object expected, Object actual) {
+ return handleAssert(expected == actual, message, stringFor(expected),
+ stringFor(actual), "==", false);
+ }
+
+ protected boolean assertNotSame(String message, Object expected,
+ Object actual) {
+ return handleAssert(expected != actual, message, stringFor(expected),
+ stringFor(actual), "!=", true);
+ }
+
+ protected boolean assertNull(String message, Object actual) {
+ return handleAssert(actual == null, message, null, stringFor(actual));
+ }
+
+ protected boolean assertNotNull(String message, Object actual) {
+ return handleAssert(actual != null, message, null, stringFor(actual),
+ "!=", true);
+ }
+
+ protected void fail(String message) {
+ errln(message);
+ }
+
+ private boolean handleAssert(boolean result, String message,
+ String expected, String actual) {
+ return handleAssert(result, message, expected, actual, null, false);
+ }
+
+ public boolean handleAssert(boolean result, String message,
+ Object expected, Object actual, String relation, boolean flip) {
+ if (!result || isVerbose()) {
+ message = message == null ? "" : " " + message;
+ relation = relation == null ? ", got " : " " + relation + " ";
+ if (result) {
+ logln("OK" + message + ": "
+ + (flip ? expected + relation + actual : expected));
+ } else {
+ // assert must assume errors are true errors and not just warnings
+ // so cannot warnln here
+ errln(message
+ + ": expected"
+ + (flip ? relation + expected : " " + expected
+ + (actual != null ? relation + actual : "")));
+ }
+ }
+ return result;
+ }
+
+ private final String stringFor(Object obj) {
+ if (obj == null)
+ return "null";
+ if (obj instanceof String)
+ return "\"" + obj + '"';
+ return obj.getClass().getName() + "<" + obj + ">";
+ }
+
+ // End JUnit-like assertions
+
+ // PrintWriter support
+
+ public PrintWriter getErrorLogPrintWriter() {
+ return new PrintWriter(new TestLogWriter(this, TestLog.ERR));
+ }
+
+ public PrintWriter getLogPrintWriter() {
+ return new PrintWriter(new TestLogWriter(this, TestLog.LOG));
+ }
+
+ // end PrintWriter support
+
+ protected TestParams params = null;
+
+ private final static String spaces = " ";
+
+}