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