001: /*
002: * Copyright 2001-2004 The Apache Software Foundation
003: *
004: * Licensed under the Apache License, Version 2.0 (the "License");
005: * you may not use this file except in compliance with the License.
006: * You may obtain a copy of the License at
007: *
008: * http://www.apache.org/licenses/LICENSE-2.0
009: *
010: * Unless required by applicable law or agreed to in writing, software
011: * distributed under the License is distributed on an "AS IS" BASIS,
012: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013: * See the License for the specific language governing permissions and
014: * limitations under the License.
015: */
016: package org.apache.commons.collections;
017:
018: import java.lang.reflect.Constructor;
019: import java.lang.reflect.InvocationTargetException;
020: import java.lang.reflect.Method;
021: import java.lang.reflect.Modifier;
022: import java.util.ArrayList;
023: import java.util.Arrays;
024: import java.util.List;
025:
026: import junit.framework.TestCase;
027: import junit.framework.TestSuite;
028:
029: /**
030: * A {@link TestCase} that can define both simple and bulk test methods.
031: * <p>
032: * A <I>simple test method</I> is the type of test traditionally
033: * supplied by by {@link TestCase}. To define a simple test, create a public
034: * no-argument method whose name starts with "test". You can specify the
035: * the name of simple test in the constructor of <code>BulkTest</code>;
036: * a subsequent call to {@link TestCase#run} will run that simple test.
037: * <p>
038: * A <I>bulk test method</I>, on the other hand, returns a new instance
039: * of <code>BulkTest</code>, which can itself define new simple and bulk
040: * test methods. By using the {@link #makeSuite} method, you can
041: * automatically create a hierarchal suite of tests and child bulk tests.
042: * <p>
043: * For instance, consider the following two classes:
044: *
045: * <Pre>
046: * public class TestSet extends BulkTest {
047: *
048: * private Set set;
049: *
050: * public TestSet(Set set) {
051: * this.set = set;
052: * }
053: *
054: * public void testContains() {
055: * boolean r = set.contains(set.iterator().next()));
056: * assertTrue("Set should contain first element, r);
057: * }
058: *
059: * public void testClear() {
060: * set.clear();
061: * assertTrue("Set should be empty after clear", set.isEmpty());
062: * }
063: * }
064: *
065: *
066: * public class TestHashMap extends BulkTest {
067: *
068: * private Map makeFullMap() {
069: * HashMap result = new HashMap();
070: * result.put("1", "One");
071: * result.put("2", "Two");
072: * return result;
073: * }
074: *
075: * public void testClear() {
076: * Map map = makeFullMap();
077: * map.clear();
078: * assertTrue("Map empty after clear", map.isEmpty());
079: * }
080: *
081: * public BulkTest bulkTestKeySet() {
082: * return new TestSet(makeFullMap().keySet());
083: * }
084: *
085: * public BulkTest bulkTestEntrySet() {
086: * return new TestSet(makeFullMap().entrySet());
087: * }
088: * }
089: * </Pre>
090: *
091: * In the above examples, <code>TestSet</code> defines two
092: * simple test methods and no bulk test methods; <code>TestHashMap</code>
093: * defines one simple test method and two bulk test methods. When
094: * <code>makeSuite(TestHashMap.class).run</code> is executed,
095: * <I>five</I> simple test methods will be run, in this order:<P>
096: *
097: * <Ol>
098: * <Li>TestHashMap.testClear()
099: * <Li>TestHashMap.bulkTestKeySet().testContains();
100: * <Li>TestHashMap.bulkTestKeySet().testClear();
101: * <Li>TestHashMap.bulkTestEntrySet().testContains();
102: * <Li>TestHashMap.bulkTestEntrySet().testClear();
103: * </Ol>
104: *
105: * In the graphical junit test runners, the tests would be displayed in
106: * the following tree:<P>
107: *
108: * <UL>
109: * <LI>TestHashMap</LI>
110: * <UL>
111: * <LI>testClear
112: * <LI>bulkTestKeySet
113: * <UL>
114: * <LI>testContains
115: * <LI>testClear
116: * </UL>
117: * <LI>bulkTestEntrySet
118: * <UL>
119: * <LI>testContains
120: * <LI>testClear
121: * </UL>
122: * </UL>
123: * </UL>
124: *
125: * A subclass can override a superclass's bulk test by
126: * returning <code>null</code> from the bulk test method. If you only
127: * want to override specific simple tests within a bulk test, use the
128: * {@link #ignoredTests} method.<P>
129: *
130: * Note that if you want to use the bulk test methods, you <I>must</I>
131: * define your <code>suite()</code> method to use {@link #makeSuite}.
132: * The ordinary {@link TestSuite} constructor doesn't know how to
133: * interpret bulk test methods.
134: *
135: * @author Paul Jack
136: * @version $Id: BulkTest.java 201765 2005-06-25 16:39:34Z scolebourne $
137: */
138: public class BulkTest extends TestCase implements Cloneable {
139:
140: // Note: BulkTest is Cloneable to make it easier to construct
141: // BulkTest instances for simple test methods that are defined in
142: // anonymous inner classes. Basically we don't have to worry about
143: // finding weird constructors. (And even if we found them, technically
144: // it'd be illegal for anyone but the outer class to invoke them).
145: // Given one BulkTest instance, we can just clone it and reset the
146: // method name for every simple test it defines.
147:
148: /**
149: * The full name of this bulk test instance. This is the full name
150: * that is compared to {@link #ignoredTests} to see if this
151: * test should be ignored. It's also displayed in the text runner
152: * to ease debugging.
153: */
154: String verboseName;
155:
156: /**
157: * Constructs a new <code>BulkTest</code> instance that will run the
158: * specified simple test.
159: *
160: * @param name the name of the simple test method to run
161: */
162: public BulkTest(String name) {
163: super (name);
164: this .verboseName = getClass().getName();
165: }
166:
167: /**
168: * Creates a clone of this <code>BulkTest</code>.<P>
169: *
170: * @return a clone of this <code>BulkTest</code>
171: */
172: public Object clone() {
173: try {
174: return super .clone();
175: } catch (CloneNotSupportedException e) {
176: throw new Error(); // should never happen
177: }
178: }
179:
180: /**
181: * Returns an array of test names to ignore.<P>
182: *
183: * If a test that's defined by this <code>BulkTest</code> or
184: * by one of its bulk test methods has a name that's in the returned
185: * array, then that simple test will not be executed.<P>
186: *
187: * A test's name is formed by taking the class name of the
188: * root <code>BulkTest</code>, eliminating the package name, then
189: * appending the names of any bulk test methods that were invoked
190: * to get to the simple test, and then appending the simple test
191: * method name. The method names are delimited by periods:
192: *
193: * <pre>
194: * TestHashMap.bulkTestEntrySet.testClear
195: * </pre>
196: *
197: * is the name of one of the simple tests defined in the sample classes
198: * described above. If the sample <code>TestHashMap</code> class
199: * included this method:
200: *
201: * <pre>
202: * public String[] ignoredTests() {
203: * return new String[] { "TestHashMap.bulkTestEntrySet.testClear" };
204: * }
205: * </pre>
206: *
207: * then the entry set's clear method wouldn't be tested, but the key
208: * set's clear method would.
209: *
210: * @return an array of the names of tests to ignore, or null if
211: * no tests should be ignored
212: */
213: public String[] ignoredTests() {
214: return null;
215: }
216:
217: /**
218: * Returns the display name of this <code>BulkTest</code>.
219: *
220: * @return the display name of this <code>BulkTest</code>
221: */
222: public String toString() {
223: return getName() + "(" + verboseName + ") ";
224: }
225:
226: /**
227: * Returns a {@link TestSuite} for testing all of the simple tests
228: * <I>and</I> all the bulk tests defined by the given class.<P>
229: *
230: * The class is examined for simple and bulk test methods; any child
231: * bulk tests are also examined recursively; and the results are stored
232: * in a hierarchal {@link TestSuite}.<P>
233: *
234: * The given class must be a subclass of <code>BulkTest</code> and must
235: * not be abstract.<P>
236: *
237: * @param c the class to examine for simple and bulk tests
238: * @return a {@link TestSuite} containing all the simple and bulk tests
239: * defined by that class
240: */
241: public static TestSuite makeSuite(Class c) {
242: if (Modifier.isAbstract(c.getModifiers())) {
243: throw new IllegalArgumentException(
244: "Class must not be abstract.");
245: }
246: if (!BulkTest.class.isAssignableFrom(c)) {
247: throw new IllegalArgumentException(
248: "Class must extend BulkTest.");
249: }
250: return new BulkTestSuiteMaker(c).make();
251: }
252:
253: }
254:
255: // It was easier to use a separate class to do all the reflection stuff
256: // for making the TestSuite instances. Having permanent state around makes
257: // it easier to handle the recursion.
258: class BulkTestSuiteMaker {
259:
260: /** The class that defines simple and bulk tests methods. */
261: private Class startingClass;
262:
263: /** List of ignored simple test names. */
264: private List ignored;
265:
266: /** The TestSuite we're currently populating. Can change over time. */
267: private TestSuite result;
268:
269: /**
270: * The prefix for simple test methods. Used to check if a test is in
271: * the ignored list.
272: */
273: private String prefix;
274:
275: /**
276: * Constructor.
277: *
278: * @param startingClass the starting class
279: */
280: public BulkTestSuiteMaker(Class startingClass) {
281: this .startingClass = startingClass;
282: }
283:
284: /**
285: * Makes a hierarchal TestSuite based on the starting class.
286: *
287: * @return the hierarchal TestSuite for startingClass
288: */
289: public TestSuite make() {
290: this .result = new TestSuite();
291: this .prefix = getBaseName(startingClass);
292: result.setName(prefix);
293:
294: BulkTest bulk = makeFirstTestCase(startingClass);
295: ignored = new ArrayList();
296: String[] s = bulk.ignoredTests();
297: if (s != null) {
298: ignored.addAll(Arrays.asList(s));
299: }
300: make(bulk);
301: return result;
302: }
303:
304: /**
305: * Appends all the simple tests and bulk tests defined by the given
306: * instance's class to the current TestSuite.
307: *
308: * @param bulk An instance of the class that defines simple and bulk
309: * tests for us to append
310: */
311: void make(BulkTest bulk) {
312: Class c = bulk.getClass();
313: Method[] all = c.getMethods();
314: for (int i = 0; i < all.length; i++) {
315: if (isTest(all[i]))
316: addTest(bulk, all[i]);
317: if (isBulk(all[i]))
318: addBulk(bulk, all[i]);
319: }
320: }
321:
322: /**
323: * Adds the simple test defined by the given method to the TestSuite.
324: *
325: * @param bulk The instance of the class that defined the method
326: * (I know it's weird. But the point is, we can clone the instance
327: * and not have to worry about constructors.)
328: * @param m The simple test method
329: */
330: void addTest(BulkTest bulk, Method m) {
331: BulkTest bulk2 = (BulkTest) bulk.clone();
332: bulk2.setName(m.getName());
333: bulk2.verboseName = prefix + "." + m.getName();
334: if (ignored.contains(bulk2.verboseName))
335: return;
336: result.addTest(bulk2);
337: }
338:
339: /**
340: * Adds a whole new suite of tests that are defined by the result of
341: * the given bulk test method. In other words, the given bulk test
342: * method is invoked, and the resulting BulkTest instance is examined
343: * for yet more simple and bulk tests.
344: *
345: * @param bulk The instance of the class that defined the method
346: * @param m The bulk test method
347: */
348: void addBulk(BulkTest bulk, Method m) {
349: String verboseName = prefix + "." + m.getName();
350: if (ignored.contains(verboseName))
351: return;
352:
353: BulkTest bulk2;
354: try {
355: bulk2 = (BulkTest) m.invoke(bulk, (Object[]) null);
356: if (bulk2 == null)
357: return;
358: } catch (InvocationTargetException ex) {
359: ex.getTargetException().printStackTrace();
360: throw new Error(); // FIXME;
361: } catch (IllegalAccessException ex) {
362: ex.printStackTrace();
363: throw new Error(); // FIXME;
364: }
365:
366: // Save current state on the stack.
367: String oldPrefix = prefix;
368: TestSuite oldResult = result;
369:
370: prefix = prefix + "." + m.getName();
371: result = new TestSuite();
372: result.setName(m.getName());
373:
374: make(bulk2);
375:
376: oldResult.addTest(result);
377:
378: // Restore the old state
379: prefix = oldPrefix;
380: result = oldResult;
381: }
382:
383: /**
384: * Returns the base name of the given class.
385: *
386: * @param c the class
387: * @return the name of that class, minus any package names
388: */
389: private static String getBaseName(Class c) {
390: String name = c.getName();
391: int p = name.lastIndexOf('.');
392: if (p > 0) {
393: name = name.substring(p + 1);
394: }
395: return name;
396: }
397:
398: // These three methods are used to create a valid BulkTest instance
399: // from a class.
400:
401: private static Constructor getTestCaseConstructor(Class c) {
402: try {
403: return c.getConstructor(new Class[] { String.class });
404: } catch (NoSuchMethodException e) {
405: throw new IllegalArgumentException(c + " must provide "
406: + "a (String) constructor");
407: }
408: }
409:
410: private static BulkTest makeTestCase(Class c, Method m) {
411: Constructor con = getTestCaseConstructor(c);
412: try {
413: return (BulkTest) con.newInstance(new Object[] { m
414: .getName() });
415: } catch (InvocationTargetException e) {
416: e.printStackTrace();
417: throw new RuntimeException(); // FIXME;
418: } catch (IllegalAccessException e) {
419: throw new Error(); // should never occur
420: } catch (InstantiationException e) {
421: throw new RuntimeException(); // FIXME;
422: }
423: }
424:
425: private static BulkTest makeFirstTestCase(Class c) {
426: Method[] all = c.getMethods();
427: for (int i = 0; i < all.length; i++) {
428: if (isTest(all[i]))
429: return makeTestCase(c, all[i]);
430: }
431: throw new IllegalArgumentException(c.getName()
432: + " must provide " + " at least one test method.");
433: }
434:
435: /**
436: * Returns true if the given method is a simple test method.
437: */
438: private static boolean isTest(Method m) {
439: if (!m.getName().startsWith("test"))
440: return false;
441: if (m.getReturnType() != Void.TYPE)
442: return false;
443: if (m.getParameterTypes().length != 0)
444: return false;
445: int mods = m.getModifiers();
446: if (Modifier.isStatic(mods))
447: return false;
448: if (Modifier.isAbstract(mods))
449: return false;
450: return true;
451: }
452:
453: /**
454: * Returns true if the given method is a bulk test method.
455: */
456: private static boolean isBulk(Method m) {
457: if (!m.getName().startsWith("bulkTest"))
458: return false;
459: if (m.getReturnType() != BulkTest.class)
460: return false;
461: if (m.getParameterTypes().length != 0)
462: return false;
463: int mods = m.getModifiers();
464: if (Modifier.isStatic(mods))
465: return false;
466: if (Modifier.isAbstract(mods))
467: return false;
468: return true;
469: }
470:
471: }
|