001: /*
002: * ========================================================================
003: *
004: * Copyright 2001-2003 The Apache Software Foundation.
005: *
006: * Licensed under the Apache License, Version 2.0 (the "License");
007: * you may not use this file except in compliance with the License.
008: * You may obtain a copy of the License at
009: *
010: * http://www.apache.org/licenses/LICENSE-2.0
011: *
012: * Unless required by applicable law or agreed to in writing, software
013: * distributed under the License is distributed on an "AS IS" BASIS,
014: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
015: * See the License for the specific language governing permissions and
016: * limitations under the License.
017: *
018: * ========================================================================
019: */
020: package org.apache.cactus.integration.maven;
021:
022: import org.apache.tools.ant.DirectoryScanner;
023: import org.apache.tools.ant.Project;
024: import org.apache.tools.ant.types.FileSet;
025: import org.apache.tools.ant.types.Path;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028:
029: import java.util.List;
030: import java.util.ArrayList;
031: import java.util.Iterator;
032: import java.io.File;
033: import java.net.MalformedURLException;
034: import java.net.URLClassLoader;
035: import java.net.URL;
036: import java.lang.reflect.Modifier;
037: import java.lang.reflect.Method;
038:
039: import junit.framework.TestCase;
040:
041: /**
042: * Process {@link FileSet} and extracts classes that are Cactus tests. As
043: * a Cactus test can be a simple JUnit test case wrapped in a Cactus suite,
044: * it is very difficult to find out only Cactus tests. Thus, in this version,
045: * we are only finding JUnit tests.
046: *
047: * A class is considered to be a JUnit Test Case if:
048: * <ul>
049: * <li>It extends {@link TestCase}</li>
050: * <li>It is not abstract</li>
051: * <li>It has at least one method that starts with "test", returns void and
052: * takes no parameters</li>
053: * </ul>
054: *
055: * @version $Id: CactusScanner.java 238815 2004-02-29 16:34:44Z vmassol $
056: */
057: public class CactusScanner {
058: /**
059: * Log instance.
060: */
061: private Log log = LogFactory.getLog(CactusScanner.class);
062:
063: /**
064: * The Ant project
065: */
066: private Project project;
067:
068: /**
069: * Lists of Cactus class names that were found in the {@link FileSet}
070: */
071: private List cactusTests = new ArrayList();
072:
073: /**
074: * @param theProject the Ant project that is currently executing
075: */
076: public void setProject(Project theProject) {
077: this .project = theProject;
078: }
079:
080: /**
081: * Remove all Cactus class names that were found in the {@link Fileset}
082: */
083: public void clear() {
084: this .cactusTests.clear();
085: }
086:
087: /**
088: * @return the list of valid Cactus test cases
089: */
090: public Iterator iterator() {
091: return this .cactusTests.iterator();
092: }
093:
094: /**
095: * Finds the Cactus test cases from a list of files.
096: *
097: * @param theFileset the list of files in which to look for Cactus tests
098: * @param theClasspath the classpaths needed to load the test classes
099: */
100: public void processFileSet(FileSet theFileset, Path theClasspath) {
101: DirectoryScanner ds = theFileset
102: .getDirectoryScanner(this .project);
103: ds.scan();
104: String[] files = ds.getIncludedFiles();
105:
106: for (int i = 0; i < files.length; i++) {
107: // The path is supposed to be a relative path that matches the
108: // package directory structure. Thus we only need to replace
109: // the directory separator char by a "." and remove the file
110: // extension to get the FQN java class name.
111:
112: // Is it a java class file?
113: if (files[i].endsWith(".class")) {
114: String fqn = files[i].substring(0,
115: files[i].length() - ".class".length()).replace(
116: File.separatorChar, '.');
117:
118: log.debug("Found candidate class: [" + fqn + "]");
119:
120: // Is it a Cactus test case?
121: if (isJUnitTestCase(fqn, theClasspath)) {
122: log.debug("Found Cactus test case: [" + fqn + "]");
123: this .cactusTests.add(fqn);
124: }
125: }
126: }
127: }
128:
129: /**
130: * @param theClassName the fully qualified name of the class to check
131: * @param theClasspath the classpaths needed to load the test classes
132: * @return true if the class is a JUnit test case
133: */
134: private boolean isJUnitTestCase(String theClassName,
135: Path theClasspath) {
136: Class clazz = loadClass(theClassName, theClasspath);
137: if (clazz == null) {
138: return false;
139: }
140:
141: Class testCaseClass = null;
142: try {
143: testCaseClass = clazz.getClassLoader().loadClass(
144: TestCase.class.getName());
145: } catch (ClassNotFoundException e) {
146: log.debug("Cannot load class", e);
147: return false;
148: }
149:
150: if (!testCaseClass.isAssignableFrom(clazz)) {
151: log.debug("Not a JUnit test as class [" + theClassName
152: + "] does " + "not inherit from ["
153: + TestCase.class.getName() + "]");
154: return false;
155: }
156:
157: // the class must not be abstract
158: if (Modifier.isAbstract(clazz.getModifiers())) {
159: log.debug("Not a JUnit test as class [" + theClassName
160: + "] is " + "abstract");
161: return false;
162: }
163:
164: // the class must have at least one test, i.e. a public method
165: // starting with "test" and that takes no parameters
166: boolean hasTestMethod = false;
167: Method[] methods = clazz.getMethods();
168: for (int i = 0; i < methods.length; i++) {
169: if (methods[i].getName().startsWith("test")
170: && (methods[i].getReturnType() == Void.TYPE)
171: && (methods[i].getParameterTypes().length == 0)) {
172: hasTestMethod = true;
173: break;
174: }
175: }
176:
177: if (!hasTestMethod) {
178: log
179: .debug("Not a JUnit test as class ["
180: + theClassName
181: + "] has "
182: + "no method that start with \"test\", returns void and has "
183: + "no parameters");
184: return false;
185: }
186:
187: return true;
188: }
189:
190: /**
191: * @param theClassName the fully qualified name of the class to check
192: * @param theClasspath the classpaths needed to load the test classes
193: * @return the class object loaded by reflection from its string name
194: */
195: private Class loadClass(String theClassName, Path theClasspath) {
196: Class clazz = null;
197: try {
198: clazz = createClassLoader(theClasspath).loadClass(
199: theClassName);
200: } catch (ClassNotFoundException e) {
201: log.error("Failed to load class [" + theClassName + "]", e);
202: }
203: return clazz;
204: }
205:
206: /**
207: * @param theClasspath the classpaths needed to load the test classes
208: * @return a ClassLoader that has all the needed classpaths for loading
209: * the Cactus tests classes
210: */
211: private ClassLoader createClassLoader(Path theClasspath) {
212: URL[] urls = new URL[theClasspath.size()];
213:
214: try {
215: for (int i = 0; i < theClasspath.size(); i++) {
216: log.debug("Adding ["
217: + new File(theClasspath.list()[i]).toURL()
218: + "] " + "to class loader classpath");
219: urls[i] = new File(theClasspath.list()[i]).toURL();
220: }
221: } catch (MalformedURLException e) {
222: log.debug("Invalid URL", e);
223: }
224:
225: return new URLClassLoader(urls);
226: }
227: }
|