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]);
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
118
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 public ClassFinder(File classPathRoot, String packageRoot)/package-summary.html">ong> 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 }