2 * Copyright (C) 2014, United States Government, as represented by the
3 * Administrator of the National Aeronautics and Space Administration.
6 * The Java Pathfinder core (jpf-core) platform is licensed under the
7 * Apache License, Version 2.0 (the "License"); you may not use this file except
8 * in compliance with the License. You may obtain a copy of the License at
10 * http://www.apache.org/licenses/LICENSE-2.0.
12 * Unless required by applicable law or agreed to in writing, software
13 * distributed under the License is distributed on an "AS IS" BASIS,
14 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15 * See the License for the specific language governing permissions and
16 * limitations under the License.
18 package gov.nasa.jpf.listener;
20 import gov.nasa.jpf.Config;
21 import gov.nasa.jpf.JPF;
22 import gov.nasa.jpf.ListenerAdapter;
23 import gov.nasa.jpf.jvm.bytecode.GOTO;
24 import gov.nasa.jpf.jvm.bytecode.IfInstruction;
25 import gov.nasa.jpf.jvm.bytecode.JVMInvokeInstruction;
26 import gov.nasa.jpf.jvm.bytecode.JVMReturnInstruction;
27 import gov.nasa.jpf.report.ConsolePublisher;
28 import gov.nasa.jpf.report.Publisher;
29 import gov.nasa.jpf.report.PublisherExtension;
30 import gov.nasa.jpf.util.Misc;
31 import gov.nasa.jpf.util.StringSetMatcher;
32 import gov.nasa.jpf.vm.AnnotationInfo;
33 import gov.nasa.jpf.vm.ChoiceGenerator;
34 import gov.nasa.jpf.vm.ClassInfo;
35 import gov.nasa.jpf.vm.ClassInfoException;
36 import gov.nasa.jpf.vm.ClassLoaderInfo;
37 import gov.nasa.jpf.vm.ExceptionHandler;
38 import gov.nasa.jpf.vm.Instruction;
39 import gov.nasa.jpf.vm.VM;
40 import gov.nasa.jpf.vm.MethodInfo;
41 import gov.nasa.jpf.vm.ThreadInfo;
44 import java.io.IOException;
45 import java.io.PrintWriter;
46 import java.util.ArrayList;
47 import java.util.BitSet;
48 import java.util.Comparator;
49 import java.util.Enumeration;
50 import java.util.HashMap;
51 import java.util.HashSet;
52 import java.util.List;
54 import java.util.jar.JarEntry;
55 import java.util.jar.JarFile;
56 import java.util.logging.Logger;
59 * a listener to report coverage statistics
61 * The idea is to collect per-class/-method/-thread information about
62 * executed instructions, and then analyze this deeper when it comes to
63 * report time (e.g. branch coverage, basic blocks, ..)
65 * Keep in mind that this is potentially a concurrent, model checked program,
66 * i.e. there is more to coverage than what hits the eye of a static analyzer
67 * (exceptions, data and thread CGs)
69 public class CoverageAnalyzer extends ListenerAdapter implements PublisherExtension {
71 static Logger log = JPF.getLogger("gov.nasa.jpf.listener.CoverageAnalyzer");
73 static class Coverage {
78 Coverage(int total, int covered) {
80 this.covered = covered;
83 public void add(Coverage cov) {
85 covered += cov.covered;
88 public int percent() {
90 return covered * 100 / total;
93 return (Integer.MIN_VALUE);
96 public int covered() {
104 public boolean isPartiallyCovered() {
105 return ((covered > 0) && (covered < total));
108 public boolean isNotCovered() {
109 return (covered == 0);
112 public boolean isFullyCovered() {
113 return (covered == total);
117 static class MethodCoverage {
121 // we base everything else on bytecode instruction coverage
123 BitSet basicBlocks; // set on demand
124 BitSet handlers; // set on demand
125 BitSet branches; // set on demand
129 MethodCoverage(MethodInfo mi) {
131 log.info("add method: " + mi.getUniqueName());
134 MethodInfo getMethodInfo() {
138 void setExecuted(ThreadInfo ti, Instruction insn) {
139 int idx = ti.getId();
141 if (covered == null) {
142 covered = new BitSet[idx + 1];
143 } else if (idx >= covered.length) {
144 BitSet[] a = new BitSet[idx + 1];
145 System.arraycopy(covered, 0, a, 0, covered.length);
149 if (covered[idx] == null) {
150 covered[idx] = new BitSet(mi.getInstructions().length);
153 int off = insn.getInstructionIndex();
154 covered[idx].set(off);
156 if (showBranchCoverage && (insn instanceof IfInstruction)) {
157 if (branchTrue == null) {
158 branchTrue = new BitSet(mi.getInstructions().length);
159 branchFalse = new BitSet(branchTrue.size());
161 if (!((IfInstruction) insn).getConditionValue()) {
164 branchFalse.set(off);
169 void setCGed(ThreadInfo ti, Instruction insn) {
170 ti = null; // Remove IDE warning about unused variable.
171 // Hmm, we have to store the bb set at this point
172 BitSet bb = getBasicBlocks();
173 Instruction next = insn.getNext();
174 if (next != null) { // insn might be a sync return
175 bb.set(next.getInstructionIndex());
179 BitSet getExecutedInsn() {
180 int nTotal = mi.getInstructions().length;
181 BitSet bUnion = new BitSet(nTotal);
183 if (covered != null) {
184 for (BitSet b : covered) {
194 Coverage getCoveredInsn() {
195 int nTotal = mi.getInstructions().length;
197 if (excludeHandlers) {
198 nTotal -= getHandlers().cardinality();
201 if (covered != null) {
202 BitSet bExec = getExecutedInsn();
203 if (excludeHandlers) {
204 bExec.andNot(getHandlers());
206 return new Coverage(nTotal, bExec.cardinality());
208 return new Coverage(nTotal, 0);
212 Coverage getCoveredLines() {
213 BitSet executable = new BitSet();
214 BitSet covered = new BitSet();
216 getCoveredLines(executable, covered);
218 return new Coverage(executable.cardinality(), covered.cardinality());
221 boolean getCoveredLines(BitSet executable, BitSet covered) {
222 Instruction inst[] = mi.getInstructions();
226 if (covered == null) {
230 insn = getExecutedInsn();
231 if (excludeHandlers) {
232 insn.andNot(getHandlers());
235 if (branchTrue != null) {
236 for (i = branchTrue.length() - 1; i >= 0; i--) {
237 boolean cTrue = branchTrue.get(i);
238 boolean cFalse = branchFalse.get(i);
240 if ((!cTrue || !cFalse) && (inst[i] instanceof IfInstruction)) {
246 for (i = inst.length - 1; i >= 0; i--) {
247 line = inst[i].getLineNumber();
250 executable.set(line);
255 for (i = inst.length - 1; i >= 0; i--) {
256 line = inst[i].getLineNumber();
257 if ((!insn.get(i)) && (line > 0)) { // TODO What do we do with instructions that don't have a line number. Is this even possible?
265 Coverage getCoveredBranches() {
266 BitSet b = getBranches();
267 int nTotal = b.cardinality();
270 if (branchTrue != null) {
271 int n = branchTrue.size();
273 for (int i = 0; i < n; i++) {
274 boolean cTrue = branchTrue.get(i);
275 boolean cFalse = branchFalse.get(i);
277 if (cTrue && cFalse) {
283 return new Coverage(nTotal, nCovered);
286 BitSet getHandlerStarts() {
287 BitSet b = new BitSet(mi.getInstructions().length);
288 ExceptionHandler[] handler = mi.getExceptions();
290 if (handler != null) {
291 for (int i = 0; i < handler.length; i++) {
292 Instruction hs = mi.getInstructionAt(handler[i].getHandler());
293 b.set(hs.getInstructionIndex());
300 BitSet getHandlers() {
301 // this algorithm is a bit subtle - we walk through the code until
302 // we hit a forward GOTO (or RETURN). If the insn following the goto is the
303 // beginning of a handler, we mark everything up to the jump address
304 // as a handler block
306 if (handlers == null) {
307 BitSet hs = getHandlerStarts();
308 Instruction[] code = mi.getInstructions();
309 BitSet b = new BitSet(code.length);
312 for (int i = 0; i < code.length; i++) {
313 Instruction insn = code[i];
314 if (insn instanceof GOTO) {
315 GOTO gotoInsn = (GOTO) insn;
316 if (!gotoInsn.isBackJump() && hs.get(i + 1)) { // jump around handler
317 int handlerEnd = gotoInsn.getTarget().getInstructionIndex();
318 for (i++; i < handlerEnd; i++) {
322 } else if (insn instanceof JVMReturnInstruction) { // everything else is handler
323 for (i++; i < code.length; i++) {
336 // that's kind of redundant with basic blocks, but not really - here
337 // we are interested in the logic behind branches, i.e. we want to know
338 // what the condition values were. We are not interested in GOTOs and exceptions
339 BitSet getBranches() {
340 if (branches == null) {
341 Instruction[] code = mi.getInstructions();
342 BitSet br = new BitSet(code.length);
344 for (int i = 0; i < code.length; i++) {
345 Instruction insn = code[i];
346 if (insn instanceof IfInstruction) {
357 BitSet getBasicBlocks() {
358 if (basicBlocks == null) {
359 Instruction[] code = mi.getInstructions();
360 BitSet bb = new BitSet(code.length);
362 bb.set(0); // first insn is always a bb start
364 // first, look at the insn type
365 for (int i = 0; i < code.length; i++) {
366 Instruction insn = code[i];
367 if (insn instanceof IfInstruction) {
368 IfInstruction ifInsn = (IfInstruction) insn;
370 Instruction tgt = ifInsn.getTarget();
371 bb.set(tgt.getInstructionIndex());
373 tgt = ifInsn.getNext();
374 bb.set(tgt.getInstructionIndex());
375 } else if (insn instanceof GOTO) {
376 Instruction tgt = ((GOTO) insn).getTarget();
377 bb.set(tgt.getInstructionIndex());
378 } else if (insn instanceof JVMInvokeInstruction) {
379 // hmm, this might be a bit too conservative, but who says we
380 // don't jump out of a caller into a handler, or even that we
381 // ever return from the call?
382 Instruction tgt = insn.getNext();
383 bb.set(tgt.getInstructionIndex());
387 // and now look at the handlers (every first insn is a bb start)
388 ExceptionHandler[] handlers = mi.getExceptions();
389 if (handlers != null) {
390 for (int i = 0; i < handlers.length; i++) {
391 Instruction tgt = mi.getInstructionAt(handlers[i].getHandler());
392 bb.set(tgt.getInstructionIndex());
399 System.out.println();
400 System.out.println(mi.getFullName());
401 for (int i=0; i<code.length; i++) {
402 System.out.print(String.format("%1$2d %2$c ",i, bb.get(i) ? '>' : ' '));
403 System.out.println(code[i]);
411 Coverage getCoveredBasicBlocks() {
412 BitSet bExec = getExecutedInsn();
413 BitSet bb = getBasicBlocks();
416 if (excludeHandlers) {
417 BitSet handlers = getHandlers();
422 BitSet bCov = new BitSet(bb.size());
425 nCov = bCov.cardinality();
428 return new Coverage(bb.cardinality(), nCov);
432 static class ClassCoverage {
434 String className; // need to store since we never might get a ClassInfo
435 ClassInfo ci; // not initialized before the class is loaded
437 HashMap<MethodInfo, MethodCoverage> methods;
439 ClassCoverage(String className) {
440 this.className = className;
443 void setLoaded(ClassInfo ci) {
444 if (methods == null) {
447 log.info("used class: " + className);
449 methods = new HashMap<MethodInfo, MethodCoverage>();
450 for (MethodInfo mi : ci.getDeclaredMethodInfos()) {
451 // <2do> what about MJI methods? we should report why we don't cover them
452 if (!mi.isNative() && !mi.isAbstract()) {
453 MethodCoverage mc = new MethodCoverage(mi);
460 boolean isInterface() {
461 if (ci == null) // never loaded
462 if (!isCodeLoaded()) // can it be loaded?
463 return false; // will never load
465 return ci.isInterface();
468 boolean isCodeLoaded() {
473 ci = ClassLoaderInfo.getCurrentResolvedClassInfo(className);
474 } catch (ClassInfoException cie) {
475 log.warning("CoverageAnalyzer problem: " + cie); // Just log the problem but don't fail. We still want the report to be written.
481 MethodCoverage getMethodCoverage(MethodInfo mi) {
482 if (methods == null) {
485 return methods.get(mi);
488 Coverage getCoveredMethods() {
489 Coverage cov = new Coverage(0, 0);
491 if (methods != null) {
492 cov.total = methods.size();
494 for (MethodCoverage mc : methods.values()) {
495 if (mc.covered != null) {
504 Coverage getCoveredInsn() {
505 Coverage cov = new Coverage(0, 0);
507 if (methods != null) {
508 for (MethodCoverage mc : methods.values()) {
509 Coverage c = mc.getCoveredInsn();
510 cov.total += c.total;
511 cov.covered += c.covered;
518 boolean getCoveredLines(BitSet executable, BitSet covered) {
519 boolean result = false;
521 if (methods == null) {
525 for (MethodCoverage mc : methods.values()) {
526 result = mc.getCoveredLines(executable, covered) || result;
532 Coverage getCoveredLines() {
533 BitSet executable = new BitSet();
534 BitSet covered = new BitSet();
536 getCoveredLines(executable, covered);
538 return new Coverage(executable.cardinality(), covered.cardinality());
541 Coverage getCoveredBasicBlocks() {
542 Coverage cov = new Coverage(0, 0);
544 if (methods != null) {
545 for (MethodCoverage mc : methods.values()) {
546 Coverage c = mc.getCoveredBasicBlocks();
547 cov.total += c.total;
548 cov.covered += c.covered;
555 Coverage getCoveredBranches() {
556 Coverage cov = new Coverage(0, 0);
558 if (methods != null) {
559 for (MethodCoverage mc : methods.values()) {
560 Coverage c = mc.getCoveredBranches();
561 cov.total += c.total;
562 cov.covered += c.covered;
570 StringSetMatcher includes = null; // means all
571 StringSetMatcher excludes = null; // means none
572 StringSetMatcher loaded;
573 static boolean loadedOnly; // means we only check loaded classes that are not filtered
574 static boolean showMethods; // do we want to show per-method coverage?
575 static boolean showMethodBodies;
576 static boolean excludeHandlers; // do we count the handlers in? (off-nominal CF)
577 static boolean showBranchCoverage; // makes only sense with showMethods
578 static boolean showRequirements; // report requirements coverage
579 HashMap<String, ClassCoverage> classes = new HashMap<String, ClassCoverage>();
581 public CoverageAnalyzer(Config conf, JPF jpf) {
582 includes = StringSetMatcher.getNonEmpty(conf.getStringArray("coverage.include"));
583 excludes = StringSetMatcher.getNonEmpty(conf.getStringArray("coverage.exclude"));
585 showMethods = conf.getBoolean("coverage.show_methods", false);
586 showMethodBodies = conf.getBoolean("coverage.show_bodies", false);
587 excludeHandlers = conf.getBoolean("coverage.exclude_handlers", false);
588 showBranchCoverage = conf.getBoolean("coverage.show_branches", true);
589 loadedOnly = conf.getBoolean("coverage.loaded_only", true);
590 showRequirements = conf.getBoolean("coverage.show_requirements", false);
593 getCoverageCandidates(); // this might take a little while
596 jpf.addPublisherExtension(ConsolePublisher.class, this);
599 void getCoverageCandidates() {
601 // doesn't matter in which order we process this, since we
602 // just store one entry per qualified class name (i.e. there won't be
604 // NOTE : this doesn't yet deal with ClassLoaders, but that's also true for BCEL
605 ClassLoaderInfo cl = ClassLoaderInfo.getCurrentClassLoader();
606 for (String s : cl.getClassPathElements()) {
607 log.fine("analyzing classpath element: " + s);
608 File f = new File(s);
610 if (f.isDirectory()) {
611 traverseDir(f, null);
612 } else if (s.endsWith(".jar")) {
619 void addClassEntry(String clsName) {
620 ClassCoverage cc = new ClassCoverage(clsName);
621 classes.put(clsName, cc);
622 log.info("added class candidate: " + clsName);
625 boolean isAnalyzedClass(String clsName) {
626 return StringSetMatcher.isMatch(clsName, includes, excludes);
629 void traverseDir(File dir, String pkgPrefix) {
630 for (File f : dir.listFiles()) {
631 if (f.isDirectory()) {
632 String prefix = f.getName();
633 if (pkgPrefix != null) {
634 prefix = pkgPrefix + '.' + prefix;
636 traverseDir(f, prefix);
638 String fname = f.getName();
639 if (fname.endsWith(".class")) {
640 if (f.canRead() && (f.length() > 0)) {
641 String clsName = fname.substring(0, fname.length() - 6);
642 if (pkgPrefix != null) {
643 clsName = pkgPrefix + '.' + clsName;
646 if (isAnalyzedClass(clsName)) {
647 addClassEntry(clsName);
650 log.warning("cannot read class file: " + fname);
657 void traverseJar(File jar) {
659 JarFile jf = new JarFile(jar);
660 for (Enumeration<JarEntry> entries = jf.entries(); entries.hasMoreElements();) {
661 JarEntry e = entries.nextElement();
662 if (!e.isDirectory()) {
663 String eName = e.getName();
664 if (eName.endsWith(".class")) {
665 if (e.getSize() > 0) {
666 String clsName = eName.substring(0, eName.length() - 6);
667 clsName = clsName.replace('/', '.');
668 if (isAnalyzedClass(clsName)) {
669 addClassEntry(clsName);
672 log.warning("cannot read jar entry: " + eName);
677 } catch (IOException iox) {
678 iox.printStackTrace();
683 HashMap<String, Integer> getGlobalRequirementsMethods() {
684 HashMap<String, Integer> map = new HashMap<String, Integer>();
686 // <2do> this is extremely braindead, since inefficient
687 // it would be much better to do this with Class<?> instead of ClassInfo, but
688 // then we need a JPF- ClassLoader, since the path might be different. As a
689 // middle ground, we could use BCEL
691 for (ClassCoverage cc : classes.values()) {
692 ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(cc.className);
693 for (MethodInfo mi : ci.getDeclaredMethodInfos()) {
694 AnnotationInfo ai = getRequirementsAnnotation(mi);
696 for (String id : ai.getValueAsStringArray()) {
697 Integer n = map.get(id);
711 int computeTotalRequirementsMethods(HashMap<String, Integer> map) {
713 for (Integer i : map.values()) {
719 private void computeCoverages(String packageFilter, List<Map.Entry<String, ClassCoverage>> clsEntries, Coverage cls, Coverage mth, Coverage branch, Coverage block, Coverage line, Coverage insn) {
720 for (Map.Entry<String, ClassCoverage> e : clsEntries) {
721 if (e.getKey().startsWith(packageFilter)) {
722 ClassCoverage cc = e.getValue();
724 if (cc.isInterface()) {
734 insn.add(cc.getCoveredInsn());
735 line.add(cc.getCoveredLines());
736 block.add(cc.getCoveredBasicBlocks());
737 branch.add(cc.getCoveredBranches());
738 mth.add(cc.getCoveredMethods());
745 * print uncovered source ranges
748 //-------- the listener interface
750 public void classLoaded(VM vm, ClassInfo ci) {
751 String clsName = ci.getName();
754 if (isAnalyzedClass(clsName)) {
755 addClassEntry(clsName);
759 ClassCoverage cc = classes.get(clsName);
764 MethodInfo lastMi = null;
765 MethodCoverage lastMc = null;
767 MethodCoverage getMethodCoverage(Instruction insn) {
769 if (!insn.isExtendedInstruction()) {
770 MethodInfo mi = insn.getMethodInfo();
774 ClassInfo ci = mi.getClassInfo();
776 ClassCoverage cc = classes.get(ci.getName());
778 lastMc = cc.getMethodCoverage(mi);
788 HashMap<String, HashSet<MethodCoverage>> requirements;
790 void updateRequirementsCoverage(String[] ids, MethodCoverage mc) {
791 if (requirements == null) {
792 requirements = new HashMap<String, HashSet<MethodCoverage>>();
795 for (String id : ids) {
796 HashSet<MethodCoverage> mcs = requirements.get(id);
798 mcs = new HashSet<MethodCoverage>();
799 requirements.put(id, mcs);
802 if (!mcs.contains(mc)) {
808 AnnotationInfo getRequirementsAnnotation(MethodInfo mi) {
809 // <2do> should probably look for overridden method annotations
810 return mi.getAnnotation("gov.nasa.jpf.Requirement");
814 public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) {
815 MethodCoverage mc = getMethodCoverage(executedInsn);
818 mc.setExecuted(ti, executedInsn);
820 if (showRequirements) {
821 if (executedInsn.getPosition() == 0) { // first insn in method, check for Requirements
822 AnnotationInfo ai = getRequirementsAnnotation(mc.getMethodInfo());
824 String[] ids = ai.getValueAsStringArray();
825 updateRequirementsCoverage(ids, mc);
833 public void choiceGeneratorSet(VM vm, ChoiceGenerator<?> newCG) {
834 /*** should be an option
835 Instruction insn = vm.getLastInstruction();
836 MethodCoverage mc = getMethodCoverage(vm);
837 mc.setCGed(vm.getLastThreadInfo(),insn);
841 //-------- the publisher interface
842 private Publisher publisher;
843 private ArrayList<Map.Entry<String, ClassCoverage>> clsEntries;
846 abstract class Publish {
850 Publish (Publisher p){
854 abstract void publish();
855 abstract void printClassCoverages();
856 abstract void printRequirementsCoverage();
859 class PublishConsole extends Publish {
860 PublishConsole (ConsolePublisher p){
866 publisher.publishTopicStart("coverage statistics");
868 printClassCoverages();
870 if (showRequirements) {
871 printRequirementsCoverage();
876 void printCoverage (Coverage cov){
877 int nTotal = cov.total();
878 int nCovered = cov.covered();
884 s = String.format("%.2f (%d/%d)", ((double) nCovered / nTotal), nCovered, nTotal);
886 pw.print(String.format("%1$-18s", s));
891 void printClassCoverages() {
893 Coverage clsCoverage = new Coverage(0, 0);
894 Coverage mthCoverage = new Coverage(0, 0);
895 Coverage bbCoverage = new Coverage(0, 0);
896 Coverage lineCoverage = new Coverage(0, 0);
897 Coverage insnCoverage = new Coverage(0, 0);
898 Coverage branchCoverage = new Coverage(0, 0);
900 computeCoverages("", clsEntries, clsCoverage, mthCoverage, branchCoverage, bbCoverage, lineCoverage, insnCoverage);
903 pw.println("-------------------------------------------- class coverage ------------------------------------------------");
904 pw.println("bytecode line basic-block branch methods location");
905 pw.println("------------------------------------------------------------------------------------------------------------");
908 for (Map.Entry<String, ClassCoverage> e : clsEntries) {
909 ClassCoverage cc = e.getValue();
911 printCoverage(cc.getCoveredInsn());
914 printCoverage(cc.getCoveredLines());
917 printCoverage(cc.getCoveredBasicBlocks());
920 printCoverage(cc.getCoveredBranches());
923 printCoverage(cc.getCoveredMethods());
926 pw.println(e.getKey());
929 printMethodCoverages(cc);
934 pw.println("------------------------------------------------------------------------------------------------------------");
936 printCoverage(insnCoverage);
938 printCoverage(lineCoverage);
940 printCoverage(bbCoverage);
942 printCoverage(branchCoverage);
944 printCoverage(mthCoverage);
946 printCoverage(clsCoverage);
947 pw.println(" total");
952 void printRequirementsCoverage() {
953 HashMap<String, Integer> reqMethods = getGlobalRequirementsMethods();
956 Coverage bbAll = new Coverage(0, 0);
957 Coverage insnAll = new Coverage(0, 0);
958 Coverage branchAll = new Coverage(0, 0);
959 Coverage mthAll = new Coverage(0, 0);
960 Coverage reqAll = new Coverage(0, 0);
962 reqAll.total = reqMethods.size();
963 mthAll.total = computeTotalRequirementsMethods(reqMethods);
967 pw.println("--------------------------------- requirements coverage -----------------------------------");
968 pw.println("bytecode basic-block branch methods requirement");
969 pw.println("-------------------------------------------------------------------------------------------");
971 for (String id : Misc.getSortedKeyStrings(reqMethods)) {
973 Coverage bbCoverage = new Coverage(0, 0);
974 Coverage insnCoverage = new Coverage(0, 0);
975 Coverage branchCoverage = new Coverage(0, 0);
976 Coverage reqMth = new Coverage(reqMethods.get(id), 0);
978 if (requirements != null && requirements.containsKey(id)) {
980 for (MethodCoverage mc : requirements.get(id)) {
981 insnCoverage.add(mc.getCoveredInsn());
982 bbCoverage.add(mc.getCoveredBasicBlocks());
983 branchCoverage.add(mc.getCoveredBranches());
990 printCoverage(insnCoverage);
992 printCoverage(bbCoverage);
994 printCoverage(branchCoverage);
996 printCoverage(reqMth);
997 pw.print("\"" + id + "\"");
1003 for (MethodCoverage mc : requirements.get(id)) {
1006 printCoverage(mc.getCoveredInsn());
1008 printCoverage(mc.getCoveredBasicBlocks());
1010 printCoverage(mc.getCoveredBranches());
1013 pw.print(mc.getMethodInfo().getFullName());
1017 } else { // requirement not covered
1018 pw.print(" - - - ");
1020 printCoverage(reqMth);
1021 pw.print("\"" + id + "\"");
1025 insnAll.add(insnCoverage);
1026 bbAll.add(bbCoverage);
1027 branchAll.add(branchCoverage);
1031 pw.println("------------------------------------------------------------------------------------------");
1033 printCoverage(insnAll);
1035 printCoverage(bbAll);
1037 printCoverage(branchAll);
1039 printCoverage(mthAll);
1041 printCoverage(reqAll);
1047 void printMethodCoverages(ClassCoverage cc) {
1049 boolean result = true;
1051 if (cc.methods == null) {
1055 ArrayList<Map.Entry<MethodInfo, MethodCoverage>> mthEntries =
1056 Misc.createSortedEntryList(cc.methods, new Comparator<Map.Entry<MethodInfo, MethodCoverage>>() {
1059 public int compare(Map.Entry<MethodInfo, MethodCoverage> o1,
1060 Map.Entry<MethodInfo, MethodCoverage> o2) {
1061 int a = o2.getValue().getCoveredInsn().percent();
1062 int b = o1.getValue().getCoveredInsn().percent();
1065 return o2.getKey().getUniqueName().compareTo(o1.getKey().getUniqueName());
1072 Coverage emptyCoverage = new Coverage(0, 0);
1074 for (Map.Entry<MethodInfo, MethodCoverage> e : mthEntries) {
1075 MethodCoverage mc = e.getValue();
1076 MethodInfo mi = mc.getMethodInfo();
1077 Coverage insnCoverage = mc.getCoveredInsn();
1078 Coverage lineCoverage = mc.getCoveredLines();
1079 Coverage branchCoverage = mc.getCoveredBranches();
1081 result = result && insnCoverage.isFullyCovered();
1085 printCoverage(insnCoverage);
1088 printCoverage(lineCoverage);
1091 printCoverage(mc.getCoveredBasicBlocks());
1094 printCoverage(branchCoverage);
1097 printCoverage(emptyCoverage);
1100 pw.print(mi.getLongName());
1103 if (showMethodBodies &&
1104 (!insnCoverage.isFullyCovered() || !branchCoverage.isFullyCovered())) {
1105 printBodyCoverage(mc);
1110 void printBodyCoverage(MethodCoverage mc) {
1111 MethodInfo mi = mc.getMethodInfo();
1112 Instruction[] code = mi.getInstructions();
1113 BitSet cov = mc.getExecutedInsn();
1116 BitSet handlers = mc.getHandlers();
1118 if (excludeHandlers) {
1119 cov.andNot(handlers);
1122 for (i = 0; i < code.length; i++) {
1123 if (!cov.get(i)) { // not covered
1129 printSourceRange(code, handlers, start, i - 1, "");
1135 printSourceRange(code, handlers, start, i - 1, "");
1138 // now print the missing branches
1139 BitSet branches = mc.getBranches();
1140 lastStart = -1; // reset in case condition and branch are in same line
1141 for (i = 0; i < code.length; i++) {
1142 if (branches.get(i)) {
1144 BitSet bTrue = mc.branchTrue;
1145 BitSet bFalse = mc.branchFalse;
1146 if (bTrue != null) { // means we have condition bit sets
1147 boolean cTrue = bTrue.get(i);
1148 boolean cFalse = bFalse.get(i);
1150 prefix = cFalse ? "" : "F "; // covered or false missing
1152 prefix = cFalse ? "T " : "N "; // true or both missing
1155 prefix = "N "; // not covered at all
1158 if (prefix != null) {
1159 printSourceRange(code, handlers, i, i, prefix);
1165 // there might be several ranges within the same source line
1168 void printSourceRange(Instruction[] code, BitSet handlers,
1169 int start, int end, String prefix) {
1171 int line = code[start].getLineNumber();
1173 if (lastStart == line) {
1179 printLocation(prefix, "at", code[start].getSourceLocation(), handlers.get(start) ? "x" : "");
1181 if (line != code[end].getLineNumber()) {
1182 printLocation(prefix, "..", code[end].getSourceLocation(), handlers.get(end) ? "x" : "");
1184 // we need the "(..)" wrapper so that Eclipse parses this
1185 // as a source location when displaying the report in a console
1188 private void printLocation(String prefix, String at, String location, String suffix) {
1190 printBlanks(pw, 84);
1199 void printBlanks(PrintWriter pw, int n) {
1200 for (int i = 0; i < n; i++) {
1209 public void publishFinished(Publisher publisher) {
1211 if (clsEntries == null) {
1212 clsEntries = Misc.createSortedEntryList(classes, new Comparator<Map.Entry<String, ClassCoverage>>() {
1215 public int compare(Map.Entry<String, ClassCoverage> o1,
1216 Map.Entry<String, ClassCoverage> o2) {
1217 return o2.getKey().compareTo(o1.getKey());
1222 this.publisher = publisher;
1224 if (publisher instanceof ConsolePublisher) {
1225 new PublishConsole((ConsolePublisher) publisher).publish();