001: package org.hansel;
002:
003: import java.io.File;
004: import java.io.FileFilter;
005: import java.io.FilenameFilter;
006: import java.lang.reflect.Method;
007: import java.lang.reflect.Modifier;
008: import java.text.NumberFormat;
009: import java.util.Comparator;
010: import java.util.Enumeration;
011: import java.util.HashSet;
012: import java.util.Iterator;
013: import java.util.Set;
014: import java.util.Stack;
015: import java.util.StringTokenizer;
016: import java.util.TreeSet;
017:
018: import junit.framework.Test;
019: import junit.framework.TestSuite;
020:
021: import org.apache.tools.ant.AntClassLoader;
022: import org.apache.tools.ant.BuildException;
023: import org.apache.tools.ant.Task;
024: import org.apache.tools.ant.types.Path;
025: import org.apache.tools.ant.types.Reference;
026:
027: public class AntCoverageTask extends Task {
028: private static final String SUITE_METHOD = "suite";
029: private static final Class[] SUITE_TYPES = new Class[0];
030: private static final Object[] SUITE_PARAMS = new Object[0];
031:
032: private static final FileFilter DIR_FILTER = new FileFilter() {
033: public boolean accept(File path) {
034: return path.isDirectory();
035: }
036: };
037:
038: private static final FilenameFilter CLASS_FILTER = new FilenameFilter() {
039: public boolean accept(File dir, String name) {
040: return (name.endsWith(".class") && (name.indexOf("$") < 0));
041: }
042: };
043:
044: private String packages;
045:
046: private Path classpath;
047: private String classFilePath;
048:
049: private String excludePackages;
050:
051: private boolean printStats;
052:
053: private ClassLoader cl;
054: private ClassLoader oldCl;
055:
056: private int numTests;
057: private int numCoverageTests;
058:
059: public AntCoverageTask() {
060: numTests = 0;
061: numCoverageTests = 0;
062: printStats = true;
063: }
064:
065: /**
066: * Set the classpath to be used when running the Java class
067: *
068: * @param s an Ant Path object containing the classpath.
069: */
070: public void setClasspath(Path s) {
071: createClasspath().append(s);
072: }
073:
074: /**
075: * Adds a path to the classpath.
076: */
077: public Path createClasspath() {
078: classpath = new Path(getProject());
079: return classpath;
080: }
081:
082: /**
083: * Classpath to use, by reference.
084: */
085: public void setClasspathRef(Reference r) {
086: createClasspath().setRefid(r);
087: }
088:
089: public void setStats(boolean printStats) {
090: this .printStats = printStats;
091: }
092:
093: public void setClassFilePath(String classFilePath) {
094: this .classFilePath = classFilePath;
095: }
096:
097: public void setPackages(String packages) {
098: this .packages = packages;
099: }
100:
101: public void setExcludePackages(String excludePackages) {
102: this .excludePackages = excludePackages;
103: }
104:
105: private void createClassloader() throws BuildException {
106: Path path = new Path(this .getProject());
107: path.setLocation(new File(classFilePath));
108:
109: if (classpath != null) {
110: classpath.append(path);
111: } else {
112: classpath = path;
113: }
114:
115: cl = new AntClassLoader(getClass().getClassLoader(),
116: getProject(), classpath, true);
117:
118: oldCl = Thread.currentThread().getContextClassLoader();
119: Thread.currentThread().setContextClassLoader(cl);
120: }
121:
122: private void resetClassLoader() {
123: Thread.currentThread().setContextClassLoader(oldCl);
124: }
125:
126: private Set createSortedSet() {
127: return new TreeSet(new Comparator() {
128: public int compare(Object o1, Object o2) {
129: return o1.toString().compareTo(o2.toString());
130: }
131:
132: public boolean equals(Object obj) {
133: return false;
134: }
135: });
136: }
137:
138: private String normalize(String packageName) {
139: while (packageName.endsWith("*")) {
140: packageName = packageName.substring(0,
141: packageName.length() - 1);
142: }
143:
144: if (packageName.endsWith(File.separator)) {
145: packageName = packageName.substring(0,
146: packageName.length() - 1);
147: }
148:
149: return packageName;
150: }
151:
152: private File getPackageFile(String packageName)
153: throws BuildException {
154: packageName = classFilePath + File.separator + packageName;
155: return testDirectory("Package", packageName);
156: }
157:
158: private void findClasses(String packageName, Set<Class> addTo) {
159: File packageFile = getPackageFile(packageName);
160: File[] classes = packageFile.listFiles(CLASS_FILTER);
161:
162: for (int i = 0; i < classes.length; i++) {
163: String name = classes[i].getName();
164: String classname = packageName.replace(File.separatorChar,
165: '.')
166: + "." + name.substring(0, name.length() - 6);
167: try {
168: addTo.add(cl.loadClass(classname));
169: } catch (ClassNotFoundException cnfe) {
170: throw new BuildException(cnfe);
171: }
172: }
173:
174: }
175:
176: private Set<String> createPackageSet(String path,
177: String packageNames) {
178: HashSet<String> result = new HashSet<String>();
179:
180: if (packageNames == null) {
181: return result;
182: }
183:
184: packageNames = packageNames.replace('.', File.separatorChar);
185: Stack<String> stack = new Stack<String>();
186: StringTokenizer st = new StringTokenizer(packageNames, ",");
187: while (st.hasMoreTokens()) {
188: stack.push(st.nextToken().trim());
189: }
190:
191: while (!stack.isEmpty()) {
192: String packageName = (String) stack.pop();
193: boolean recurse = packageName.endsWith("*");
194:
195: packageName = normalize(packageName);
196: File packageFile = getPackageFile(packageName);
197:
198: result.add(packageName);
199:
200: if (recurse) {
201: File[] subdirs = packageFile.listFiles(DIR_FILTER);
202: for (int i = 0; i < subdirs.length; i++) {
203: stack.push(packageName + File.separator
204: + subdirs[i].getName() + "*");
205: }
206: }
207: }
208:
209: return result;
210: }
211:
212: private Set getClasses(Set packages) {
213: Set result = createSortedSet();
214:
215: Iterator it = packages.iterator();
216:
217: while (it.hasNext()) {
218: String packageName = (String) it.next();
219: findClasses(packageName, result);
220: }
221:
222: return result;
223: }
224:
225: private void addTest(Set<String> testClasses, Set<Class> resultSet,
226: Test test) throws ClassNotFoundException {
227: String className = test.getClass().getName();
228: if (testClasses.contains(className)) {
229: return;
230: }
231:
232: // Don't include TestSuites/CoverageDecorator + private classes
233: // in statistics.
234: if (!(className.startsWith(TestSuite.class.getName()) || className
235: .startsWith(CoverageDecorator.class.getName()))) {
236: numTests++;
237: testClasses.add(className);
238: }
239:
240: if (test instanceof TestSuite) {
241: Enumeration e = ((TestSuite) test).tests();
242: while (e.hasMoreElements()) {
243: addTest(testClasses, resultSet, (Test) e.nextElement());
244: }
245: }
246:
247: if (test instanceof CoverageDecorator) {
248: numCoverageTests++;
249: Class[] classes = ((CoverageDecorator) test)
250: .getClassesCovered();
251: for (int i = 0; i < classes.length; i++) {
252: resultSet.add(classes[i]);
253: }
254: }
255: }
256:
257: private Set getClassesCovered(Set<String> classSet)
258: throws ClassNotFoundException {
259: Set result = createSortedSet();
260: Iterator it = classSet.iterator();
261:
262: while (it.hasNext()) {
263: Set testClasses = new HashSet();
264:
265: Class next = (Class) it.next();
266: if (!next.isInterface()
267: && ((next.getModifiers() & Modifier.ABSTRACT) == 0)
268: && Test.class.isAssignableFrom(next)) {
269:
270: try {
271: Method suitMethod = next.getMethod(SUITE_METHOD,
272: SUITE_TYPES);
273: try {
274: addTest(testClasses, result, (Test) suitMethod
275: .invoke(null, SUITE_PARAMS));
276: } catch (Exception e) {
277: throw new BuildException(e);
278: }
279:
280: } catch (NoSuchMethodException nsme) {
281: // mmh, no suite() method, probably not a coverage
282: // test. But add anyway, so we get the statistics right.
283: Enumeration e = new TestSuite(next).tests();
284: while (e.hasMoreElements()) {
285: addTest(testClasses, result, (Test) e
286: .nextElement());
287: }
288: }
289:
290: }
291: }
292:
293: return result;
294: }
295:
296: private Set getClassesToCover(Set<Class> classes) {
297: Set<Class> result = createSortedSet();
298:
299: Iterator<Class> it = classes.iterator();
300:
301: while (it.hasNext()) {
302: Class next = (Class) it.next();
303:
304: if (!next.isInterface()
305: && !Test.class.isAssignableFrom(next)) {
306: result.add(next);
307: }
308: }
309:
310: return result;
311: }
312:
313: private File testDirectory(String name, String path)
314: throws BuildException {
315: if (path == null) {
316: throw new BuildException("'" + name
317: + "' property has to be set.");
318: }
319:
320: File dir = new File(path);
321: if (!(dir.exists() && dir.isDirectory())) {
322: throw new BuildException(name + " directory: '" + path
323: + "' does not exist.");
324: }
325:
326: return dir;
327: }
328:
329: public void execute() throws BuildException {
330: try {
331: testDirectory("ClassFilePath", classFilePath);
332: createClassloader();
333:
334: Set packageSet = createPackageSet(classFilePath, packages);
335: Set excludePackageSet = createPackageSet(classFilePath,
336: excludePackages);
337:
338: Set allClasses = getClasses(packageSet);
339: int numClasses = allClasses.size();
340:
341: Set classesCovered = getClassesCovered(allClasses);
342:
343: packageSet.removeAll(excludePackageSet);
344:
345: Set classes = getClasses(packageSet);
346: Set classesToCover = getClassesToCover(classes);
347:
348: int numClassesToCover = classesToCover.size();
349:
350: classesToCover.removeAll(classesCovered);
351:
352: Iterator it = classesToCover.iterator();
353: while (it.hasNext()) {
354: handleErrorOutput("Class not covered by a coverage test: '"
355: + ((Class) it.next()).getName() + "'");
356: }
357:
358: int numUncoveredClasses = classesToCover.size();
359: int numCoveredClasses = (numClassesToCover - numUncoveredClasses);
360:
361: if (printStats) {
362: handleOutput("");
363: handleOutput("Coverage Statistics");
364: handleOutput("--------------------");
365: handleOutput("Classes: "
366: + Util.leftFill(5, "" + numClasses));
367: handleOutput("Classes requiring coverage: "
368: + Util.leftFill(5, "" + numClassesToCover));
369: handleOutput("Testcases: "
370: + Util.leftFill(5, "" + numTests));
371: handleOutput("Coverage Testcases: "
372: + Util.leftFill(5, "" + numCoverageTests));
373: handleOutput("Classes covered: "
374: + Util.leftFill(5, "" + numCoveredClasses));
375: NumberFormat nf = NumberFormat.getPercentInstance();
376: String p = nf.format((double) numCoveredClasses
377: / (double) numClassesToCover);
378: handleOutput("Percentage covered: "
379: + Util.leftFill(5, p));
380: }
381:
382: resetClassLoader();
383: } catch (Throwable t) {
384: t.printStackTrace();
385: throw new BuildException(t);
386: }
387:
388: }
389:
390: }
|