001: /*
002: * @(#)TestClassParser.java
003: */
004:
005: package net.sourceforge.groboutils.junit.v1.parser;
006:
007: import java.util.Vector;
008: import java.util.Enumeration;
009:
010: import java.io.PrintWriter;
011: import java.io.StringWriter;
012:
013: import java.lang.reflect.Method;
014: import java.lang.reflect.Modifier;
015:
016: import junit.framework.TestSuite;
017: import junit.framework.TestCase;
018: import junit.framework.Test;
019:
020: import org.apache.log4j.Logger;
021:
022: /**
023: * Parses Test classes to discover the usable test methods.
024: * <P>
025: * Ripped the test method discovery code out of junit.framework.TestSuite to
026: * allow it to have usable logic.
027: * <P>
028: * This is not covered under the GroboUtils license, but rather under the
029: * JUnit license (IBM Public License). This heading may not be totally
030: * in line with the license, so I'll change it when I find out what needs to
031: * be changed.
032: *
033: * @author Matt Albrecht <a href="mailto:groboclown@users.sourceforge.net">groboclown@users.sourceforge.net</a>
034: * @version $Date: 2002/11/05 00:49:31 $
035: * @since March 28, 2002
036: */
037: public class TestClassParser {
038: private static final Logger LOG = Logger
039: .getLogger(TestClassParser.class);
040:
041: private Class testClass;
042: Vector testMethods = new Vector();
043: private Vector warnings = new Vector();
044:
045: /**
046: * The primary constructor, which will cause this instance to know how to
047: * parse only the passed-in class.
048: *
049: * @param theClass the class to parse for testing.
050: * @exception IllegalArgumentException if <tt>theClass</tt> is
051: * <tt>null</tt>.
052: */
053: public TestClassParser(final Class theClass) {
054: if (theClass == null) {
055: throw new IllegalArgumentException("no null arguments");
056: }
057: this .testClass = theClass;
058:
059: if (testClass(theClass)) {
060: discoverTestMethods(theClass);
061: }
062: }
063:
064: //-------------------------------------------------------------------------
065: // Public methods
066:
067: /**
068: * Retrieve all warnings generated during the introspection of the class,
069: * or test creation. If a <tt>clearWarnings()</tt> call was ever made, then
070: * only those warnings that were encountered after the call will be
071: * returned.
072: *
073: * @return an array of all warnings generated while creating the test
074: * array.
075: */
076: public String[] getWarnings() {
077: String w[] = new String[this .warnings.size()];
078: this .warnings.copyInto(w);
079: return w;
080: }
081:
082: /**
083: * Remove all current warnings.
084: */
085: public void clearWarnings() {
086: this .warnings.removeAllElements();
087: }
088:
089: /**
090: * Retrieve all public test methods discovered through inspection.
091: *
092: * @return all test methods.
093: */
094: public Method[] getTestMethods() {
095: Method m[] = new Method[this .testMethods.size()];
096: this .testMethods.copyInto(m);
097: return m;
098: }
099:
100: /**
101: * Get the name of the test suite. By default, this is the class name.
102: *
103: * @return the name of the test suite.
104: */
105: public String getName() {
106: return this .testClass.getName();
107: }
108:
109: /**
110: * Get the class under test. This will never return <tt>null</tt>, and
111: * will always match the class passed into the constructor.
112: *
113: * @return the class under test.
114: */
115: public Class getTestClass() {
116: return this .testClass;
117: }
118:
119: //-------------------------------------------------------------------------
120: // Parse methods
121:
122: /**
123: * Discover if the given class is a valid testing class.
124: *
125: * @param theClass the class to parse for testing.
126: * @return <tt>true</tt> if the class is a public test class, otherwise
127: * <tt>false</tt>.
128: */
129: protected boolean testClass(final Class theClass) {
130: boolean result = true;
131: if (!Modifier.isPublic(theClass.getModifiers())) {
132: warning("Class " + theClass.getName() + " is not public.");
133: result = false;
134: }
135: if (!Test.class.isAssignableFrom(theClass)) {
136: warning("Class " + theClass.getName()
137: + " does not implement " + Test.class.getName());
138: result = false;
139: }
140: return result;
141: }
142:
143: /**
144: * Discover and record the test methods of the public test class
145: * <tt>theClass</tt>.
146: *
147: * @param theClass the class to parse for testing.
148: */
149: protected void discoverTestMethods(final Class theClass) {
150: Class super Class = theClass;
151: Vector names = new Vector();
152: while (Test.class.isAssignableFrom(super Class)) {
153: Method[] methods = super Class.getDeclaredMethods();
154: for (int i = 0; i < methods.length; i++) {
155: addTestMethod(methods[i], names);
156: }
157: super Class = super Class.getSuperclass();
158: }
159: }
160:
161: /**
162: * Adds the method <tt>m</tt> to the inner list of known test methods,
163: * but only if it is a public test method.
164: *
165: * @param m the method to add.
166: * @param names a list of method names that have already been inspected.
167: */
168: protected void addTestMethod(Method m, Vector names) {
169: String name = m.getName();
170: if (names.contains(name) || this .testMethods.contains(m)) {
171: return;
172: }
173:
174: if (isPublicTestMethod(m)) {
175: names.addElement(name);
176:
177: this .testMethods.addElement(m);
178: } else {
179: // almost a test method
180: if (isTestMethod(m)) {
181: warning("Test method isn't public: " + m.getName());
182: }
183: }
184: }
185:
186: /**
187: * Asserts that the method is public, and that it is also a test method.
188: *
189: * @param m the method under scrutiny.
190: * @return <tt>true</tt> if <tt>m</tt> is a public test method, otherwise
191: * <tt>false</tt>.
192: * @see #isTestMethod( Method )
193: */
194: protected boolean isPublicTestMethod(Method m) {
195: return isTestMethod(m) && Modifier.isPublic(m.getModifiers());
196: }
197:
198: /**
199: * Test if method <tt>m</tt> is a test method, which means it accepts
200: * no parameters, returns <tt>void</tt>, and the name of the method
201: * begins with <tt>test</tt>.
202: *
203: * @param m the method under scrutiny.
204: * @return <tt>true</tt> if <tt>m</tt> is a public test method, otherwise
205: * <tt>false</tt>.
206: */
207: protected boolean isTestMethod(Method m) {
208: String name = m.getName();
209: Class[] parameters = m.getParameterTypes();
210: Class returnType = m.getReturnType();
211: return parameters.length == 0 && name.startsWith("test")
212: && returnType.equals(Void.TYPE);
213: }
214:
215: /**
216: * Adds a warning message to the inner list of warnings.
217: *
218: * @param message the message describing the warning.
219: */
220: protected void warning(final String message) {
221: LOG.debug("WARNING: " + message);
222: this.warnings.addElement(message);
223: }
224: }
|