/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package testprogress2; import com.sun.javadoc.AnnotationDesc; import com.sun.javadoc.AnnotationTypeDoc; import com.sun.javadoc.AnnotationValue; import com.sun.javadoc.ClassDoc; import com.sun.javadoc.ConstructorDoc; import com.sun.javadoc.Doc; import com.sun.javadoc.ExecutableMemberDoc; import com.sun.javadoc.LanguageVersion; import com.sun.javadoc.MethodDoc; import com.sun.javadoc.PackageDoc; import com.sun.javadoc.ParameterizedType; import com.sun.javadoc.RootDoc; import com.sun.javadoc.Tag; import com.sun.javadoc.AnnotationDesc.ElementValuePair; import testprogress2.TestMethodInformation.Color; import testprogress2.TestMethodInformation.Level; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.PrintWriter; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; /** * this doclet generates a .html report about the test coverage of the core api * reading test method's annotations. */ public class TestCoverageDoclet { /** * color for the stats: green, yellow, red */ public static final String[] COLORS = { "#a0ffa0", "#ffffa0", "#ff8080" }; // debugging output private static final boolean DEBUG = false; /** * Holds our basic output directory. */ private File directory; private boolean _ignoreInterfacesAndAbstractMethods = false; private boolean _doIncludeDisabledTests = false; private boolean _acceptCompleteWithOtherStatus = false; private Map resolved = new HashMap(8192); /** * Helper class for comparing element with each other, in oder to determine * an order. Uses lexicographic order of names. */ private class DocComparator implements Comparator { public int compare(Doc elem1, Doc elem2) { return elem1.name().compareTo(elem2.name()); } public boolean equals(Doc elem) { return this == elem; } } private class MemberComparator implements Comparator { public int compare(ExecutableMemberDoc mem1, ExecutableMemberDoc mem2) { return mem1.toString().compareTo(mem2.toString()); } } /** * Holds our comparator instance for everything. */ private DocComparator classComparator = new DocComparator(); private MemberComparator memberComparator = new MemberComparator(); private Map> classToSpecialTargets = new HashMap>(); /** * Creates a new instance of the TestProgressDoclet for a given target * directory. * * @param directory */ public TestCoverageDoclet(String directory) { this.directory = new File(directory); } /** * Opens a new output file and writes the usual HTML header. Directories are * created on demand. */ private PrintWriter openFile(String filename, String title) { File file = new File(directory, filename); File parent = file.getParentFile(); parent.mkdirs(); PrintWriter printer; try { printer = new PrintWriter(new FileOutputStream(file)); } catch (FileNotFoundException e) { throw new RuntimeException("file not found:" + e.getMessage()); } printer.println(""); printer.println(" "); printer.println(" " + title + ""); printer.println(""); printer.println(" "); printer.println(" "); printer.println("

" + title + "

"); return printer; } /** * Closes the given output file, writing the usual HTML footer before. */ private void closeFile(PrintWriter printer) { printer.println(" "); printer.println(""); printer.flush(); printer.close(); } private class TablePrinter { private PrintWriter pr; public TablePrinter(PrintWriter pr) { this.pr = pr; } public void printRow(String... columns) { pr.print(""); for (String col : columns) { pr.print("" + col + ""); } pr.print(""); } public void printPlain(String val) { pr.print(val); } public void printTableStart() { pr.print(""); } public void printTableEnd() { pr.print("
"); } public void printPlainLn(String val) { printPlain(val); pr.print("
"); } } /** * Processes the whole list of classes that JavaDoc knows about. */ private void process(RootDoc root) { System.out.println("V 0.9a"); String mode = getOption(root, "-f", 1, "dummy-see bash script"); System.out.println("mode: " + mode); // standard mode is to include disabled tests _doIncludeDisabledTests = mode.contains("countdisabled"); _acceptCompleteWithOtherStatus = mode.contains("acceptcandx"); if (_doIncludeDisabledTests) { System.out.println("- including disabled tests"); } else { System.out.println("- excluding disabled tests"); } if (_acceptCompleteWithOtherStatus) { System.out.println("- accepting complete tests with partial tests"); } else { System.out.println("- not accepting complete tests with partial " + "tests"); } // 1. traverse all test-classes (those extending JUnit's TestCase) // and collect the annotation info. // =================================================================== System.out.println("stage 1 - get targets from all junit test methods"); PrintWriter pr = openFile("testcoverage/test-annotation.html", "test class annotation coverage"); TablePrinter printer = new TablePrinter(pr); printer.printTableStart(); printer.printRow("Test package name", "JUnit classes", "Meth", "Unt", "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail"); ColorStat totalStats = new ColorStat("All test packages", null); PackageDoc[] packages = root.specifiedPackages(); Arrays.sort(packages, classComparator); for (PackageDoc pack : packages) { if (pack.allClasses().length != 0) { ColorStat packageStat = processTestPackage(pack); if (!packageStat.ignored) { // ignore those packages which have 0 tests in it printTestStats(printer, packageStat, true); totalStats.add(packageStat); } } } printer.printTableEnd(); printer.printPlainLn("

Summary of all test packages

"); printer.printTableStart(); printTestStats(printer, totalStats, false); printer.printTableEnd(); closeFile(pr); System.out.println("stage 2 - proxy test targets to abstract classes" + "and interfaces"); // 1/2 - traverse all normal (non-junit) classes for interface // and abstract methods implementation tests. ClassDoc[] classes = root.classes(); for (ClassDoc classDoc : classes) { if (!extendsJUnitTestCase(classDoc)) { MethodDoc[] methods = classDoc.methods(); for (MethodDoc methodDoc : methods) { AnnotationPointer ap = getAnnotationPointer(methodDoc, false); if (ap != null) { // if there are already tests targeting this method, // search for abstract/interface methods which this // method // implements, and mark them as tested by this method, // too. List impls = implementingMethods(methodDoc); for (MethodDoc impl : impls) { AnnotationPointer apImpl = getAnnotationPointer( impl, true); // add all tests which pointed to the original // method. apImpl.addProxiesFrom(ap); } } } } } // 2. traverse all "normal" (non-junit) source files // =================================================================== System.out.println("stage 3 - generating report for target api"); pr = openFile("index.html", "All target api packages"); printer = new TablePrinter(pr); printer.printPlainLn("Generated " + new Date().toString() + " - V0.9a
"); printer.printPlainLn("" + "annotation progress of test classes

"); printer.printTableStart(); printer.printRow("Package", "Classes", "Methods", "Untested", "Partial", "Complete", "Disabled", "Broken", "ToBeFixed", "KnownFail"); totalStats = new ColorStat("All target packages", null); packages = root.specifiedPackages(); Arrays.sort(packages, classComparator); int classCount = 0; for (PackageDoc pack : packages) { if (pack.allClasses().length != 0) { ColorStat packageStat = processPackage(pack); if (!packageStat.ignored) { printStats(printer, packageStat, true); totalStats.add(packageStat); classCount += Integer.parseInt(packageStat.getExtra()); } } } printer.printTableEnd(); totalStats.setExtra("" + classCount); printer.printPlainLn("

Summary of all target packages

"); printer.printTableStart(); printStats(printer, totalStats, false); printer.printTableEnd(); closeFile(pr); } /** * Returns the interface(s) method(s) and the abstract method(s) that a * given method implements, or an empty list if it does not implement * anything. */ private List implementingMethods(MethodDoc doc) { List resultmethods = new ArrayList(); ClassDoc clazz = doc.containingClass(); implementedMethod0(resultmethods, doc, clazz, false); return resultmethods; } /** * Recursive helper method for finding out which interface methods or * abstract methods a given method implements. */ private void implementedMethod0(List resultmethods, MethodDoc doc, ClassDoc testClass, boolean testMethods) { // test all methods of this class if (testMethods) { MethodDoc[] methods = testClass.methods(); for (int j = 0; j < methods.length; j++) { MethodDoc methodDoc = methods[j]; if ((methodDoc.isAbstract() || testClass.isInterface()) && doc.overrides(methodDoc)) { resultmethods.add(methodDoc); } } } // test all implementing interfaces ClassDoc[] ifs = testClass.interfaces(); for (int i = 0; i < ifs.length; i++) { ClassDoc iface = ifs[i]; implementedMethod0(resultmethods, doc, iface, true); } // test the superclass ClassDoc superclass = testClass.superclass(); if (superclass != null) { implementedMethod0(resultmethods, doc, superclass, true); } } private ColorStat processTestPackage(PackageDoc packageDoc) { ColorStat stats = new ColorStat(packageDoc.name(), getPackageBaseLink(packageDoc) + "/package.html"); if (hasHideFlag(packageDoc)) { stats.ignored = true; return stats; } String file = getPackageDir("testcoverage", packageDoc) + "/package.html"; PrintWriter pr = openFile(file, "Test package " + packageDoc.name()); TablePrinter printer = new TablePrinter(pr); printer.printTableStart(); printer.printRow("Class", "Extra", "Meth", "Unt", "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail"); ClassDoc[] classes = packageDoc.allClasses(); Arrays.sort(classes, classComparator); int junitCnt = 0; for (ClassDoc clazz : classes) { if (extendsJUnitTestCase(clazz)) { junitCnt++; ColorStat subStats = processTestClass(clazz); printTestStats(printer, subStats, true); stats.add(subStats); } else { printer.printRow(clazz.name() + " ignored (no junit class): ", "", "", "", "", "", "", "", ""); } } printer.printTableEnd(); printer.printPlainLn("

Test package summary

"); printer.printTableStart(); printStats(printer, stats, false); printer.printTableEnd(); closeFile(pr); if (junitCnt == 0) { if ((packageDoc.name().contains("tests.") || packageDoc.name().contains("junit.") || packageDoc .name().contains(".testframework")) && !(packageDoc.name().equals("junit.framework"))) { System.err.println("warning!: no junit classes in package '" + packageDoc.name() + "' even though package name " + "contains tests.,junit. or .testframework"); } stats = new ColorStat(packageDoc.name(), getPackageBaseLink(packageDoc) + "/package.html"); stats.incColor(TestMethodInformation.Color.GREEN); stats.setExtra("Ignored since no Junit test and suites"); stats.ignored = true; } else { stats.setExtra("" + junitCnt); } return stats; } private ColorStat processPackage(PackageDoc packageDoc) { ColorStat stats = new ColorStat(packageDoc.name(), getPackageBaseLink(packageDoc) + "/package.html"); if (hasHideFlag(packageDoc)) { stats.ignored = true; return stats; } String file = getPackageDir("", packageDoc) + "/package.html"; PrintWriter pr = openFile(file, "Package " + packageDoc.name()); TablePrinter printer = new TablePrinter(pr); printer.printTableStart(); printer.printRow("Class", "Extra", "Meth", "Unt", "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail"); ClassDoc[] classes = packageDoc.allClasses(); Arrays.sort(classes, classComparator); int cnt = 0; int junitCnt = 0; for (ClassDoc clazz : classes) { cnt++; if (hasHideFlag(clazz)) { // ignored since it has a @hide in the javadoc on the class // level } else if (extendsJUnitTestCase(clazz)) { printer.printRow("ignored (junit class): " + clazz.name()); junitCnt++; } else if (clazz.name().equals("AllTests")) { printer.printRow("ignored (junit test suite class): " + clazz.name()); junitCnt++; } else { ColorStat subStats = processClass(clazz); printStats(printer, subStats, true); stats.add(subStats); } } printer.printTableEnd(); printer.printPlainLn("

Target package summary

"); printer.printTableStart(); printStats(printer, stats, false); printer.printTableEnd(); closeFile(pr); if (junitCnt == cnt || packageDoc.name().contains("tests.") || packageDoc.name().contains("junit.") || packageDoc.name().contains(".testframework") || packageDoc.name().endsWith(".cts")) { // we only have junit classes -> mark green stats = new ColorStat(packageDoc.name(), getPackageBaseLink(packageDoc) + "/package.html"); stats.incColor(TestMethodInformation.Color.GREEN); stats .setExtra(junitCnt == cnt ? "Ignored since only Junit test and " + "suites" : "Ignored since \"tests.\" in name - recheck"); stats.ignored = true; } else { stats.setExtra("" + cnt); } return stats; } private boolean hasHideFlag(Doc doc) { // workaround for the non-recognized @hide tag in package.html if (doc instanceof PackageDoc) { String comment = doc.getRawCommentText(); return comment != null && comment.contains("@hide"); } else { Tag[] hideTags = doc.tags("hide"); return hideTags.length > 0; } } private ColorStat processTestClass(ClassDoc clazz) { String file = getPackageDir("testcoverage", clazz.containingPackage()) + "/" + clazz.name() + ".html"; PrintWriter pr = openFile(file, "Test class " + clazz.qualifiedName()); TablePrinter printer = new TablePrinter(pr); ColorStat classStat = new ColorStat(clazz.name(), clazz.name() + ".html"); TestTargetClass testTargetClass = getTargetClass(clazz); ClassDoc targetClass = testTargetClass.targetClass; String note = "Note:"; if (targetClass == null) { note += "
targetClass annotation missing!
"; } else { // add untested[] annotations to statistics ClassOriginator co = new ClassOriginator(clazz, null); AnnotationDesc[] annotsC = testTargetClass.untestedMethods .toArray(new AnnotationDesc[] {}); if (annotsC.length > 0) { // we have "untested" refs ColorStat classLevelStat = new ColorStat(clazz.name(), null); TestMethodInformation tminfo = new TestMethodInformation(co, annotsC, targetClass); if (tminfo.getError() != null) { printer.printPlainLn("Error:" + tminfo.getError()); classLevelStat.incColor(Color.RED); } else { linkTargets(tminfo.getTargets()); classLevelStat.incColor(Color.GREEN); } classStat.add(classLevelStat); } } printer.printPlainLn(note); printer.printTableStart(); printer.printRow("Method", "Note", "Meth", "Unt", "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail"); int methodCnt = 0; // also collects test... methods from all superclasses below TestCase, // since those are called as well List testMethods = collectAllTestMethods(clazz); Collections.sort(testMethods, memberComparator); for (MethodDoc testMethod : testMethods) { methodCnt++; // Make disabled tests visible in the report so we don't forget // them. boolean disTest = testMethod.name().startsWith("_test"); ColorStat methodStat = new ColorStat(testMethod.name(), null); if (disTest) { methodStat.incDisabledTestsCnt(); } String comments = disTest ? "" + "DISABLED" : null; MethodOriginator mo = new MethodOriginator(testMethod, clazz, comments); AnnotationDesc[] annots = testMethod.annotations(); TestMethodInformation minfo = new TestMethodInformation(mo, annots, targetClass); linkTargets(minfo.getTargets()); String extra = null; if (comments != null) { if (extra == null) extra = ""; extra += comments; } if (minfo.getError() != null) { // error case if (extra == null) extra = ""; extra += "Error: " + minfo.getError() + "
"; methodStat.addMethodInfo(minfo); } else { if (mo.getKnownFailure() != null) { methodStat.incKnownFailureCnt(); if (extra == null) extra = ""; extra += mo.getKnownFailure(); } // check for @BrokenTest if (mo.getBrokenTest() != null) { // override with warning methodStat.incBrokenTestCnt(); methodStat.incColor(Color.YELLOW); if (extra == null) extra = ""; extra += mo.getBrokenTest(); } // check for @ToBeFixed if (mo.getToBeFixed() != null) { // override with warning methodStat.incToBeFixedCnt(); methodStat.incColor(Color.YELLOW); if (extra == null) { extra = ""; } extra += mo.getToBeFixed(); } else { // add regular stats methodStat.addMethodInfo(minfo); } } if (extra != null) { methodStat.setExtra(extra); } printTestStats(printer, methodStat, false); classStat.add(methodStat); } printer.printTableEnd(); printer.printPlainLn("

Test class summary

"); printer.printTableStart(); printStats(printer, classStat, false); printer.printTableEnd(); closeFile(pr); classStat.setExtra("#methods: " + testMethods.size()); return classStat; } private void linkTargets(List targets) { for (TestTargetNew ttn : targets) { if (ttn.getTargetMethod() != null) { AnnotationPointer tar = getAnnotationPointer(ttn .getTargetMethod(), true); tar.addTestTargetNew(ttn); } else if (ttn.getTargetClass() != null) { // some special target only pointing to a class, not a method. addToClassTargets(ttn.getTargetClass(), ttn); } } } private boolean isGreen(TestMethodInformation.Level level) { boolean lComplete = level == TestMethodInformation.Level.COMPLETE; boolean lSufficient = level == TestMethodInformation.Level.SUFFICIENT; boolean lPartialOk = level == TestMethodInformation.Level.PARTIAL_COMPLETE; boolean lPartial = level == TestMethodInformation.Level.PARTIAL; boolean lTodo = level == TestMethodInformation.Level.TODO; boolean lNotFeasible = level == TestMethodInformation.Level.NOT_FEASIBLE; boolean lNotNecessary = level == TestMethodInformation.Level.NOT_NECESSARY; return lComplete || lPartialOk || lSufficient || lNotFeasible || lNotNecessary; } private ColorStat processClass(ClassDoc clazz) { String file = getPackageDir("", clazz.containingPackage()) + "/" + clazz.name() + ".html"; String classDesc = getClassString(clazz); PrintWriter pr = openFile(file, classDesc); TablePrinter printer = new TablePrinter(pr); printer.printPlain("package " + clazz.containingPackage() + ""); ColorStat classStats = new ColorStat(classDesc, clazz.name() + ".html"); // list special targets List classTargets = getTargetsFor(clazz); if (classTargets != null) { printer.printPlain("

Class level tests

"); printer.printPlain("
    "); for (TestTargetNew ttn : classTargets) { String line = "
  • " + ttn.getOriginator().asString(); Level lev = ttn.getLevel(); line += " " + lev.name() + "" + (ttn.getNotes() != null ? "
    Notes: " + ttn.getNotes() : "") + "
  • "; printer.printPlain(line); } printer.printPlainLn("
"); } printer.printPlain("

Method level tests

"); printer.printTableStart(); printer.printRow("Method", "Tested by", "Meth", "Unt", "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail"); ConstructorDoc[] constructors = clazz.constructors(); Arrays.sort(constructors, classComparator); int cnt = 0; for (ConstructorDoc constructor : constructors) { if (!hasHideFlag(constructor) && !hasHideFlag(clazz)) { cnt++; ColorStat memberStat = processElement(constructor); printStats(printer, memberStat, false); classStats.add(memberStat); } } MethodDoc[] methods = clazz.methods(); Arrays.sort(methods, classComparator); for (MethodDoc method : methods) { if (!hasHideFlag(method) && !hasHideFlag(clazz)) { cnt++; ColorStat subStat = processElement(method); printStats(printer, subStat, false); classStats.add(subStat); } } printer.printTableEnd(); printer.printPlainLn("

Target class summary

"); printer.printTableStart(); printStats(printer, classStats, false); printer.printTableEnd(); closeFile(pr); classStats.setExtra("#methods: " + cnt); // mark as green if (_ignoreInterfacesAndAbstractMethods && clazz.isInterface()) { classStats = new ColorStat(clazz.name() + (clazz.isInterface() ? " (Interface)" : ""), clazz.name() + ".html"); int mcnt = clazz.methods().length; // fake all methods to green for (int i = 0; i < mcnt; i++) { classStats.incColor(TestMethodInformation.Color.GREEN); } classStats.setExtra("Ignored since interface"); } return classStats; } private class TestTargetClass { ClassDoc targetClass; List untestedMethods = new ArrayList(); // a List of @TestTargetNew annotations } private TestTargetClass getTargetClass(ClassDoc classDoc) { // get the class annotation which names the default test target class TestTargetClass ttc = new TestTargetClass(); ClassDoc targetClass = null; AnnotationDesc[] cAnnots = classDoc.annotations(); for (AnnotationDesc cAnnot : cAnnots) { AnnotationTypeDoc atype = cAnnot.annotationType(); if (atype.toString().equals("dalvik.annotation.TestTargetClass")) { ElementValuePair[] cpairs = cAnnot.elementValues(); for (int i = 0; i < cpairs.length; i++) { ElementValuePair ev = cpairs[i]; String elName = ev.element().name(); if (elName.equals("value")) { // the target class AnnotationValue av = ev.value(); Object obj = av.value(); // value must be a class doc if (obj instanceof ClassDoc) { targetClass = (ClassDoc)obj; } else if (obj instanceof ParameterizedType) { targetClass = ((ParameterizedType)obj).asClassDoc(); } else throw new RuntimeException( "annotation elem value is of type " + obj.getClass().getName()); } else if (elName.equals("untestedMethods")) { // TestTargetNew[] untestedMethods() default {}; AnnotationValue[] targets = (AnnotationValue[])ev .value().value(); for (AnnotationValue ttn : targets) { // each untested method must be a TestTargetNew AnnotationDesc ttnd = (AnnotationDesc)ttn.value(); ttc.untestedMethods.add(ttnd); } } } } } ttc.targetClass = targetClass; return ttc; } private List collectAllTestMethods(ClassDoc classDoc) { List m = new ArrayList(); ClassDoc curCl = classDoc; do { m.addAll(getJunitTestMethods(curCl)); } while ((curCl = curCl.superclass()) != null && !curCl.qualifiedName().equals("junit.framework.TestCase")); return m; } private List getJunitTestMethods(ClassDoc classDoc) { List cl = new ArrayList(); for (MethodDoc methodDoc : classDoc.methods()) { if (methodDoc.isPublic() && (methodDoc.name().startsWith("test") || methodDoc.name() .startsWith("_test"))) { cl.add(methodDoc); } } return cl; } private class ColorStat { private String name; private String link; private String extra; public boolean ignored; private int[] cntCol = new int[4]; private int disabledTestsCnt = 0; private int brokenTestCnt = 0; private int toBeFixedCnt = 0; private int knownFailureCnt = 0; public String getName() { return name; } public String getLink() { return link; } public ColorStat(String name, String link) { this.name = name; this.link = link; } public void add(ColorStat subStat) { for (int i = 0; i < cntCol.length; i++) { cntCol[i] += subStat.cntCol[i]; } disabledTestsCnt += subStat.disabledTestsCnt; brokenTestCnt += subStat.brokenTestCnt; toBeFixedCnt += subStat.toBeFixedCnt; knownFailureCnt += subStat.knownFailureCnt; } public void incDisabledTestsCnt() { disabledTestsCnt++; } public void incBrokenTestCnt() { brokenTestCnt++; } public void incToBeFixedCnt() { toBeFixedCnt++; } public void incKnownFailureCnt() { knownFailureCnt++; } public void incColor(TestMethodInformation.Color color) { cntCol[color.ordinal()]++; } public int getColorCnt(TestMethodInformation.Color color) { return cntCol[color.ordinal()]; } public void addMethodInfo(TestMethodInformation minfo) { TestMethodInformation.Color c = minfo.getColor(); int ord = c.ordinal(); cntCol[ord]++; } public void setExtra(String extra) { this.extra = extra; } public String getExtra() { return extra; } public int getDisabledTestsCnt() { return disabledTestsCnt; } public int getBrokenTestCnt() { return brokenTestCnt; } public int getToBeFixedCnt() { return toBeFixedCnt; } public int getKnownFailureCnt() { return knownFailureCnt; } } private AnnotationPointer getAnnotationPointer( ExecutableMemberDoc targetMethod, boolean create) { AnnotationPointer ap = resolved.get(targetMethod); if (create && ap == null) { ap = new AnnotationPointer(targetMethod); resolved.put(targetMethod, ap); } return ap; } private void addToClassTargets(ClassDoc targetClass, TestTargetNew ttn) { List targets = classToSpecialTargets.get(targetClass); if (targets == null) { targets = new ArrayList(); classToSpecialTargets.put(targetClass, targets); } targets.add(ttn); } private List getTargetsFor(ClassDoc targetClass) { return classToSpecialTargets.get(targetClass); } private boolean extendsJUnitTestCase(ClassDoc classDoc) { // junit.framework.TestCase.java ClassDoc curClass = classDoc; while ((curClass = curClass.superclass()) != null) { if (curClass.toString().equals("junit.framework.TestCase")) { return true; } } return false; } /** * Processes a single method/constructor. */ private ColorStat processElement(ExecutableMemberDoc method) { if (DEBUG) System.out.println("Processing " + method); ColorStat memberStats = new ColorStat(getMethodString(method), null); TestMethodInformation.Color c = TestMethodInformation.Color.RED; // if flagged, consider abstract method ok also without tests if (_ignoreInterfacesAndAbstractMethods && method instanceof MethodDoc && ((MethodDoc)method).isAbstract()) { c = TestMethodInformation.Color.GREEN; memberStats.setExtra("ignored since abstract"); } else { AnnotationPointer ap = getAnnotationPointer(method, false); int testedByCnt = 0; if (ap != null) { List targets = ap.getTargets(); testedByCnt = targets.size(); if (testedByCnt == 0) { throw new RuntimeException( "existing annotation pointer with no entries!, " + "method:" + method); } // at least tested by one method String by = "
    "; int completeTestCnt = 0; int partialOkTestCnt = 0; int partialTestCnt = 0; int todoTestCnt = 0; int notFeasableTestCnt = 0; int notNecessaryTestCnt = 0; int sufficientTestCnt = 0; // int totalCnt = targets.size(); for (TestTargetNew target : targets) { Originator originator = target.getOriginator(); boolean disabledTest = originator.isDisabled(); boolean brokenTest = originator.getBrokenTest() != null; boolean toBeFixed = originator.getToBeFixed() != null; boolean knownFailure = originator.getKnownFailure() != null; by += "
  • " + originator.asString(); TestMethodInformation.Level lev; if (target.isHavingProblems()) { lev = TestMethodInformation.Level.TODO; } else { lev = target.getLevel(); } // TODO: improve: avoid c&p by adding ColorStat.addInfo // (originator) or similar if (disabledTest) { memberStats.incDisabledTestsCnt(); } if (brokenTest) { memberStats.incBrokenTestCnt(); } if (toBeFixed) { memberStats.incToBeFixedCnt(); } if (knownFailure) { memberStats.incKnownFailureCnt(); } boolean lComplete = lev == TestMethodInformation.Level.COMPLETE; boolean lSufficient = lev == TestMethodInformation.Level.SUFFICIENT; boolean lPartialOk = lev == TestMethodInformation.Level.PARTIAL_COMPLETE; boolean lPartial = lev == TestMethodInformation.Level.PARTIAL; boolean lTodo = lev == TestMethodInformation.Level.TODO; boolean lNotFeasible = lev == TestMethodInformation.Level.NOT_FEASIBLE; boolean lNotNecessary = lev == TestMethodInformation.Level.NOT_NECESSARY; by += " " + lev.name() + "" + (target.getNotes() != null ? "
    Notes: " + target.getNotes() : ""); // only count tests when they are either ok (not disabled) // or if the doIncludeDisabledTests flag is set if ((_doIncludeDisabledTests || !disabledTest) && (!brokenTest) && (!toBeFixed)) { if (lComplete) { completeTestCnt++; } else if (lPartialOk) { partialOkTestCnt++; } else if (lPartial) { partialTestCnt++; } else if (lTodo) { todoTestCnt++; } else if (lSufficient) { sufficientTestCnt++; } else if (lNotFeasible) { notFeasableTestCnt++; } else if (lNotNecessary) { notNecessaryTestCnt++; } } if (toBeFixed) { partialTestCnt++; } if (DEBUG) { System.out.println("completeTestCnt: " + completeTestCnt + ", partialOkTestCnt: " + partialOkTestCnt + ", partialTestCnt: " + partialTestCnt + ", todoTestCnt: " + todoTestCnt + ", sufficientTestCnt: " + sufficientTestCnt + ", notFeasableTestCnt: " + notFeasableTestCnt + ", notNecessaryTestCnt: " + notNecessaryTestCnt); } by += "
  • "; } String warnings = ""; // calculate the color int singularTestCnt = notFeasableTestCnt + notNecessaryTestCnt; boolean isAbstract = (method.containingClass().isInterface() || (method instanceof MethodDoc) && ((MethodDoc)method).isAbstract()); if (_acceptCompleteWithOtherStatus && (completeTestCnt > 0 || sufficientTestCnt > 0)) { c = TestMethodInformation.Color.GREEN; } else if (_acceptCompleteWithOtherStatus && (partialOkTestCnt > 1)) { c = TestMethodInformation.Color.GREEN; } else { if (singularTestCnt > 0) { // we have tests which claim not_neccessary or // not_feasible if (targets.size() > singularTestCnt) { // other tests exist c = TestMethodInformation.Color.RED; warnings += "WARNING:NOT_FEASIBLE or " + "NOT_NECESSARY together with other " + "status!
    "; } else { // a collection of not_necessary and/or not_feasible if (notNecessaryTestCnt > 0 && notFeasableTestCnt > 0) { // a blend of both -> error warnings += "WARNING:both NOT_FEASIBLE " + "and NOT_NECESSARY together!
    "; c = TestMethodInformation.Color.RED; } else { // just one sort of tests -> ok and // sufficent c = TestMethodInformation.Color.GREEN; } } } else if (todoTestCnt > 0) { c = TestMethodInformation.Color.RED; } else if (partialTestCnt > 0) { // at least one partial test c = TestMethodInformation.Color.YELLOW; if (completeTestCnt > 0 || sufficientTestCnt > 0) { if (_acceptCompleteWithOtherStatus) { // accept complete even if there are partials c = TestMethodInformation.Color.GREEN; } else if (completeTestCnt > 0) { // yellow+warning: mixed PARTIAL_COMPLETE and // COMPLETE status warnings += "WARNING: mixed PARTIAL " + "and COMPLETE status
    "; } } } else if (partialOkTestCnt > 0 || sufficientTestCnt > 0) { // at least one partialOk test and no partial tests c = TestMethodInformation.Color.GREEN; if (partialOkTestCnt == 1) { // yellow: 1 PARTIAL_COMPLETE (we need either zero // or >=2 PARTIAL_OK tests) warnings += "WARNING: only one " + "PARTIAL_COMPLETE status
    "; c = TestMethodInformation.Color.YELLOW; } } else if (completeTestCnt > 0 || singularTestCnt == 1) { // only complete tests c = TestMethodInformation.Color.GREEN; } if (completeTestCnt > 1 && !isAbstract && !_acceptCompleteWithOtherStatus) { // green+warning: >1 COMPLETE (we need either 0 or 1 // COMPLETE, more would be strange, but possible) warnings += "WARNING: more than one " + "COMPLETE status
    "; if (c != TestMethodInformation.Color.RED) { c = TestMethodInformation.Color.YELLOW; } } } by = warnings + by; memberStats.setExtra(by); } else { // else this class has no single test that targets the // current method // handle special cases: // can be ok if the current method is a default constructor // which does not occur in the source code. if (method.isConstructor() && method.signature().equals("()")) { if (method.position() != null) { // hacky stuff - the doclet should return null for a // default constructor, // but instead returns a source position pointing to the // same location as // the class. String constPos = method.position().toString(); String classPos = method.containingClass().position() .toString(); if (constPos.equals(classPos)) { // we have a default constructor not visible in // source code -> mark green, no testing needed c = TestMethodInformation.Color.GREEN; memberStats .setExtra("automatically marked green " + "since implicit default " + "constructor"); } } else { // should be called for default constructors according // to the doclet spec, but is never called. // spec: // "A default constructor returns null because it has no // location in the source file" // ?? // what about default constructors being in the source // code? System.err.println("warning: doclet returned null for " + "source position: method:" + method); } } else if (method.containingClass().superclass() != null && method.containingClass().superclass() .qualifiedName().equals("java.lang.Enum")) { // check enum types // valueOf (java.lang.String) and // values() // do not need to be tested, since they are autogenerated // by the compiler String sig = method.name() + method.signature(); if (sig.equals("valueOf(java.lang.String)") || sig.equals("values()")) { c = TestMethodInformation.Color.GREEN; memberStats .setExtra("automatically marked green since " + "generated by compiler for enums"); } } } } memberStats.incColor(c); return memberStats; } private String getMethodString(ExecutableMemberDoc method) { String methodDesc = (method.isPublic() ? "public " : method .isProtected() ? "protected " : method.isPrivate() ? "private " : ""); return methodDesc + "" + method.name() + " " + method.signature(); } private String getClassString(ClassDoc clazz) { return (clazz.isPublic() ? "public " : clazz.isProtected() ? "protected " : clazz.isPrivate() ? "private " : "") + (clazz.isInterface() ? "interface" : "class") + " " + clazz.name(); } private void printTestStats(TablePrinter printer, ColorStat stat, boolean wantLink) { printStats(printer, stat, wantLink); } private void printStats(TablePrinter printer, ColorStat stat, boolean wantLink) { int redCnt = stat.getColorCnt(TestMethodInformation.Color.RED); int yellowCnt = stat.getColorCnt(TestMethodInformation.Color.YELLOW); int greenCnt = stat.getColorCnt(TestMethodInformation.Color.GREEN); int disabledCnt = stat.getDisabledTestsCnt(); int brokenTestCnt = stat.getBrokenTestCnt(); int toBeFixedCnt = stat.getToBeFixedCnt(); int knownFailureCnt = stat.getKnownFailureCnt(); int total = redCnt + yellowCnt + greenCnt; String link = stat.getLink(); String namePart; if (wantLink && link != null) { namePart = "" + stat.getName() + ""; } else { namePart = stat.getName(); } String extra = stat.getExtra() == null ? "" : stat.getExtra(); int totalDots = 120; float toP = total == 0 ? 0 : (((float)totalDots) / total); int redD = (int)(toP * redCnt); if (redD == 0 && redCnt > 0) { // never let red cut to zero; redD = 1; } int yellowD = (int)(toP * yellowCnt); if (yellowD == 0 && yellowCnt > 0) { yellowD = 1; } int greenD = totalDots - redD - yellowD; // (int)(toP*greenCnt); printer.printRow(namePart, extra, "" + total, "" + redCnt, "" + yellowCnt, "" + greenCnt, "" + disabledCnt, "" + brokenTestCnt, "" + toBeFixedCnt, "" + knownFailureCnt, "" + (redCnt == 0 ? "" : "" + getDots(redD) + "") + (yellowCnt == 0 ? "" : "" + getDots(yellowD) + "") + (greenCnt == 0 && total > 0 ? "" : "" + getDots(greenD) + "") + "   " + getDots(total / 10) + ""); } private String getDots(int cnt) { StringBuffer sb = new StringBuffer(); for (int i = 0; i < cnt; i++) { sb.append(" "); } return sb.toString(); } /** * Returns the directory for a given package. Basically converts embedded * dots in the name into slashes. */ private String getPackageBaseLink(PackageDoc pack) { return pack.name().replace('.', '/'); } private File getPackageDir(String prefix, PackageDoc pack) { if (pack == null || pack.name() == null || "".equals(pack.name())) { return new File(prefix + "/" + "."); } else { return new File(prefix + "/" + pack.name().replace('.', '/')); } } /** * Called by JavaDoc to find our which command line arguments are supported * and how many parameters they take. Part of the JavaDoc API. * * @param option the options * @return an int */ public static int optionLength(String option) { if ("-d".equals(option)) { return 2; } if ("-f".equals(option)) { return 2; } else { return 0; } } /** * Called by JavaDoc to query a specific command line argument. Part of the * JavaDoc API. */ private static String getOption(RootDoc root, String option, int index, String defValue) { String[][] allOptions = root.options(); for (int i = 0; i < allOptions.length; i++) { if (allOptions[i][0].equals(option)) { return allOptions[i][index]; } } return defValue; } /** * Called by JavaDoc to find out which Java version we claim to support. * Part of the JavaDoc API. * * @return the version of the language */ public static LanguageVersion languageVersion() { return LanguageVersion.JAVA_1_5; } /** * The main entry point called by JavaDoc after all required information has * been collected. Part of the JavaDoc API. * * @param root * @return whether an error occurred */ public static boolean start(RootDoc root) { try { String target = getOption(root, "-d", 1, "."); TestCoverageDoclet doclet = new TestCoverageDoclet(target); doclet.process(root); File file = new File(target, "index.html"); System.out.println("Please see complete report in " + file.getAbsolutePath()); } catch (Exception ex) { ex.printStackTrace(); return false; } return true; } }