1 /* 2 * Copyright (C) 2008 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package testprogress2; 18 19 import com.sun.javadoc.AnnotationDesc; 20 import com.sun.javadoc.AnnotationTypeDoc; 21 import com.sun.javadoc.AnnotationValue; 22 import com.sun.javadoc.ClassDoc; 23 import com.sun.javadoc.ConstructorDoc; 24 import com.sun.javadoc.Doc; 25 import com.sun.javadoc.ExecutableMemberDoc; 26 import com.sun.javadoc.LanguageVersion; 27 import com.sun.javadoc.MethodDoc; 28 import com.sun.javadoc.PackageDoc; 29 import com.sun.javadoc.ParameterizedType; 30 import com.sun.javadoc.RootDoc; 31 import com.sun.javadoc.Tag; 32 import com.sun.javadoc.AnnotationDesc.ElementValuePair; 33 34 import testprogress2.TestMethodInformation.Color; 35 import testprogress2.TestMethodInformation.Level; 36 37 import java.io.File; 38 import java.io.FileNotFoundException; 39 import java.io.FileOutputStream; 40 import java.io.PrintWriter; 41 import java.util.ArrayList; 42 import java.util.Arrays; 43 import java.util.Collections; 44 import java.util.Comparator; 45 import java.util.Date; 46 import java.util.HashMap; 47 import java.util.List; 48 import java.util.Map; 49 50 /** 51 * this doclet generates a .html report about the test coverage of the core api 52 * reading test method's annotations. 53 */ 54 public class TestCoverageDoclet { 55 56 /** 57 * color for the stats: green, yellow, red 58 */ 59 public static final String[] COLORS = { 60 "#a0ffa0", "#ffffa0", "#ff8080" 61 }; 62 63 // debugging output 64 private static final boolean DEBUG = false; 65 66 /** 67 * Holds our basic output directory. 68 */ 69 private File directory; 70 71 private boolean _ignoreInterfacesAndAbstractMethods = false; 72 73 private boolean _doIncludeDisabledTests = false; 74 75 private boolean _acceptCompleteWithOtherStatus = false; 76 77 private Map<ExecutableMemberDoc, AnnotationPointer> resolved = 78 new HashMap<ExecutableMemberDoc, AnnotationPointer>(8192); 79 80 /** 81 * Helper class for comparing element with each other, in oder to determine 82 * an order. Uses lexicographic order of names. 83 */ 84 private class DocComparator implements Comparator<Doc> { 85 public int compare(Doc elem1, Doc elem2) { 86 return elem1.name().compareTo(elem2.name()); 87 } 88 89 public boolean equals(Doc elem) { 90 return this == elem; 91 } 92 } 93 94 private class MemberComparator implements Comparator<ExecutableMemberDoc> { 95 public int compare(ExecutableMemberDoc mem1, ExecutableMemberDoc mem2) { 96 return mem1.toString().compareTo(mem2.toString()); 97 } 98 } 99 100 /** 101 * Holds our comparator instance for everything. 102 */ 103 private DocComparator classComparator = new DocComparator(); 104 105 private MemberComparator memberComparator = new MemberComparator(); 106 107 private Map<ClassDoc, List<TestTargetNew>> classToSpecialTargets = 108 new HashMap<ClassDoc, List<TestTargetNew>>(); 109 110 /** 111 * Creates a new instance of the TestProgressDoclet for a given target 112 * directory. 113 * 114 * @param directory 115 */ 116 public TestCoverageDoclet(String directory) { 117 this.directory = new File(directory); 118 } 119 120 /** 121 * Opens a new output file and writes the usual HTML header. Directories are 122 * created on demand. 123 */ 124 private PrintWriter openFile(String filename, String title) { 125 File file = new File(directory, filename); 126 File parent = file.getParentFile(); 127 parent.mkdirs(); 128 129 PrintWriter printer; 130 try { 131 printer = new PrintWriter(new FileOutputStream(file)); 132 } catch (FileNotFoundException e) { 133 throw new RuntimeException("file not found:" + e.getMessage()); 134 } 135 136 printer.println("<html>"); 137 printer.println(" <head>"); 138 printer.println(" <title>" + title + "</title>"); 139 printer.println("<style type=\"text/css\">\n" 140 + "body, body table tr td { font-size:10pt;font-family: " 141 + " sans-serif; }\n" + "li { padding-bottom:2px }\n" 142 + "table { width:100%; border-width: 0px; border: solid; " 143 + "border-collapse: collapse;}\n" 144 + "table tr td { vertical-align:top; padding:3px; border: " 145 + "1px solid black;}\n" + "</style>"); 146 printer.println(" </head>"); 147 printer.println(" <body>"); 148 printer.println(" <h1>" + title + "</h1>"); 149 150 return printer; 151 } 152 153 /** 154 * Closes the given output file, writing the usual HTML footer before. 155 */ 156 private void closeFile(PrintWriter printer) { 157 printer.println(" </body>"); 158 printer.println("</html>"); 159 printer.flush(); 160 printer.close(); 161 } 162 163 private class TablePrinter { 164 private PrintWriter pr; 165 166 public TablePrinter(PrintWriter pr) { 167 this.pr = pr; 168 } 169 170 public void printRow(String... columns) { 171 pr.print("<tr style=\"background-color:white\">"); 172 for (String col : columns) { 173 pr.print("<td>" + col + "</td>"); 174 } 175 pr.print("</tr>"); 176 } 177 178 public void printPlain(String val) { 179 pr.print(val); 180 } 181 182 public void printTableStart() { 183 pr.print("<table>"); 184 } 185 186 public void printTableEnd() { 187 pr.print("</table>"); 188 } 189 190 public void printPlainLn(String val) { 191 printPlain(val); 192 pr.print("<br>"); 193 } 194 } 195 196 /** 197 * Processes the whole list of classes that JavaDoc knows about. 198 */ 199 private void process(RootDoc root) { 200 System.out.println("V 0.9a"); 201 String mode = getOption(root, "-f", 1, "dummy-see bash script"); 202 System.out.println("mode: " + mode); 203 204 // standard mode is to include disabled tests 205 _doIncludeDisabledTests = mode.contains("countdisabled"); 206 _acceptCompleteWithOtherStatus = mode.contains("acceptcandx"); 207 if (_doIncludeDisabledTests) { 208 System.out.println("- including disabled tests"); 209 } else { 210 System.out.println("- excluding disabled tests"); 211 } 212 if (_acceptCompleteWithOtherStatus) { 213 System.out.println("- accepting complete tests with partial tests"); 214 } else { 215 System.out.println("- not accepting complete tests with partial " 216 + "tests"); 217 } 218 219 // 1. traverse all test-classes (those extending JUnit's TestCase) 220 // and collect the annotation info. 221 // =================================================================== 222 System.out.println("stage 1 - get targets from all junit test methods"); 223 PrintWriter pr = openFile("testcoverage/test-annotation.html", 224 "test class annotation coverage"); 225 TablePrinter printer = new TablePrinter(pr); 226 printer.printTableStart(); 227 printer.printRow("Test package name", "JUnit classes", "Meth", "Unt", 228 "Part", "Compl", "Disab", "Broken", "ToBeFixed", "KnownFail"); 229 230 ColorStat totalStats = new ColorStat("All test packages", null); 231 PackageDoc[] packages = root.specifiedPackages(); 232 Arrays.sort(packages, classComparator); 233 for (PackageDoc pack : packages) { 234 if (pack.allClasses().length != 0) { 235 ColorStat packageStat = processTestPackage(pack); 236 if (!packageStat.ignored) { 237 // ignore those packages which have 0 tests in it 238 printTestStats(printer, packageStat, true); 239 totalStats.add(packageStat); 240 } 241 } 242 } 243 printer.printTableEnd(); 244 245 printer.printPlainLn("<h2>Summary of all test packages</h2>"); 246 printer.printTableStart(); 247 printTestStats(printer, totalStats, false); 248 printer.printTableEnd(); 249 closeFile(pr); 250 251 System.out.println("stage 2 - proxy test targets to abstract classes" 252 + "and interfaces"); 253 // 1/2 - traverse all normal (non-junit) classes for interface 254 // and abstract methods implementation tests. 255 ClassDoc[] classes = root.classes(); 256 for (ClassDoc classDoc : classes) { 257 if (!extendsJUnitTestCase(classDoc)) { 258 MethodDoc[] methods = classDoc.methods(); 259 for (MethodDoc methodDoc : methods) { 260 AnnotationPointer ap = getAnnotationPointer(methodDoc, 261 false); 262 if (ap != null) { 263 // if there are already tests targeting this method, 264 // search for abstract/interface methods which this 265 // method 266 // implements, and mark them as tested by this method, 267 // too. 268 List<MethodDoc> impls = implementingMethods(methodDoc); 269 for (MethodDoc impl : impls) { 270 AnnotationPointer apImpl = getAnnotationPointer( 271 impl, true); 272 // add all tests which pointed to the original 273 // method. 274 apImpl.addProxiesFrom(ap); 275 } 276 } 277 } 278 } 279 } 280 281 // 2. traverse all "normal" (non-junit) source files 282 // =================================================================== 283 System.out.println("stage 3 - generating report for target api"); 284 pr = openFile("index.html", "All target api packages"); 285 printer = new TablePrinter(pr); 286 printer.printPlainLn("Generated " + new Date().toString() 287 + " - V0.9a<br>"); 288 printer.printPlainLn("<a href=\"testcoverage/test-annotation.html\">" 289 + "annotation progress of test classes</a><br><br>"); 290 291 printer.printTableStart(); 292 printer.printRow("Package", "Classes", "Methods", "Untested", 293 "Partial", "Complete", "Disabled", "Broken", "ToBeFixed", "KnownFail"); 294 295 totalStats = new ColorStat("All target packages", null); 296 packages = root.specifiedPackages(); 297 Arrays.sort(packages, classComparator); 298 299 int classCount = 0; 300 for (PackageDoc pack : packages) { 301 if (pack.allClasses().length != 0) { 302 ColorStat packageStat = processPackage(pack); 303 304 if (!packageStat.ignored) { 305 printStats(printer, packageStat, true); 306 totalStats.add(packageStat); 307 308 classCount += Integer.parseInt(packageStat.getExtra()); 309 } 310 } 311 } 312 printer.printTableEnd(); 313 314 totalStats.setExtra("" + classCount); 315 printer.printPlainLn("<h2>Summary of all target packages</h2>"); 316 printer.printTableStart(); 317 printStats(printer, totalStats, false); 318 printer.printTableEnd(); 319 closeFile(pr); 320 } 321 322 /** 323 * Returns the interface(s) method(s) and the abstract method(s) that a 324 * given method implements, or an empty list if it does not implement 325 * anything. 326 */ 327 private List<MethodDoc> implementingMethods(MethodDoc doc) { 328 List<MethodDoc> resultmethods = new ArrayList<MethodDoc>(); 329 ClassDoc clazz = doc.containingClass(); 330 implementedMethod0(resultmethods, doc, clazz, false); 331 return resultmethods; 332 } 333 334 /** 335 * Recursive helper method for finding out which interface methods or 336 * abstract methods a given method implements. 337 */ 338 private void implementedMethod0(List<MethodDoc> resultmethods, 339 MethodDoc doc, ClassDoc testClass, boolean testMethods) { 340 341 // test all methods of this class 342 if (testMethods) { 343 MethodDoc[] methods = testClass.methods(); 344 for (int j = 0; j < methods.length; j++) { 345 MethodDoc methodDoc = methods[j]; 346 if ((methodDoc.isAbstract() || testClass.isInterface()) 347 && doc.overrides(methodDoc)) { 348 resultmethods.add(methodDoc); 349 } 350 } 351 } 352 353 // test all implementing interfaces 354 ClassDoc[] ifs = testClass.interfaces(); 355 for (int i = 0; i < ifs.length; i++) { 356 ClassDoc iface = ifs[i]; 357 implementedMethod0(resultmethods, doc, iface, true); 358 } 359 360 // test the superclass 361 ClassDoc superclass = testClass.superclass(); 362 if (superclass != null) { 363 implementedMethod0(resultmethods, doc, superclass, true); 364 } 365 } 366 367 private ColorStat processTestPackage(PackageDoc packageDoc) { 368 ColorStat stats = new ColorStat(packageDoc.name(), 369 getPackageBaseLink(packageDoc) + "/package.html"); 370 if (hasHideFlag(packageDoc)) { 371 stats.ignored = true; 372 return stats; 373 } 374 String file = getPackageDir("testcoverage", packageDoc) 375 + "/package.html"; 376 PrintWriter pr = openFile(file, "Test package " + packageDoc.name()); 377 TablePrinter printer = new TablePrinter(pr); 378 printer.printTableStart(); 379 printer.printRow("Class", "Extra", "Meth", "Unt", "Part", "Compl", 380 "Disab", "Broken", "ToBeFixed", "KnownFail"); 381 382 ClassDoc[] classes = packageDoc.allClasses(); 383 Arrays.sort(classes, classComparator); 384 int junitCnt = 0; 385 for (ClassDoc clazz : classes) { 386 if (extendsJUnitTestCase(clazz)) { 387 junitCnt++; 388 ColorStat subStats = processTestClass(clazz); 389 printTestStats(printer, subStats, true); 390 stats.add(subStats); 391 } else { 392 printer.printRow(clazz.name() + " ignored (no junit class): ", 393 "", "", "", "", "", "", "", ""); 394 } 395 } 396 printer.printTableEnd(); 397 398 printer.printPlainLn("<h2>Test package summary</h2>"); 399 printer.printTableStart(); 400 printStats(printer, stats, false); 401 printer.printTableEnd(); 402 403 closeFile(pr); 404 if (junitCnt == 0) { 405 if ((packageDoc.name().contains("tests.") 406 || packageDoc.name().contains("junit.") || packageDoc 407 .name().contains(".testframework")) 408 && !(packageDoc.name().equals("junit.framework"))) { 409 System.err.println("warning!: no junit classes in package '" 410 + packageDoc.name() + "' even though package name " 411 + "contains tests.,junit. or .testframework"); 412 } 413 stats = new ColorStat(packageDoc.name(), 414 getPackageBaseLink(packageDoc) + "/package.html"); 415 stats.incColor(TestMethodInformation.Color.GREEN); 416 stats.setExtra("Ignored since no Junit test and suites"); 417 stats.ignored = true; 418 } else { 419 stats.setExtra("" + junitCnt); 420 } 421 return stats; 422 } 423 424 private ColorStat processPackage(PackageDoc packageDoc) { 425 ColorStat stats = new ColorStat(packageDoc.name(), 426 getPackageBaseLink(packageDoc) + "/package.html"); 427 if (hasHideFlag(packageDoc)) { 428 stats.ignored = true; 429 return stats; 430 } 431 String file = getPackageDir("", packageDoc) + "/package.html"; 432 PrintWriter pr = openFile(file, "Package " + packageDoc.name()); 433 TablePrinter printer = new TablePrinter(pr); 434 printer.printTableStart(); 435 printer.printRow("Class", "Extra", "Meth", "Unt", "Part", "Compl", 436 "Disab", "Broken", "ToBeFixed", "KnownFail"); 437 438 ClassDoc[] classes = packageDoc.allClasses(); 439 Arrays.sort(classes, classComparator); 440 int cnt = 0; 441 int junitCnt = 0; 442 for (ClassDoc clazz : classes) { 443 cnt++; 444 if (hasHideFlag(clazz)) { 445 // ignored since it has a @hide in the javadoc on the class 446 // level 447 } else if (extendsJUnitTestCase(clazz)) { 448 printer.printRow("ignored (junit class): " + clazz.name()); 449 junitCnt++; 450 } else if (clazz.name().equals("AllTests")) { 451 printer.printRow("ignored (junit test suite class): " 452 + clazz.name()); 453 junitCnt++; 454 } else { 455 ColorStat subStats = processClass(clazz); 456 printStats(printer, subStats, true); 457 stats.add(subStats); 458 } 459 } 460 printer.printTableEnd(); 461 462 printer.printPlainLn("<h2>Target package summary</h2>"); 463 printer.printTableStart(); 464 printStats(printer, stats, false); 465 printer.printTableEnd(); 466 467 closeFile(pr); 468 if (junitCnt == cnt || packageDoc.name().contains("tests.") 469 || packageDoc.name().contains("junit.") 470 || packageDoc.name().contains(".testframework") 471 || packageDoc.name().endsWith(".cts")) { 472 // we only have junit classes -> mark green 473 stats = new ColorStat(packageDoc.name(), 474 getPackageBaseLink(packageDoc) + "/package.html"); 475 stats.incColor(TestMethodInformation.Color.GREEN); 476 stats 477 .setExtra(junitCnt == cnt ? "Ignored since only Junit test and " 478 + "suites" 479 : "Ignored since \"tests.\" in name - recheck"); 480 stats.ignored = true; 481 } else { 482 stats.setExtra("" + cnt); 483 } 484 return stats; 485 } 486 487 private boolean hasHideFlag(Doc doc) { 488 // workaround for the non-recognized @hide tag in package.html 489 if (doc instanceof PackageDoc) { 490 String comment = doc.getRawCommentText(); 491 return comment != null && comment.contains("@hide"); 492 } else { 493 Tag[] hideTags = doc.tags("hide"); 494 return hideTags.length > 0; 495 } 496 } 497 498 private ColorStat processTestClass(ClassDoc clazz) { 499 String file = getPackageDir("testcoverage", clazz.containingPackage()) 500 + "/" + clazz.name() + ".html"; 501 PrintWriter pr = openFile(file, "Test class " + clazz.qualifiedName()); 502 TablePrinter printer = new TablePrinter(pr); 503 ColorStat classStat = new ColorStat(clazz.name(), clazz.name() 504 + ".html"); 505 506 TestTargetClass testTargetClass = getTargetClass(clazz); 507 ClassDoc targetClass = testTargetClass.targetClass; 508 509 String note = "Note:"; 510 if (targetClass == null) { 511 note += "<br>targetClass annotation missing!<br>"; 512 } else { 513 // add untested[] annotations to statistics 514 ClassOriginator co = new ClassOriginator(clazz, null); 515 516 AnnotationDesc[] annotsC = testTargetClass.untestedMethods 517 .toArray(new AnnotationDesc[] {}); 518 if (annotsC.length > 0) { 519 // we have "untested" refs 520 ColorStat classLevelStat = new ColorStat(clazz.name(), null); 521 TestMethodInformation tminfo = new TestMethodInformation(co, 522 annotsC, targetClass); 523 if (tminfo.getError() != null) { 524 printer.printPlainLn("<b>Error:</b>" + tminfo.getError()); 525 classLevelStat.incColor(Color.RED); 526 } else { 527 linkTargets(tminfo.getTargets()); 528 classLevelStat.incColor(Color.GREEN); 529 } 530 classStat.add(classLevelStat); 531 } 532 } 533 534 printer.printPlainLn(note); 535 536 printer.printTableStart(); 537 printer.printRow("Method", "Note", "Meth", "Unt", "Part", "Compl", 538 "Disab", "Broken", "ToBeFixed", "KnownFail"); 539 540 int methodCnt = 0; 541 // also collects test... methods from all superclasses below TestCase, 542 // since those are called as well 543 List<MethodDoc> testMethods = collectAllTestMethods(clazz); 544 Collections.sort(testMethods, memberComparator); 545 546 for (MethodDoc testMethod : testMethods) { 547 methodCnt++; 548 549 // Make disabled tests visible in the report so we don't forget 550 // them. 551 boolean disTest = testMethod.name().startsWith("_test"); 552 553 ColorStat methodStat = new ColorStat(testMethod.name(), null); 554 if (disTest) { 555 methodStat.incDisabledTestsCnt(); 556 } 557 String comments = disTest ? "<b><span style=\"background:red\">" 558 + "DISABLED</span></b>" : null; 559 560 MethodOriginator mo = new MethodOriginator(testMethod, clazz, 561 comments); 562 AnnotationDesc[] annots = testMethod.annotations(); 563 TestMethodInformation minfo = new TestMethodInformation(mo, annots, 564 targetClass); 565 linkTargets(minfo.getTargets()); 566 567 String extra = null; 568 if (comments != null) { 569 if (extra == null) 570 extra = ""; 571 extra += comments; 572 } 573 574 if (minfo.getError() != null) { // error case 575 if (extra == null) 576 extra = ""; 577 extra += "<b>Error:</b> " + minfo.getError() + "<br>"; 578 methodStat.addMethodInfo(minfo); 579 } else { 580 if (mo.getKnownFailure() != null) { 581 methodStat.incKnownFailureCnt(); 582 if (extra == null) 583 extra = ""; 584 extra += mo.getKnownFailure(); 585 } 586 587 // check for @BrokenTest 588 if (mo.getBrokenTest() != null) { 589 // override with warning 590 methodStat.incBrokenTestCnt(); 591 methodStat.incColor(Color.YELLOW); 592 if (extra == null) 593 extra = ""; 594 extra += mo.getBrokenTest(); 595 } 596 597 // check for @ToBeFixed 598 if (mo.getToBeFixed() != null) { 599 // override with warning 600 methodStat.incToBeFixedCnt(); 601 methodStat.incColor(Color.YELLOW); 602 if (extra == null) { 603 extra = ""; 604 } 605 606 extra += mo.getToBeFixed(); 607 } else { // add regular stats 608 methodStat.addMethodInfo(minfo); 609 } 610 } 611 if (extra != null) { 612 methodStat.setExtra(extra); 613 } 614 615 printTestStats(printer, methodStat, false); 616 classStat.add(methodStat); 617 } 618 printer.printTableEnd(); 619 620 printer.printPlainLn("<h2>Test class summary</h2>"); 621 printer.printTableStart(); 622 printStats(printer, classStat, false); 623 printer.printTableEnd(); 624 625 closeFile(pr); 626 classStat.setExtra("#methods: " + testMethods.size()); 627 return classStat; 628 } 629 630 private void linkTargets(List<TestTargetNew> targets) { 631 for (TestTargetNew ttn : targets) { 632 if (ttn.getTargetMethod() != null) { 633 AnnotationPointer tar = getAnnotationPointer(ttn 634 .getTargetMethod(), true); 635 tar.addTestTargetNew(ttn); 636 } else if (ttn.getTargetClass() != null) { 637 // some special target only pointing to a class, not a method. 638 addToClassTargets(ttn.getTargetClass(), ttn); 639 } 640 } 641 } 642 643 private boolean isGreen(TestMethodInformation.Level level) { 644 boolean lComplete = level == TestMethodInformation.Level.COMPLETE; 645 boolean lSufficient = level == TestMethodInformation.Level.SUFFICIENT; 646 boolean lPartialOk = level == TestMethodInformation.Level.PARTIAL_COMPLETE; 647 boolean lPartial = level == TestMethodInformation.Level.PARTIAL; 648 boolean lTodo = level == TestMethodInformation.Level.TODO; 649 boolean lNotFeasible = level == TestMethodInformation.Level.NOT_FEASIBLE; 650 boolean lNotNecessary = level == TestMethodInformation.Level.NOT_NECESSARY; 651 652 return lComplete || lPartialOk || lSufficient || lNotFeasible 653 || lNotNecessary; 654 } 655 656 private ColorStat processClass(ClassDoc clazz) { 657 String file = getPackageDir("", clazz.containingPackage()) + "/" 658 + clazz.name() + ".html"; 659 String classDesc = getClassString(clazz); 660 PrintWriter pr = openFile(file, classDesc); 661 TablePrinter printer = new TablePrinter(pr); 662 printer.printPlain("<b>package " + clazz.containingPackage() + "</b>"); 663 ColorStat classStats = new ColorStat(classDesc, clazz.name() + ".html"); 664 665 // list special targets 666 List<TestTargetNew> classTargets = getTargetsFor(clazz); 667 if (classTargets != null) { 668 printer.printPlain("<h3>Class level tests</h3>"); 669 printer.printPlain("<ul>"); 670 for (TestTargetNew ttn : classTargets) { 671 String line = "<li>" + ttn.getOriginator().asString(); 672 Level lev = ttn.getLevel(); 673 line += " <font color=\"" 674 + (isGreen(lev) ? "green" : "red") 675 + "\"><b>" 676 + lev.name() 677 + "</b></font>" 678 + (ttn.getNotes() != null ? "<br>Notes: " 679 + ttn.getNotes() : "") + "</li>"; 680 printer.printPlain(line); 681 } 682 printer.printPlainLn("</ul>"); 683 } 684 685 printer.printPlain("<h3>Method level tests</h3>"); 686 printer.printTableStart(); 687 printer.printRow("Method", "Tested by", "Meth", "Unt", "Part", "Compl", 688 "Disab", "Broken", "ToBeFixed", "KnownFail"); 689 ConstructorDoc[] constructors = clazz.constructors(); 690 Arrays.sort(constructors, classComparator); 691 int cnt = 0; 692 for (ConstructorDoc constructor : constructors) { 693 if (!hasHideFlag(constructor) && !hasHideFlag(clazz)) { 694 cnt++; 695 ColorStat memberStat = processElement(constructor); 696 printStats(printer, memberStat, false); 697 classStats.add(memberStat); 698 } 699 } 700 701 MethodDoc[] methods = clazz.methods(); 702 Arrays.sort(methods, classComparator); 703 for (MethodDoc method : methods) { 704 if (!hasHideFlag(method) && !hasHideFlag(clazz)) { 705 cnt++; 706 ColorStat subStat = processElement(method); 707 printStats(printer, subStat, false); 708 classStats.add(subStat); 709 } 710 } 711 printer.printTableEnd(); 712 713 printer.printPlainLn("<h2>Target class summary</h2>"); 714 printer.printTableStart(); 715 printStats(printer, classStats, false); 716 printer.printTableEnd(); 717 718 closeFile(pr); 719 classStats.setExtra("#methods: " + cnt); 720 721 // mark as green 722 if (_ignoreInterfacesAndAbstractMethods && clazz.isInterface()) { 723 classStats = new ColorStat(clazz.name() 724 + (clazz.isInterface() ? " (Interface)" : ""), clazz.name() 725 + ".html"); 726 int mcnt = clazz.methods().length; 727 // fake all methods to green 728 for (int i = 0; i < mcnt; i++) { 729 classStats.incColor(TestMethodInformation.Color.GREEN); 730 } 731 classStats.setExtra("Ignored since interface"); 732 } 733 return classStats; 734 } 735 736 private class TestTargetClass { 737 ClassDoc targetClass; 738 739 List<AnnotationDesc> untestedMethods = new ArrayList<AnnotationDesc>(); 740 // a List of @TestTargetNew annotations 741 } 742 743 private TestTargetClass getTargetClass(ClassDoc classDoc) { 744 // get the class annotation which names the default test target class 745 TestTargetClass ttc = new TestTargetClass(); 746 ClassDoc targetClass = null; 747 AnnotationDesc[] cAnnots = classDoc.annotations(); 748 for (AnnotationDesc cAnnot : cAnnots) { 749 AnnotationTypeDoc atype = cAnnot.annotationType(); 750 if (atype.toString().equals("dalvik.annotation.TestTargetClass")) { 751 ElementValuePair[] cpairs = cAnnot.elementValues(); 752 for (int i = 0; i < cpairs.length; i++) { 753 ElementValuePair ev = cpairs[i]; 754 String elName = ev.element().name(); 755 if (elName.equals("value")) { 756 // the target class 757 AnnotationValue av = ev.value(); 758 Object obj = av.value(); 759 // value must be a class doc 760 if (obj instanceof ClassDoc) { 761 targetClass = (ClassDoc)obj; 762 } else if (obj instanceof ParameterizedType) { 763 targetClass = ((ParameterizedType)obj).asClassDoc(); 764 } else 765 throw new RuntimeException( 766 "annotation elem value is of type " 767 + obj.getClass().getName()); 768 } else if (elName.equals("untestedMethods")) { 769 // TestTargetNew[] untestedMethods() default {}; 770 AnnotationValue[] targets = (AnnotationValue[])ev 771 .value().value(); 772 for (AnnotationValue ttn : targets) { 773 // each untested method must be a TestTargetNew 774 AnnotationDesc ttnd = (AnnotationDesc)ttn.value(); 775 ttc.untestedMethods.add(ttnd); 776 } 777 } 778 } 779 } 780 } 781 ttc.targetClass = targetClass; 782 return ttc; 783 } 784 785 private List<MethodDoc> collectAllTestMethods(ClassDoc classDoc) { 786 List<MethodDoc> m = new ArrayList<MethodDoc>(); 787 788 ClassDoc curCl = classDoc; 789 do { 790 m.addAll(getJunitTestMethods(curCl)); 791 } while ((curCl = curCl.superclass()) != null 792 && !curCl.qualifiedName().equals("junit.framework.TestCase")); 793 return m; 794 } 795 796 private List<MethodDoc> getJunitTestMethods(ClassDoc classDoc) { 797 List<MethodDoc> cl = new ArrayList<MethodDoc>(); 798 for (MethodDoc methodDoc : classDoc.methods()) { 799 if (methodDoc.isPublic() 800 && (methodDoc.name().startsWith("test") || methodDoc.name() 801 .startsWith("_test"))) { 802 cl.add(methodDoc); 803 } 804 } 805 return cl; 806 } 807 808 private class ColorStat { 809 private String name; 810 811 private String link; 812 813 private String extra; 814 815 public boolean ignored; 816 817 private int[] cntCol = new int[4]; 818 819 private int disabledTestsCnt = 0; 820 821 private int brokenTestCnt = 0; 822 823 private int toBeFixedCnt = 0; 824 825 private int knownFailureCnt = 0; 826 827 public String getName() { 828 return name; 829 } 830 831 public String getLink() { 832 return link; 833 } 834 835 public ColorStat(String name, String link) { 836 this.name = name; 837 this.link = link; 838 } 839 840 public void add(ColorStat subStat) { 841 for (int i = 0; i < cntCol.length; i++) { 842 cntCol[i] += subStat.cntCol[i]; 843 } 844 disabledTestsCnt += subStat.disabledTestsCnt; 845 brokenTestCnt += subStat.brokenTestCnt; 846 toBeFixedCnt += subStat.toBeFixedCnt; 847 knownFailureCnt += subStat.knownFailureCnt; 848 } 849 850 public void incDisabledTestsCnt() { 851 disabledTestsCnt++; 852 } 853 854 public void incBrokenTestCnt() { 855 brokenTestCnt++; 856 } 857 858 public void incToBeFixedCnt() { 859 toBeFixedCnt++; 860 } 861 862 public void incKnownFailureCnt() { 863 knownFailureCnt++; 864 } 865 866 public void incColor(TestMethodInformation.Color color) { 867 cntCol[color.ordinal()]++; 868 } 869 870 public int getColorCnt(TestMethodInformation.Color color) { 871 return cntCol[color.ordinal()]; 872 } 873 874 public void addMethodInfo(TestMethodInformation minfo) { 875 TestMethodInformation.Color c = minfo.getColor(); 876 int ord = c.ordinal(); 877 cntCol[ord]++; 878 } 879 880 public void setExtra(String extra) { 881 this.extra = extra; 882 } 883 884 public String getExtra() { 885 return extra; 886 } 887 888 public int getDisabledTestsCnt() { 889 return disabledTestsCnt; 890 } 891 892 public int getBrokenTestCnt() { 893 return brokenTestCnt; 894 } 895 896 public int getToBeFixedCnt() { 897 return toBeFixedCnt; 898 } 899 900 public int getKnownFailureCnt() { 901 return knownFailureCnt; 902 } 903 } 904 905 private AnnotationPointer getAnnotationPointer( 906 ExecutableMemberDoc targetMethod, boolean create) { 907 AnnotationPointer ap = resolved.get(targetMethod); 908 if (create && ap == null) { 909 ap = new AnnotationPointer(targetMethod); 910 resolved.put(targetMethod, ap); 911 } 912 return ap; 913 } 914 915 private void addToClassTargets(ClassDoc targetClass, TestTargetNew ttn) { 916 List<TestTargetNew> targets = classToSpecialTargets.get(targetClass); 917 if (targets == null) { 918 targets = new ArrayList<TestTargetNew>(); 919 classToSpecialTargets.put(targetClass, targets); 920 } 921 targets.add(ttn); 922 } 923 924 private List<TestTargetNew> getTargetsFor(ClassDoc targetClass) { 925 return classToSpecialTargets.get(targetClass); 926 } 927 928 private boolean extendsJUnitTestCase(ClassDoc classDoc) { 929 // junit.framework.TestCase.java 930 ClassDoc curClass = classDoc; 931 while ((curClass = curClass.superclass()) != null) { 932 if (curClass.toString().equals("junit.framework.TestCase")) { 933 return true; 934 } 935 } 936 return false; 937 } 938 939 /** 940 * Processes a single method/constructor. 941 */ 942 private ColorStat processElement(ExecutableMemberDoc method) { 943 if (DEBUG) System.out.println("Processing " + method); 944 ColorStat memberStats = new ColorStat(getMethodString(method), null); 945 TestMethodInformation.Color c = TestMethodInformation.Color.RED; 946 // if flagged, consider abstract method ok also without tests 947 if (_ignoreInterfacesAndAbstractMethods && method instanceof MethodDoc 948 && ((MethodDoc)method).isAbstract()) { 949 c = TestMethodInformation.Color.GREEN; 950 memberStats.setExtra("ignored since abstract"); 951 } else { 952 AnnotationPointer ap = getAnnotationPointer(method, false); 953 int testedByCnt = 0; 954 if (ap != null) { 955 List<TestTargetNew> targets = ap.getTargets(); 956 testedByCnt = targets.size(); 957 if (testedByCnt == 0) { 958 throw new RuntimeException( 959 "existing annotation pointer with no entries!, " 960 + "method:" + method); 961 } 962 // at least tested by one method 963 String by = "<ul>"; 964 int completeTestCnt = 0; 965 int partialOkTestCnt = 0; 966 int partialTestCnt = 0; 967 int todoTestCnt = 0; 968 int notFeasableTestCnt = 0; 969 int notNecessaryTestCnt = 0; 970 int sufficientTestCnt = 0; 971 // int totalCnt = targets.size(); 972 973 for (TestTargetNew target : targets) { 974 Originator originator = target.getOriginator(); 975 boolean disabledTest = originator.isDisabled(); 976 boolean brokenTest = originator.getBrokenTest() != null; 977 boolean toBeFixed = originator.getToBeFixed() != null; 978 boolean knownFailure = originator.getKnownFailure() != null; 979 by += "<li>" + originator.asString(); 980 TestMethodInformation.Level lev; 981 if (target.isHavingProblems()) { 982 lev = TestMethodInformation.Level.TODO; 983 } else { 984 lev = target.getLevel(); 985 } 986 // TODO: improve: avoid c&p by adding ColorStat.addInfo 987 // (originator) or similar 988 if (disabledTest) { 989 memberStats.incDisabledTestsCnt(); 990 } 991 if (brokenTest) { 992 memberStats.incBrokenTestCnt(); 993 } 994 if (toBeFixed) { 995 memberStats.incToBeFixedCnt(); 996 } 997 if (knownFailure) { 998 memberStats.incKnownFailureCnt(); 999 } 1000 1001 boolean lComplete = lev == TestMethodInformation.Level.COMPLETE; 1002 boolean lSufficient = lev == TestMethodInformation.Level.SUFFICIENT; 1003 boolean lPartialOk = lev == TestMethodInformation.Level.PARTIAL_COMPLETE; 1004 boolean lPartial = lev == TestMethodInformation.Level.PARTIAL; 1005 boolean lTodo = lev == TestMethodInformation.Level.TODO; 1006 boolean lNotFeasible = lev == TestMethodInformation.Level.NOT_FEASIBLE; 1007 boolean lNotNecessary = lev == TestMethodInformation.Level.NOT_NECESSARY; 1008 1009 by += " <font color=\"" 1010 + (lComplete || lPartialOk || lSufficient 1011 || lNotFeasible || lNotNecessary ? "green" 1012 : "red") 1013 + "\"><b>" 1014 + lev.name() 1015 + "</b></font>" 1016 + (target.getNotes() != null ? "<br>Notes: " 1017 + target.getNotes() : ""); 1018 1019 // only count tests when they are either ok (not disabled) 1020 // or if the doIncludeDisabledTests flag is set 1021 if ((_doIncludeDisabledTests || !disabledTest) 1022 && (!brokenTest) && (!toBeFixed)) { 1023 if (lComplete) { 1024 completeTestCnt++; 1025 } else if (lPartialOk) { 1026 partialOkTestCnt++; 1027 } else if (lPartial) { 1028 partialTestCnt++; 1029 } else if (lTodo) { 1030 todoTestCnt++; 1031 } else if (lSufficient) { 1032 sufficientTestCnt++; 1033 } else if (lNotFeasible) { 1034 notFeasableTestCnt++; 1035 } else if (lNotNecessary) { 1036 notNecessaryTestCnt++; 1037 } 1038 } 1039 1040 if (toBeFixed) { 1041 partialTestCnt++; 1042 } 1043 1044 if (DEBUG) { 1045 System.out.println("completeTestCnt: " + completeTestCnt 1046 + ", partialOkTestCnt: " + partialOkTestCnt 1047 + ", partialTestCnt: " + partialTestCnt 1048 + ", todoTestCnt: " + todoTestCnt 1049 + ", sufficientTestCnt: " + sufficientTestCnt 1050 + ", notFeasableTestCnt: " + notFeasableTestCnt 1051 + ", notNecessaryTestCnt: " + notNecessaryTestCnt); 1052 } 1053 by += "</li>"; 1054 } 1055 1056 String warnings = ""; 1057 // calculate the color 1058 int singularTestCnt = notFeasableTestCnt + notNecessaryTestCnt; 1059 boolean isAbstract = (method.containingClass().isInterface() || 1060 (method instanceof MethodDoc) && ((MethodDoc)method).isAbstract()); 1061 1062 if (_acceptCompleteWithOtherStatus 1063 && (completeTestCnt > 0 || sufficientTestCnt > 0)) { 1064 c = TestMethodInformation.Color.GREEN; 1065 } else if (_acceptCompleteWithOtherStatus 1066 && (partialOkTestCnt > 1)) { 1067 c = TestMethodInformation.Color.GREEN; 1068 } else { 1069 if (singularTestCnt > 0) { 1070 // we have tests which claim not_neccessary or 1071 // not_feasible 1072 if (targets.size() > singularTestCnt) { 1073 // other tests exist 1074 c = TestMethodInformation.Color.RED; 1075 warnings += "<b>WARNING:</b>NOT_FEASIBLE or " 1076 + "NOT_NECESSARY together with other " 1077 + "status!<br>"; 1078 } else { 1079 // a collection of not_necessary and/or not_feasible 1080 if (notNecessaryTestCnt > 0 1081 && notFeasableTestCnt > 0) { 1082 // a blend of both -> error 1083 warnings += "<b>WARNING:</b>both NOT_FEASIBLE " 1084 + "and NOT_NECESSARY together!<br>"; 1085 c = TestMethodInformation.Color.RED; 1086 } else { // just one sort of tests -> ok and 1087 // sufficent 1088 c = TestMethodInformation.Color.GREEN; 1089 } 1090 } 1091 } else if (todoTestCnt > 0) { 1092 c = TestMethodInformation.Color.RED; 1093 } else if (partialTestCnt > 0) { 1094 // at least one partial test 1095 c = TestMethodInformation.Color.YELLOW; 1096 if (completeTestCnt > 0 || sufficientTestCnt > 0) { 1097 if (_acceptCompleteWithOtherStatus) { 1098 // accept complete even if there are partials 1099 c = TestMethodInformation.Color.GREEN; 1100 } else if (completeTestCnt > 0) { 1101 // yellow+warning: mixed PARTIAL_COMPLETE and 1102 // COMPLETE status 1103 warnings += "<b>WARNING</b>: mixed PARTIAL " 1104 + "and COMPLETE status<br>"; 1105 } 1106 } 1107 } else if (partialOkTestCnt > 0 || sufficientTestCnt > 0) { 1108 // at least one partialOk test and no partial tests 1109 c = TestMethodInformation.Color.GREEN; 1110 if (partialOkTestCnt == 1) { 1111 // yellow: 1 PARTIAL_COMPLETE (we need either zero 1112 // or >=2 PARTIAL_OK tests) 1113 warnings += "<b>WARNING</b>: only one " 1114 + "PARTIAL_COMPLETE status<br>"; 1115 c = TestMethodInformation.Color.YELLOW; 1116 } 1117 } else if (completeTestCnt > 0 || singularTestCnt == 1) { 1118 // only complete tests 1119 c = TestMethodInformation.Color.GREEN; 1120 } 1121 1122 if (completeTestCnt > 1 && !isAbstract 1123 && !_acceptCompleteWithOtherStatus) { 1124 // green+warning: >1 COMPLETE (we need either 0 or 1 1125 // COMPLETE, more would be strange, but possible) 1126 warnings += "<b>WARNING</b>: more than one " 1127 + "COMPLETE status<br>"; 1128 if (c != TestMethodInformation.Color.RED) { 1129 c = TestMethodInformation.Color.YELLOW; 1130 } 1131 } 1132 } 1133 by = warnings + by; 1134 memberStats.setExtra(by); 1135 } else { // else this class has no single test that targets the 1136 // current method 1137 // handle special cases: 1138 1139 // can be ok if the current method is a default constructor 1140 // which does not occur in the source code. 1141 if (method.isConstructor() && method.signature().equals("()")) { 1142 if (method.position() != null) { 1143 // hacky stuff - the doclet should return null for a 1144 // default constructor, 1145 // but instead returns a source position pointing to the 1146 // same location as 1147 // the class. 1148 String constPos = method.position().toString(); 1149 String classPos = method.containingClass().position() 1150 .toString(); 1151 if (constPos.equals(classPos)) { 1152 // we have a default constructor not visible in 1153 // source code -> mark green, no testing needed 1154 c = TestMethodInformation.Color.GREEN; 1155 memberStats 1156 .setExtra("automatically marked green " 1157 + "since implicit default " 1158 + "constructor"); 1159 } 1160 } else { 1161 // should be called for default constructors according 1162 // to the doclet spec, but is never called. 1163 // spec: 1164 // "A default constructor returns null because it has no 1165 // location in the source file" 1166 // ?? 1167 // what about default constructors being in the source 1168 // code? 1169 System.err.println("warning: doclet returned null for " 1170 + "source position: method:" + method); 1171 } 1172 } else if (method.containingClass().superclass() != null 1173 && method.containingClass().superclass() 1174 .qualifiedName().equals("java.lang.Enum")) { 1175 // check enum types 1176 // <anyreturnclass> valueOf (java.lang.String) and 1177 // <anyreturnclass[]> values() 1178 // do not need to be tested, since they are autogenerated 1179 // by the compiler 1180 String sig = method.name() + method.signature(); 1181 if (sig.equals("valueOf(java.lang.String)") 1182 || sig.equals("values()")) { 1183 c = TestMethodInformation.Color.GREEN; 1184 memberStats 1185 .setExtra("automatically marked green since " 1186 + "generated by compiler for enums"); 1187 } 1188 } 1189 } 1190 } 1191 memberStats.incColor(c); 1192 return memberStats; 1193 } 1194 1195 private String getMethodString(ExecutableMemberDoc method) { 1196 String methodDesc = (method.isPublic() ? "public " : method 1197 .isProtected() ? "protected " : method.isPrivate() ? "private " 1198 : ""); 1199 1200 return methodDesc + "<b>" + method.name() + "</b> " 1201 + method.signature(); 1202 } 1203 1204 private String getClassString(ClassDoc clazz) { 1205 return (clazz.isPublic() ? "public " 1206 : clazz.isProtected() ? "protected " 1207 : clazz.isPrivate() ? "private " : "") 1208 + (clazz.isInterface() ? "interface" : "class") 1209 + " " 1210 + clazz.name(); 1211 } 1212 1213 private void printTestStats(TablePrinter printer, ColorStat stat, 1214 boolean wantLink) { 1215 printStats(printer, stat, wantLink); 1216 } 1217 1218 private void printStats(TablePrinter printer, ColorStat stat, 1219 boolean wantLink) { 1220 int redCnt = stat.getColorCnt(TestMethodInformation.Color.RED); 1221 int yellowCnt = stat.getColorCnt(TestMethodInformation.Color.YELLOW); 1222 int greenCnt = stat.getColorCnt(TestMethodInformation.Color.GREEN); 1223 int disabledCnt = stat.getDisabledTestsCnt(); 1224 int brokenTestCnt = stat.getBrokenTestCnt(); 1225 int toBeFixedCnt = stat.getToBeFixedCnt(); 1226 int knownFailureCnt = stat.getKnownFailureCnt(); 1227 1228 int total = redCnt + yellowCnt + greenCnt; 1229 1230 String link = stat.getLink(); 1231 String namePart; 1232 if (wantLink && link != null) { 1233 namePart = "<a href=\"" + link + "\">" + stat.getName() + "</a>"; 1234 } else { 1235 namePart = stat.getName(); 1236 } 1237 1238 String extra = stat.getExtra() == null ? "" : stat.getExtra(); 1239 1240 int totalDots = 120; 1241 1242 float toP = total == 0 ? 0 : (((float)totalDots) / total); 1243 1244 int redD = (int)(toP * redCnt); 1245 if (redD == 0 && redCnt > 0) { 1246 // never let red cut to zero; 1247 redD = 1; 1248 } 1249 int yellowD = (int)(toP * yellowCnt); 1250 if (yellowD == 0 && yellowCnt > 0) { 1251 yellowD = 1; 1252 } 1253 int greenD = totalDots - redD - yellowD; // (int)(toP*greenCnt); 1254 1255 printer.printRow(namePart, extra, "" + total, "" + redCnt, "" 1256 + yellowCnt, "" + greenCnt, "" + disabledCnt, "" 1257 + brokenTestCnt, "" + toBeFixedCnt, "" + knownFailureCnt, "" 1258 + (redCnt == 0 ? "" : "<span style=\"background:" 1259 + COLORS[TestMethodInformation.Color.RED.ordinal()] 1260 + "\">" + getDots(redD) + "</span>") 1261 + (yellowCnt == 0 ? "" : "<span style=\"background:" 1262 + COLORS[TestMethodInformation.Color.YELLOW.ordinal()] 1263 + "\">" + getDots(yellowD) + "</span>") 1264 + (greenCnt == 0 && total > 0 ? "" 1265 : "<span style=\"background:" 1266 + COLORS[TestMethodInformation.Color.GREEN 1267 .ordinal()] + "\">" + getDots(greenD) 1268 + "</span>") 1269 + " <span style=\"background:blue\">" 1270 + getDots(total / 10) + "</span>"); 1271 } 1272 1273 private String getDots(int cnt) { 1274 StringBuffer sb = new StringBuffer(); 1275 for (int i = 0; i < cnt; i++) { 1276 sb.append(" "); 1277 } 1278 return sb.toString(); 1279 } 1280 1281 /** 1282 * Returns the directory for a given package. Basically converts embedded 1283 * dots in the name into slashes. 1284 */ 1285 1286 private String getPackageBaseLink(PackageDoc pack) { 1287 return pack.name().replace('.', '/'); 1288 } 1289 1290 private File getPackageDir(String prefix, PackageDoc pack) { 1291 if (pack == null || pack.name() == null || "".equals(pack.name())) { 1292 return new File(prefix + "/" + "."); 1293 } else { 1294 return new File(prefix + "/" + pack.name().replace('.', '/')); 1295 } 1296 } 1297 1298 /** 1299 * Called by JavaDoc to find our which command line arguments are supported 1300 * and how many parameters they take. Part of the JavaDoc API. 1301 * 1302 * @param option the options 1303 * @return an int 1304 */ 1305 public static int optionLength(String option) { 1306 if ("-d".equals(option)) { 1307 return 2; 1308 } 1309 if ("-f".equals(option)) { 1310 return 2; 1311 } else { 1312 return 0; 1313 } 1314 } 1315 1316 /** 1317 * Called by JavaDoc to query a specific command line argument. Part of the 1318 * JavaDoc API. 1319 */ 1320 private static String getOption(RootDoc root, String option, int index, 1321 String defValue) { 1322 String[][] allOptions = root.options(); 1323 for (int i = 0; i < allOptions.length; i++) { 1324 if (allOptions[i][0].equals(option)) { 1325 return allOptions[i][index]; 1326 } 1327 } 1328 return defValue; 1329 } 1330 1331 /** 1332 * Called by JavaDoc to find out which Java version we claim to support. 1333 * Part of the JavaDoc API. 1334 * 1335 * @return the version of the language 1336 */ 1337 public static LanguageVersion languageVersion() { 1338 return LanguageVersion.JAVA_1_5; 1339 } 1340 1341 /** 1342 * The main entry point called by JavaDoc after all required information has 1343 * been collected. Part of the JavaDoc API. 1344 * 1345 * @param root 1346 * @return whether an error occurred 1347 */ 1348 public static boolean start(RootDoc root) { 1349 try { 1350 String target = getOption(root, "-d", 1, "."); 1351 TestCoverageDoclet doclet = new TestCoverageDoclet(target); 1352 doclet.process(root); 1353 1354 File file = new File(target, "index.html"); 1355 System.out.println("Please see complete report in " + 1356 file.getAbsolutePath()); 1357 1358 } catch (Exception ex) { 1359 ex.printStackTrace(); 1360 return false; 1361 } 1362 return true; 1363 } 1364 1365 } 1366