// © 2016 and later: Unicode, Inc. and others. // License & terms of use: http://www.unicode.org/copyright.html#License /* ******************************************************************************* * Copyright (C) 2006-2011, International Business Machines Corporation and * * others. All Rights Reserved. * ******************************************************************************* */ package com.ibm.icu.tests; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; import java.io.Serializable; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.Locale; import com.ibm.icu.util.TimeZone; import com.ibm.icu.util.ULocale; import junit.framework.TestCase; /** * Implement boilerplate tests. * Currently there is only one method, testEHCS, which tests equals, hashCode, * clone, and serialization. */ public abstract class ICUTestCase extends TestCase { private static final Object[] EMPTY_ARGS = {}; private static final Class[] EMPTY_CLASSES = {}; private static final Locale oldLocale = Locale.getDefault(); private static final ULocale oldULocale = ULocale.getDefault(); private static final java.util.TimeZone oldJTimeZone = java.util.TimeZone.getDefault(); private static final TimeZone oldITimeZone = TimeZone.getDefault(); // TODO: what's the best way to check this? public static final boolean testingWrapper = true; protected void setUp() throws Exception { super.setUp(); Locale.setDefault(Locale.US); ULocale.setDefault(ULocale.US); java.util.TimeZone.setDefault(java.util.TimeZone.getTimeZone("PST")); TimeZone.setDefault(TimeZone.getTimeZone("PST")); } protected void tearDown() throws Exception { ULocale.setDefault(oldULocale); Locale.setDefault(oldLocale); TimeZone.setDefault(oldITimeZone); java.util.TimeZone.setDefault(oldJTimeZone); super.tearDown(); } private static final Object test = new Object(); /** * Assert that two objects are _not_ equal. Curiously missing from Assert. * @param lhs an object to test, may be null * @param rhs an object to test, may be null */ public static void assertNotEqual(Object lhs, Object rhs) { if (lhs == null) { if (rhs == null) fail("null equals null"); } else { if (lhs.equals(rhs)) { fail(lhs.toString() + " equals " + rhs); } } } public static void assertNotEqual(long lhs, long rhs) { if (lhs == rhs) { fail("values are equal: " + lhs); } } /** * Test whether equality, hashCode, clone, and serialization work as expected. * Equals(Object) is assumed to return false (not throw an exception) if passed * null or an object of an incompatible class. * Hashcodes must be equal iff the two objects compare equal. No attempt is made to * evaluate the quality of the hashcode distribution, so (in particular) degenerate * hashcode implementations will pass this test. * Clone will be tested if the method "clone" is public on the class of obj. * It is assumed to return an object that compares equal to obj. * Serialization will be tested if object implements Serializable or Externalizable. * It is assumed the serialized/deserialized object compares equal to obj. * @param obj the object to test * @param eq an object that should compare equal to, but is not the same as, obj. * it should be assignable to the class of obj. * @param neq a non-null object that should not compare equal to obj. * it should be assignable to the class of obj. */ public static void testEHCS(Object obj, Object eq, Object neq) { if (obj == null || eq == null || neq == null) { throw new NullPointerException(); } Class cls = obj.getClass(); if (!(cls.isAssignableFrom(eq.getClass()) && cls.isAssignableFrom(neq.getClass()))) { throw new IllegalArgumentException("unassignable classes"); } // reflexive assertEquals(obj, obj); // should return false, not throw exception assertNotEqual(obj, test); assertNotEqual(obj, null); // commutative assertEquals(obj, eq); assertEquals(eq, obj); assertNotEqual(obj, neq); assertNotEqual(neq, obj); // equal objects MUST have equal hashes, unequal objects MAY have equal hashes assertEquals(obj.hashCode(), eq.hashCode()); Object clone = null; try { // look for public clone method and call it if available Method method_clone = cls.getMethod("clone", EMPTY_CLASSES); clone = method_clone.invoke(obj, EMPTY_ARGS); assertNotNull(clone); } catch(NoSuchMethodException e) { // ok } catch(InvocationTargetException e) { // ok } catch(IllegalAccessException e) { // ok } if (clone != null) { assertEquals(obj, clone); assertEquals(clone, obj); } if (obj instanceof Serializable || obj instanceof Externalizable) { Object ser = null; try { ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); oos.writeObject(clone); oos.close(); ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); ObjectInputStream ois = new ObjectInputStream(bis); ser = ois.readObject(); ois.close(); } catch(IOException e) { System.err.println(e.getMessage()); throw new RuntimeException(e); } catch(ClassNotFoundException e) { System.err.println(e.getMessage()); throw new RuntimeException(e); } if (ser != null) { assertEquals(obj, ser); assertEquals(ser, obj); assertEquals(obj.hashCode(), ser.hashCode()); } } } /** * Fail if the arrays are not equal. To be equal, the arrays must * be the same length, and each element in the left array must compare * equal to the corresponding element of the right array. * Also fails if one of the objects is not an array. * @param lhs the left array * @param rhs the right array */ public static void assertArraysEqual(Object lhs, Object rhs) { Class lcls = lhs.getClass(); Class rcls = rhs.getClass(); if (!(lcls.isArray() && rcls.isArray())) { fail("objects are not arrays"); } String result = arraysAreEqual(lhs, rhs); if (result != null) { fail(result); } } /** * Fail if the arrays are equal. Also fails if one or the other * argument is not an array. * @param lhs the left array * @param rhs the right array */ public static void assertArraysNotEqual(Object lhs, Object rhs) { Class lcls = lhs.getClass(); Class rcls = rhs.getClass(); if (!(lcls.isArray() && rcls.isArray())) { fail("objects are not arrays"); } String result = arraysAreEqual(lhs, rhs); if (result == null) { fail("arrays are equal"); } } // slow but general private static String arraysAreEqual(Object lhsa, Object rhsa) { int lhsl = Array.getLength(lhsa); int rhsl = Array.getLength(rhsa); if (lhsl != rhsl) { return "length " + lhsl + " != " + rhsl; } boolean lhsaA = lhsa.getClass().getComponentType().isArray(); boolean rhsaA = rhsa.getClass().getComponentType().isArray(); if (lhsaA != rhsaA) { return (lhsaA ? "" : "non-") + "array != " + (rhsaA ? "" : "non-") + "array"; } for (int i = 0; i < lhsl; ++i) { Object lhse = Array.get(lhsa, i); Object rhse = Array.get(rhsa, i); if (lhse == null) { if (rhse != null) { return "null != " + rhse; } } else { if (lhsaA) { String result = arraysAreEqual(lhse, rhse); if (result != null) { if (result.charAt(0) != '[') { result = " " + result; } return "[" + i + "]" + result; } } else { if (!lhse.equals(rhse)) { return lhse.toString() + " != " + rhse; } } } } return null; } // much more painful and slow than it should be... partly because of the // oddness of clone, partly because arrays don't provide a Method for // 'clone' despite the fact that they implement it and make it public. public static Object cloneComplex(Object obj) { Object result = null; if (obj != null) { Class cls = obj.getClass(); if (cls.isArray()) { int len = Array.getLength(obj); Class typ = cls.getComponentType(); result = Array.newInstance(typ, len); boolean prim = typ.isPrimitive(); for (int i = 0; i < len; ++i) { Object elem = Array.get(obj, i); Array.set(result, i, prim ? elem : cloneComplex(elem)); } } else { result = obj; // default try { Method cloneM = cls.getMethod("clone", EMPTY_CLASSES); result = cloneM.invoke(obj, EMPTY_ARGS); } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } } } return result; } }