欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

Jacoco原理简析

程序员文章站 2022-07-05 11:00:00
...

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

 

Jacoco原理简析
//接受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));
}

Jacoco原理简析

 

Jacoco原理简析
//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();
}

Jacoco原理简析

 

 程序保持运行,当调用接口覆盖了代码后

Jacoco原理简析
//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);
     }
  }

};
}

Jacoco原理简析

 

Jacoco原理简析
//覆盖率统计

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);
}
}
}

Jacoco原理简析

 

 

在我們操作后,覆蓋率數據也在生成。在我們dump數據后,會調用
package org.jacoco.ant.ReportTask

順著createReport方法 ,我們看到最後是調用

Jacoco原理简析
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);
      }
   }
}
Jacoco原理简析

 

 

接着我们看看这些highlight是如何生成的:

Jacoco原理简析这些红红绿绿的覆盖效果(highlight)

 

1.获取class每一行和之前运行生成的覆盖率行的类型做对比(之前应该有做class的一致性校验,不然行数就没意义)

2.根据type给予css做颜色标识(绿色为覆盖,红色为未覆盖)

Jacoco原理简析
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,
“%1dofd of %2d branches missed.”, branches);
default:
return pre.span(style, lineId);
}
}

pre.source span.pc {
background-color:#ffffcc;
}

Jacoco原理简析

 

如果我们要加入增量代码的覆盖率标识怎么做:

1.git diff出增加了哪些代码

2.重写highlight方法,如果读取的class的line是新增的话,往html里面加标识(“+++”)

3.重新构建javaagent.jar

 

 最后效果:

新增代码前面会有 “+++” 标识覆盖

Jacoco原理简析