001: /*
002: * @(#)TestRunnable.java
003: *
004: * The basics are taken from an article by Andy Schneider
005: * andrew.schneider@javaworld.com
006: * The article is "JUnit Best Practices"
007: * http://www.javaworld.com/javaworld/jw-12-2000/jw-1221-junit_p.html
008: *
009: * Part of the GroboUtils package at:
010: * http://groboutils.sourceforge.net
011: *
012: * Permission is hereby granted, free of charge, to any person obtaining a
013: * copy of this software and associated documentation files (the "Software"),
014: * to deal in the Software without restriction, including without limitation
015: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
016: * and/or sell copies of the Software, and to permit persons to whom the
017: * Software is furnished to do so, subject to the following conditions:
018: *
019: * The above copyright notice and this permission notice shall be included in
020: * all copies or substantial portions of the Software.
021: *
022: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
023: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
024: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
025: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
026: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
027: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
028: * DEALINGS IN THE SOFTWARE.
029: */
030:
031: package net.sourceforge.groboutils.junit.v1;
032:
033: import org.apache.log4j.Logger;
034: import junit.framework.TestCase;
035: import junit.framework.TestResult;
036: import junit.framework.AssertionFailedError;
037: import junit.framework.Assert;
038:
039: /**
040: * Instances of this class only execute in the
041: * <tt>runTestRunnables</tt> method of
042: * the <tt>MultiThreadedTestRunner</tt> class.
043: * TestCases should define inner classes as a subclass of this,
044: * implement the <tt>runTest()</tt> method, and pass in the
045: * instantiated class as part of an array to the
046: * <tt>runTestRunnables</tt> method. Call <tt>delay( long )</tt>
047: * to easily include a waiting period. This class allows for
048: * all assertions to be invoked, so that subclasses can be static or
049: * defined outside a TestCase. If an exception is thrown from the
050: * <tt>runTest()</tt> method, then all other test threads will
051: * terminate due to the error.
052: * <P>
053: * The <tt>runTest()</tt> method needs to be responsive to
054: * <tt>InterruptedException</tt>, resulting from the owning
055: * <tt>MultiThreadedTestRunner</tt> interrupting the thread in order to
056: * signal the early termination of the threads. The
057: * <tt>InterruptedException</tt>s may be propigated outside the
058: * <tt>runTest()</tt> implementation with no harmful effects. Note that
059: * this means that <tt>InterruptedException</tt>s are part of the
060: * framework, and as such carry information that your <tt>runTest()</tt>
061: * implementations cannot override; in other words, don't let your test
062: * propigate an <tt>InterruptedException</tt> to indicate an error.
063: * <P>
064: * Tests which perform a set of monitoring checks on the object-under-test
065: * should extend <tt>TestMonitorRunnable</tt>, since monitors run until
066: * told to stop. The <tt>Thread.stop()</tt> command will be sent with a
067: * <tt>MultiThreadedTestRunner.TestDeathException</tt>.
068: *
069: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
070: * @version $Date: 2003/10/03 14:26:45 $
071: * @since March 28, 2002
072: */
073: public abstract class TestRunnable extends Assert implements Runnable {
074: private static final Class THIS_CLASS = TestRunnable.class;
075: protected static final Logger LOG = Logger.getLogger(THIS_CLASS);
076: private static int testCount = 0;
077:
078: private MultiThreadedTestRunner mttr;
079: private int testIndex;
080: private boolean ignoreStopErrors = false;
081:
082: public TestRunnable() {
083: synchronized (THIS_CLASS) {
084: this .testIndex = testCount++;
085: }
086: }
087:
088: TestRunnable(boolean ignoreStopErrors) {
089: this ();
090: this .ignoreStopErrors = ignoreStopErrors;
091: }
092:
093: /**
094: * Performs the set of processing or checks on the object-under-test,
095: * which will be in parallel with other <tt>TestRunnable</tt>
096: * instances.
097: * <P>
098: * The implementation should be responsive to
099: * <tt>InterruptedException</tt> exceptions or to the status of
100: * <tt>Thread.isInterrupted()</tt>, as that is the signal used to tell
101: * running <tt>TestRunnable</tt> instances to halt their processing;
102: * instances which do not stop within a reasonable time frame will
103: * have <tt>Thread.stop()</tt> called on them.
104: * <P>
105: * Non-monitor instances must have this method implemented such that
106: * it runs in a finite time; if any instance executes over the
107: * <tt>MultiThreadedTestRunner</tt> instance maximum time limit, then
108: * the <tt>MultiThreadedTestRunner</tt> instance assumes that a
109: * test error occurred.
110: *
111: * @exception Throwable any exception may be thrown and will be
112: * reported as a test failure, except for
113: * <tt>InterruptedException</tt>s, which will be ignored.
114: */
115: public abstract void runTest() throws Throwable;
116:
117: /**
118: * Sleep for <tt>millis</tt> milliseconds. A convenience method.
119: *
120: * @exception InterruptedException if an interrupt occured during the
121: 8 sleep.
122: */
123: public void delay(long millis) throws InterruptedException {
124: Thread.sleep(millis);
125: }
126:
127: /**
128: * Unable to make this a "final" method due to JDK 1.1 compatibility.
129: * However, implementations should not override this method.
130: */
131: public void run() {
132: if (this .mttr == null) {
133: throw new IllegalStateException(
134: "Owning runner never defined. The runnables should only be "
135: + "started through the MultiThreadedTestRunner instance.");
136: }
137:
138: LOG.info("Starting test thread " + this .testIndex);
139: try {
140: runTest();
141: } catch (InterruptedException ie) {
142: // ignore these exceptions - they represent the MTTR
143: // interrupting the tests.
144: } catch (MultiThreadedTestRunner.TestDeathException tde) {
145: // ignore these exceptions as they relate to thread-related
146: // exceptions. These represent the MTTR stopping us.
147: // Our response is to actually rethrow the exception.
148: if (!this .ignoreStopErrors) {
149: LOG.info("Aborted test thread " + this .testIndex);
150: throw tde;
151: }
152: } catch (Throwable t) {
153: // for any exception, handle it and interrupt the
154: // other threads
155:
156: // Note that ThreadDeath exceptions must be re-thrown after
157: // the interruption has occured.
158: this .mttr.handleException(t);
159: }
160: LOG.info("Ended test thread " + this .testIndex);
161: }
162:
163: /**
164: * Returns the status of the owning <tt>MultiThreadedTestRunner</tt>
165: * instance: <tt>true</tt> means that the tests have completed (monitors
166: * may still be active), and <tt>false</tt> means that the tests are
167: * still running.
168: *
169: * @return <tt>true</tt> if the tests have completed their run,
170: * otherwise <tt>false</tt>.
171: */
172: public boolean isDone() {
173: return this .mttr.areThreadsFinished();
174: }
175:
176: void setTestRunner(MultiThreadedTestRunner mttr) {
177: this.mttr = mttr;
178: }
179: }
|