1   package org.w3c.tidy.servlet;
2   
3   import java.io.File;
4   import java.lang.reflect.Method;
5   import java.lang.reflect.Modifier;
6   import java.net.URL;
7   import java.util.ArrayList;
8   import java.util.Iterator;
9   import java.util.List;
10  
11  import junit.framework.Test;
12  import junit.framework.TestCase;
13  import junit.framework.TestSuite;
14  
15  import org.apache.commons.logging.Log;
16  import org.apache.commons.logging.LogFactory;
17  
18  
19  /***
20   * Dynamic test suite derived from http://www.javaworld.com/javaworld/jw-12-2000/jw-1221-junit.html. Runs all Java test
21   * cases in the source tree that extend TestCase. This helps running tests faster with ant/maven since httpunit tests
22   * requires forking and starting a new java process for each test is too slow.
23   * @author Fabrizio Giustina
24   * @version $Revision: 1.1 $ ($Author: fgiust $)
25   */
26  public class TestAll extends TestCase
27  {
28  
29      /***
30       * Root package containing tests.
31       */
32      private static final String PACKAGE_ROOT = "org.w3c.tidy.servlet";
33  
34      /***
35       * logger.
36       */
37      private static Log log = LogFactory.getLog(TestAll.class);
38  
39      /***
40       * Basic constructor - called by the test runners.
41       * @param name test name
42       */
43      public TestAll(String name)
44      {
45          super(name);
46      }
47  
48      /***
49       * Iterates over the classes accessible via the iterator and adds them to the test suite.
50       * @param suite TestSuite empty test suite
51       * @param classIterator iterator on loaded classes
52       * @return int number of testcases added to suite
53       */
54      private static int addAllTests(TestSuite suite, Iterator classIterator)
55      {
56          int testClassCount = 0;
57          while (classIterator.hasNext())
58          {
59              Class testCaseClass = (Class) classIterator.next();
60  
61              try
62              {
63                  Method suiteMethod = testCaseClass.getMethod("suite", new Class[0]);
64                  Test test = (Test) suiteMethod.invoke(null, new Class[0]); // static method
65                  suite.addTest(test);
66              }
67              catch (NoSuchMethodException e)
68              {
69                  suite.addTest(new TestSuite(testCaseClass));
70              }
71              catch (Exception e)
72              {
73                  log.error("Failed to execute suite ()", e);
74              }
75              if (log.isDebugEnabled())
76              {
77                  log.debug("Loaded test case: " + testCaseClass.getName());
78              }
79              testClassCount++;
80          }
81          return testClassCount;
82      }
83  
84      /***
85       * Dynamically create a test suite from a set of class files in a directory tree.
86       * @throws Throwable in running the suite() method
87       * @return TestSuite for all the found tests
88       */
89      public static Test suite() throws Throwable
90      {
91          try
92          {
93              String className = TestAll.class.getName();
94              URL testFile = TestAll.class.getResource("TestAll.class");
95              log.debug(testFile.getFile());
96              File classRoot = new File(testFile.getFile()).getParentFile();
97              while (className.indexOf(".") > -1)
98              {
99                  classRoot = classRoot.getParentFile();
100                 className = className.substring(className.indexOf(".") + 1, className.length());
101             }
102             log.debug("Looking for classes in " + classRoot);
103 
104             ClassFinder classFinder = new ClassFinder(classRoot, PACKAGE_ROOT);
105             TestCaseLoader testCaseLoader = new TestCaseLoader();
106             testCaseLoader.loadTestCases(classFinder.getClasses());
107             TestSuite suite = new TestSuite();
108             int numberOfTests = addAllTests(suite, testCaseLoader.getClasses());
109             if (log.isDebugEnabled())
110             {
111                 log.debug("Number of test classes found: " + numberOfTests);
112             }
113             return suite;
114         }
115         catch (Throwable t)
116         {
117             // This ensures we have extra information.
118             // Otherwise all we get is a "Could not invoke the suite method." message.
119             log.error("suite()", t);
120             throw t;
121         }
122     }
123 }
124 
125 /***
126  * This class is responsible for searching a directory for class files. It builds a list of fully qualified class names
127  * from the class files in the directory tree.
128  * @author Fabrizio Giustina
129  * @version $Revision: 1.1 $ ($Author: fgiust $)
130  */
131 
132 
133 class ClassFinder
134 {
135 
136     /***
137      * List of found classes (names).
138      */
139     private List classNameList = new ArrayList();
140 
141     /***
142      * length of the base package String.
143      */
144     private int startPackageLength;
145 
146     /***
147      * Construct the class finder and locate all the classes in the directory structured pointed to by
148      * <code>classPathRoot</code>. Only classes in the package <code>packageRoot</code> are considered.
149      * @param classPathRoot classpath directory where to search for test cases
150      * @param packageRoot root package for tests to be included
151      */
152     publicong> ClassFinder(File classPathRoot, String packageRoot)
153     {
154         startPackageLength = classPathRoot.getAbsolutePath().length() + 1;
155         String directoryOffset = packageRoot.replace('.', File.separatorChar);
156         findAndStoreTestClasses(new File(classPathRoot, directoryOffset));
157     }
158 
159     /***
160      * Given a file name, guess the fully qualified class name.
161      * @param file class file
162      * @return class name
163      */
164     private String computeClassName(File file)
165     {
166         String absPath = file.getAbsolutePath();
167         String packageBase = absPath.substring(startPackageLength, absPath.length() - 6);
168         String className;
169         className = packageBase.replace(File.separatorChar, '.');
170         return className;
171     }
172 
173     /***
174      * This method does all the work. It runs down the directory structure looking for java classes.
175      * @param currentDirectory directory to search class files in
176      */
177     private void findAndStoreTestClasses(File currentDirectory)
178     {
179         String[] files = currentDirectory.list();
180         for (int i = 0; i < files.length; i++)
181         {
182             File file = new File(currentDirectory, files[i]);
183             String fileBase = file.getName();
184             int idx = fileBase.indexOf(".class");
185             final int CLASS_EXTENSION_LENGTH = 6;
186 
187             if (idx != -1 && (fileBase.length() - idx) == CLASS_EXTENSION_LENGTH)
188             {
189                 String className = computeClassName(file);
190                 classNameList.add(className);
191             }
192             else
193             {
194                 if (file.isDirectory())
195                 {
196                     findAndStoreTestClasses(file);
197                 }
198             }
199         }
200     }
201 
202     /***
203      * Return the found classes.
204      * @return Iterator on classes names
205      */
206     public Iterator getClasses()
207     {
208         return classNameList.iterator();
209     }
210 }
211 
212 /***
213  * Responsible for loading classes representing valid test cases.
214  * @author Fabrizio Giustina
215  * @version $Revision: 1.1 $ ($Author: fgiust $)
216  */
217 
218 
219 class TestCaseLoader
220 {
221 
222     /***
223      * list containing laded classes.
224      */
225     private List classList = new ArrayList();
226 
227     /***
228      * Load the classes that represent test cases we are interested.
229      * @param classNamesIterator An iterator over a collection of fully qualified class names
230      */
231     public void loadTestCases(Iterator classNamesIterator)
232     {
233         while (classNamesIterator.hasNext())
234         {
235             String className = (String) classNamesIterator.next();
236             try
237             {
238                 Class candidateClass = Class.forName(className);
239                 addClassIfTestCase(candidateClass);
240             }
241             catch (ClassNotFoundException e)
242             {
243                 System.err.println("Cannot load class: " + className + " " + e.getMessage());
244             }
245             catch (NoClassDefFoundError e)
246             {
247                 System.err.println("Cannot load class that " + className + " is dependant on");
248             }
249         }
250     }
251 
252     /***
253      * Adds testCaseClass to the list of classes if the class extends TestCase and it's not abstract.
254      * @param testCaseClass class to test
255      */
256     private void addClassIfTestCase(Class testCaseClass)
257     {
258         if (TestCase.class.isAssignableFrom(testCaseClass)
259             && !TestAll.class.isAssignableFrom(testCaseClass)
260             && !Modifier.isAbstract(testCaseClass.getModifiers()))
261         {
262             classList.add(testCaseClass);
263         }
264     }
265 
266     /***
267      * Obtain an iterator over the collection of test case classes loaded by <code>loadTestCases</code>.
268      * @return Iterator on loaded classes list
269      */
270     public Iterator getClasses()
271     {
272         return classList.iterator();
273     }
274 }