首先從注入方式開始:css
JVM中經過-javaagent參數指定特定的jar文件啓動Instrumentation的代理程序,代理程序在經過Class Loader裝載一個class前判斷是否轉換修改class文件,將統計代碼插入class,測試覆蓋率分析能夠在JVM執行測試代碼的過程當中完成。html
開始 找入口,找入口java
在jvm 啓動參數指定javaagent後,指定了jacoco的jar,啓動jvm實例會調用程序裏面的permain方法git
位於org.jacoco.agent.rt.internal.PreMainjvm
//接受jvm參數
package org.jacoco.agent.rt.internal.PreMain public static void premain(final String options, final Instrumentation inst) throws Exception { final AgentOptions agentOptions = new AgentOptions(options); final Agent agent = Agent.getInstance(agentOptions); final IRuntime runtime = createRuntime(inst); runtime.startup(agent.getData()); inst.addTransformer(new CoverageTransformer(runtime, agentOptions, IExceptionLogger.SYSTEM_ERR)); }
//ASM 注入class method public byte[] instrument(final ClassReader reader) { final ClassWriter writer = new ClassWriter(reader, 0) { @Override protected String getCommonSuperClass(final String type1, final String type2) { throw new IllegalStateException(); } }; final IProbeArrayStrategy strategy = ProbeArrayStrategyFactory .createFor(reader, accessorGenerator); final ClassVisitor visitor = new ClassProbesAdapter( new ClassInstrumenter(strategy, writer), true); reader.accept(visitor, ClassReader.EXPAND_FRAMES); return writer.toByteArray(); }
程序保持運行,當調用接口覆蓋了代碼後ide
//ASM回調方法,同時jacoco調用分析方法 Override public final MethodVisitor visitMethod(final int access, final String name, final String desc, final String signature, final String[] exceptions) { final MethodProbesVisitor methodProbes; final MethodProbesVisitor mv = cv.visitMethod(access, name, desc, signature, exceptions); if (mv == null) { // We need to visit the method in any case, otherwise probe ids // are not reproducible methodProbes = EMPTY_METHOD_PROBES_VISITOR; } else { methodProbes = mv; } return new MethodSanitizer(null, access, name, desc, signature, exceptions) { @Override public void visitEnd() { super.visitEnd(); LabelFlowAnalyzer.markLabels(this); final MethodProbesAdapter probesAdapter = new MethodProbesAdapter( methodProbes, ClassProbesAdapter.this); if (trackFrames) { final AnalyzerAdapter analyzer = new AnalyzerAdapter( ClassProbesAdapter.this.name, access, name, desc, probesAdapter); probesAdapter.setAnalyzer(analyzer); methodProbes.accept(this, analyzer); //注入數據分析 } else { methodProbes.accept(this, probesAdapter); } } }; }
//覆蓋率統計 public void increment(final ISourceNode child) { instructionCounter = instructionCounter.increment(child .getInstructionCounter()); branchCounter = branchCounter.increment(child.getBranchCounter()); complexityCounter = complexityCounter.increment(child .getComplexityCounter()); methodCounter = methodCounter.increment(child.getMethodCounter()); classCounter = classCounter.increment(child.getClassCounter()); final int firstLine = child.getFirstLine(); if (firstLine != UNKNOWN_LINE) { final int lastLine = child.getLastLine(); ensureCapacity(firstLine, lastLine); for (int i = firstLine; i <= lastLine; i++) { final ILine line = child.getLine(i); incrementLine(line.getInstructionCounter(), line.getBranchCounter(), i); } } }
在我們操做後,覆蓋率數據也在生成。在我們dump數據後,會調用
package org.jacoco.ant.ReportTask測試
順著createReport方法 ,我們看到最後是調用ui
private void createReport(final IReportGroupVisitor visitor, final GroupElement group) throws IOException { if (group.name == null) { throw new BuildException("Group name must be supplied", getLocation()); } if (group.children.isEmpty()) { final IBundleCoverage bundle = createBundle(group); final SourceFilesElement sourcefiles = group.sourcefiles; final AntResourcesLocator locator = new AntResourcesLocator( sourcefiles.encoding, sourcefiles.tabWidth); locator.addAll(sourcefiles.iterator()); if (!locator.isEmpty()) { checkForMissingDebugInformation(bundle); } visitor.visitBundle(bundle, locator); } else { final IReportGroupVisitor groupVisitor = visitor .visitGroup(group.name); for (final GroupElement child : group.children) { createReport(groupVisitor, child); } } }
接着咱們看看這些highlight是如何生成的:this
這些紅紅綠綠的覆蓋效果(highlight)spa
1.獲取class每一行和以前運行生成的覆蓋率行的類型作對比(以前應該有作class的一致性校驗,否則行數就沒意義)
2.根據type給予css作顏色標識(綠色爲覆蓋,紅色爲未覆蓋)
public void render(final HTMLElement parent, final ISourceNode source, final Reader contents) throws IOException { final HTMLElement pre = parent.pre(Styles.SOURCE + " lang-" + lang + " linenums"); final BufferedReader lineBuffer = new BufferedReader(contents); String line; int nr = 0; while ((line = lineBuffer.readLine()) != null) { nr++; renderCodeLine(pre, line, source.getLine(nr), nr); } } HTMLElement highlight(final HTMLElement pre, final ILine line, final int lineNr) throws IOException { final String style; switch (line.getStatus()) { case ICounter.NOT_COVERED: style = Styles.NOT_COVERED; break; case ICounter.FULLY_COVERED: style = Styles.FULLY_COVERED; break; case ICounter.PARTLY_COVERED: style = Styles.PARTLY_COVERED; break; default: return pre; } final String lineId = "L" + Integer.toString(lineNr); final ICounter branches = line.getBranchCounter(); switch (branches.getStatus()) { case ICounter.NOT_COVERED: return span(pre, lineId, style, Styles.BRANCH_NOT_COVERED, "All %2$d branches missed.", branches); case ICounter.FULLY_COVERED: return span(pre, lineId, style, Styles.BRANCH_FULLY_COVERED, "All %2$d branches covered.", branches); case ICounter.PARTLY_COVERED: return span(pre, lineId, style, Styles.BRANCH_PARTLY_COVERED, "%1$d of %2$d branches missed.", branches); default: return pre.span(style, lineId); } } pre.source span.pc { background-color:#ffffcc; }
若是咱們要加入增量代碼的覆蓋率標識怎麼作:
1.git diff出增長了哪些代碼
2.重寫highlight方法,若是讀取的class的line是新增的話,往html裏面加標識(「+++」)
3.從新構建javaagent.jar
最後效果:
新增代碼前面會有 「+++」 標識覆蓋