001: /*
002: * @(#)InterfaceTestSuite.java
003: *
004: * Copyright (C) 2002-2003 Matt Albrecht
005: * groboclown@users.sourceforge.net
006: * http://groboutils.sourceforge.net
007: *
008: * Permission is hereby granted, free of charge, to any person obtaining a
009: * copy of this software and associated documentation files (the "Software"),
010: * to deal in the Software without restriction, including without limitation
011: * the rights to use, copy, modify, merge, publish, distribute, sublicense,
012: * and/or sell copies of the Software, and to permit persons to whom the
013: * Software is furnished to do so, subject to the following conditions:
014: *
015: * The above copyright notice and this permission notice shall be included in
016: * all copies or substantial portions of the Software.
017: *
018: * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
019: * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
020: * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
021: * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
022: * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
023: * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
024: * DEALINGS IN THE SOFTWARE.
025: */
026:
027: package net.sourceforge.groboutils.junit.v1.iftc;
028:
029: import junit.framework.Test;
030: import junit.framework.TestSuite;
031: import java.util.Enumeration;
032: import java.util.Vector;
033: import java.lang.reflect.Method;
034: import java.lang.reflect.Constructor;
035:
036: import net.sourceforge.groboutils.junit.v1.parser.TestClassParser;
037: import net.sourceforge.groboutils.junit.v1.parser.TestClassCreator;
038: import net.sourceforge.groboutils.junit.v1.parser.ITestCreator;
039: import net.sourceforge.groboutils.junit.v1.parser.DelegateTestCreator;
040: import net.sourceforge.groboutils.junit.v1.parser.JUnitOrigCreator;
041: import net.sourceforge.groboutils.junit.v1.parser.JUnit3_8Creator;
042: import net.sourceforge.groboutils.junit.v1.parser.IftcOrigCreator;
043:
044: import org.apache.log4j.Logger;
045:
046: /**
047: * Allows for tests to be written on interfaces or abstract classes. These
048: * must be run through an InterfaceTestSuite to have the implemented object
049: * be set correctly.
050: * <P>
051: * This class extends <tt>TestSuite</tt> only for the purpose of being a testing
052: * repository. The act of parsing TestCase classes is delegated to
053: * new <tt>TestSuite</tt> instances. A new instance will be created for each
054: * test method (just as <tt>TestSuite</tt> does), If a <tt>TestCase</tt> class
055: * has a constructor which is of the form <tt>( String, ImplFactory )</tt>,
056: * then each test method instance will be created
057: * once for each known <tt>ImplFactory</tt> object; these will be
058: * stored and executed through the <tt>ImplFactory</tt> class. All other
059: * classes will be added just as TestSuite does (the standard method).
060: * <P>
061: * The creation of test instances is delayed until the tests are actually
062: * retrieved via the <tt>testAt()</tt>, <tt>tests()</tt>, and
063: * <tt>testCount()</tt> methods. Therefore, adding new Classes and
064: * ImplFactory instances after the creation time will cause an error, due to
065: * problems with <tt>addTest()</tt> (they cannot be removed).
066: * <P>
067: * Currently, this class is slow: it does not do smart things like cache
068: * results from inspection on the same class object.
069: *
070: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
071: * @version $Date: 2003/02/10 22:52:20 $
072: * @since March 2, 2002
073: * @see InterfaceTestCase
074: * @see ImplFactory
075: * @see junit.framework.TestSuite
076: */
077: public class InterfaceTestSuite extends TestSuite {
078: private static final Logger LOG = Logger
079: .getLogger(InterfaceTestSuite.class);
080:
081: // these are not private for test-case usage.
082: Vector creators = new Vector();
083: Vector classes = new Vector();
084:
085: /**
086: * Constructs a TestSuite from the given class, and sets the initial
087: * set of creators. Adds all the methods
088: * starting with "test" as test cases to the suite.
089: */
090: public InterfaceTestSuite() {
091: // do nothing
092: }
093:
094: /**
095: * Constructs a TestSuite from the given class, and sets the initial
096: * set of creators. Adds all the methods
097: * starting with "test" as test cases to the suite.
098: *
099: * @param theClass the class under inspection
100: */
101: public InterfaceTestSuite(Class theClass) {
102: addTestSuite(theClass);
103: }
104:
105: /**
106: * Constructs a TestSuite from the given class, and sets the initial
107: * set of creators. Adds all the methods
108: * starting with "test" as test cases to the suite.
109: *
110: * @param theClass the class under inspection
111: * @param f a factory to add to this suite.
112: */
113: public InterfaceTestSuite(Class theClass, ImplFactory f) {
114: addTestSuite(theClass);
115: addFactory(f);
116: }
117:
118: /**
119: * Add a new Implementation factory to the suite. This should only be
120: * called before any tests are extracted from this suite. If it is
121: * called after, then an IllegalStateException will be generated.
122: *
123: * @param f a factory to add to this suite.
124: * @exception IllegalArgumentException if <tt>f</tt> is <tt>null</tt>
125: * @exception IllegalStateException if the tests have already been generated
126: */
127: public void addFactory(ImplFactory f) {
128: if (f == null) {
129: throw new IllegalArgumentException("no null args");
130: }
131: if (creators == null) {
132: throw new IllegalStateException(
133: "Already created TestSuites.");
134: }
135: this .creators.addElement(f);
136: }
137:
138: /**
139: * Add an array of new Implementation factories to the suite.
140: * This should only be
141: * called before any tests are extracted from this suite.
142: *
143: * @param f a set of factories to add to this suite.
144: * @exception IllegalArgumentException if <tt>f</tt> is <tt>null</tt>, or
145: * any element in the list is <tt>null</tt>
146: * @exception IllegalStateException if the tests have already been generated
147: */
148: public void addFactories(ImplFactory f[]) {
149: if (f == null) {
150: throw new IllegalArgumentException("no null args");
151: }
152: for (int i = 0; i < f.length; ++i) {
153: addFactory(f[i]);
154: }
155: }
156:
157: /**
158: * Add an InterfaceTestSuite to this suite. If an interface extends
159: * another interface, it should add it's super interface's test suite
160: * through this method. The same goes for any abstract or base class.
161: * Adding the parent suite through this method will cause both suites to
162: * share creators. In fact, the parent suite <b>cannot</b> have any
163: * factories when passed into this method, because they will be ignored.
164: * <P>
165: * This allows for the flexibility of determining whether to add a full
166: * test suite, without sharing factories, or not.
167: *
168: * @param t a test to add to the suite. It can be <tt>null</tt>.
169: */
170: public void addInterfaceTestSuite( InterfaceTestSuite t )
171: {
172: if (t != null)
173: {
174: if (t.creators != null && t.classes != null && t.classes.size() > 0)
175: {
176: if (t.creators.size() > 0)
177: {
178: LOG.warn( "Passed in InterfaceTestSuite "+t+
179: " with factories registered. This is a no-no. "+
180: "You need to pass it in through addTest(), or not add "+
181: "factories to it." );
182: }
183: else
184: {
185: Enumeration enum = t.classes.elements();
186: while (enum.hasMoreElements())
187: {
188: addTestSuite( (Class)enum.nextElement() );
189: }
190: }
191: }
192: }
193: }
194:
195: /**
196: * Add an array of tests to the suite.
197: *
198: * @param t a set of tests to add to this suite.
199: * @param IllegalArgumentException if <tt>t</tt> is <tt>null</tt>
200: */
201: public void addTests(Test[] t) {
202: if (t == null) {
203: throw new IllegalArgumentException("no null arguments");
204: }
205: for (int i = 0; i < t.length; ++i) {
206: addTest(t[i]);
207: }
208: }
209:
210: /**
211: * Adds all the methods
212: * starting with "test" as test cases to the suite.
213: * <P>
214: * Overrides the parent implementation to allow for InterfaceTests.
215: *
216: * @param theClass the class under inspection
217: * @exception IllegalArgumentException if <tt>theClass</tt> is <tt>null</tt>
218: * @exception IllegalStateException if the tests have already been generated
219: */
220: public void addTestSuite(Class theClass) {
221: if (theClass == null) {
222: throw new IllegalArgumentException("no null arguments");
223: }
224: if (this .classes == null) {
225: throw new IllegalStateException(
226: "Class "
227: + theClass.getName()
228: + " added after the load time. See JavaDoc for proper usage.");
229: }
230: this .classes.addElement(theClass);
231: }
232:
233: // from parent
234: public Test testAt(int index) {
235: loadTestSuites();
236: return super .testAt(index);
237: }
238:
239: // from parent
240: public int testCount() {
241: loadTestSuites();
242: return super .testCount();
243: }
244:
245: // from parent
246: public Enumeration tests() {
247: loadTestSuites();
248: return super .tests();
249: }
250:
251: /**
252: * Load all the tests from the cache of classes and factories.
253: */
254: protected void loadTestSuites()
255: {
256: // if either of these Vectors are null, then the loading has
257: // already been done.
258: if (this .creators == null || this .classes == null)
259: {
260: return;
261: }
262:
263: ITestCreator tc = createTestCreator( this .creators );
264: TestClassCreator tcc = new TestClassCreator( tc );
265: for (Enumeration enum = this .classes.elements();
266: enum.hasMoreElements();)
267: {
268: Class c = (Class)enum.nextElement();
269: loadTestSuite( c, tcc );
270: }
271:
272: // tell the instance to not load test suites again, and not allow
273: // new factories to be registered.
274: this .creators = null;
275: this .classes = null;
276: }
277:
278: /**
279: * Load all the tests and warnings from the class and the creator
280: * type into this instance's suite of tests.
281: *
282: * @param testClass the class being inspected for test instance
283: * creation.
284: * @param tcc the creator type that will be used to create new tests.
285: */
286: protected void loadTestSuite(Class testClass, TestClassCreator tcc) {
287: TestClassParser tcp = new TestClassParser(testClass);
288:
289: // ensure that all unwanted warnings are removed.
290: tcc.clearWarnings();
291:
292: Test t[] = tcc.createTests(tcp);
293: if (t == null || t.length <= 0) {
294: // no discovered tests, so create an error test
295: LOG.info("No tests for class discovered.");
296: addTest(TestClassCreator
297: .createWarningTest("No tests found in test class "
298: + testClass.getName()));
299: } else {
300: addTests(t);
301: }
302: addTests(tcc.createWarningTests(tcp));
303:
304: // be a nice citizen and clean up after ourself.
305: tcc.clearWarnings();
306: }
307:
308: /**
309: * Create a TestCreator that contains the knowledge of how to properly
310: * parse and generate tests for all types of supported test classes.
311: *
312: * @param factories a vector of ImplFactory instances to load Interface
313: * test class instances.
314: * @return the new creator.
315: */
316: protected ITestCreator createTestCreator(Vector vf) {
317: ImplFactory factories[] = new ImplFactory[vf.size()];
318: vf.copyInto(factories);
319:
320: // Order matters!!!
321: //
322: // Use the original version before the new technique for backwards
323: // compatibility.
324: ITestCreator tc = new DelegateTestCreator(new ITestCreator[] {
325: new IftcOrigCreator(factories),
326: //new Iftc3_8Creator( factories ),
327: new JUnitOrigCreator(), new JUnit3_8Creator() });
328: return tc;
329: }
330: }
|