2011/08/05 - Jakarta Cactus has been retired.

For more information, please explore the Attic.

View Javadoc

1   /* 
2    * ========================================================================
3    * 
4    * Licensed to the Apache Software Foundation (ASF) under one or more
5    * contributor license agreements.  See the NOTICE file distributed with
6    * this work for additional information regarding copyright ownership.
7    * The ASF licenses this file to You under the Apache License, Version 2.0
8    * (the "License"); you may not use this file except in compliance with
9    * the License.  You may obtain a copy of the License at
10   * 
11   *   http://www.apache.org/licenses/LICENSE-2.0
12   * 
13   * Unless required by applicable law or agreed to in writing, software
14   * distributed under the License is distributed on an "AS IS" BASIS,
15   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16   * See the License for the specific language governing permissions and
17   * limitations under the License.
18   * 
19   * ========================================================================
20   */
21  package org.apache.cactus.integration.maven;
22  
23  import org.apache.tools.ant.DirectoryScanner;
24  import org.apache.tools.ant.Project;
25  import org.apache.tools.ant.types.FileSet;
26  import org.apache.tools.ant.types.Path;
27  import org.apache.commons.logging.Log;
28  import org.apache.commons.logging.LogFactory;
29  
30  import java.util.List;
31  import java.util.ArrayList;
32  import java.util.Iterator;
33  import java.io.File;
34  import java.net.MalformedURLException;
35  import java.net.URLClassLoader;
36  import java.net.URL;
37  import java.lang.reflect.Modifier;
38  import java.lang.reflect.Method;
39  
40  import junit.framework.TestCase;
41  
42  /**
43   * Process {@link FileSet} and extracts classes that are Cactus tests. As
44   * a Cactus test can be a simple JUnit test case wrapped in a Cactus suite,
45   * it is very difficult to find out only Cactus tests. Thus, in this version,
46   * we are only finding JUnit tests.
47   *
48   * A class is considered to be a JUnit Test Case if:
49   * <ul>
50   *   <li>It extends {@link TestCase}</li>
51   *   <li>It is not abstract</li>
52   *   <li>It has at least one method that starts with "test", returns void and
53   *   takes no parameters</li>
54   * </ul>
55   *
56   * @version $Id: CactusScanner.java 238815 2004-02-29 16:34:44Z vmassol $
57   */
58  public class CactusScanner
59  {
60      /**
61       * Log instance.
62       */
63      private Log log = LogFactory.getLog(CactusScanner.class);
64  
65      /**
66       * The Ant project.
67       */
68      private Project project;
69  
70      /**
71       * Lists of Cactus class names that were found in the {@link FileSet}.
72       */
73      private List cactusTests = new ArrayList();
74  
75      /**
76       * @param theProject the Ant project that is currently executing
77       */
78      public void setProject(Project theProject)
79      {
80          this.project = theProject;
81      }
82  
83      /**
84       * Remove all Cactus class names that were found in the {@link Fileset}.
85       */
86      public void clear()
87      {
88          this.cactusTests.clear();
89      }
90  
91      /**
92       * @return the list of valid Cactus test cases
93       */
94      public Iterator iterator()
95      {
96          return this.cactusTests.iterator();
97      }
98  
99      /**
100      * Finds the Cactus test cases from a list of files.
101      *
102      * @param theFileset the list of files in which to look for Cactus tests
103      * @param theClasspath the classpaths needed to load the test classes
104      */
105     public void processFileSet(FileSet theFileset, Path theClasspath)
106     {
107         DirectoryScanner ds = theFileset.getDirectoryScanner(this.project);
108         ds.scan();
109         String[] files = ds.getIncludedFiles();
110 
111         for (int i = 0; i < files.length; i++)
112         {
113             // The path is supposed to be a relative path that matches the
114             // package directory structure. Thus we only need to replace
115             // the directory separator char by a "." and remove the file
116             // extension to get the FQN java class name.
117 
118             // Is it a java class file?
119             if (files[i].endsWith(".class"))
120             {
121                 String fqn = files[i]
122                     .substring(0, files[i].length() - ".class".length())
123                     .replace(File.separatorChar, '.');
124 
125                 log.debug("Found candidate class: [" + fqn + "]");
126 
127                 // Is it a Cactus test case?
128                 if (isJUnitTestCase(fqn, theClasspath))
129                 {
130                     log.debug("Found Cactus test case: [" + fqn + "]");
131                     this.cactusTests.add(fqn);
132                 }
133             }
134         }
135     }
136 
137     /**
138      * @param theClassName the fully qualified name of the class to check
139      * @param theClasspath the classpaths needed to load the test classes
140      * @return true if the class is a JUnit test case
141      */
142     private boolean isJUnitTestCase(String theClassName, Path theClasspath)
143     {
144         Class clazz = loadClass(theClassName, theClasspath);
145         if (clazz == null)
146         {
147             return false;
148         }
149 
150         Class testCaseClass = null;
151         try
152         {
153             testCaseClass = clazz.getClassLoader().loadClass(
154                 TestCase.class.getName());
155         }
156         catch (ClassNotFoundException e)
157         {
158             log.debug("Cannot load class", e);
159             return false;
160         }
161 
162         if (!testCaseClass.isAssignableFrom(clazz))
163         {
164             log.debug("Not a JUnit test as class [" + theClassName + "] does "
165                 + "not inherit from [" + TestCase.class.getName()
166                 + "]");
167             return false;
168         }
169 
170         // the class must not be abstract
171         if (Modifier.isAbstract(clazz.getModifiers()))
172         {
173             log.debug("Not a JUnit test as class [" + theClassName + "] is "
174                 + "abstract");
175             return false;
176         }
177 
178         // the class must have at least one test, i.e. a public method
179         // starting with "test" and that takes no parameters
180         boolean hasTestMethod = false;
181         Method[] methods = clazz.getMethods();
182         for (int i = 0; i < methods.length; i++)
183         {
184             if (methods[i].getName().startsWith("test")
185                 && (methods[i].getReturnType() == Void.TYPE)
186                 && (methods[i].getParameterTypes().length == 0))
187             {
188                 hasTestMethod = true;
189                 break;
190             }
191         }
192 
193         if (!hasTestMethod)
194         {
195             log.debug("Not a JUnit test as class [" + theClassName + "] has "
196                 + "no method that start with \"test\", returns void and has "
197                 + "no parameters");
198             return false;
199         }
200 
201         return true;
202     }
203 
204     /**
205      * @param theClassName the fully qualified name of the class to check
206      * @param theClasspath the classpaths needed to load the test classes
207      * @return the class object loaded by reflection from its string name
208      */
209     private Class loadClass(String theClassName, Path theClasspath)
210     {
211         Class clazz = null;
212         try
213         {
214             clazz = createClassLoader(theClasspath).loadClass(theClassName);
215         }
216         catch (ClassNotFoundException e)
217         {
218             log.error("Failed to load class [" + theClassName + "]", e);
219         }
220         return clazz;
221     }
222 
223     /**
224      * @param theClasspath the classpaths needed to load the test classes
225      * @return a ClassLoader that has all the needed classpaths for loading
226      *         the Cactus tests classes
227      */
228     private ClassLoader createClassLoader(Path theClasspath)
229     {
230         URL[] urls = new URL[theClasspath.size()];
231 
232         try
233         {
234             for (int i = 0; i < theClasspath.size(); i++)
235             {
236                 log.debug("Adding ["
237                     + new File(theClasspath.list()[i]).toURL() + "] "
238                     + "to class loader classpath");
239                 urls[i] = new File(theClasspath.list()[i]).toURL();
240             }
241         }
242         catch (MalformedURLException e)
243         {
244             log.debug("Invalid URL", e);
245         }
246 
247         return new URLClassLoader(urls);
248     }
249 }