zoukankan      html  css  js  c++  java
  • Jacoco(一)简析原理 和 改造新增代码覆盖率标识进入报告

      首先从注入方式开始:

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

     最后效果:

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

  • 相关阅读:
    正则匹配任意字(包括换行符)
    linux终端光标的快捷键操作
    正则向前查找和向后查找
    正则表达式软件Expresso
    JsonP 跨域完全解析
    PHP代码阅读Phpxref
    ubuntu 操作用户名和密码
    curl多线程解释[转]
    php递归创建多级目录
    离散数学 第一章 命题逻辑 11 命题及其表示法
  • 原文地址:https://www.cnblogs.com/season-xie/p/8608935.html
Copyright © 2011-2022 走看看