1. 程式人生 > >Jacoco(一)簡析原理 和 改造新增代碼覆蓋率標識進入報告

Jacoco(一)簡析原理 和 改造新增代碼覆蓋率標識進入報告

element ssp fff scounter except div clas ioe ntop

首先從註入方式開始:

  • On-the-fly插樁:

JVM中通過-javaagent參數指定特定的jar文件啟動Instrumentation的代理程序,代理程序在通過Class Loader裝載一個class前判斷是否轉換修改class文件,將統計代碼插入class,測試覆蓋率分析可以在JVM執行測試代碼的過程中完成。

在jvm 啟動參數指定javaagent後,指定了jacoco的jar,啟動jvm實例會調用程序裏面的permain方法

//接受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
      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方法 ,我們看到最後是調用

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 %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

最後效果:

新增代碼前面會有 “+++” 標識覆蓋

技術分享圖片

Jacoco(一)簡析原理 和 改造新增代碼覆蓋率標識進入報告