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 }