| // © 2016 and later: Unicode, Inc. and others. |
| // License & terms of use: http://www.unicode.org/copyright.html#License |
| /* |
| ******************************************************************************* |
| * Copyright (C) 1996-2015, International Business Machines Corporation and * |
| * others. All Rights Reserved. * |
| ******************************************************************************* |
| */ |
| package com.ibm.icu.dev.test; |
| |
| import java.security.Policy; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.List; |
| import java.util.Locale; |
| import java.util.Map; |
| import java.util.Properties; |
| import java.util.Random; |
| import java.util.TreeMap; |
| |
| import org.junit.After; |
| import org.junit.Assert; |
| import org.junit.Before; |
| |
| import com.ibm.icu.util.TimeZone; |
| import com.ibm.icu.util.ULocale; |
| |
| /** |
| * 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. |
| */ |
| abstract public class TestFmwk extends AbstractTestLog { |
| /** |
| * The default time zone for all of our tests. Used in @Before |
| */ |
| private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("America/Los_Angeles"); |
| |
| /** |
| * The default locale used for all of our tests. Used in @Before |
| */ |
| private final static Locale defaultLocale = Locale.US; |
| |
| private static final String EXHAUSTIVENESS = "ICU.exhaustive"; |
| private static final int DEFAULT_EXHAUSTIVENESS = 0; |
| private static final int MAX_EXHAUSTIVENESS = 10; |
| |
| private static final String LOGGING_LEVEL = "ICU.logging"; |
| private static final int DEFAULT_LOGGING_LEVEL = 0; |
| private static final int MAX_LOGGING_LEVEL = 3; |
| |
| public static final int LOGGING_NONE = 0; |
| public static final int LOGGING_WARN = 1; |
| public static final int LOGGING_INFO = 2; |
| public static final int LOGGING_DEBUG = 3; |
| |
| private static final String SEED = "ICU.seed"; |
| private static final String SECURITY_POLICY = "ICU.securitypolicy"; |
| |
| private static final TestParams testParams; |
| static { |
| testParams = TestParams.create(); |
| } |
| |
| protected TestFmwk() { |
| } |
| |
| @Before |
| public void testInitialize() { |
| Locale.setDefault(defaultLocale); |
| TimeZone.setDefault(defaultTimeZone); |
| |
| if (getParams().testSecurityManager != null) { |
| System.setSecurityManager(getParams().testSecurityManager); |
| } |
| } |
| |
| @After |
| public void testTeardown() { |
| if (getParams().testSecurityManager != null) { |
| System.setSecurityManager(getParams().originalSecurityManager); |
| } |
| } |
| |
| private static TestParams getParams() { |
| //return paramsReference.get(); |
| return testParams; |
| } |
| |
| protected static boolean isVerbose() { |
| return getParams().getLoggingLevel() >= LOGGING_INFO; |
| } |
| |
| /** |
| * 0 = fewest tests, 5 is normal build, 10 is most tests |
| */ |
| protected static int getExhaustiveness() { |
| return getParams().inclusion; |
| } |
| |
| protected static boolean isQuick() { |
| return getParams().getInclusion() == 0; |
| } |
| |
| // use this instead of new random so we get a consistent seed |
| // for our tests |
| protected Random createRandom() { |
| return new Random(getParams().getSeed()); |
| } |
| |
| static final String ICU_TRAC_URL = "http://bugs.icu-project.org/trac/ticket/"; |
| static final String CLDR_TRAC_URL = "http://unicode.org/cldr/trac/ticket/"; |
| static final String CLDR_TICKET_PREFIX = "cldrbug:"; |
| |
| /** |
| * Log the known issue. |
| * This method returns true unless -prop:logKnownIssue=no is specified |
| * in the argument list. |
| * |
| * @param ticket A ticket number string. For an ICU ticket, use numeric characters only, |
| * such as "10245". For a CLDR ticket, use prefix "cldrbug:" followed by ticket number, |
| * such as "cldrbug:5013". |
| * @param comment Additional comment, or null |
| * @return true unless -prop:logKnownIssue=no is specified in the test command line argument. |
| */ |
| protected static boolean logKnownIssue(String ticket, String comment) { |
| if (!getBooleanProperty("logKnownIssue", true)) { |
| return false; |
| } |
| |
| StringBuffer descBuf = new StringBuffer(); |
| // TODO(junit) : what to do about this? |
| //getParams().stack.appendPath(descBuf); |
| if (comment != null && comment.length() > 0) { |
| descBuf.append(" (" + comment + ")"); |
| } |
| String description = descBuf.toString(); |
| |
| String ticketLink = "Unknown Ticket"; |
| if (ticket != null && ticket.length() > 0) { |
| boolean isCldr = false; |
| ticket = ticket.toLowerCase(Locale.ENGLISH); |
| if (ticket.startsWith(CLDR_TICKET_PREFIX)) { |
| isCldr = true; |
| ticket = ticket.substring(CLDR_TICKET_PREFIX.length()); |
| } |
| ticketLink = (isCldr ? CLDR_TRAC_URL : ICU_TRAC_URL) + ticket; |
| } |
| |
| if (getParams().knownIssues == null) { |
| getParams().knownIssues = new TreeMap<String, List<String>>(); |
| } |
| List<String> lines = getParams().knownIssues.get(ticketLink); |
| if (lines == null) { |
| lines = new ArrayList<String>(); |
| getParams().knownIssues.put(ticketLink, lines); |
| } |
| if (!lines.contains(description)) { |
| lines.add(description); |
| } |
| |
| return true; |
| } |
| |
| protected static String getProperty(String key) { |
| return getParams().getProperty(key); |
| } |
| |
| protected static boolean getBooleanProperty(String key) { |
| return getParams().getBooleanProperty(key); |
| } |
| |
| protected static boolean getBooleanProperty(String key, boolean defVal) { |
| return getParams().getBooleanProperty(key, defVal); |
| } |
| |
| protected static int getIntProperty(String key, int defVal) { |
| return getParams().getIntProperty(key, defVal); |
| } |
| |
| protected static int getIntProperty(String key, int defVal, int maxVal) { |
| return getParams().getIntProperty(key, defVal, maxVal); |
| } |
| |
| protected static 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; |
| } |
| |
| |
| // Utility Methods |
| |
| protected 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(); |
| } |
| |
| protected 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(); |
| } |
| |
| protected 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; |
| } |
| |
| protected 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; |
| } |
| |
| protected static String hex(CharSequence s) { |
| StringBuilder result = new StringBuilder(); |
| for (int i = 0; i < s.length(); ++i) { |
| if (i != 0) |
| result.append(','); |
| result.append(hex(s.charAt(i))); |
| } |
| return result.toString(); |
| } |
| |
| protected static String prettify(CharSequence s) { |
| StringBuilder result = new StringBuilder(); |
| int ch; |
| for (int i = 0; i < s.length(); i += Character.charCount(ch)) { |
| ch = Character.codePointAt(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 < 0x20 || 0x7e < ch) { |
| result.append("\\u"); |
| result.append(hex(ch)); |
| } else { |
| result.append((char) ch); |
| } |
| |
| } |
| return result.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(); |
| } |
| |
| private static class TestParams { |
| |
| private int inclusion; |
| private long seed; |
| private int loggingLevel; |
| |
| private String policyFileName; |
| private SecurityManager testSecurityManager; |
| private SecurityManager originalSecurityManager; |
| |
| private Map<String, List<String>> knownIssues; |
| |
| private Properties props; |
| |
| |
| private TestParams() { |
| } |
| |
| static TestParams create() { |
| TestParams params = new TestParams(); |
| Properties props = System.getProperties(); |
| params.parseProperties(props); |
| return params; |
| } |
| |
| private void parseProperties(Properties props) { |
| this.props = props; |
| |
| inclusion = getIntProperty(EXHAUSTIVENESS, DEFAULT_EXHAUSTIVENESS, MAX_EXHAUSTIVENESS); |
| seed = getLongProperty(SEED, System.currentTimeMillis()); |
| loggingLevel = getIntProperty(LOGGING_LEVEL, DEFAULT_LOGGING_LEVEL, MAX_LOGGING_LEVEL); |
| |
| policyFileName = getProperty(SECURITY_POLICY); |
| if (policyFileName != null) { |
| String originalPolicyFileName = System.getProperty("java.security.policy"); |
| originalSecurityManager = System.getSecurityManager(); |
| System.setProperty("java.security.policy", policyFileName); |
| Policy.getPolicy().refresh(); |
| testSecurityManager = new SecurityManager(); |
| System.setProperty("java.security.policy", originalPolicyFileName==null ? "" : originalPolicyFileName); |
| } |
| } |
| |
| public String getProperty(String key) { |
| String val = null; |
| if (key != null && key.length() > 0) { |
| val = props.getProperty(key); |
| } |
| return val; |
| } |
| |
| public boolean getBooleanProperty(String key) { |
| return getBooleanProperty(key, false); |
| } |
| |
| public boolean getBooleanProperty(String key, boolean defVal) { |
| String s = getProperty(key); |
| if (s == null) { |
| return defVal; |
| } |
| if (s.equalsIgnoreCase("yes") || s.equalsIgnoreCase("true") || s.equals("1")) { |
| return true; |
| } |
| return false; |
| } |
| |
| public int getIntProperty(String key, int defVal) { |
| return getIntProperty(key, defVal, -1); |
| } |
| |
| public int getIntProperty(String key, int defVal, int maxVal) { |
| String s = getProperty(key); |
| if (s == null) { |
| return defVal; |
| } |
| return (maxVal == -1) ? Integer.valueOf(s) : Math.max(Integer.valueOf(s), maxVal); |
| } |
| |
| public long getLongProperty(String key, long defVal) { |
| String s = getProperty(key); |
| if (s == null) { |
| return defVal; |
| } |
| return Long.valueOf(s); |
| } |
| |
| public int getInclusion() { |
| return inclusion; |
| } |
| |
| public long getSeed() { |
| return seed; |
| } |
| |
| public int getLoggingLevel() { |
| return loggingLevel; |
| } |
| } |
| |
| /** |
| * 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 static 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 static 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 static 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 static boolean assertTrue(String message, boolean condition) { |
| return handleAssert(condition, message, "true", null); |
| } |
| |
| protected static boolean assertFalse(String message, boolean condition) { |
| return handleAssert(!condition, message, "false", null); |
| } |
| |
| protected static boolean assertEquals(String message, boolean expected, |
| boolean actual) { |
| return handleAssert(expected == actual, message, String |
| .valueOf(expected), String.valueOf(actual)); |
| } |
| |
| protected static 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 static 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 static 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 static <T> boolean assertEquals(String message, T[] expected, T[] actual) { |
| // Use toString on a List to get useful, readable messages |
| String expectedString = expected == null ? "null" : Arrays.asList(expected).toString(); |
| String actualString = actual == null ? "null" : Arrays.asList(actual).toString(); |
| return assertEquals(message, expectedString, actualString); |
| } |
| |
| protected static 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 static 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 static boolean assertNotSame(String message, Object expected, |
| Object actual) { |
| return handleAssert(expected != actual, message, stringFor(expected), |
| stringFor(actual), "!=", true); |
| } |
| |
| protected static boolean assertNull(String message, Object actual) { |
| return handleAssert(actual == null, message, null, stringFor(actual)); |
| } |
| |
| protected static boolean assertNotNull(String message, Object actual) { |
| return handleAssert(actual != null, message, null, stringFor(actual), |
| "!=", true); |
| } |
| |
| protected static void fail() { |
| fail(""); |
| } |
| |
| protected static void fail(String message) { |
| if (message == null) { |
| message = ""; |
| } |
| if (!message.equals("")) { |
| message = ": " + message; |
| } |
| errln(sourceLocation() + message); |
| } |
| |
| private static boolean handleAssert(boolean result, String message, |
| String expected, String actual) { |
| return handleAssert(result, message, expected, actual, null, false); |
| } |
| |
| public static boolean handleAssert(boolean result, String message, |
| Object expected, Object actual, String relation, boolean flip) { |
| if (!result || isVerbose()) { |
| if (message == null) { |
| message = ""; |
| } |
| if (!message.equals("")) { |
| message = ": " + 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 static final String stringFor(Object obj) { |
| if (obj == null) { |
| return "null"; |
| } |
| if (obj instanceof String) { |
| return "\"" + obj + '"'; |
| } |
| return obj.getClass().getName() + "<" + obj + ">"; |
| } |
| |
| // Return the source code location of the caller located callDepth frames up the stack. |
| protected static String sourceLocation() { |
| // Walk up the stack to the first call site outside this file |
| for (StackTraceElement st : new Throwable().getStackTrace()) { |
| String source = st.getFileName(); |
| if (source != null && !source.equals("TestFmwk.java") && !source.equals("AbstractTestLog.java")) { |
| String methodName = st.getMethodName(); |
| if (methodName != null && |
| (methodName.startsWith("Test") || methodName.startsWith("test") || methodName.equals("main"))) { |
| return "(" + source + ":" + st.getLineNumber() + ") "; |
| } |
| } |
| } |
| throw new InternalError(); |
| } |
| |
| |
| // End JUnit-like assertions |
| |
| // TODO (sgill): added to keep errors away |
| /* (non-Javadoc) |
| * @see com.ibm.icu.dev.test.TestLog#msg(java.lang.String, int, boolean, boolean) |
| */ |
| //@Override |
| protected static void msg(String message, int level, boolean incCount, boolean newln) { |
| if (level == TestLog.WARN || level == TestLog.ERR) { |
| Assert.fail(message); |
| } |
| // TODO(stuartg): turned off - causing OOM running under ant |
| // while (level > 0) { |
| // System.out.print(" "); |
| // level--; |
| // } |
| // System.out.print(message); |
| // if (newln) { |
| // System.out.println(); |
| // } |
| } |
| |
| } |