Jacoco原理简析
</h1>
<div class="clear"></div>
<div class="postBody">
首先从注入方式开始:
- On-the-fly插桩:
JVM中通过-javaagent参数指定特定的jar文件启动Instrumentation的代理程序,代理程序在通过Class Loader装载一个class前判断是否转换修改class文件,将统计代码插入class,测试覆盖率分析可以在JVM执行测试代码的过程中完成。
开始 找入口,找入口
在jvm 启动参数指定javaagent后,指定了jacoco的jar,启动jvm实例会调用程序里面的permain方法
位于org.jacoco.agent.rt.internal.PreMain
//接受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();
}
程序保持运行,当调用接口覆盖了代码后
//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 </span><span style="color: #0000ff;">public</span> <span style="color: #0000ff;">void</span><span style="color: #000000;"> visitEnd() { </span><span style="color: #0000ff;">super</span><span style="color: #000000;">.visitEnd(); LabelFlowAnalyzer.markLabels(</span><span style="color: #0000ff;">this</span><span style="color: #000000;">); </span><span style="color: #0000ff;">final</span> MethodProbesAdapter probesAdapter = <span style="color: #0000ff;">new</span><span style="color: #000000;"> MethodProbesAdapter( methodProbes, ClassProbesAdapter.</span><span style="color: #0000ff;">this</span><span style="color: #000000;">); </span><span style="color: #0000ff;">if</span><span style="color: #000000;"> (trackFrames) { </span><span style="color: #0000ff;">final</span> AnalyzerAdapter analyzer = <span style="color: #0000ff;">new</span><span style="color: #000000;"> AnalyzerAdapter( ClassProbesAdapter.</span><span style="color: #0000ff;">this</span><span style="color: #000000;">.name, access, name, desc, probesAdapter); probesAdapter.setAnalyzer(analyzer); methodProbes.accept(</span><span style="color: #0000ff;">this</span>, analyzer); <span style="color: #008000;">//</span><span style="color: #008000;">注入数据分析</span> } <span style="color: #0000ff;">else</span><span style="color: #000000;"> { methodProbes.accept(</span><span style="color: #0000ff;">this</span><span style="color: #000000;">, 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方法 ,我們看到最後是調用
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是如何生成的:
这些红红绿绿的覆盖效果(highlight)
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 %2KaTeX parse error: Expected 'EOF', got '#' at position 40: … style="color: #̲000000;">, bran…d branches covered.”, branches);
case ICounter.PARTLY_COVERED:
return span(pre, lineId, style, Styles.BRANCH_PARTLY_COVERED,
“%1d 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
最后效果:
新增代码前面会有 “+++” 标识覆盖
上一篇: 根据IP限制指定时间内访问接口的次数