1// © 2016 and later: Unicode, Inc. and others. 2// License & terms of use: http://www.unicode.org/copyright.html#License 3/* 4 ******************************************************************************* 5 * Copyright (C) 1996-2015, International Business Machines Corporation and * 6 * others. All Rights Reserved. * 7 ******************************************************************************* 8 */ 9package com.ibm.icu.dev.test; 10 11import java.lang.reflect.Constructor; 12import java.lang.reflect.Method; 13import java.lang.reflect.Modifier; 14import java.security.Policy; 15import java.util.ArrayList; 16import java.util.Arrays; 17import java.util.List; 18import java.util.Locale; 19import java.util.Map; 20import java.util.Properties; 21import java.util.Random; 22import java.util.TreeMap; 23 24import org.junit.After; 25import org.junit.Assert; 26import org.junit.Before; 27 28import com.ibm.icu.util.TimeZone; 29import com.ibm.icu.util.ULocale; 30 31/** 32 * TestFmwk is a base class for tests that can be run conveniently from the 33 * command line as well as under the Java test harness. 34 * <p> 35 * Sub-classes implement a set of methods named Test <something>. Each of these 36 * methods performs some test. Test methods should indicate errors by calling 37 * either err or errln. This will increment the errorCount field and may 38 * optionally print a message to the log. Debugging information may also be 39 * added to the log via the log and logln methods. These methods will add their 40 * arguments to the log only if the test is being run in verbose mode. 41 */ 42abstract public class TestFmwk extends AbstractTestLog { 43 /** 44 * The default time zone for all of our tests. Used in @Before 45 */ 46 private final static TimeZone defaultTimeZone = TimeZone.getTimeZone("America/Los_Angeles"); 47 48 /** 49 * The default locale used for all of our tests. Used in @Before 50 */ 51 private final static Locale defaultLocale = Locale.US; 52 53 private static final String EXHAUSTIVENESS = "ICU.exhaustive"; 54 private static final int DEFAULT_EXHAUSTIVENESS = 0; 55 private static final int MAX_EXHAUSTIVENESS = 10; 56 57 private static final String LOGGING_LEVEL = "ICU.logging"; 58 private static final int DEFAULT_LOGGING_LEVEL = 0; 59 private static final int MAX_LOGGING_LEVEL = 3; 60 61 public static final int LOGGING_NONE = 0; 62 public static final int LOGGING_WARN = 1; 63 public static final int LOGGING_INFO = 2; 64 public static final int LOGGING_DEBUG = 3; 65 66 private static final String SEED = "ICU.seed"; 67 private static final String SECURITY_POLICY = "ICU.securitypolicy"; 68 69 private static final TestParams testParams; 70 static { 71 testParams = TestParams.create(); 72 } 73 74 protected TestFmwk() { 75 } 76 77 @Before 78 public void testInitialize() { 79 Locale.setDefault(defaultLocale); 80 TimeZone.setDefault(defaultTimeZone); 81 82 if (getParams().testSecurityManager != null) { 83 System.setSecurityManager(getParams().testSecurityManager); 84 } 85 } 86 87 @After 88 public void testTeardown() { 89 if (getParams().testSecurityManager != null) { 90 System.setSecurityManager(getParams().originalSecurityManager); 91 } 92 } 93 94 private static TestParams getParams() { 95 //return paramsReference.get(); 96 return testParams; 97 } 98 99 protected static boolean isVerbose() { 100 return getParams().getLoggingLevel() >= LOGGING_INFO; 101 } 102 103 /** 104 * 0 = fewest tests, 5 is normal build, 10 is most tests 105 */ 106 protected static int getExhaustiveness() { 107 return getParams().inclusion; 108 } 109 110 protected static boolean isQuick() { 111 return getParams().getInclusion() == 0; 112 } 113 114 // use this instead of new random so we get a consistent seed 115 // for our tests 116 protected Random createRandom() { 117 return new Random(getParams().getSeed()); 118 } 119 120 static final String ICU_TRAC_URL = "http://bugs.icu-project.org/trac/ticket/"; 121 static final String CLDR_TRAC_URL = "http://unicode.org/cldr/trac/ticket/"; 122 static final String CLDR_TICKET_PREFIX = "cldrbug:"; 123 124 /** 125 * Log the known issue. 126 * This method returns true unless -prop:logKnownIssue=no is specified 127 * in the argument list. 128 * 129 * @param ticket A ticket number string. For an ICU ticket, use numeric characters only, 130 * such as "10245". For a CLDR ticket, use prefix "cldrbug:" followed by ticket number, 131 * such as "cldrbug:5013". 132 * @param comment Additional comment, or null 133 * @return true unless -prop:logKnownIssue=no is specified in the test command line argument. 134 */ 135 protected static boolean logKnownIssue(String ticket, String comment) { 136 if (!getBooleanProperty("logKnownIssue", true)) { 137 return false; 138 } 139 140 StringBuffer descBuf = new StringBuffer(); 141 // TODO(junit) : what to do about this? 142 //getParams().stack.appendPath(descBuf); 143 if (comment != null && comment.length() > 0) { 144 descBuf.append(" (" + comment + ")"); 145 } 146 String description = descBuf.toString(); 147 148 String ticketLink = "Unknown Ticket"; 149 if (ticket != null && ticket.length() > 0) { 150 boolean isCldr = false; 151 ticket = ticket.toLowerCase(Locale.ENGLISH); 152 if (ticket.startsWith(CLDR_TICKET_PREFIX)) { 153 isCldr = true; 154 ticket = ticket.substring(CLDR_TICKET_PREFIX.length()); 155 } 156 ticketLink = (isCldr ? CLDR_TRAC_URL : ICU_TRAC_URL) + ticket; 157 } 158 159 if (getParams().knownIssues == null) { 160 getParams().knownIssues = new TreeMap<String, List<String>>(); 161 } 162 List<String> lines = getParams().knownIssues.get(ticketLink); 163 if (lines == null) { 164 lines = new ArrayList<String>(); 165 getParams().knownIssues.put(ticketLink, lines); 166 } 167 if (!lines.contains(description)) { 168 lines.add(description); 169 } 170 171 return true; 172 } 173 174 protected static String getProperty(String key) { 175 return getParams().getProperty(key); 176 } 177 178 protected static boolean getBooleanProperty(String key) { 179 return getParams().getBooleanProperty(key); 180 } 181 182 protected static boolean getBooleanProperty(String key, boolean defVal) { 183 return getParams().getBooleanProperty(key, defVal); 184 } 185 186 protected static int getIntProperty(String key, int defVal) { 187 return getParams().getIntProperty(key, defVal); 188 } 189 190 protected static int getIntProperty(String key, int defVal, int maxVal) { 191 return getParams().getIntProperty(key, defVal, maxVal); 192 } 193 194 protected static TimeZone safeGetTimeZone(String id) { 195 TimeZone tz = TimeZone.getTimeZone(id); 196 if (tz == null) { 197 // should never happen 198 errln("FAIL: TimeZone.getTimeZone(" + id + ") => null"); 199 } 200 if (!tz.getID().equals(id)) { 201 warnln("FAIL: TimeZone.getTimeZone(" + id + ") => " + tz.getID()); 202 } 203 return tz; 204 } 205 206 207 // Utility Methods 208 209 protected static String hex(char[] s){ 210 StringBuffer result = new StringBuffer(); 211 for (int i = 0; i < s.length; ++i) { 212 if (i != 0) result.append(','); 213 result.append(hex(s[i])); 214 } 215 return result.toString(); 216 } 217 218 protected static String hex(byte[] s){ 219 StringBuffer result = new StringBuffer(); 220 for (int i = 0; i < s.length; ++i) { 221 if (i != 0) result.append(','); 222 result.append(hex(s[i])); 223 } 224 return result.toString(); 225 } 226 227 protected static String hex(char ch) { 228 StringBuffer result = new StringBuffer(); 229 String foo = Integer.toString(ch, 16).toUpperCase(); 230 for (int i = foo.length(); i < 4; ++i) { 231 result.append('0'); 232 } 233 return result + foo; 234 } 235 236 protected static String hex(int ch) { 237 StringBuffer result = new StringBuffer(); 238 String foo = Integer.toString(ch, 16).toUpperCase(); 239 for (int i = foo.length(); i < 4; ++i) { 240 result.append('0'); 241 } 242 return result + foo; 243 } 244 245 protected static String hex(CharSequence s) { 246 StringBuilder result = new StringBuilder(); 247 for (int i = 0; i < s.length(); ++i) { 248 if (i != 0) 249 result.append(','); 250 result.append(hex(s.charAt(i))); 251 } 252 return result.toString(); 253 } 254 255 protected static String prettify(CharSequence s) { 256 StringBuilder result = new StringBuilder(); 257 int ch; 258 for (int i = 0; i < s.length(); i += Character.charCount(ch)) { 259 ch = Character.codePointAt(s, i); 260 if (ch > 0xfffff) { 261 result.append("\\U00"); 262 result.append(hex(ch)); 263 } else if (ch > 0xffff) { 264 result.append("\\U000"); 265 result.append(hex(ch)); 266 } else if (ch < 0x20 || 0x7e < ch) { 267 result.append("\\u"); 268 result.append(hex(ch)); 269 } else { 270 result.append((char) ch); 271 } 272 273 } 274 return result.toString(); 275 } 276 277 private static java.util.GregorianCalendar cal; 278 279 /** 280 * Return a Date given a year, month, and day of month. This is similar to 281 * new Date(y-1900, m, d). It uses the default time zone at the time this 282 * method is first called. 283 * 284 * @param year 285 * use 2000 for 2000, unlike new Date() 286 * @param month 287 * use Calendar.JANUARY etc. 288 * @param dom 289 * day of month, 1-based 290 * @return a Date object for the given y/m/d 291 */ 292 protected static synchronized java.util.Date getDate(int year, int month, 293 int dom) { 294 if (cal == null) { 295 cal = new java.util.GregorianCalendar(); 296 } 297 cal.clear(); 298 cal.set(year, month, dom); 299 return cal.getTime(); 300 } 301 302 private static class TestParams { 303 304 private int inclusion; 305 private long seed; 306 private int loggingLevel; 307 308 private String policyFileName; 309 private SecurityManager testSecurityManager; 310 private SecurityManager originalSecurityManager; 311 312 private Map<String, List<String>> knownIssues; 313 314 private Properties props; 315 316 317 private TestParams() { 318 } 319 320 static TestParams create() { 321 TestParams params = new TestParams(); 322 Properties props = System.getProperties(); 323 params.parseProperties(props); 324 return params; 325 } 326 327 private void parseProperties(Properties props) { 328 this.props = props; 329 330 inclusion = getIntProperty(EXHAUSTIVENESS, DEFAULT_EXHAUSTIVENESS, MAX_EXHAUSTIVENESS); 331 seed = getLongProperty(SEED, System.currentTimeMillis()); 332 loggingLevel = getIntProperty(LOGGING_LEVEL, DEFAULT_LOGGING_LEVEL, MAX_LOGGING_LEVEL); 333 334 policyFileName = getProperty(SECURITY_POLICY); 335 if (policyFileName != null) { 336 String originalPolicyFileName = System.getProperty("java.security.policy"); 337 originalSecurityManager = System.getSecurityManager(); 338 System.setProperty("java.security.policy", policyFileName); 339 Policy.getPolicy().refresh(); 340 testSecurityManager = new SecurityManager(); 341 System.setProperty("java.security.policy", originalPolicyFileName==null ? "" : originalPolicyFileName); 342 } 343 } 344 345 public String getProperty(String key) { 346 String val = null; 347 if (key != null && key.length() > 0) { 348 val = props.getProperty(key); 349 } 350 return val; 351 } 352 353 public boolean getBooleanProperty(String key) { 354 return getBooleanProperty(key, false); 355 } 356 357 public boolean getBooleanProperty(String key, boolean defVal) { 358 String s = getProperty(key); 359 if (s == null) { 360 return defVal; 361 } 362 if (s.equalsIgnoreCase("yes") || s.equalsIgnoreCase("true") || s.equals("1")) { 363 return true; 364 } 365 return false; 366 } 367 368 public int getIntProperty(String key, int defVal) { 369 return getIntProperty(key, defVal, -1); 370 } 371 372 public int getIntProperty(String key, int defVal, int maxVal) { 373 String s = getProperty(key); 374 if (s == null) { 375 return defVal; 376 } 377 return (maxVal == -1) ? Integer.valueOf(s) : Math.max(Integer.valueOf(s), maxVal); 378 } 379 380 public long getLongProperty(String key, long defVal) { 381 String s = getProperty(key); 382 if (s == null) { 383 return defVal; 384 } 385 return Long.valueOf(s); 386 } 387 388 public int getInclusion() { 389 return inclusion; 390 } 391 392 public long getSeed() { 393 return seed; 394 } 395 396 public int getLoggingLevel() { 397 return loggingLevel; 398 } 399 } 400 401 /** 402 * Check the given array to see that all the strings in the expected array 403 * are present. 404 * 405 * @param msg 406 * string message, for log output 407 * @param array 408 * array of strings to check 409 * @param expected 410 * array of strings we expect to see, or null 411 * @return the length of 'array', or -1 on error 412 */ 413 protected static int checkArray(String msg, String array[], String expected[]) { 414 int explen = (expected != null) ? expected.length : 0; 415 if (!(explen >= 0 && explen < 31)) { // [sic] 31 not 32 416 errln("Internal error"); 417 return -1; 418 } 419 int i = 0; 420 StringBuffer buf = new StringBuffer(); 421 int seenMask = 0; 422 for (; i < array.length; ++i) { 423 String s = array[i]; 424 if (i != 0) 425 buf.append(", "); 426 buf.append(s); 427 // check expected list 428 for (int j = 0, bit = 1; j < explen; ++j, bit <<= 1) { 429 if ((seenMask & bit) == 0) { 430 if (s.equals(expected[j])) { 431 seenMask |= bit; 432 logln("Ok: \"" + s + "\" seen"); 433 } 434 } 435 } 436 } 437 logln(msg + " = [" + buf + "] (" + i + ")"); 438 // did we see all expected strings? 439 if (((1 << explen) - 1) != seenMask) { 440 for (int j = 0, bit = 1; j < expected.length; ++j, bit <<= 1) { 441 if ((seenMask & bit) == 0) { 442 errln("\"" + expected[j] + "\" not seen"); 443 } 444 } 445 } 446 return array.length; 447 } 448 449 /** 450 * Check the given array to see that all the locales in the expected array 451 * are present. 452 * 453 * @param msg 454 * string message, for log output 455 * @param array 456 * array of locales to check 457 * @param expected 458 * array of locales names we expect to see, or null 459 * @return the length of 'array' 460 */ 461 protected static int checkArray(String msg, Locale array[], String expected[]) { 462 String strs[] = new String[array.length]; 463 for (int i = 0; i < array.length; ++i) { 464 strs[i] = array[i].toString(); 465 } 466 return checkArray(msg, strs, expected); 467 } 468 469 /** 470 * Check the given array to see that all the locales in the expected array 471 * are present. 472 * 473 * @param msg 474 * string message, for log output 475 * @param array 476 * array of locales to check 477 * @param expected 478 * array of locales names we expect to see, or null 479 * @return the length of 'array' 480 */ 481 protected static int checkArray(String msg, ULocale array[], String expected[]) { 482 String strs[] = new String[array.length]; 483 for (int i = 0; i < array.length; ++i) { 484 strs[i] = array[i].toString(); 485 } 486 return checkArray(msg, strs, expected); 487 } 488 489 // JUnit-like assertions. 490 491 protected static boolean assertTrue(String message, boolean condition) { 492 return handleAssert(condition, message, "true", null); 493 } 494 495 protected static boolean assertFalse(String message, boolean condition) { 496 return handleAssert(!condition, message, "false", null); 497 } 498 499 protected static boolean assertEquals(String message, boolean expected, 500 boolean actual) { 501 return handleAssert(expected == actual, message, String 502 .valueOf(expected), String.valueOf(actual)); 503 } 504 505 protected static boolean assertEquals(String message, long expected, long actual) { 506 return handleAssert(expected == actual, message, String 507 .valueOf(expected), String.valueOf(actual)); 508 } 509 510 // do NaN and range calculations to precision of float, don't rely on 511 // promotion to double 512 protected static boolean assertEquals(String message, float expected, 513 float actual, double error) { 514 boolean result = Float.isInfinite(expected) 515 ? expected == actual 516 : !(Math.abs(expected - actual) > error); // handles NaN 517 return handleAssert(result, message, String.valueOf(expected) 518 + (error == 0 ? "" : " (within " + error + ")"), String 519 .valueOf(actual)); 520 } 521 522 protected static boolean assertEquals(String message, double expected, 523 double actual, double error) { 524 boolean result = Double.isInfinite(expected) 525 ? expected == actual 526 : !(Math.abs(expected - actual) > error); // handles NaN 527 return handleAssert(result, message, String.valueOf(expected) 528 + (error == 0 ? "" : " (within " + error + ")"), String 529 .valueOf(actual)); 530 } 531 532 protected static <T> boolean assertEquals(String message, T[] expected, T[] actual) { 533 // Use toString on a List to get useful, readable messages 534 String expectedString = expected == null ? "null" : Arrays.asList(expected).toString(); 535 String actualString = actual == null ? "null" : Arrays.asList(actual).toString(); 536 return assertEquals(message, expectedString, actualString); 537 } 538 539 protected static boolean assertEquals(String message, Object expected, 540 Object actual) { 541 boolean result = expected == null ? actual == null : expected 542 .equals(actual); 543 return handleAssert(result, message, stringFor(expected), 544 stringFor(actual)); 545 } 546 547 protected static boolean assertNotEquals(String message, Object expected, 548 Object actual) { 549 boolean result = !(expected == null ? actual == null : expected 550 .equals(actual)); 551 return handleAssert(result, message, stringFor(expected), 552 stringFor(actual), "not equal to", true); 553 } 554 555 protected boolean assertSame(String message, Object expected, Object actual) { 556 return handleAssert(expected == actual, message, stringFor(expected), 557 stringFor(actual), "==", false); 558 } 559 560 protected static boolean assertNotSame(String message, Object expected, 561 Object actual) { 562 return handleAssert(expected != actual, message, stringFor(expected), 563 stringFor(actual), "!=", true); 564 } 565 566 protected static boolean assertNull(String message, Object actual) { 567 return handleAssert(actual == null, message, null, stringFor(actual)); 568 } 569 570 protected static boolean assertNotNull(String message, Object actual) { 571 return handleAssert(actual != null, message, null, stringFor(actual), 572 "!=", true); 573 } 574 575 protected static void fail() { 576 fail(""); 577 } 578 579 protected static void fail(String message) { 580 if (message == null) { 581 message = ""; 582 } 583 if (!message.equals("")) { 584 message = ": " + message; 585 } 586 errln(sourceLocation() + message); 587 } 588 589 private static boolean handleAssert(boolean result, String message, 590 String expected, String actual) { 591 return handleAssert(result, message, expected, actual, null, false); 592 } 593 594 public static boolean handleAssert(boolean result, String message, 595 Object expected, Object actual, String relation, boolean flip) { 596 if (!result || isVerbose()) { 597 if (message == null) { 598 message = ""; 599 } 600 if (!message.equals("")) { 601 message = ": " + message; 602 } 603 relation = relation == null ? ", got " : " " + relation + " "; 604 if (result) { 605 logln("OK " + message + ": " 606 + (flip ? expected + relation + actual : expected)); 607 } else { 608 // assert must assume errors are true errors and not just warnings 609 // so cannot warnln here 610 errln( message 611 + ": expected" 612 + (flip ? relation + expected : " " + expected 613 + (actual != null ? relation + actual : ""))); 614 } 615 } 616 return result; 617 } 618 619 private static final String stringFor(Object obj) { 620 if (obj == null) { 621 return "null"; 622 } 623 if (obj instanceof String) { 624 return "\"" + obj + '"'; 625 } 626 return obj.getClass().getName() + "<" + obj + ">"; 627 } 628 629 // Return the source code location of the caller located callDepth frames up the stack. 630 protected static String sourceLocation() { 631 // Walk up the stack to the first call site outside this file 632 for (StackTraceElement st : new Throwable().getStackTrace()) { 633 String source = st.getFileName(); 634 if (source != null && !source.equals("TestFmwk.java") && !source.equals("AbstractTestLog.java")) { 635 String methodName = st.getMethodName(); 636 if (methodName != null && 637 (methodName.startsWith("Test") || methodName.startsWith("test") || methodName.equals("main"))) { 638 return "(" + source + ":" + st.getLineNumber() + ") "; 639 } 640 } 641 } 642 throw new InternalError(); 643 } 644 645 protected static boolean checkDefaultPrivateConstructor(String fullyQualifiedClassName) throws Exception { 646 return checkDefaultPrivateConstructor(Class.forName(fullyQualifiedClassName)); 647 } 648 649 protected static boolean checkDefaultPrivateConstructor(Class<?> classToBeTested) throws Exception { 650 Constructor<?> constructor = classToBeTested.getDeclaredConstructor(); 651 652 // Check that the constructor is private. 653 boolean isPrivate = Modifier.isPrivate(constructor.getModifiers()); 654 655 // Call the constructor for coverage. 656 constructor.setAccessible(true); 657 constructor.newInstance(); 658 659 if (!isPrivate) { 660 errln("Default private constructor for class: " + classToBeTested.getName() + " is not private."); 661 } 662 return isPrivate; 663 } 664 665 /** 666 * Tests the toString method on a private or hard-to-reach class. Assumes constructor of the class does not 667 * take any arguments. 668 * @param fullyQualifiedClassName 669 * @return The output of the toString method. 670 * @throws Exception 671 */ 672 protected static String invokeToString(String fullyQualifiedClassName) throws Exception { 673 return invokeToString(fullyQualifiedClassName, new Class<?>[]{}, new Object[]{}); 674 } 675 676 /** 677 * Tests the toString method on a private or hard-to-reach class. Assumes constructor of the class does not 678 * take any arguments. 679 * @param classToBeTested 680 * @return The output of the toString method. 681 * @throws Exception 682 */ 683 protected static String invokeToString(Class<?> classToBeTested) throws Exception { 684 return invokeToString(classToBeTested, new Class<?>[]{}, new Object[]{}); 685 } 686 687 /** 688 * Tests the toString method on a private or hard-to-reach class. Allows you to specify the argument types for 689 * the constructor. 690 * @param fullyQualifiedClassName 691 * @return The output of the toString method. 692 * @throws Exception 693 */ 694 protected static String invokeToString(String fullyQualifiedClassName, 695 Class<?>[] constructorParamTypes, Object[] constructorParams) throws Exception { 696 return invokeToString(Class.forName(fullyQualifiedClassName), constructorParamTypes, constructorParams); 697 } 698 699 /** 700 * Tests the toString method on a private or hard-to-reach class. Allows you to specify the argument types for 701 * the constructor. 702 * @param classToBeTested 703 * @return The output of the toString method. 704 * @throws Exception 705 */ 706 protected static String invokeToString(Class<?> classToBeTested, 707 Class<?>[] constructorParamTypes, Object[] constructorParams) throws Exception { 708 Constructor<?> constructor = classToBeTested.getDeclaredConstructor(constructorParamTypes); 709 constructor.setAccessible(true); 710 Object obj = constructor.newInstance(constructorParams); 711 Method toStringMethod = classToBeTested.getDeclaredMethod("toString"); 712 toStringMethod.setAccessible(true); 713 return (String) toStringMethod.invoke(obj); 714 } 715 716 717 // End JUnit-like assertions 718 719 // TODO (sgill): added to keep errors away 720 /* (non-Javadoc) 721 * @see com.ibm.icu.dev.test.TestLog#msg(java.lang.String, int, boolean, boolean) 722 */ 723 //@Override 724 protected static void msg(String message, int level, boolean incCount, boolean newln) { 725 if (level == TestLog.WARN || level == TestLog.ERR) { 726 Assert.fail(message); 727 } 728 // TODO(stuartg): turned off - causing OOM running under ant 729// while (level > 0) { 730// System.out.print(" "); 731// level--; 732// } 733// System.out.print(message); 734// if (newln) { 735// System.out.println(); 736// } 737 } 738 739} 740