当前位置: 首页 > article >正文

2.3.4 JacocoCli二次开发

在覆盖率测试过程中,根据业务需要,一次测试需要同时生成全量报告和增量报告,对比网上现存在的方案,经过各种尝试后决定采取对jacococli做二次开发。

一,二次开发Jacoco

jacoco二开,主要加入了增量代码匹配的功能,使用的开源项目:jacoco: jacoco二开,支持增量代码覆盖率

1,修改KotlinInlineFilter.java

经过各种尝试,发现这个项目在文件:org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java 中与jacoco github上的有差别::https://github.com/jacoco/jacoco/blob/master/org.jacoco.core/src/org/jacoco/core/internal/analysis/filter/KotlinInlineFilter.java

主要是下面这个函数,修改成与github上的一样就可以,可能是这个jacoco项目比较早的原因:

private static int getFirstGeneratedLineNumber(final String sourceFileName,
      final String smap) {
   try {
      final BufferedReader br = new BufferedReader(
            new StringReader(smap));
      expectLine(br, "SMAP");
      // OutputFileName
      expectLine(br, sourceFileName);
      // DefaultStratumId
      expectLine(br, "Kotlin");
      // StratumSection
      expectLine(br, "*S Kotlin");
      // FileSection
      expectLine(br, "*F");
      final BitSet sourceFileIds = new BitSet();
      String line;
      while (!"*L".equals(line = br.readLine())) {
         // AbsoluteFileName
         br.readLine();

         final Matcher m = FILE_INFO_PATTERN.matcher(line);
         if (!m.matches()) {
            throw new IllegalStateException(
                  "Unexpected SMAP line: " + line);
         }
         final String fileName = m.group(2);
         if (fileName.equals(sourceFileName)) {
            sourceFileIds.set(Integer.parseInt(m.group(1)));
         }
      }
      if (sourceFileIds.isEmpty()) {
         throw new IllegalStateException("Unexpected SMAP FileSection");
      }
      // LineSection
      int min = Integer.MAX_VALUE;
      while (true) {
         line = br.readLine();
         if (line.equals("*E") || line.equals("*S KotlinDebug")) {
            break;
         }
         final Matcher m = LINE_INFO_PATTERN.matcher(line);
         if (!m.matches()) {
            throw new IllegalStateException(
                  "Unexpected SMAP line: " + line);
         }
         final int inputStartLine = Integer.parseInt(m.group(1));
         final int lineFileID = Integer
               .parseInt(m.group(2).substring(1));
         final int outputStartLine = Integer.parseInt(m.group(4));
         if (sourceFileIds.get(lineFileID)
               && inputStartLine == outputStartLine) {
            continue;
         }
         min = Math.min(outputStartLine, min);
      }
      return min;
   } catch (final IOException e) {
      // Must not happen with StringReader
      throw new AssertionError(e);
   }
}

2,修改CodeDiffUtil.java

开源项目的函数检测CodeDiffUtil.java也有问题,需要改成如下所示:

/*******************************************************************************
 * Copyright (c) 2009, 2021 Mountainminds GmbH & Co. KG and Contributors
 * This program and the accompanying materials are made available under
 * the terms of the Eclipse Public License 2.0 which is available at
 * http://www.eclipse.org/legal/epl-2.0
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 *    Marc R. Hoffmann - initial API and implementation
 *
 *******************************************************************************/
package org.jacoco.core.internal.diff;

import org.jacoco.core.analysis.CoverageBuilder;
import org.objectweb.asm.Type;

import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

/**
 * @ProjectName: root
 * @Package: org.jacoco.core.internal.diff
 * @Description: 差异代码处理类
 * @Author: duanrui
 * @CreateDate: 2021/1/12 15:17
 * @Version: 1.0
 *           <p>
 *           Copyright: Copyright (c) 2021
 */
public class CodeDiffUtil {

   private final static String OPERATE_ADD = "ADD";
   private static String OutputStream;

   /**
    * 检测类是否在差异代码中
    *
    * @param className
    * @return Boolean
    */
   public static Boolean checkClassIn(String className,
         List<ClassInfoDto> classInfos) {
      if (null == classInfos || classInfos.isEmpty() || null == className) {
         return Boolean.FALSE;
      }
        System.out.println("className="+className)
      // 这里要考虑匿名内部类的问题
      return classInfos.stream()
            .anyMatch(c -> className.equals(c.getClassFile())
                  || className.split("\\$")[0].equals(c.getClassFile()));
   }

   /**
    * 检测方法是否在差异代码中
    *
    * @param className
    * @param methodName
    * @return Boolean
    */
   public static Boolean checkMethodIn(String className, String methodName,
         String desc, List<ClassInfoDto> classInfos) {
      // 参数校验
      if (null == classInfos || classInfos.isEmpty() || null == methodName
            || null == className) {
         return Boolean.FALSE;
      }
      ClassInfoDto classInfoDto = classInfos.stream()
            .filter(c -> className.equals(c.getClassFile())
                  || className.split("\\$")[0].equals(c.getClassFile()))
            .findFirst().orElse(null);
      if (null == classInfoDto) {
         return Boolean.FALSE;
      }
      // 如果是新增类,不用匹配方法,直接运行
      if (OPERATE_ADD.equals(classInfoDto.getType())) {
         return Boolean.TRUE;
      }
      if (null == classInfoDto.getMethodInfos()
            || classInfoDto.getMethodInfos().isEmpty()) {
         return Boolean.FALSE;
      }
      // 匹配了方法,参数也需要校验
      return classInfoDto.getMethodInfos().stream().anyMatch(m -> {
         if (methodName.equals(m.getMethodName())) {
            // System.out.println("className=" + className + ",methodName="
            // + methodName + ",parmas=" + desc + ",m.getParameters()="
            // + m.getParameters().toString());
            return checkParamsIn(m.getParameters(), desc);
            // lambda表示式匹配
         } else if (methodName.contains("lambda$")
               && methodName.split("\\$")[1].equals(m.getMethodName())) {
            return Boolean.TRUE;
         } else {
            return Boolean.FALSE;
         }
      });

   }

   /**
    * 匹配餐数
    * @param params
    *            格式:String a
    * @param desc
    *            转换后格式: java.lang.String
    * @return
    */
   public static Boolean checkParamsIn(List<String> params, String desc) {
      // 解析ASM获取的参数
      Type[] argumentTypes = Type.getArgumentTypes(desc);
      // 处理一下params,直接使用list有问题
      String ckparams = "";
      if (params.size() == 1) {
         ckparams = params.get(0);
         ckparams = ckparams.trim();
         if (ckparams.length() == 0 && argumentTypes.length == 0) {
            return Boolean.TRUE;
         } else {
            String[] diffParams = ckparams.split(",");
            // 只有参数数量完全相等才做下一次比较,Type格式:I C Ljava/lang/String;
            if (diffParams.length > 0
                  && argumentTypes.length == diffParams.length) {
               for (int i = 0; i < argumentTypes.length; i++) {
                  // 去掉包名只保留最后一位匹配,getClassName格式: int java/lang/String
                  String[] args = argumentTypes[i].getClassName()
                        .split("\\.");
                  String arg = args[args.length - 1];
                  // 如果参数是内部类类型,再截取下
                  if (arg.contains("$")) {
                     arg = arg.split("\\$")[arg.split("\\$").length - 1];
                  }
                  if (!diffParams[i].toLowerCase()
                        .contains(arg.toLowerCase())) {
                     return Boolean.FALSE;
                  }
               }
               // 只有个数和类型全匹配到才算匹配
               return Boolean.TRUE;
            }
            return Boolean.FALSE;
         }
      } else {
         return Boolean.FALSE;
      }
   }
}

二,构建项目

从jacoco: jacoco二开,支持增量代码覆盖率下载项目,修改上面提到的文件后,对项目进行打包构建,执行打包命令:

mvn clean install -Dmaven.test.skip=true -Dmaven.javadoc.skip=true

项目会下载很多依赖的包,耐心等待即可。最后命令执行完成后,发现出错了,如下所示:

其实核心的内容已经构建完成,这几个失败和跳过的项目没有关系,找到我们需要的jacococli项目,查看构建效果。

相应的jar包打包完成,没有问题。

三,测试修改后的包

看项目的介绍,需要使用org.jacoco.cli-0.8.7-SNAPSHOT-nodeps.jar,修改成newjacococli.jar以便进行测试。

1,生成正常的覆盖率报告

执行如下命令:

java -jar ./newjacococli.jar report ./coverage-4.1.100.66230-2023-08-11-17_48_31.ec --classfiles /Users/sxf/Downloads/jacocoincrease/build_classes_12354/61/appstoreRelease/*********/RoomListFragment.class --sourcefiles /Users/********/src/main/java/ --encoding utf-8 --html ./jacoco

没有指定diff信息,可以生成指定类的全量覆盖率报告:

2,生成diff报告信息

通过其他的功能,拿到diff文件信息,格式如下所示:

[ {"classFile":"com/*********t/RoomListFragment", "methodInfos":[ {"methodName":"updateFolderTabLayout","parameters":["folderList: List<ConversationFolderUIEntity>"]}, {"methodName":"onCreate","parameters":["savedInstanceState: Bundle?"]} ], "type":"MODIFY" } ]

将上面的json转换成String,执行如下命令生成增量报告:

java -jar ./newjacococli.jar report ./coverage-4.1.100.66230-2023-08-11-17_48_31.ec --classfiles /Users/sxf/Downloads/jacocoincrease/build_classes_12354/************/RoomListFragment.class --sourcefiles /Users/********/src/main/java/ --encoding utf-8 --html ./jacoco2 --diffCode "[{\"classFile\":\"com/*******/RoomListFragment\",\"methodInfos\":[{\"methodName\":\"updateFolderTabLayout\",\"parameters\":[\"folderList: List<ConversationFolderUIEntity>\"]},{\"methodName\":\"onCreate\",\"parameters\":[\"savedInstanceState: Bundle?\"]}],\"type\":\"MODIFY\"}]"

生成的增量报告结果如下:

渲染结果如下:

通过上面的测试,可以达到想要的效果,现在就需要再修改一下Android agent先找到diff的信息,再去执行新的增量覆盖率的命令。

四,文件格式指定增量信息

如果diff文件过多,则无法使用命令行方式建议改成以json文件传递。将diff信息放到diff_files.json文件中,命令变成:

java -jar ./newjacococli.jar report ./packages/******/outputs/code-coverage/connected/mergedcoverage.ec 
--classfiles ./build_classes_12397/62/appstoreRelease/*******/media/MediaFragment.class 
--sourcefiles ./packages/********8/src/main/java 
--encoding utf-8 --html ./diffreportsxf --diffCodeFiles 
./diff_files.json


http://www.kler.cn/a/599318.html

相关文章:

  • 毫米波雷达标定(2)
  • 充电桩测试系统(源码+文档+讲解+演示)
  • 什么是 Ansible Playbook?
  • Dynamics 365 Business Central 财务经常性一般日记帐做帐方法简介
  • fastapi+angular评论和回复
  • 【AVRCP】GOEP互操作性深度解析:蓝牙封面艺术传输的技术实现与演进
  • 索引优化的第一步,explain计划详细操作
  • 常见框架漏洞之五:中间件
  • 【从零实现Json-Rpc框架】- 入门准备篇
  • UE4学习笔记 FPS游戏制作16 重构FppShooter和RoboteShooter 提出父类Shooter
  • 蓝桥杯刷题 Day 4 栈与链表
  • NLP高频面试题(十四)——DPO、PPO等强化学习训练方法介绍
  • BKA-CNN-LSTM、CNN-LSTM、LSTM、CNN四模型多变量时序光伏功率预测,附模型研究报告
  • 鸿蒙NEXT开发案例:程序员计算器
  • Docker 镜像构建与优化
  • 上海瀛旻信息科技有限公司
  • Git 裸仓库:局域网仓库共享
  • 练习:统计满足条件的数字
  • Fiddle快速入门(抓包工具)
  • Spring Boot 项目打包运行