0001: /*
0002: * Licensed to the Apache Software Foundation (ASF) under one
0003: * or more contributor license agreements. See the NOTICE file
0004: * distributed with this work for additional information
0005: * regarding copyright ownership. The ASF licenses this file
0006: * to you under the Apache License, Version 2.0 (the
0007: * "License"); you may not use this file except in compliance
0008: * with the License. You may obtain a copy of the License at
0009: *
0010: * http://www.apache.org/licenses/LICENSE-2.0
0011: *
0012: * Unless required by applicable law or agreed to in writing,
0013: * software distributed under the License is distributed on an
0014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0015: * KIND, either express or implied. See the License for the
0016: * specific language governing permissions and limitations
0017: * under the License.
0018: */
0019: /*
0020: * Licensed to the Apache Software Foundation (ASF) under one
0021: * or more contributor license agreements. See the NOTICE file
0022: * distributed with this work for additional information
0023: * regarding copyright ownership. The ASF licenses this file
0024: * to you under the Apache License, Version 2.0 (the
0025: * "License"); you may not use this file except in compliance
0026: * with the License. You may obtain a copy of the License at
0027: *
0028: * http://www.apache.org/licenses/LICENSE-2.0
0029: *
0030: * Unless required by applicable law or agreed to in writing,
0031: * software distributed under the License is distributed on an
0032: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
0033: * KIND, either express or implied. See the License for the
0034: * specific language governing permissions and limitations
0035: * under the License.
0036: */
0037: package org.apache.openjpa.persistence.common.utils;
0038:
0039: import java.util.Map;
0040: import java.util.Date;
0041: import java.util.Collection;
0042: import java.util.EnumSet;
0043: import java.util.ArrayList;
0044: import java.util.HashMap;
0045: import java.util.List;
0046: import java.util.Iterator;
0047: import java.util.Collections;
0048: import java.util.LinkedList;
0049: import java.util.StringTokenizer;
0050: import java.util.ListIterator;
0051: import java.util.NoSuchElementException;
0052: import java.util.Arrays;
0053: import java.util.Comparator;
0054: import java.math.BigInteger;
0055: import java.math.BigDecimal;
0056: import java.lang.reflect.InvocationTargetException;
0057: import java.lang.reflect.Method;
0058: import java.beans.BeanInfo;
0059: import java.beans.Introspector;
0060: import java.beans.PropertyDescriptor;
0061: import java.io.IOException;
0062: import java.io.ByteArrayOutputStream;
0063: import java.io.ObjectOutputStream;
0064: import java.io.ByteArrayInputStream;
0065: import java.io.ObjectInputStream;
0066: import java.io.StringWriter;
0067: import java.io.PrintWriter;
0068: import java.io.PrintStream;
0069: import java.net.URL;
0070: import javax.persistence.EntityManager;
0071: import javax.persistence.Query;
0072: import javax.persistence.EntityManagerFactory;
0073: import javax.management.IntrospectionException;
0074:
0075: import org.apache.regexp.RESyntaxException;
0076: import org.apache.regexp.RE;
0077: import org.apache.regexp.REUtil;
0078: import org.apache.openjpa.persistence.test.PersistenceTestCase;
0079: import org.apache.openjpa.persistence.OpenJPAEntityManagerFactory;
0080: import org.apache.openjpa.persistence.OpenJPAEntityManager;
0081: import org.apache.openjpa.persistence.JPAFacadeHelper;
0082: import org.apache.openjpa.persistence.OpenJPAPersistence;
0083: import org.apache.openjpa.conf.OpenJPAConfiguration;
0084: import org.apache.openjpa.kernel.OpenJPAStateManager;
0085: import org.apache.openjpa.kernel.BrokerFactory;
0086: import org.apache.openjpa.lib.log.Log;
0087: import org.apache.openjpa.meta.ClassMetaData;
0088:
0089: public abstract class AbstractTestCase extends PersistenceTestCase {
0090:
0091: private String persistenceXmlResource;
0092: private Map<Map, OpenJPAEntityManagerFactory> emfs = new HashMap<Map, OpenJPAEntityManagerFactory>();
0093: private OpenJPAEntityManager currentEntityManager;
0094:
0095: protected enum Platform {
0096: EMPRESS, HYPERSONIC, POSTGRESQL, MYSQL, SQLSERVER, DB2, ORACLE, DERBY, INFORMIX, POINTBASE, SYBASE,
0097: }
0098:
0099: protected String multiThreadExecuting = null;
0100: protected boolean inTimeoutThread = false;
0101:
0102: public AbstractTestCase(String name, String s) {
0103: setName(name);
0104: persistenceXmlResource = computePersistenceXmlResource(s);
0105: }
0106:
0107: public void tearDown() throws Exception {
0108: try {
0109: super .tearDown();
0110: } finally {
0111: for (EntityManagerFactory emf : emfs.values()) {
0112: try {
0113: closeEMF(emf);
0114: } catch (Exception e) {
0115: e.printStackTrace();
0116: }
0117: }
0118: }
0119: }
0120:
0121: public AbstractTestCase() {
0122: }
0123:
0124: public AbstractTestCase(String name) {
0125: setName(name);
0126: }
0127:
0128: protected String computePersistenceXmlResource(String s) {
0129: String resourceName = getClass().getPackage().getName()
0130: .replaceAll("\\.", "/");
0131: resourceName += "/common/apps/META-INF/persistence.xml";
0132: URL resource = getClass().getClassLoader().getResource(
0133: resourceName);
0134: if (resource != null)
0135: return resourceName;
0136: return defaultPersistenceXmlResource();
0137: }
0138:
0139: protected String defaultPersistenceXmlResource() {
0140: return "org/apache/openjpa/persistence/"
0141: + "common/apps/META-INF/persistence.xml";
0142: }
0143:
0144: protected OpenJPAStateManager getStateManager(Object obj,
0145: EntityManager em) {
0146: return JPAFacadeHelper.toBroker(em).getStateManager(obj);
0147: }
0148:
0149: protected int deleteAll(Class type, EntityManager em) {
0150: ClassMetaData meta = JPAFacadeHelper.getMetaData(em, type);
0151: if (meta != null)
0152: return em.createQuery("delete from " + meta.getTypeAlias())
0153: .executeUpdate();
0154: else
0155: return -1;
0156: }
0157:
0158: protected int deleteAll(Class... types) {
0159: EntityManager em = getEmf().createEntityManager();
0160: em.getTransaction().begin();
0161: int ret = 0;
0162: for (Class type : types)
0163: ret += deleteAll(type, em);
0164: em.getTransaction().commit();
0165: em.close();
0166: return ret;
0167: }
0168:
0169: protected OpenJPAEntityManagerFactory getEmf(Map map) {
0170: if (map == null)
0171: map = new HashMap();
0172: Collection keys = new ArrayList();
0173: for (Object key : map.keySet())
0174: if (key.toString().startsWith("kodo"))
0175: keys.add(key);
0176: if (keys.size() > 0)
0177: throw new IllegalArgumentException(
0178: "kodo-prefixed properties must be converted to openjpa. "
0179: + "Properties: " + keys);
0180:
0181: addProperties(map);
0182:
0183: OpenJPAEntityManagerFactory emf = emfs.get(map);
0184: if (emf == null) {
0185: emf = OpenJPAPersistence.createEntityManagerFactory(
0186: "TestConv", persistenceXmlResource, map);
0187: emfs.put(map, emf);
0188: }
0189: return emf;
0190: }
0191:
0192: protected void addProperties(Map map) {
0193: if (!map.containsKey("openjpa.jdbc.SynchronizeMappings"))
0194: map
0195: .put(
0196: "openjpa.jdbc.SynchronizeMappings",
0197: "buildSchema(ForeignKeys=true,"
0198: + "SchemaAction='add,deleteTableContents')");
0199: map.put("openjpa.Log", "SQL=TRACE");
0200: }
0201:
0202: protected OpenJPAEntityManagerFactory getEmf() {
0203: Map m = new HashMap();
0204: return getEmf(m);
0205: }
0206:
0207: protected BrokerFactory getBrokerFactory() {
0208: return JPAFacadeHelper.toBrokerFactory(getEmf());
0209: }
0210:
0211: protected BrokerFactory getBrokerFactory(String[] args) {
0212: if (args.length % 2 != 0)
0213: throw new IllegalArgumentException(
0214: "odd number of elements in arg array");
0215: Map map = new HashMap();
0216: for (int i = 0; i < args.length; i = i + 2)
0217: map.put(args[i], args[i + 1]);
0218: return JPAFacadeHelper.toBrokerFactory(getEmf(map));
0219: }
0220:
0221: protected OpenJPAEntityManager currentEntityManager() {
0222: if (currentEntityManager == null
0223: || !currentEntityManager.isOpen())
0224: currentEntityManager = getEmf().createEntityManager();
0225: return currentEntityManager;
0226: }
0227:
0228: protected void startTx(EntityManager em) {
0229: em.getTransaction().begin();
0230: }
0231:
0232: protected boolean isActiveTx(EntityManager em) {
0233: return em.getTransaction().isActive();
0234: }
0235:
0236: protected void endTx(EntityManager em) {
0237: if (em.getTransaction().isActive()) {
0238: if (em.getTransaction().getRollbackOnly())
0239: em.getTransaction().rollback();
0240: else
0241: em.getTransaction().commit();
0242: }
0243: }
0244:
0245: protected void rollbackTx(EntityManager em) {
0246: em.getTransaction().rollback();
0247: }
0248:
0249: protected void endEm(EntityManager em) {
0250: if (em.isOpen())
0251: em.close();
0252: if (em == currentEntityManager)
0253: currentEntityManager = null;
0254: }
0255:
0256: protected Object getStackTrace(Throwable t) {
0257: throw new UnsupportedOperationException();
0258: }
0259:
0260: protected OpenJPAConfiguration getConfiguration() {
0261: return getEmf().getConfiguration();
0262: }
0263:
0264: protected Platform getCurrentPlatform() {
0265: throw new UnsupportedOperationException();
0266: }
0267:
0268: protected void bug(int id, String s) {
0269: bug(id, null, s);
0270: }
0271:
0272: protected void bug(Platform platform, int id, Throwable t, String s) {
0273: bug(EnumSet.of(platform), id, t, s);
0274: }
0275:
0276: protected void bug(EnumSet<Platform> platforms, int id,
0277: Throwable t, String s) {
0278: if (platforms.contains(getCurrentPlatform()))
0279: bug(id, t, s);
0280: else
0281: fail(String.format(
0282: "bug %s is unexpectedly occurring on platform %s",
0283: id, getCurrentPlatform()));
0284: }
0285:
0286: protected void bug(int id, Throwable t, String s) {
0287: if (t != null) {
0288: if (t instanceof RuntimeException)
0289: throw (RuntimeException) t;
0290: else
0291: throw new RuntimeException(t);
0292: } else {
0293: fail(s);
0294: }
0295: }
0296:
0297: /**
0298: * Support method to get a random Integer for testing.
0299: */
0300: public static Integer randomInt() {
0301: return new Integer((int) (Math.random() * Integer.MAX_VALUE));
0302: }
0303:
0304: /**
0305: * Support method to get a random Character for testing.
0306: */
0307: public static Character randomChar() {
0308: char[] TEST_CHAR_ARRAY = new char[] { 'a', 'b', 'c', 'd', 'e',
0309: 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p',
0310: 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', '1',
0311: '2', '3', '4', '5', '6', '7', '8', '9' };
0312:
0313: return new Character(
0314: TEST_CHAR_ARRAY[(int) (Math.random() * TEST_CHAR_ARRAY.length)]);
0315: }
0316:
0317: /**
0318: * Support method to get a random Long for testing.
0319: */
0320: public static Long randomLong() {
0321: return new Long((long) (Math.random() * Long.MAX_VALUE));
0322: }
0323:
0324: /**
0325: * Support method to get a random Short for testing.
0326: */
0327: public static Short randomShort() {
0328: return new Short((short) (Math.random() * Short.MAX_VALUE));
0329: }
0330:
0331: /**
0332: * Support method to get a random Double for testing.
0333: */
0334: public static Double randomDouble() {
0335: return new Double(
0336: (double) (Math.round(Math.random() * 5000d)) / 1000d);
0337: }
0338:
0339: /**
0340: * Support method to get a random Float for testing.
0341: */
0342: public static Float randomFloat() {
0343: return new Float(
0344: (float) (Math.round(Math.random() * 5000f)) / 1000f);
0345: }
0346:
0347: /**
0348: * Support method to get a random Byte for testing.
0349: */
0350: public static Byte randomByte() {
0351: return new Byte((byte) (Math.random() * Byte.MAX_VALUE));
0352: }
0353:
0354: /**
0355: * Support method to get a random Boolean for testing.
0356: */
0357: public static Boolean randomBoolean() {
0358: return new Boolean(Math.random() > 0.5 ? true : false);
0359: }
0360:
0361: /**
0362: * Support method to get a random Date for testing.
0363: */
0364: public static Date randomDate() {
0365: long millis = (long) (Math.random() * System
0366: .currentTimeMillis());
0367:
0368: // round millis to the nearest 1000: this is because some
0369: // databases do not store the milliseconds correctly (e.g., MySQL).
0370: // This is a really a bug we should fix. FC #27.
0371: millis -= (millis % 1000);
0372:
0373: return new Date(millis);
0374: }
0375:
0376: /**
0377: * Support method to get a random String for testing.
0378: */
0379: public static String randomString() {
0380: // default to a small string, in case column sizes are
0381: // limited (such as with a string primary key)
0382: return randomString(50);
0383: }
0384:
0385: /**
0386: * Support method to get a random String for testing.
0387: */
0388: public static String randomString(int len) {
0389: StringBuffer buf = new StringBuffer();
0390: for (int i = 0; i < (int) (Math.random() * len) + 1; i++)
0391: buf.append(randomChar());
0392: return buf.toString();
0393: }
0394:
0395: /**
0396: * Support method to get a random clob for testing.
0397: */
0398: public static String randomClob() {
0399: StringBuffer sbuf = new StringBuffer();
0400: while (sbuf.length() < (5 * 1024)) // at least 5K
0401: {
0402: sbuf.append(randomString(1024));
0403: }
0404:
0405: return sbuf.toString();
0406: }
0407:
0408: /**
0409: * Support method to get a random BigInteger for testing.
0410: */
0411: public static BigInteger randomBigInteger() {
0412: // too many of our test databases don't support bigints > MAX_LONG:
0413: // I don't like it, but for now, let's only test below MAX_LONG
0414: BigInteger lng = new BigInteger(
0415: ((long) (Math.random() * Long.MAX_VALUE)) + "");
0416:
0417: BigInteger multiplier = new BigInteger("1");
0418: // (1 + (int)(Math.random () * 10000)) + "");
0419: if (Math.random() < 0.5)
0420: multiplier = multiplier.multiply(new BigInteger("-1"));
0421:
0422: return lng.multiply(multiplier);
0423: }
0424:
0425: /**
0426: * Support method to get a random BigDecimal for testing.
0427: */
0428: public static BigDecimal randomBigDecimal() {
0429: BigInteger start = randomBigInteger();
0430: String str = start.toString();
0431: // truncate off the last 8 digits: we still get some
0432: // overflows with lame databases.
0433: for (int i = 0; i < 8; i++)
0434: if (str.length() > 2)
0435: str = str.substring(0, str.length() - 1);
0436: start = new BigInteger(str);
0437:
0438: String val = start + "." + ((int) (Math.random() * 10))
0439: + ((int) (Math.random() * 10))
0440: + ((int) (Math.random() * 10))
0441: + ((int) (Math.random() * 10))
0442: + ((int) (Math.random() * 10))
0443: + ((int) (Math.random() * 10))
0444: + ((int) (Math.random() * 10))
0445: + ((int) (Math.random() * 10))
0446: + ((int) (Math.random() * 10))
0447: + ((int) (Math.random() * 10));
0448:
0449: return new BigDecimal(val);
0450: }
0451:
0452: /**
0453: * Support method to get a random blob for testing.
0454: */
0455: public static byte[] randomBlob() {
0456: // up to 100K blob
0457: byte[] blob = new byte[(int) (Math.random() * 1024 * 100)];
0458: for (int i = 0; i < blob.length; i++)
0459: blob[i] = randomByte().byteValue();
0460:
0461: return blob;
0462: }
0463:
0464: /**
0465: * Invoke setters for pimitives and primitive wrappers on the
0466: * specified object.
0467: */
0468: public static Object randomizeBean(Object bean)
0469: throws IntrospectionException, IllegalAccessException,
0470: InvocationTargetException,
0471: java.beans.IntrospectionException {
0472: BeanInfo info = Introspector.getBeanInfo(bean.getClass());
0473: PropertyDescriptor[] props = info.getPropertyDescriptors();
0474: for (int i = 0; i < props.length; i++) {
0475: Method write = props[i].getWriteMethod();
0476: if (write == null)
0477: continue;
0478:
0479: Class[] params = write.getParameterTypes();
0480: if (params == null || params.length != 1)
0481: continue;
0482:
0483: Class paramType = params[0];
0484: Object arg = null;
0485:
0486: if (paramType == boolean.class
0487: || paramType == Boolean.class)
0488: arg = randomBoolean();
0489: else if (paramType == byte.class || paramType == Byte.class)
0490: arg = randomByte();
0491: else if (paramType == char.class
0492: || paramType == Character.class)
0493: arg = randomChar();
0494: else if (paramType == short.class
0495: || paramType == Short.class)
0496: arg = randomShort();
0497: else if (paramType == int.class
0498: || paramType == Integer.class)
0499: arg = randomInt();
0500: else if (paramType == long.class || paramType == Long.class)
0501: arg = randomLong();
0502: else if (paramType == double.class
0503: || paramType == Double.class)
0504: arg = randomDouble();
0505: else if (paramType == float.class
0506: || paramType == Float.class)
0507: arg = randomFloat();
0508: else if (paramType == String.class)
0509: arg = randomString();
0510: else if (paramType == BigInteger.class)
0511: arg = randomBigInteger();
0512: else if (paramType == BigDecimal.class)
0513: arg = randomBigDecimal();
0514: else if (paramType == Date.class)
0515: arg = randomDate();
0516:
0517: if (arg != null)
0518: write.invoke(bean, new Object[] { arg });
0519: }
0520:
0521: return bean;
0522: }
0523:
0524: protected void assertSize(int size, Collection c) {
0525: assertEquals(size, c.size());
0526: }
0527:
0528: protected void assertSize(int size, Query q) {
0529: assertEquals(size, q.getResultList().size());
0530: }
0531:
0532: /**
0533: * Serialize and deserialize the object.
0534: *
0535: * @param validateEquality make sure the hashCode and equals
0536: * methods hold true
0537: */
0538: public static Object roundtrip(Object orig, boolean validateEquality)
0539: throws IOException, ClassNotFoundException {
0540: assertNotNull(orig);
0541:
0542: ByteArrayOutputStream bout = new ByteArrayOutputStream();
0543: ObjectOutputStream out = new ObjectOutputStream(bout);
0544: out.writeObject(orig);
0545: ByteArrayInputStream bin = new ByteArrayInputStream(bout
0546: .toByteArray());
0547: ObjectInputStream in = new ObjectInputStream(bin);
0548: Object result = in.readObject();
0549:
0550: if (validateEquality) {
0551: assertEquals(orig.hashCode(), result.hashCode());
0552: assertEquals(orig, result);
0553: }
0554:
0555: return result;
0556: }
0557:
0558: /**
0559: * @return true if the specified input matches the regular expression regex.
0560: */
0561: public static boolean matches(String regex, String input)
0562: throws RESyntaxException {
0563: RE re = REUtil.createRE(regex);
0564: return re.match(input);
0565: }
0566:
0567: public static void assertMatches(String regex, String input) {
0568: try {
0569: if (!(matches(regex, input)))
0570: fail("Expected regular expression: <" + regex + ">"
0571: + " did not match: <" + input + ">");
0572: } catch (RESyntaxException e) {
0573: throw new IllegalArgumentException(e.toString());
0574: }
0575: }
0576:
0577: public static void assertNotMatches(String regex, String input) {
0578: try {
0579: if (matches(regex, input))
0580: fail("Regular expression: <" + regex + ">"
0581: + " should not match: <" + input + ">");
0582: } catch (RESyntaxException e) {
0583: throw new IllegalArgumentException(e.toString());
0584: }
0585: }
0586:
0587: /**
0588: * Check the list if strings and return the ones that match
0589: * the specified match.
0590: */
0591: public static List matches(String regex, Collection input)
0592: throws RESyntaxException {
0593: List matches = new ArrayList();
0594: for (Iterator i = input.iterator(); i.hasNext();) {
0595: String check = (String) i.next();
0596: if (matches(regex, check))
0597: matches.add(check);
0598: }
0599:
0600: return matches;
0601: }
0602:
0603: /**
0604: * Assert that the specified collection of Strings contains at least
0605: * one string that matches the specified regular expression.
0606: */
0607: public static void assertMatches(String regex, Collection input) {
0608: try {
0609: if (matches(regex, input).size() == 0)
0610: fail("The specified list of size " + input.size()
0611: + " did not contain any strings that match the"
0612: + " specified regular expression(\"" + regex
0613: + "\")");
0614: } catch (RESyntaxException e) {
0615: throw new IllegalArgumentException(e.toString());
0616: }
0617: }
0618:
0619: /**
0620: * Assert that the specified collection of Strings does not match
0621: * the specified regular expression.
0622: */
0623: public static void assertNotMatches(String regex, Collection input) {
0624: try {
0625: List matches;
0626:
0627: if (((matches = matches(regex, input))).size() > 0)
0628: fail("The specified list of size "
0629: + input.size()
0630: + " did contain one or more strings that matchs the"
0631: + " specified illegal regular expression"
0632: + " (\"" + regex + "\")."
0633: + " First example of a matching message is: "
0634: + matches.iterator().next());
0635: } catch (RESyntaxException e) {
0636: throw new IllegalArgumentException(e.toString());
0637: }
0638: }
0639:
0640: protected Log getLog() {
0641: return getConfiguration().getLog("Tests");
0642: }
0643:
0644: ///////////////////
0645: // Multi threading
0646: ///////////////////
0647:
0648: /**
0649: * Re-execute the invoking method a random number of times
0650: * in a random number of Threads.
0651: */
0652: public void mttest() throws ThreadingException {
0653: // 6 iterations in 8 threads is a good trade-off between
0654: // tests taking way too long and having a decent chance of
0655: // identifying MT problems.
0656: int iterations = 6;
0657: int threads = 8;
0658:
0659: mttest(threads, iterations);
0660: }
0661:
0662: /**
0663: * Execute the calling method <code>iterations</code>
0664: * times in <code>threads</code> Threads.
0665: */
0666: public void mttest(int threads, int iterations) {
0667: mttest(0, threads, iterations);
0668: }
0669:
0670: public void mttest(int serialCount, int threads, int iterations)
0671: throws ThreadingException {
0672: String methodName = callingMethod("mttest");
0673: mttest(serialCount, threads, iterations, methodName,
0674: new Object[0]);
0675: }
0676:
0677: /**
0678: * Execute a test method in multiple threads.
0679: *
0680: * @param threads the number of Threads to run in
0681: * @param iterations the number of times the method should
0682: * be execute in a single Thread
0683: * @param method the name of the method to execute
0684: * @param args the arguments to pass to the method
0685: * @throws ThreadingException if an errors occur in
0686: * any of the Threads. The actual exceptions
0687: * will be embedded in the exception. Note that
0688: * this means that assert() failures will be
0689: * treated as errors rather than warnings.
0690: * @author Marc Prud'hommeaux
0691: */
0692: public void mttest(int threads, int iterations,
0693: final String method, final Object[] args)
0694: throws ThreadingException {
0695: mttest(0, threads, iterations, method, args);
0696: }
0697:
0698: public void mttest(int serialCount, int threads, int iterations,
0699: final String method, final Object[] args)
0700: throws ThreadingException {
0701: if (multiThreadExecuting != null
0702: && multiThreadExecuting.equals(method)) {
0703: // we are currently executing in multi-threaded mode:
0704: // don't deadlock!
0705: return;
0706: }
0707:
0708: multiThreadExecuting = method;
0709:
0710: try {
0711: Class[] paramClasses = new Class[args.length];
0712: for (int i = 0; i < paramClasses.length; i++)
0713: paramClasses[i] = args[i].getClass();
0714:
0715: final Method meth;
0716:
0717: try {
0718: meth = getClass().getMethod(method, paramClasses);
0719: } catch (NoSuchMethodException nsme) {
0720: throw new ThreadingException(nsme.toString(), nsme);
0721: }
0722:
0723: final Object thiz = this ;
0724:
0725: mttest("reflection invocation: (" + method + ")",
0726: serialCount, threads, iterations,
0727: new VolatileRunnable() {
0728: public void run() throws Exception {
0729: meth.invoke(thiz, args);
0730: }
0731: });
0732: } finally {
0733: multiThreadExecuting = null;
0734: }
0735: }
0736:
0737: public void mttest(String title, final int threads,
0738: final int iterations, final VolatileRunnable runner)
0739: throws ThreadingException {
0740: mttest(title, 0, threads, iterations, runner);
0741: }
0742:
0743: /**
0744: * Execute a test method in multiple threads.
0745: *
0746: * @param title a description of the test, for inclusion in the
0747: * error message
0748: * @param serialCount the number of times to run the method
0749: * serially before spawning threads.
0750: * @param threads the number of Threads to run in
0751: * @param iterations the number of times the method should
0752: * @param runner the VolatileRunnable that will execute
0753: * the actual test from within the Thread.
0754: * @throws ThreadingException if an errors occur in
0755: * any of the Threads. The actual exceptions
0756: * will be embedded in the exception. Note that
0757: * this means that assert() failures will be
0758: * treated as errors rather than warnings.
0759: * @author Marc Prud'hommeaux be execute in a single Thread
0760: * @author Marc Prud'hommeaux
0761: */
0762: public void mttest(String title, final int serialCount,
0763: final int threads, final int iterations,
0764: final VolatileRunnable runner) throws ThreadingException {
0765: final List exceptions = Collections
0766: .synchronizedList(new LinkedList());
0767:
0768: Thread[] runners = new Thread[threads];
0769:
0770: final long startMillis = System.currentTimeMillis() + 1000;
0771:
0772: for (int i = 1; i <= threads; i++) {
0773: final int this Thread = i;
0774:
0775: runners[i - 1] = new Thread(title + " [" + i + " of "
0776: + threads + "]") {
0777: public void run() {
0778: // do our best to have all threads start at the exact
0779: // same time. This is imperfect, but the closer we
0780: // get to everyone starting at the same time, the
0781: // better chance we have for identifying MT problems.
0782: while (System.currentTimeMillis() < startMillis)
0783: yield();
0784:
0785: int this Iteration = 1;
0786: try {
0787: for (; this Iteration <= iterations; this Iteration++) {
0788: // go go go!
0789: runner.run();
0790: }
0791: } catch (Throwable error) {
0792: synchronized (exceptions) {
0793: // embed the exception into something that gives
0794: // us some more information about the threading
0795: // environment
0796: exceptions.add(new ThreadingException(
0797: "thread=" + this .toString()
0798: + ";threadNum="
0799: + this Thread
0800: + ";maxThreads=" + threads
0801: + ";iteration="
0802: + this Iteration
0803: + ";maxIterations="
0804: + iterations, error));
0805: }
0806: }
0807: }
0808: };
0809: }
0810:
0811: // start the serial tests(does not spawn the threads)
0812: for (int i = 0; i < serialCount; i++) {
0813: runners[0].run();
0814: }
0815:
0816: // start the multithreaded
0817: for (int i = 0; i < threads; i++) {
0818: runners[i].start();
0819: }
0820:
0821: // wait for them all to complete
0822: for (int i = 0; i < threads; i++) {
0823: try {
0824: runners[i].join();
0825: } catch (InterruptedException e) {
0826: }
0827: }
0828:
0829: if (exceptions.size() == 0)
0830: return; // sweeeeeeeet: no errors
0831:
0832: // embed all the exceptions that were throws into a
0833: // ThreadingException
0834: Throwable[] errors = (Throwable[]) exceptions
0835: .toArray(new Throwable[0]);
0836: throw new ThreadingException("The " + errors.length
0837: + " embedded errors " + "occured in the execution of "
0838: + iterations + " iterations " + "of " + threads
0839: + " threads: [" + title + "]", errors);
0840: }
0841:
0842: /**
0843: * Check to see if we are in the top-level execution stack.
0844: */
0845: public boolean isRootThread() {
0846: return multiThreadExecuting == null;
0847: }
0848:
0849: /**
0850: * A Runnable that can throw an Exception: used to test cases.
0851: */
0852: public static interface VolatileRunnable {
0853:
0854: public void run() throws Exception;
0855: }
0856:
0857: /**
0858: * Exception for errors caught during threading tests.
0859: */
0860: public class ThreadingException extends RuntimeException {
0861:
0862: private final Throwable[] _nested;
0863:
0864: public ThreadingException(String msg, Throwable nested) {
0865: super (msg);
0866: if (nested == null)
0867: _nested = new Throwable[0];
0868: else
0869: _nested = new Throwable[] { nested };
0870: }
0871:
0872: public ThreadingException(String msg, Throwable[] nested) {
0873: super (msg);
0874: if (nested == null)
0875: _nested = new Throwable[0];
0876: else
0877: _nested = nested;
0878: }
0879:
0880: public void printStackTrace() {
0881: printStackTrace(System.out);
0882: }
0883:
0884: public void printStackTrace(PrintStream out) {
0885: printStackTrace(new PrintWriter(out));
0886: }
0887:
0888: public void printStackTrace(PrintWriter out) {
0889: super .printStackTrace(out);
0890: for (int i = 0; i < _nested.length; i++) {
0891: out.print("Nested Throwable #" + (i + 1) + ": ");
0892: _nested[i].printStackTrace(out);
0893: }
0894: }
0895: }
0896:
0897: /**
0898: * Return the last method name that called this one by
0899: * parsing the current stack trace.
0900: *
0901: * @param exclude a method name to skip
0902: * @throws IllegalStateException If the calling method could not be
0903: * identified.
0904: * @author Marc Prud'hommeaux
0905: */
0906: public String callingMethod(String exclude) {
0907: // determine the currently executing method by
0908: // looking at the stack track. Hackish, but convenient.
0909: StringWriter sw = new StringWriter();
0910: new Exception().printStackTrace(new PrintWriter(sw));
0911: for (StringTokenizer stackTrace = new StringTokenizer(sw
0912: .toString(), System.getProperty("line.separator")); stackTrace
0913: .hasMoreTokens();) {
0914: String line = stackTrace.nextToken().trim();
0915:
0916: // not a stack trace element
0917: if (!(line.startsWith("at ")))
0918: continue;
0919:
0920: String fullMethodName = line
0921: .substring(0, line.indexOf("("));
0922:
0923: String shortMethodName = fullMethodName
0924: .substring(fullMethodName.lastIndexOf(".") + 1);
0925:
0926: // skip our own methods!
0927: if (shortMethodName.equals("callingMethod"))
0928: continue;
0929: if (exclude != null && shortMethodName.equals(exclude))
0930: continue;
0931:
0932: return shortMethodName;
0933: }
0934:
0935: throw new IllegalStateException("Could not identify calling "
0936: + "method in stack trace");
0937: }
0938:
0939: //////////
0940: // Timing
0941: //////////
0942:
0943: /**
0944: * Sleep the current Thread for a random amount of time from 0-1000 ms.
0945: */
0946: public void sleepRandom() {
0947: sleepRandom(1000);
0948: }
0949:
0950: /**
0951: * Sleep the current Thread for a random amount of time from
0952: * 0-<code>max</code> ms.
0953: */
0954: public void sleepRandom(int max) {
0955: try {
0956: Thread.currentThread().sleep((long) (Math.random() * max));
0957: } catch (InterruptedException ex) {
0958: }
0959: }
0960:
0961: /**
0962: * Re-run this method in the current thread, timing out
0963: * after the specified number of seconds.
0964: * Usage:
0965: * <pre> public void timeOutOperation() { if (timeout(5 * 1000)) return;
0966: * Thread.currentThread().sleep(10 * 1000); }
0967: * </pre>
0968: * <p/>
0969: * <p/>
0970: * <strong>Warning</strong> this method should be used sparingly,
0971: * and only when you expect that a timeout will <strong>not</strong>
0972: * occur. It utilized the deprecated {@link Thread#stop()} and
0973: * {@link Thread#interrupt} methods, which can leave monitors in an
0974: * invalid state. It is only used because it provides more
0975: * meaningful information than just seeing that the entire autobuild
0976: * timed out.
0977: *
0978: * @param millis the number of milliseconds we should wait.
0979: * @return true if we are are in the thread that requested the
0980: * timeout, false if we are in the timeout thread itself.
0981: */
0982: public boolean timeout(long millis) throws Throwable {
0983: String methodName = callingMethod("timeout");
0984: return timeout(millis, methodName);
0985: }
0986:
0987: /**
0988: * @see #timeout(long)
0989: */
0990: public boolean timeout(long millis, String methodName)
0991: throws Throwable {
0992: // we are in the timing out-thread: do nothing so the
0993: // actual test method can run
0994: if (inTimeoutThread)
0995: return false;
0996:
0997: inTimeoutThread = true;
0998: long endTime = System.currentTimeMillis() + millis;
0999:
1000: try {
1001: final Method method = getClass().getMethod(methodName,
1002: (Class[]) null);
1003: final Object thz = this ;
1004:
1005: // spawn thread
1006: TimeOutThread tot = new TimeOutThread("TimeOutThread ["
1007: + methodName + "] (" + millis + "ms)") {
1008: public void run() {
1009: try {
1010: method.invoke(thz, (Object[]) null);
1011: } catch (Throwable t) {
1012: throwable = t;
1013: } finally {
1014: completed = true;
1015: }
1016: }
1017: };
1018:
1019: tot.start();
1020:
1021: // wait for the completion or a timeout to occur
1022: tot.join(millis);
1023:
1024: // have we timed out? Kill the thread and throw an exception
1025: if (System.currentTimeMillis() >= endTime) {
1026: // if we are waiting on a monitor, this will give
1027: // us a useful stack trace.
1028: try {
1029: tot.interrupt();
1030: } catch (Throwable e) {
1031: }
1032: Thread.currentThread().sleep(500);
1033:
1034: // try to kill the thread
1035: try {
1036: tot.stop();
1037: } catch (Throwable e) {
1038: }
1039: Thread.currentThread().sleep(500);
1040:
1041: throw new OperationTimedOutException("Execution of \""
1042: + methodName + "\" timed out after " + millis
1043: + " milliseconds", tot.throwable);
1044: }
1045:
1046: // throw any exceptions that may have occured
1047: if (tot.throwable != null)
1048: throw tot.throwable;
1049:
1050: // I guess everything was OK
1051: return true;
1052: } finally {
1053: inTimeoutThread = false;
1054: }
1055: }
1056:
1057: private static class TimeOutThread extends Thread {
1058:
1059: public Throwable throwable = null;
1060: public boolean completed = false;
1061:
1062: public TimeOutThread(String name) {
1063: super (name);
1064: setDaemon(true);
1065: }
1066: }
1067:
1068: /**
1069: * Indicates that a timeout occured.
1070: */
1071: public static class OperationTimedOutException extends
1072: RuntimeException {
1073:
1074: private final Throwable _err;
1075:
1076: public OperationTimedOutException(String msg,
1077: Throwable throwable) {
1078: super (msg);
1079: _err = throwable;
1080: }
1081:
1082: public void printStackTrace() {
1083: printStackTrace(System.out);
1084: }
1085:
1086: public void printStackTrace(PrintStream out) {
1087: printStackTrace(new PrintWriter(out));
1088: }
1089:
1090: public void printStackTrace(PrintWriter out) {
1091: super .printStackTrace(out);
1092: if (_err != null) {
1093: out.print("Nested Throwable: ");
1094: _err.printStackTrace(out);
1095: }
1096: }
1097: }
1098:
1099: ///////////////
1100: // Collections
1101: ///////////////
1102:
1103: /**
1104: * Validate that the specified {@link Collection} fulfills the
1105: * Collection contract as specified by the Collections API.
1106: * <p/>
1107: * <strong>Note</strong>: does not validate mutable operations
1108: */
1109: public static void validateCollection(Collection collection) {
1110: int size = collection.size();
1111: int iterated = 0;
1112: // ensure we can walk along the iterator
1113: for (Iterator i = collection.iterator(); i.hasNext();) {
1114: iterated++;
1115: i.next();
1116: }
1117:
1118: // ensure the number of values iterated is the same as the list size
1119: assertEquals(size, iterated);
1120:
1121: // also validate the list
1122: if (collection instanceof List) {
1123: List ll = new ArrayList();
1124: for (int i = 0; i < 100; i++)
1125: ll.add(new Integer(i));
1126: validateList((List) ll);
1127: validateList((List) collection);
1128: }
1129: }
1130:
1131: /**
1132: * Validate that the specified {@link List} fulfills the
1133: * List contract as specified by the Collections API.
1134: * <p/>
1135: * <strong>Note</strong>: does not validate mutable operations
1136: */
1137: public static void validateList(List list) {
1138: Object[] coreValues = list.toArray();
1139: Object[] values1 = new Object[list.size()];
1140: Object[] values2 = new Object[list.size()];
1141: Object[] values3 = new Object[list.size()];
1142: Object[] values4 = new Object[list.size()];
1143:
1144: // fill sequential index access list
1145: for (int i = 0; i < list.size(); i++)
1146: values1[i] = list.get(i);
1147:
1148: // fill sequential list
1149: int index = 0;
1150: ListIterator iter;
1151: for (iter = list.listIterator(0); iter.hasNext();) {
1152: assertEquals(index, iter.nextIndex());
1153: assertEquals(index, iter.previousIndex() + 1);
1154: values2[index] = iter.next();
1155: assertTrue(list.contains(values2[index]));
1156: index++;
1157: }
1158:
1159: // ensure NoSuchElementException is thrown as appropriate
1160: try {
1161: iter.next();
1162: fail("next() should have resulted in a NoSuchElementException");
1163: } catch (NoSuchElementException e) {
1164: } // as expected
1165:
1166: // fill reverse sequential list
1167: int back = 0;
1168: for (iter = list.listIterator(list.size()); iter.hasPrevious();) {
1169: assertEquals(index, iter.previousIndex() + 1);
1170: assertEquals(index, iter.nextIndex());
1171: values3[--index] = iter.previous();
1172: back++;
1173: }
1174: assertEquals(list.size(), back);
1175:
1176: // ensure NoSuchElementException is thrown as appropriate
1177: try {
1178: iter.previous();
1179: fail("previous() should have resulted in a "
1180: + "NoSuchElementException");
1181: } catch (NoSuchElementException e) {
1182: } // as expected
1183:
1184: // fill random access list
1185: List indices = new LinkedList();
1186: for (int i = 0; i < list.size(); i++)
1187: indices.add(new Integer(i));
1188:
1189: for (int i = 0; i < list.size(); i++) {
1190: int rand = (int) (Math.random() * indices.size());
1191: Integer randIndex = (Integer) indices.remove(rand);
1192: values4[randIndex.intValue()] = list.get(randIndex
1193: .intValue());
1194: }
1195:
1196: assertEquals(Arrays.asList(coreValues), Arrays.asList(values1));
1197: assertIdentical(Arrays.asList(coreValues), Arrays
1198: .asList(values1));
1199: assertEquals(Arrays.asList(coreValues), Arrays.asList(values2));
1200: assertIdentical(Arrays.asList(coreValues), Arrays
1201: .asList(values2));
1202: assertEquals(Arrays.asList(coreValues), Arrays.asList(values4));
1203: assertIdentical(Arrays.asList(coreValues), Arrays
1204: .asList(values4));
1205: assertEquals(Arrays.asList(coreValues), Arrays.asList(values3));
1206: assertIdentical(Arrays.asList(coreValues), Arrays
1207: .asList(values3));
1208: }
1209:
1210: /**
1211: * Assert that the given List contain the exact same
1212: * elements. This is different than the normal List contract, which
1213: * states that list1.equals(list2) if each element e1.equals(e2).
1214: * This method asserts that e1 == n2.
1215: */
1216: public static void assertIdentical(List c1, List c2) {
1217: assertEquals(c1.size(), c2.size());
1218: for (Iterator i1 = c1.iterator(), i2 = c2.iterator(); i1
1219: .hasNext()
1220: && i2.hasNext();)
1221: assertTrue(i1.next() == i2.next());
1222: }
1223:
1224: /**
1225: * Assert that the collection parameter is already ordered
1226: * according to the specified comparator.
1227: */
1228: public void assertOrdered(Collection c, Comparator comp) {
1229: List l1 = new LinkedList(c);
1230: List l2 = new LinkedList(c);
1231: assertEquals(l1, l2);
1232: Collections.sort(l2, comp);
1233: assertEquals(l1, l2);
1234: Collections.sort(l1, comp);
1235: assertEquals(l1, l2);
1236: }
1237: }
|