// // Copyright (C) 2007 United States Government as represented by the // Administrator of the National Aeronautics and Space Administration // (NASA). All Rights Reserved. // // This software is distributed under the NASA Open Source Agreement // (NOSA), version 1.3. The NOSA has been approved by the Open Source // Initiative. See the file NOSA-1.3-JPF at the top of the distribution // directory tree for the complete NOSA document. // // THE SUBJECT SOFTWARE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY OF ANY // KIND, EITHER EXPRESSED, IMPLIED, OR STATUTORY, INCLUDING, BUT NOT // LIMITED TO, ANY WARRANTY THAT THE SUBJECT SOFTWARE WILL CONFORM TO // SPECIFICATIONS, ANY IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR // A PARTICULAR PURPOSE, OR FREEDOM FROM INFRINGEMENT, ANY WARRANTY THAT // THE SUBJECT SOFTWARE WILL BE ERROR FREE, OR ANY WARRANTY THAT // DOCUMENTATION, IF PROVIDED, WILL CONFORM TO THE SUBJECT SOFTWARE. // package gov.nasa.jpf.listener; import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.util.ArrayList; import java.util.BitSet; import java.util.Comparator; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.jar.JarEntry; import java.util.jar.JarFile; import java.util.logging.Logger; import gov.nasa.jpf.Config; import gov.nasa.jpf.JPF; import gov.nasa.jpf.ListenerAdapter; import gov.nasa.jpf.jvm.bytecode.GOTO; import gov.nasa.jpf.jvm.bytecode.IfInstruction; import gov.nasa.jpf.jvm.bytecode.InvokeInstruction; import gov.nasa.jpf.jvm.bytecode.ReturnInstruction; import gov.nasa.jpf.report.ConsolePublisher; import gov.nasa.jpf.report.Publisher; import gov.nasa.jpf.report.PublisherExtension; import gov.nasa.jpf.util.Misc; import gov.nasa.jpf.util.StringSetMatcher; import gov.nasa.jpf.vm.AnnotationInfo; import gov.nasa.jpf.vm.ChoiceGenerator; import gov.nasa.jpf.vm.ClassInfo; import gov.nasa.jpf.vm.ClassInfoException; import gov.nasa.jpf.vm.ClassLoaderInfo; import gov.nasa.jpf.vm.ExceptionHandler; import gov.nasa.jpf.vm.Instruction; import gov.nasa.jpf.vm.MethodInfo; import gov.nasa.jpf.vm.ThreadInfo; import gov.nasa.jpf.vm.VM; /** * a listener to report coverage statistics * * The idea is to collect per-class/-method/-thread information about * executed instructions, and then analyze this deeper when it comes to * report time (e.g. branch coverage, basic blocks, ..) * * Keep in mind that this is potentially a concurrent, model checked program, * i.e. there is more to coverage than what hits the eye of a static analyzer * (exceptions, data and thread CGs) */ public class CoverageAnalyzer extends ListenerAdapter implements PublisherExtension { static Logger log = JPF.getLogger("gov.nasa.jpf.listener.CoverageAnalyzer"); static class Coverage { int total; int covered; Coverage(int total, int covered) { this.total = total; this.covered = covered; } public void add(Coverage cov) { total += cov.total; covered += cov.covered; } public int percent() { if (total > 0) { return covered * 100 / total; } return (Integer.MIN_VALUE); } public int covered() { return covered; } public int total() { return total; } public boolean isPartiallyCovered() { return ((covered > 0) && (covered < total)); } public boolean isNotCovered() { return (covered == 0); } public boolean isFullyCovered() { return (covered == total); } } static class MethodCoverage { MethodInfo mi; // we base everything else on bytecode instruction coverage BitSet[] covered; BitSet basicBlocks; // set on demand BitSet handlers; // set on demand BitSet branches; // set on demand BitSet branchTrue; BitSet branchFalse; MethodCoverage(MethodInfo mi) { this.mi = mi; log.info("add method: " + mi.getUniqueName()); } MethodInfo getMethodInfo() { return mi; } void setExecuted(ThreadInfo ti, Instruction insn) { int idx = ti.getId(); if (covered == null) { covered = new BitSet[idx + 1]; } else if (idx >= covered.length) { BitSet[] a = new BitSet[idx + 1]; System.arraycopy(covered, 0, a, 0, covered.length); covered = a; } if (covered[idx] == null) { covered[idx] = new BitSet(mi.getInstructions().length); } int off = insn.getInstructionIndex(); covered[idx].set(off); if (showBranchCoverage && (insn instanceof IfInstruction)) { if (branchTrue == null) { branchTrue = new BitSet(mi.getInstructions().length); branchFalse = new BitSet(branchTrue.size()); } if (!((IfInstruction) insn).getConditionValue()) { branchTrue.set(off); } else { branchFalse.set(off); } } } void setCGed(ThreadInfo ti, Instruction insn) { // ti = null; // Remove IDE warning about unused variable. // Hmm, we have to store the bb set at this point BitSet bb = getBasicBlocks(); Instruction next = insn.getNext(); if (next != null) { // insn might be a sync return bb.set(next.getInstructionIndex()); } } BitSet getExecutedInsn() { int nTotal = mi.getInstructions().length; BitSet bUnion = new BitSet(nTotal); if (covered != null) { for (BitSet b : covered) { if (b != null) { bUnion.or(b); } } } return bUnion; } Coverage getCoveredInsn() { int nTotal = mi.getInstructions().length; if (excludeHandlers) { nTotal -= getHandlers().cardinality(); } if (covered != null) { BitSet bExec = getExecutedInsn(); if (excludeHandlers) { bExec.andNot(getHandlers()); } return new Coverage(nTotal, bExec.cardinality()); } else { return new Coverage(nTotal, 0); } } Coverage getCoveredLines() { BitSet executable = new BitSet(); BitSet covered = new BitSet(); getCoveredLines(executable, covered); return new Coverage(executable.cardinality(), covered.cardinality()); } boolean getCoveredLines(BitSet executable, BitSet covered) { Instruction inst[] = mi.getInstructions(); BitSet insn; int i, line; if (covered == null) { return false; } insn = getExecutedInsn(); if (excludeHandlers) { insn.andNot(getHandlers()); } if (branchTrue != null) { for (i = branchTrue.length() - 1; i >= 0; i--) { boolean cTrue = branchTrue.get(i); boolean cFalse = branchFalse.get(i); if ((!cTrue || !cFalse) && (inst[i] instanceof IfInstruction)) { insn.clear(i); } } } for (i = inst.length - 1; i >= 0; i--) { line = inst[i].getLineNumber(); if (line > 0) { executable.set(line); covered.set(line); } } for (i = inst.length - 1; i >= 0; i--) { line = inst[i].getLineNumber(); if ((!insn.get(i)) && (line > 0)) { // TODO What do we do with instructions that don't have a line number. Is this even possible? covered.clear(line); } } return true; } Coverage getCoveredBranches() { BitSet b = getBranches(); int nTotal = b.cardinality(); int nCovered = 0; if (branchTrue != null) { int n = branchTrue.size(); for (int i = 0; i < n; i++) { boolean cTrue = branchTrue.get(i); boolean cFalse = branchFalse.get(i); if (cTrue && cFalse) { nCovered++; } } } return new Coverage(nTotal, nCovered); } BitSet getHandlerStarts() { BitSet b = new BitSet(mi.getInstructions().length); ExceptionHandler[] handler = mi.getExceptions(); if (handler != null) { for (int i = 0; i < handler.length; i++) { Instruction hs = mi.getInstructionAt(handler[i].getHandler()); b.set(hs.getInstructionIndex()); } } return b; } BitSet getHandlers() { // this algorithm is a bit subtle - we walk through the code until // we hit a forward GOTO (or RETURN). If the insn following the goto is the // beginning of a handler, we mark everything up to the jump address // as a handler block if (handlers == null) { BitSet hs = getHandlerStarts(); Instruction[] code = mi.getInstructions(); BitSet b = new BitSet(code.length); if (!hs.isEmpty()) { for (int i = 0; i < code.length; i++) { Instruction insn = code[i]; if (insn instanceof GOTO) { GOTO gotoInsn = (GOTO) insn; if (!gotoInsn.isBackJump() && hs.get(i + 1)) { // jump around handler int handlerEnd = gotoInsn.getTarget().getInstructionIndex(); for (i++; i < handlerEnd; i++) { b.set(i); } } } else if (insn instanceof ReturnInstruction) { // everything else is handler for (i++; i < code.length; i++) { b.set(i); } } } } handlers = b; } return handlers; } // that's kind of redundant with basic blocks, but not really - here // we are interested in the logic behind branches, i.e. we want to know // what the condition values were. We are not interested in GOTOs and exceptions BitSet getBranches() { if (branches == null) { Instruction[] code = mi.getInstructions(); BitSet br = new BitSet(code.length); for (int i = 0; i < code.length; i++) { Instruction insn = code[i]; if (insn instanceof IfInstruction) { br.set(i); } } branches = br; } return branches; } BitSet getBasicBlocks() { if (basicBlocks == null) { Instruction[] code = mi.getInstructions(); BitSet bb = new BitSet(code.length); bb.set(0); // first insn is always a bb start // first, look at the insn type for (int i = 0; i < code.length; i++) { Instruction insn = code[i]; if (insn instanceof IfInstruction) { IfInstruction ifInsn = (IfInstruction) insn; Instruction tgt = ifInsn.getTarget(); bb.set(tgt.getInstructionIndex()); tgt = ifInsn.getNext(); bb.set(tgt.getInstructionIndex()); } else if (insn instanceof GOTO) { Instruction tgt = ((GOTO) insn).getTarget(); bb.set(tgt.getInstructionIndex()); } else if (insn instanceof InvokeInstruction) { // hmm, this might be a bit too conservative, but who says we // don't jump out of a caller into a handler, or even that we // ever return from the call? Instruction tgt = insn.getNext(); bb.set(tgt.getInstructionIndex()); } } // and now look at the handlers (every first insn is a bb start) ExceptionHandler[] handlers = mi.getExceptions(); if (handlers != null) { for (int i = 0; i < handlers.length; i++) { Instruction tgt = mi.getInstructionAt(handlers[i].getHandler()); bb.set(tgt.getInstructionIndex()); } } basicBlocks = bb; /** dump System.out.println(); System.out.println(mi.getFullName()); for (int i=0; i<code.length; i++) { System.out.print(String.format("%1$2d %2$c ",i, bb.get(i) ? '>' : ' ')); System.out.println(code[i]); } **/ } return basicBlocks; } Coverage getCoveredBasicBlocks() { BitSet bExec = getExecutedInsn(); BitSet bb = getBasicBlocks(); int nCov = 0; if (excludeHandlers) { BitSet handlers = getHandlers(); bb.and(handlers); } if (bExec != null) { BitSet bCov = new BitSet(bb.size()); bCov.or(bb); bCov.and(bExec); nCov = bCov.cardinality(); } return new Coverage(bb.cardinality(), nCov); } } static class ClassCoverage { String className; // need to store since we never might get a ClassInfo ClassInfo ci; // not initialized before the class is loaded boolean covered; HashMap<MethodInfo, MethodCoverage> methods; ClassCoverage(String className) { this.className = className; } void setLoaded(ClassInfo ci) { if (methods == null) { this.ci = ci; covered = true; log.info("used class: " + className); methods = new HashMap<MethodInfo, MethodCoverage>(); for (MethodInfo mi : ci.getDeclaredMethodInfos()) { // <2do> what about MJI methods? we should report why we don't cover them if (!mi.isNative() && !mi.isAbstract()) { MethodCoverage mc = new MethodCoverage(mi); methods.put(mi, mc); } } } } boolean isInterface() { if (ci == null) // never loaded if (!isCodeLoaded()) // can it be loaded? return false; // will never load return ci.isInterface(); } boolean isCodeLoaded() { if (ci != null) return true; try { ci = ClassLoaderInfo.getCurrentResolvedClassInfo(className); } catch (ClassInfoException cie) { log.warning("CoverageAnalyzer problem: " + cie); // Just log the problem but don't fail. We still want the report to be written. } return ci != null; } MethodCoverage getMethodCoverage(MethodInfo mi) { if (methods == null) { setLoaded(ci); } return methods.get(mi); } Coverage getCoveredMethods() { Coverage cov = new Coverage(0, 0); if (methods != null) { cov.total = methods.size(); for (MethodCoverage mc : methods.values()) { if (mc.covered != null) { cov.covered++; } } } return cov; } Coverage getCoveredInsn() { Coverage cov = new Coverage(0, 0); if (methods != null) { for (MethodCoverage mc : methods.values()) { Coverage c = mc.getCoveredInsn(); cov.total += c.total; cov.covered += c.covered; } } return cov; } boolean getCoveredLines(BitSet executable, BitSet covered) { boolean result = false; if (methods == null) { return false; } for (MethodCoverage mc : methods.values()) { result = mc.getCoveredLines(executable, covered) || result; } return result; } Coverage getCoveredLines() { BitSet executable = new BitSet(); BitSet covered = new BitSet(); getCoveredLines(executable, covered); return new Coverage(executable.cardinality(), covered.cardinality()); } Coverage getCoveredBasicBlocks() { Coverage cov = new Coverage(0, 0); if (methods != null) { for (MethodCoverage mc : methods.values()) { Coverage c = mc.getCoveredBasicBlocks(); cov.total += c.total; cov.covered += c.covered; } } return cov; } Coverage getCoveredBranches() { Coverage cov = new Coverage(0, 0); if (methods != null) { for (MethodCoverage mc : methods.values()) { Coverage c = mc.getCoveredBranches(); cov.total += c.total; cov.covered += c.covered; } } return cov; } } StringSetMatcher includes = null; // means all StringSetMatcher excludes = null; // means none StringSetMatcher loaded; static boolean loadedOnly; // means we only check loaded classes that are not filtered static boolean showMethods; // do we want to show per-method coverage? static boolean showMethodBodies; static boolean excludeHandlers; // do we count the handlers in? (off-nominal CF) static boolean showBranchCoverage; // makes only sense with showMethods static boolean showRequirements; // report requirements coverage HashMap<String, ClassCoverage> classes = new HashMap<String, ClassCoverage>(); public CoverageAnalyzer(Config conf, JPF jpf) { includes = StringSetMatcher.getNonEmpty(conf.getStringArray("coverage.include")); excludes = StringSetMatcher.getNonEmpty(conf.getStringArray("coverage.exclude")); showMethods = conf.getBoolean("coverage.show_methods", false); showMethodBodies = conf.getBoolean("coverage.show_bodies", false); excludeHandlers = conf.getBoolean("coverage.exclude_handlers", false); showBranchCoverage = conf.getBoolean("coverage.show_branches", true); loadedOnly = conf.getBoolean("coverage.loaded_only", true); showRequirements = conf.getBoolean("coverage.show_requirements", false); if (!loadedOnly) { getCoverageCandidates(); // this might take a little while } jpf.addPublisherExtension(ConsolePublisher.class, this); } void getCoverageCandidates() { // doesn't matter in which order we process this, since we // just store one entry per qualified class name (i.e. there won't be // multiple entries) // NOTE : this doesn't yet deal with ClassLoaders, but that's also true for BCEL ClassLoaderInfo cl = ClassLoaderInfo.getCurrentClassLoader(); for (String s : cl.getClassPathElements()) { log.fine("analyzing classpath element: " + s); File f = new File(s); if (f.exists()) { if (f.isDirectory()) { traverseDir(f, null); } else if (s.endsWith(".jar")) { traverseJar(f); } } } } void addClassEntry(String clsName) { ClassCoverage cc = new ClassCoverage(clsName); classes.put(clsName, cc); log.info("added class candidate: " + clsName); } boolean isAnalyzedClass(String clsName) { return StringSetMatcher.isMatch(clsName, includes, excludes); } void traverseDir(File dir, String pkgPrefix) { for (File f : dir.listFiles()) { if (f.isDirectory()) { String prefix = f.getName(); if (pkgPrefix != null) { prefix = pkgPrefix + '.' + prefix; } traverseDir(f, prefix); } else { String fname = f.getName(); if (fname.endsWith(".class")) { if (f.canRead() && (f.length() > 0)) { String clsName = fname.substring(0, fname.length() - 6); if (pkgPrefix != null) { clsName = pkgPrefix + '.' + clsName; } if (isAnalyzedClass(clsName)) { addClassEntry(clsName); } } else { log.warning("cannot read class file: " + fname); } } } } } void traverseJar(File jar) { try (JarFile jf = new JarFile(jar)) { for (Enumeration<JarEntry> entries = jf.entries(); entries.hasMoreElements();) { JarEntry e = entries.nextElement(); if (!e.isDirectory()) { String eName = e.getName(); if (eName.endsWith(".class")) { if (e.getSize() > 0) { String clsName = eName.substring(0, eName.length() - 6); clsName = clsName.replace('/', '.'); if (isAnalyzedClass(clsName)) { addClassEntry(clsName); } } else { log.warning("cannot read jar entry: " + eName); } } } } } catch (IOException iox) { iox.printStackTrace(); } } HashMap<String, Integer> getGlobalRequirementsMethods() { HashMap<String, Integer> map = new HashMap<String, Integer>(); // <2do> this is extremely braindead, since inefficient // it would be much better to do this with Class<?> instead of ClassInfo, but // then we need a JPF- ClassLoader, since the path might be different. As a // middle ground, we could use BCEL for (ClassCoverage cc : classes.values()) { ClassInfo ci = ClassLoaderInfo.getCurrentResolvedClassInfo(cc.className); for (MethodInfo mi : ci.getDeclaredMethodInfos()) { AnnotationInfo ai = getRequirementsAnnotation(mi); if (ai != null) { for (String id : ai.getValueAsStringArray()) { Integer n = map.get(id); if (n == null) { map.put(id, 1); } else { map.put(id, n + 1); } } } } } return map; } int computeTotalRequirementsMethods(HashMap<String, Integer> map) { int n = 0; for (Integer i : map.values()) { n += i; } return n; } private void computeCoverages(String packageFilter, List<Map.Entry<String, ClassCoverage>> clsEntries, Coverage cls, Coverage mth, Coverage branch, Coverage block, Coverage line, Coverage insn) { for (Map.Entry<String, ClassCoverage> e : clsEntries) { if (e.getKey().startsWith(packageFilter)) { ClassCoverage cc = e.getValue(); if (cc.isInterface()) { continue; // no code } cls.total++; if (cc.covered) { cls.covered++; } insn.add(cc.getCoveredInsn()); line.add(cc.getCoveredLines()); block.add(cc.getCoveredBasicBlocks()); branch.add(cc.getCoveredBranches()); mth.add(cc.getCoveredMethods()); } } } /** * print uncovered source ranges */ //-------- the listener interface @Override public void classLoaded(VM vm, ClassInfo ci) { String clsName = ci.getName(); if (loadedOnly) { if (isAnalyzedClass(clsName)) { addClassEntry(clsName); } } ClassCoverage cc = classes.get(clsName); if (cc != null) { cc.setLoaded(ci); } } MethodInfo lastMi = null; MethodCoverage lastMc = null; MethodCoverage getMethodCoverage(Instruction insn) { if (!insn.isExtendedInstruction()) { MethodInfo mi = insn.getMethodInfo(); if (mi != lastMi) { lastMc = null; lastMi = mi; ClassInfo ci = mi.getClassInfo(); if (ci != null) { ClassCoverage cc = classes.get(ci.getName()); if (cc != null) { lastMc = cc.getMethodCoverage(mi); } } } return lastMc; } return null; } HashMap<String, HashSet<MethodCoverage>> requirements; void updateRequirementsCoverage(String[] ids, MethodCoverage mc) { if (requirements == null) { requirements = new HashMap<String, HashSet<MethodCoverage>>(); } for (String id : ids) { HashSet<MethodCoverage> mcs = requirements.get(id); if (mcs == null) { mcs = new HashSet<MethodCoverage>(); requirements.put(id, mcs); } if (!mcs.contains(mc)) { mcs.add(mc); } } } AnnotationInfo getRequirementsAnnotation(MethodInfo mi) { // <2do> should probably look for overridden method annotations return mi.getAnnotation("gov.nasa.jpf.Requirement"); } @Override public void instructionExecuted(VM vm, ThreadInfo ti, Instruction nextInsn, Instruction executedInsn) { MethodCoverage mc = getMethodCoverage(executedInsn); if (mc != null) { mc.setExecuted(ti, executedInsn); if (showRequirements) { if (executedInsn.getPosition() == 0) { // first insn in method, check for Requirements AnnotationInfo ai = getRequirementsAnnotation(mc.getMethodInfo()); if (ai != null) { String[] ids = ai.getValueAsStringArray(); updateRequirementsCoverage(ids, mc); } } } } } @Override public void choiceGeneratorSet(VM vm, ChoiceGenerator<?> newCG) { /*** should be an option Instruction insn = vm.getLastInstruction(); MethodCoverage mc = getMethodCoverage(vm); mc.setCGed(vm.getLastThreadInfo(),insn); ***/ } //-------- the publisher interface private Publisher publisher; private ArrayList<Map.Entry<String, ClassCoverage>> clsEntries; abstract class Publish { PrintWriter pw; Publish () {} Publish (Publisher p){ pw = p.getOut(); } abstract void publish(); abstract void printClassCoverages(); abstract void printRequirementsCoverage(); } class PublishConsole extends Publish { PublishConsole (ConsolePublisher p){ super(p); } void publish() { publisher.publishTopicStart("coverage statistics"); printClassCoverages(); if (showRequirements) { printRequirementsCoverage(); } } void printCoverage (Coverage cov){ int nTotal = cov.total(); int nCovered = cov.covered(); String s; if (nTotal <= 0) { s = " - "; } else { s = String.format("%.2f (%d/%d)", ((double) nCovered / nTotal), nCovered, nTotal); } pw.print(String.format("%1$-18s", s)); } void printClassCoverages() { String space = " "; Coverage clsCoverage = new Coverage(0, 0); Coverage mthCoverage = new Coverage(0, 0); Coverage bbCoverage = new Coverage(0, 0); Coverage lineCoverage = new Coverage(0, 0); Coverage insnCoverage = new Coverage(0, 0); Coverage branchCoverage = new Coverage(0, 0); computeCoverages("", clsEntries, clsCoverage, mthCoverage, branchCoverage, bbCoverage, lineCoverage, insnCoverage); pw.println(); pw.println("-------------------------------------------- class coverage ------------------------------------------------"); pw.println("bytecode line basic-block branch methods location"); pw.println("------------------------------------------------------------------------------------------------------------"); // Write Body for (Map.Entry<String, ClassCoverage> e : clsEntries) { ClassCoverage cc = e.getValue(); printCoverage(cc.getCoveredInsn()); pw.print(space); printCoverage(cc.getCoveredLines()); pw.print(space); printCoverage(cc.getCoveredBasicBlocks()); pw.print(space); printCoverage(cc.getCoveredBranches()); pw.print(space); printCoverage(cc.getCoveredMethods()); pw.print(space); pw.println(e.getKey()); if (showMethods) { printMethodCoverages(cc); } } pw.println(); pw.println("------------------------------------------------------------------------------------------------------------"); printCoverage(insnCoverage); pw.print(space); printCoverage(lineCoverage); pw.print(space); printCoverage(bbCoverage); pw.print(space); printCoverage(branchCoverage); pw.print(space); printCoverage(mthCoverage); pw.print(space); printCoverage(clsCoverage); pw.println(" total"); } void printRequirementsCoverage() { HashMap<String, Integer> reqMethods = getGlobalRequirementsMethods(); String space = " "; Coverage bbAll = new Coverage(0, 0); Coverage insnAll = new Coverage(0, 0); Coverage branchAll = new Coverage(0, 0); Coverage mthAll = new Coverage(0, 0); Coverage reqAll = new Coverage(0, 0); reqAll.total = reqMethods.size(); mthAll.total = computeTotalRequirementsMethods(reqMethods); pw.println(); pw.println(); pw.println("--------------------------------- requirements coverage -----------------------------------"); pw.println("bytecode basic-block branch methods requirement"); pw.println("-------------------------------------------------------------------------------------------"); for (String id : Misc.getSortedKeyStrings(reqMethods)) { Coverage bbCoverage = new Coverage(0, 0); Coverage insnCoverage = new Coverage(0, 0); Coverage branchCoverage = new Coverage(0, 0); Coverage reqMth = new Coverage(reqMethods.get(id), 0); if (requirements != null && requirements.containsKey(id)) { reqAll.covered++; for (MethodCoverage mc : requirements.get(id)) { insnCoverage.add(mc.getCoveredInsn()); bbCoverage.add(mc.getCoveredBasicBlocks()); branchCoverage.add(mc.getCoveredBranches()); mthAll.covered++; reqMth.covered++; } printCoverage(insnCoverage); pw.print(space); printCoverage(bbCoverage); pw.print(space); printCoverage(branchCoverage); pw.print(space); printCoverage(reqMth); pw.print("\"" + id + "\""); pw.println(); if (showMethods) { for (MethodCoverage mc : requirements.get(id)) { pw.print(space); printCoverage(mc.getCoveredInsn()); pw.print(space); printCoverage(mc.getCoveredBasicBlocks()); pw.print(space); printCoverage(mc.getCoveredBranches()); pw.print(space); pw.print(mc.getMethodInfo().getFullName()); pw.println(); } } } else { // requirement not covered pw.print(" - - - "); printCoverage(reqMth); pw.print("\"" + id + "\""); pw.println(); } insnAll.add(insnCoverage); bbAll.add(bbCoverage); branchAll.add(branchCoverage); } pw.println(); pw.println("------------------------------------------------------------------------------------------"); printCoverage(insnAll); pw.print(space); printCoverage(bbAll); pw.print(space); printCoverage(branchAll); pw.print(space); printCoverage(mthAll); pw.print(space); printCoverage(reqAll); pw.print(" total"); pw.println(); } void printMethodCoverages(ClassCoverage cc) { String space = " "; boolean result = true; if (cc.methods == null) { return; } ArrayList<Map.Entry<MethodInfo, MethodCoverage>> mthEntries = Misc.createSortedEntryList(cc.methods, new Comparator<Map.Entry<MethodInfo, MethodCoverage>>() { public int compare(Map.Entry<MethodInfo, MethodCoverage> o1, Map.Entry<MethodInfo, MethodCoverage> o2) { int a = o2.getValue().getCoveredInsn().percent(); int b = o1.getValue().getCoveredInsn().percent(); if (a == b) { return o2.getKey().getUniqueName().compareTo(o1.getKey().getUniqueName()); } else { return a - b; } } }); Coverage emptyCoverage = new Coverage(0, 0); for (Map.Entry<MethodInfo, MethodCoverage> e : mthEntries) { MethodCoverage mc = e.getValue(); MethodInfo mi = mc.getMethodInfo(); Coverage insnCoverage = mc.getCoveredInsn(); Coverage lineCoverage = mc.getCoveredLines(); Coverage branchCoverage = mc.getCoveredBranches(); result = result && insnCoverage.isFullyCovered(); pw.print(space); printCoverage(insnCoverage); pw.print(space); printCoverage(lineCoverage); pw.print(space); printCoverage(mc.getCoveredBasicBlocks()); pw.print(space); printCoverage(branchCoverage); pw.print(space); printCoverage(emptyCoverage); pw.print(space); pw.print(mi.getLongName()); pw.println(); if (showMethodBodies && (!insnCoverage.isFullyCovered() || !branchCoverage.isFullyCovered())) { printBodyCoverage(mc); } } } void printBodyCoverage(MethodCoverage mc) { MethodInfo mi = mc.getMethodInfo(); Instruction[] code = mi.getInstructions(); BitSet cov = mc.getExecutedInsn(); int i, start = -1; BitSet handlers = mc.getHandlers(); if (excludeHandlers) { cov.andNot(handlers); } for (i = 0; i < code.length; i++) { if (!cov.get(i)) { // not covered if (start == -1) { start = i; } } else { // covered if (start != -1) { printSourceRange(code, handlers, start, i - 1, ""); start = -1; } } } if (start != -1) { printSourceRange(code, handlers, start, i - 1, ""); } // now print the missing branches BitSet branches = mc.getBranches(); lastStart = -1; // reset in case condition and branch are in same line for (i = 0; i < code.length; i++) { if (branches.get(i)) { String prefix = ""; BitSet bTrue = mc.branchTrue; BitSet bFalse = mc.branchFalse; if (bTrue != null) { // means we have condition bit sets boolean cTrue = bTrue.get(i); boolean cFalse = bFalse.get(i); if (cTrue) { prefix = cFalse ? "" : "F "; // covered or false missing } else { prefix = cFalse ? "T " : "N "; // true or both missing } } else { prefix = "N "; // not covered at all } if (prefix != null) { printSourceRange(code, handlers, i, i, prefix); } } } } // there might be several ranges within the same source line int lastStart = -1; void printSourceRange(Instruction[] code, BitSet handlers, int start, int end, String prefix) { int line = code[start].getLineNumber(); if (lastStart == line) { return; } lastStart = line; printLocation(prefix, "at", code[start].getSourceLocation(), handlers.get(start) ? "x" : ""); if (line != code[end].getLineNumber()) { printLocation(prefix, "..", code[end].getSourceLocation(), handlers.get(end) ? "x" : ""); } // we need the "(..)" wrapper so that Eclipse parses this // as a source location when displaying the report in a console } private void printLocation(String prefix, String at, String location, String suffix) { printBlanks(pw, 84); pw.print(prefix); pw.print(at); pw.print(' '); pw.print(location); pw.print(' '); pw.println(suffix); } void printBlanks(PrintWriter pw, int n) { for (int i = 0; i < n; i++) { pw.print(' '); } } } @Override public void publishFinished(Publisher publisher) { if (clsEntries == null) { clsEntries = Misc.createSortedEntryList(classes, new Comparator<Map.Entry<String, ClassCoverage>>() { public int compare(Map.Entry<String, ClassCoverage> o1, Map.Entry<String, ClassCoverage> o2) { return o2.getKey().compareTo(o1.getKey()); } }); } this.publisher = publisher; if (publisher instanceof ConsolePublisher) { new PublishConsole((ConsolePublisher) publisher).publish(); } } }