| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2000, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| * |
| * $Source: /xsrl/Nsvn/icu/icu4j/src/com/ibm/icu/dev/test/TestFmwk.java,v $ |
| * $Date: 2002/11/06 19:07:02 $ |
| * $Revision: 1.33 $ |
| * |
| ***************************************************************************************** |
| */ |
| package com.ibm.icu.dev.test; |
| |
| import java.io.*; |
| import java.lang.reflect.*; |
| import java.text.*; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.Map; |
| import java.util.Set; |
| import java.util.TreeMap; |
| import java.util.TreeSet; |
| import java.util.Vector; |
| |
| import com.ibm.icu.impl.Utility; |
| import com.ibm.icu.text.UnicodeSet; |
| import com.ibm.icu.text.UTF16; |
| |
| /** |
| * 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 implements TestLog { |
| |
| /** |
| * Puts a copyright in the .class file |
| */ |
| private static final String copyrightNotice |
| = "Copyright \u00a91997-1998 IBM Corp. All rights reserved."; |
| |
| //------------------------------------------------------------------------ |
| // 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() { |
| } |
| |
| /** |
| * Default is to create a hashmap containing all the test methods. |
| * Data-driven tests can override to provide a different mapping |
| * of test names to test methods, and a different list. If this |
| * test suite is invalid, subclassers should return an empty |
| * collection. |
| */ |
| protected Map getAvailableTests() { |
| Map result = Collections.EMPTY_MAP; |
| if (validate()) { |
| result = new HashMap(); |
| Method[] methods = getClass().getDeclaredMethods(); |
| for( int i=0; i<methods.length; i++ ) { |
| if( methods[i].getName().startsWith("Test") |
| || methods[i].getName().startsWith("test")) { |
| result.put(methods[i].getName(), methods[i] ); |
| } |
| } |
| } |
| return result; |
| } |
| |
| private Map getTestsToRun(Set testNames) { |
| Map methodsToRun = getAvailableTests(); |
| if (!(testNames == null || testNames.isEmpty())) { |
| methodsToRun.keySet().retainAll(testNames); |
| } |
| return methodsToRun; |
| } |
| |
| private Iterator getTestEntryIterator(Map testsToRun) { |
| TreeMap sortedMethods = new TreeMap(String.CASE_INSENSITIVE_ORDER); |
| sortedMethods.putAll(testsToRun); |
| return sortedMethods.entrySet().iterator(); |
| } |
| |
| private void _run() throws Exception { |
| _run(getTestsToRun(null)); |
| } |
| |
| private void _run(Map testsToRun) throws Exception { |
| writeTestName(getClass().getName()); |
| params.indentLevel++; |
| int oldClassCount = params.errorCount; |
| int oldClassInvalidCount = params.invalidCount; |
| |
| if (validate()) { |
| Iterator iter = getTestEntryIterator(testsToRun); |
| |
| // Run the list of tests given in the test arguments |
| final Object[] NO_ARGS = new Object[0]; |
| while (iter.hasNext()) { |
| int oldCount = params.errorCount; |
| int oldInvalidCount = params.invalidCount; |
| |
| Map.Entry entry = (Map.Entry)iter.next(); |
| String testName = (String)entry.getKey(); |
| Method testMethod = (Method)entry.getValue(); |
| |
| writeTestName(testName); |
| |
| if (validateMethod(testName)) { |
| try { |
| testMethod.invoke(this, NO_ARGS); |
| } catch( IllegalAccessException e ) { |
| errln("Can't access test method " + testName); |
| } catch( InvocationTargetException e ) { |
| errln("Uncaught exception \"" + e |
| +"\" thrown in test method " + testMethod.getName() |
| +" accessed under name " + testName); |
| e.getTargetException().printStackTrace(this.params.log); |
| } |
| } else { |
| params.invalidCount++; |
| } |
| writeTestResult(params.errorCount - oldCount, params.invalidCount - oldInvalidCount); |
| } |
| } else { |
| params.invalidCount++; |
| } |
| params.indentLevel--; |
| |
| writeTestResult(params.errorCount - oldClassCount, params.invalidCount - oldClassInvalidCount); |
| } |
| |
| public void run(String[] args) throws Exception { |
| if (params == null) params = new TestParams(); |
| |
| // Parse the test arguments. They can be either the flag |
| // "-verbose" or names of test methods. Create a list of |
| // tests to be run. |
| boolean usageError = false; |
| Set testNames = null; |
| for (int i = 0; i < args.length; i++) { |
| if (args[i].equals("-verbose") || args[i].equals("-v")) { |
| params.verbose = true; |
| } |
| else if (args[i].equals("-prompt")) { |
| params.prompt = true; |
| } else if (args[i].equals("-nothrow") || args[i].equals("-n")) { |
| params.nothrow = true; |
| } else if (args[i].equals("-describe")) { |
| params.describe = true; |
| } else if (args[i].startsWith("-e")) { |
| // see above |
| params.inclusion = (args[i].length() == 2) ? 5 : Integer.parseInt(args[i].substring(2)); |
| if (params.inclusion < 0 || params.inclusion > 10) { |
| usageError = true; |
| } |
| } else if (args[i].toLowerCase().startsWith("-filter:")) { |
| params.filter = args[i].substring(8); |
| } else { |
| if (testNames == null) { |
| testNames = new TreeSet(); |
| } |
| testNames.add(args[i]); |
| } |
| } |
| |
| Map testsToRun = null; |
| if (!usageError) { |
| testsToRun = getTestsToRun(testNames); |
| } |
| if (usageError || |
| (testNames != null && testsToRun.size() != testNames.size())) { |
| usage(); |
| return; |
| } |
| |
| _run(testsToRun); |
| |
| if (params.prompt) { |
| System.out.println("Hit RETURN to exit..."); |
| try { |
| System.in.read(); |
| } catch (IOException e) { |
| System.out.println("Exception: " + e.toString() + e.getMessage()); |
| } |
| } |
| if (params.nothrow) { |
| System.exit(params.errorCount); |
| } |
| } |
| |
| /** |
| * Return true if we can run this test (allows test to inspect jvm, environment, params before running) |
| */ |
| protected boolean validate() { |
| return true; |
| } |
| |
| protected String getDescription() { |
| return null; |
| } |
| |
| protected boolean validateMethod(String name) { |
| return true; |
| } |
| |
| protected String getMethodDescription(String name) { |
| return null; |
| } |
| |
| protected void run(TestFmwk childTest) throws Exception { |
| run(new TestFmwk[] { childTest }); |
| } |
| |
| protected void run(TestFmwk[] tests) throws Exception { |
| for (int i=0; i<tests.length; ++i) { |
| tests[i].params = this.params; |
| params.indentLevel++; |
| tests[i]._run(); |
| params.indentLevel--; |
| } |
| } |
| |
| protected boolean isVerbose() { |
| return params.verbose; |
| } |
| |
| /** |
| * 0 = fewest tests, 5 is normal build, 10 is most tests |
| */ |
| public int getInclusion() { |
| return params.inclusion; |
| } |
| |
| public boolean isQuick() { |
| return params.inclusion == 0; |
| } |
| |
| public String getFilter() { |
| return params.filter; |
| } |
| |
| /** |
| * Adds given string to the log if we are in verbose mode. |
| */ |
| public void log( String message ) { |
| log(message, true, false); |
| } |
| |
| public void logln( String message ) { |
| logln(message, true, false); |
| } |
| |
| /** |
| * Add a given string to the log. |
| * @param message text to add |
| * @param pass if true and if in verbose mode, or if false, then add |
| * the text; otherwise suppress it |
| * @param incrementCount if pass if false and incrementCount is true, |
| * then increment the failure count; if pass is true, then this param |
| * is ignored |
| */ |
| public void log( String message, boolean pass, boolean incrementCount ) { |
| if (!pass && incrementCount) { |
| params.errorCount++; |
| } |
| |
| if (!pass || params.verbose) { |
| if (!params.suppressIndent) indent(params.indentLevel + 1); |
| params.log.print( message ); |
| params.log.flush(); |
| } |
| |
| if (!pass && !params.nothrow) { |
| throw new RuntimeException(message); |
| } |
| |
| params.suppressIndent = true; // don't indent on successive calls to log() |
| } |
| |
| public void logln( String message, boolean pass, boolean incrementCount ) { |
| log(message + System.getProperty("line.separator"), pass, incrementCount); |
| params.suppressIndent = false; // time to indent again |
| } |
| |
| /** |
| * Convenience overloads |
| */ |
| public void log( String message, boolean pass ) { |
| log(message, pass, true); |
| } |
| |
| public void logln( String message, boolean pass ) { |
| logln(message, pass, true); |
| } |
| |
| /** |
| * Report an error |
| */ |
| public void err( String message ) { |
| log(message, false, true); |
| } |
| |
| public void errln( String message ) { |
| logln(message, false, true); |
| } |
| |
| protected int getErrorCount() { |
| return params.errorCount; |
| } |
| |
| protected void writeTestName(String testName) { |
| indent(params.indentLevel); |
| params.log.print(testName); |
| params.log.flush(); |
| params.needLineFeed = true; |
| } |
| |
| protected void writeTestResult(int failCount, int invalidCount) { |
| if (!params.needLineFeed) { |
| indent(params.indentLevel); |
| params.log.print("}"); |
| } |
| params.needLineFeed = false; |
| |
| if (failCount != 0) { |
| params.log.println(" FAILED (" + failCount + " failures" + |
| ((invalidCount != 0) ? |
| ", " + invalidCount + " tests skipped)" : |
| ")")); |
| } else if (invalidCount != 0) { |
| params.log.println(" Qualified (" + invalidCount + " tests skipped)"); |
| } else { |
| params.log.println(" Passed"); |
| } |
| } |
| |
| private final void indent(int distance) { |
| if (params.needLineFeed) { |
| params.log.println(" {"); |
| params.needLineFeed = false; |
| } |
| params.log.print(spaces.substring(0, distance * 2)); |
| } |
| |
| /** |
| * Print a usage message for this test class. |
| */ |
| void usage() { |
| System.out.println(getClass().getName() + |
| ": [-verbose] [-nothrow] [-prompt] [-describe]\n [-e<n>] [-filter:<str>] [test names]"); |
| System.out.println("Options:"); |
| System.out.println(" -v[erbose] Show all output"); |
| System.out.println(" -n[othrow] Message on test failure rather than exception"); |
| System.out.println(" -prompt Prompt before exiting"); |
| System.out.println(" -describe ?"); |
| System.out.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."); |
| System.out.println(" -filter:<str> ?"); |
| |
| boolean valid = params.describe && validate(); |
| if (valid) { |
| String testDescription = getDescription(); |
| if (testDescription != null) { |
| System.out.println("-- " + testDescription); |
| } |
| } |
| |
| Iterator testEntries = getTestEntryIterator(getAvailableTests()); |
| |
| System.out.println("Test names:"); |
| while(testEntries.hasNext() ) { |
| Map.Entry e = (Map.Entry)testEntries.next(); |
| String methodName = (String)e.getKey(); |
| |
| System.out.print("\t" + methodName ); |
| if (valid) { |
| String methodDescription = getMethodDescription(methodName); |
| if (methodDescription != null) { |
| System.out.print(" -- " + methodDescription); |
| } |
| } |
| System.out.println(); |
| } |
| } |
| |
| 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()); |
| } |
| |
| private static class ASCIIWriter extends PrintWriter { |
| private Writer w; |
| private StringBuffer buffer = new StringBuffer(); |
| |
| // Characters that we think are printable but that escapeUnprintable |
| // doesn't |
| private static final UnicodeSet S = |
| new UnicodeSet("[\\u0009\\u000A\\u000D]"); |
| |
| 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 (!S.contains(c) && Utility.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 = UTF16.charAt(buf, 0, buf.length, off); |
| off += UTF16.getCharCount(c); |
| if (!S.contains(c) && Utility.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); |
| } |
| } |
| |
| private static class TestParams { |
| public boolean prompt = false; |
| public boolean nothrow = false; |
| public boolean verbose = false; |
| public boolean describe = false; |
| public int inclusion = 0; |
| public String filter = null; |
| |
| public PrintWriter log = new ASCIIWriter(System.out, true); |
| public int indentLevel = 0; |
| public boolean needLineFeed = false; |
| public boolean suppressIndent = false; |
| public int errorCount = 0; |
| public int invalidCount = 0; |
| } |
| |
| private TestParams params = null; |
| private Map testMethods; |
| private Set testsToRun; |
| private final String spaces = " "; |
| } |