• jacoco-实战篇-增量覆盖率


    获取增量覆盖率报告的改动源码的步骤:

    第一步:拉取jacoco源码,源码下载地址:点我

    第二步:修改org.jacoco.core项目中

      1、增加项目依赖

        修改pom.xml文件,增加依赖如下:

    复制代码
      <!--java文件编译class-->
        <dependency>
          <groupId>org.eclipse.jdt</groupId>
          <artifactId>org.eclipse.jdt.core</artifactId>
          <version>3.19.0</version>
        </dependency>
    <!--git操作--> <dependency> <groupId>org.eclipse.jgit</groupId> <artifactId>org.eclipse.jgit</artifactId> <version>5.5.0.201909110433-r</version> </dependency>
    复制代码

      2、修改项目中org.jacoco.core.analysis包下的CoverageBuilder类:

    复制代码
      public static List<ClassInfo> classInfos;    // 新增的成员变量
    
        /**
         * 分支与master对比
         * @param gitPath local gitPath
         * @param branchName new test branch name
         */
        public CoverageBuilder(String gitPath, String branchName) {
            this.classes = new HashMap<String, IClassCoverage>();
            this.sourcefiles = new HashMap<String, ISourceFileCoverage>();
            classInfos = CodeDiff.diffBranchToBranch(gitPath, branchName,CodeDiff.MASTER);
        }
    
        /**
         * 分支与分支之间对比
         * @param gitPath local gitPath
         * @param newBranchName newBranchName
         * @param oldBranchName oldBranchName
         */
        public CoverageBuilder(String gitPath, String newBranchName, String oldBranchName) {
            this.classes = new HashMap<String, IClassCoverage>();
            this.sourcefiles = new HashMap<String, ISourceFileCoverage>();
            classInfos = CodeDiff.diffBranchToBranch(gitPath, newBranchName, oldBranchName);
        }
    
        /**
         * tag与tag之间对比
         * @param gitPath local gitPath
         * @param branchName develop branchName
         * @param newTag new Tag
         * @param oldTag old Tag
         */
        public CoverageBuilder(String gitPath, String branchName, String newTag, String oldTag) {
            this.classes = new HashMap<String, IClassCoverage>();
            this.sourcefiles = new HashMap<String, ISourceFileCoverage>();
            classInfos = CodeDiff.diffTagToTag(gitPath,branchName, newTag, oldTag);
        }
    复制代码

    第三步:新增文件

      在org.jacoco.core项目 org.jacoco.core.internal包下新增diff包(目录),然后在diff包下新增如下文件:

      1、新增ASTGenerator类

    复制代码
    package org.jacoco.core.internal.diff;
    
    import org.eclipse.jdt.core.JavaCore;
    import org.eclipse.jdt.core.dom.*;
    import sun.misc.BASE64Encoder;
    import java.io.*;
    import java.security.MessageDigest;
    import java.security.NoSuchAlgorithmException;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.Map;
    
    /**
     * AST编译java源文件
     */
    public class ASTGenerator {
        private String javaText;
        private CompilationUnit compilationUnit;
    
        public ASTGenerator(String javaText) {
            this.javaText = javaText;
            this.initCompilationUnit();
        }
    
        /**
         * 获取AST编译单元,首次加载很慢
         */
        private void initCompilationUnit() {
            //  AST编译
            final ASTParser astParser = ASTParser.newParser(8);
            final Map<String, String> options = JavaCore.getOptions();
            JavaCore.setComplianceOptions(JavaCore.VERSION_1_8, options);
            astParser.setCompilerOptions(options);
            astParser.setKind(ASTParser.K_COMPILATION_UNIT);
            astParser.setResolveBindings(true);
            astParser.setBindingsRecovery(true);
            astParser.setStatementsRecovery(true);
            astParser.setSource(javaText.toCharArray());
            compilationUnit = (CompilationUnit) astParser.createAST(null);
        }
    
        /**
         * 获取java类包名
         */
        public String getPackageName() {
            if (compilationUnit == null) {
                return "";
            }
            PackageDeclaration packageDeclaration = compilationUnit.getPackage();
            if (packageDeclaration == null){
                return "";
            }
            String packageName = packageDeclaration.getName().toString();
            return packageName;
        }
    
        /**
         * 获取普通类单元
         */
        public TypeDeclaration getJavaClass() {
            if (compilationUnit == null) {
                return null;
            }
            TypeDeclaration typeDeclaration = null;
            final List<?> types = compilationUnit.types();
            for (final Object type : types) {
                if (type instanceof TypeDeclaration) {
                    typeDeclaration = (TypeDeclaration) type;
                    break;
                }
            }
            return typeDeclaration;
        }
    
        /**
         * 获取java类中所有方法
         * @return 类中所有方法
         */
        public MethodDeclaration[] getMethods() {
            TypeDeclaration typeDec = getJavaClass();
            if (typeDec == null) {
                return new MethodDeclaration[]{};
            }
            MethodDeclaration[] methodDec = typeDec.getMethods();
            return methodDec;
        }
    
        /**
         * 获取新增类中的所有方法信息
         */
        public List<MethodInfo> getMethodInfoList() {
            MethodDeclaration[] methodDeclarations = getMethods();
            List<MethodInfo> methodInfoList = new ArrayList<MethodInfo>();
            for (MethodDeclaration method: methodDeclarations) {
                MethodInfo methodInfo = new MethodInfo();
                setMethodInfo(methodInfo, method);
                methodInfoList.add(methodInfo);
            }
            return methodInfoList;
        }
    
        /**
         * 获取修改类型的类的信息以及其中的所有方法,排除接口类
         */
        public ClassInfo getClassInfo(List<MethodInfo> methodInfos, List<int[]> addLines, List<int[]> delLines) {
            TypeDeclaration typeDec = getJavaClass();
            if (typeDec == null || typeDec.isInterface()) {
                return null;
            }
            ClassInfo classInfo = new ClassInfo();
            classInfo.setClassName(getJavaClass().getName().toString());
            classInfo.setPackages(getPackageName());
            classInfo.setMethodInfos(methodInfos);
            classInfo.setAddLines(addLines);
            classInfo.setDelLines(delLines);
            classInfo.setType("REPLACE");
            return classInfo;
        }
    
        /**
         * 获取新增类型的类的信息以及其中的所有方法,排除接口类
         */
        public ClassInfo getClassInfo() {
            TypeDeclaration typeDec = getJavaClass();
            if (typeDec == null || typeDec.isInterface()) {
                return null;
            }
            MethodDeclaration[] methodDeclarations = getMethods();
            ClassInfo classInfo = new ClassInfo();
            classInfo.setClassName(getJavaClass().getName().toString());
            classInfo.setPackages(getPackageName());
            classInfo.setType("ADD");
            List<MethodInfo> methodInfoList = new ArrayList<MethodInfo>();
            for (MethodDeclaration method: methodDeclarations) {
                MethodInfo methodInfo = new MethodInfo();
                setMethodInfo(methodInfo, method);
                methodInfoList.add(methodInfo);
            }
            classInfo.setMethodInfos(methodInfoList);
            return classInfo;
        }
    
        /**
         * 获取修改中的方法
         */
        public MethodInfo getMethodInfo(MethodDeclaration methodDeclaration) {
            MethodInfo methodInfo = new MethodInfo();
            setMethodInfo(methodInfo, methodDeclaration);
            return methodInfo;
        }
    
        private void setMethodInfo(MethodInfo methodInfo,MethodDeclaration methodDeclaration) {
            methodInfo.setMd5(MD5Encode(methodDeclaration.toString()));
            methodInfo.setMethodName(methodDeclaration.getName().toString());
            methodInfo.setParameters(methodDeclaration.parameters().toString());
        }
    
        /**
         * 计算方法的MD5的值
         */
        public static String MD5Encode(String s) {
            String MD5String = "";
            try {
                MessageDigest md5 = MessageDigest.getInstance("MD5");
                BASE64Encoder base64en = new BASE64Encoder();
                MD5String = base64en.encode(md5.digest(s.getBytes("utf-8")));
            } catch (NoSuchAlgorithmException e) {
                e.printStackTrace();
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return MD5String;
        }
    
        /**
         * 判断方法是否存在
         * @param method        新分支的方法
         * @param methodsMap    master分支的方法
         * @return
         */
        public static boolean isMethodExist(final MethodDeclaration method, final Map<String, MethodDeclaration> methodsMap) {
            // 方法名+参数一致才一致
            if (!methodsMap.containsKey(method.getName().toString() + method.parameters().toString())) {
                return false;
            }
            return true;
        }
    
        /**
         * 判断方法是否一致
         */
        public static boolean isMethodTheSame(final MethodDeclaration method1,final MethodDeclaration method2) {
            if (MD5Encode(method1.toString()).equals(MD5Encode(method2.toString()))) {
                return true;
            }
            return false;
        }
    }
    复制代码

      2、新增ClassInfo类:

    复制代码
    package org.jacoco.core.internal.diff;
    
    import java.util.List;
    
    public class ClassInfo {
        /**
         * java文件
         */
        private String classFile;
        /**
         * 类名
         */
        private String className;
        /**
         * 包名
         */
        private String packages;
    
        /**
         * 类中的方法
         */
        private List<MethodInfo> methodInfos;
    
        /**
         * 新增的行数
         */
        private List<int[]> addLines;
    
        /**
         * 删除的行数
         */
        private List<int[]> delLines;
    
        /**
         * 修改类型
         */
        private String type;
    
        public String getType() {
            return type;
        }
    
        public void setType(String type) {
            this.type = type;
        }
    
        public List<int[]> getAddLines() {
            return addLines;
        }
    
        public void setAddLines(List<int[]> addLines) {
            this.addLines = addLines;
        }
    
        public List<int[]> getDelLines() {
            return delLines;
        }
    
        public void setDelLines(List<int[]> delLines) {
            this.delLines = delLines;
        }
    
        public String getClassFile() {
            return classFile;
        }
    
        public void setClassFile(String classFile) {
            this.classFile = classFile;
        }
    
        public String getClassName() {
            return className;
        }
    
        public void setClassName(String className) {
            this.className = className;
        }
    
        public String getPackages() {
            return packages;
        }
    
        public void setPackages(String packages) {
            this.packages = packages;
        }
    
        public List<MethodInfo> getMethodInfos() {
            return methodInfos;
        }
    
        public void setMethodInfos(List<MethodInfo> methodInfos) {
            this.methodInfos = methodInfos;
        }
    }
    复制代码

      3、新增CodeDiff类:

    复制代码
    package org.jacoco.core.internal.diff;
    
    import org.eclipse.jdt.core.dom.MethodDeclaration;
    import org.eclipse.jgit.api.Git;
    import org.eclipse.jgit.diff.*;
    import org.eclipse.jgit.lib.ObjectId;
    import org.eclipse.jgit.lib.ObjectReader;
    import org.eclipse.jgit.lib.Ref;
    import org.eclipse.jgit.lib.Repository;
    import org.eclipse.jgit.patch.FileHeader;
    import org.eclipse.jgit.treewalk.AbstractTreeIterator;
    import org.eclipse.jgit.treewalk.CanonicalTreeParser;
    import org.eclipse.jgit.util.StringUtils;
    import java.io.ByteArrayOutputStream;
    import java.io.File;
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    import java.util.concurrent.*;
    
    /**
     * 代码版本比较
     */
    public class CodeDiff {
        public final static String REF_HEADS = "refs/heads/";
        public final static  String MASTER = "master";
    
        /**
         * 分支和分支之间的比较
         * @param gitPath           git路径
         * @param newBranchName     新分支名称
         * @param oldBranchName     旧分支名称
         * @return
         */
        public static List<ClassInfo> diffBranchToBranch(String gitPath, String newBranchName, String oldBranchName) {
            List<ClassInfo> classInfos = diffMethods(gitPath, newBranchName, oldBranchName);
            return classInfos;
        }
        private static List<ClassInfo> diffMethods(String gitPath, String newBranchName, String oldBranchName) {
            try {
                //  获取本地分支
                GitAdapter gitAdapter = new GitAdapter(gitPath);
                Git git = gitAdapter.getGit();
                Ref localBranchRef = gitAdapter.getRepository().exactRef(REF_HEADS + newBranchName);
                Ref localMasterRef = gitAdapter.getRepository().exactRef(REF_HEADS + oldBranchName);
                //  更新本地分支
                gitAdapter.checkOutAndPull(localMasterRef, oldBranchName);
                gitAdapter.checkOutAndPull(localBranchRef, newBranchName);
                //  获取分支信息
                AbstractTreeIterator newTreeParser = gitAdapter.prepareTreeParser(localBranchRef);
                AbstractTreeIterator oldTreeParser = gitAdapter.prepareTreeParser(localMasterRef);
                //  对比差异
                List<DiffEntry> diffs = git.diff().setOldTree(oldTreeParser).setNewTree(newTreeParser).setShowNameAndStatusOnly(true).call();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                DiffFormatter df = new DiffFormatter(out);
                //设置比较器为忽略空白字符对比(Ignores all whitespace)
                df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
                df.setRepository(git.getRepository());
                List<ClassInfo> allClassInfos = batchPrepareDiffMethod(gitAdapter, newBranchName, oldBranchName, df, diffs);
                return allClassInfos;
            }catch (Exception e) {
                e.printStackTrace();
            }
            return  new ArrayList<ClassInfo>();
        }
    
        /**
         * 单分支Tag版本之间的比较
         * @param gitPath    本地git代码仓库路径
         * @param newTag     新Tag版本
         * @param oldTag     旧Tag版本
         * @return
         */
        public static List<ClassInfo> diffTagToTag(String gitPath, String branchName, String newTag, String oldTag) {
            if(StringUtils.isEmptyOrNull(gitPath) || StringUtils.isEmptyOrNull(branchName)  || StringUtils.isEmptyOrNull(newTag)  || StringUtils.isEmptyOrNull(oldTag) ){
                throw new IllegalArgumentException("Parameter(local gitPath,develop branchName,new Tag,old Tag) can't be empty or null !");
            }else if(newTag.equals(oldTag)){
                throw new IllegalArgumentException("Parameter new Tag and old Tag can't be the same");
            }
            File gitPathDir = new File(gitPath);
            if(!gitPathDir.exists()){
                throw new IllegalArgumentException("Parameter local gitPath is not exit !");
            }
    
            List<ClassInfo> classInfos = diffTagMethods(gitPath,branchName, newTag, oldTag);
            return classInfos;
        }
        private static List<ClassInfo> diffTagMethods(String gitPath,String branchName, String newTag, String oldTag) {
            try {
                //  init local repository
                GitAdapter gitAdapter = new GitAdapter(gitPath);
                Git git = gitAdapter.getGit();
                Repository repo =  gitAdapter.getRepository();
                Ref localBranchRef = repo.exactRef(REF_HEADS + branchName);
    
                //  update local repository
                gitAdapter.checkOutAndPull(localBranchRef, branchName);
    
                ObjectId head = repo.resolve(newTag+"^{tree}");
                ObjectId previousHead = repo.resolve(oldTag+"^{tree}");
    
                // Instanciate a reader to read the data from the Git database
                ObjectReader reader = repo.newObjectReader();
                // Create the tree iterator for each commit
                CanonicalTreeParser oldTreeIter = new CanonicalTreeParser();
                oldTreeIter.reset(reader, previousHead);
                CanonicalTreeParser newTreeIter = new CanonicalTreeParser();
                newTreeIter.reset(reader, head);
    
                //  对比差异
                List<DiffEntry> diffs = git.diff().setOldTree(oldTreeIter).setNewTree(newTreeIter).setShowNameAndStatusOnly(true).call();
                ByteArrayOutputStream out = new ByteArrayOutputStream();
                DiffFormatter df = new DiffFormatter(out);
                //设置比较器为忽略空白字符对比(Ignores all whitespace)
                df.setDiffComparator(RawTextComparator.WS_IGNORE_ALL);
                df.setRepository(repo);
                List<ClassInfo> allClassInfos = batchPrepareDiffMethodForTag(gitAdapter, newTag,oldTag, df, diffs);
                return allClassInfos;
            }catch (Exception e) {
                e.printStackTrace();
            }
            return  new ArrayList<ClassInfo>();
        }
        /**
         * 多线程执行对比
         */
        private static List<ClassInfo> batchPrepareDiffMethodForTag(final GitAdapter gitAdapter, final String newTag, final String oldTag, final DiffFormatter df, List<DiffEntry> diffs) {
            int threadSize = 100;
            int dataSize = diffs.size();
            int threadNum = dataSize / threadSize + 1;
            boolean special = dataSize % threadSize == 0;
            ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
    
            List<Callable<List<ClassInfo>>> tasks = new ArrayList<Callable<List<ClassInfo>>>();
            Callable<List<ClassInfo>> task = null;
            List<DiffEntry> cutList = null;
            //  分解每条线程的数据
            for (int i = 0; i < threadNum; i++) {
                if (i == threadNum - 1) {
                    if (special) {
                        break;
                    }
                    cutList = diffs.subList(threadSize * i, dataSize);
                } else {
                    cutList = diffs.subList(threadSize * i, threadSize * (i + 1));
                }
                final List<DiffEntry> diffEntryList = cutList;
                task = new Callable<List<ClassInfo>>() {
                    public List<ClassInfo> call() throws Exception {
                        List<ClassInfo> allList = new ArrayList<ClassInfo>();
                        for (DiffEntry diffEntry : diffEntryList) {
                            ClassInfo classInfo = prepareDiffMethodForTag(gitAdapter, newTag, oldTag, df, diffEntry);
                            if (classInfo != null) {
                                allList.add(classInfo);
                            }
                        }
                        return allList;
                    }
                };
                // 这里提交的任务容器列表和返回的Future列表存在顺序对应的关系
                tasks.add(task);
            }
            List<ClassInfo> allClassInfoList = new ArrayList<ClassInfo>();
            try {
                List<Future<List<ClassInfo>>> results = executorService.invokeAll(tasks);
                //结果汇总
                for (Future<List<ClassInfo>> future : results ) {
                    allClassInfoList.addAll(future.get());
                }
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                // 关闭线程池
                executorService.shutdown();
            }
            return allClassInfoList;
        }
    
        /**
         * 单个差异文件对比
         */
        private synchronized static ClassInfo prepareDiffMethodForTag(GitAdapter gitAdapter, String newTag, String oldTag, DiffFormatter df, DiffEntry diffEntry) {
            List<MethodInfo> methodInfoList = new ArrayList<MethodInfo>();
            try {
                String newJavaPath = diffEntry.getNewPath();
                //  排除测试类
                if (newJavaPath.contains("/src/test/java/")) {
                    return null;
                }
                //  非java文件 和 删除类型不记录
                if (!newJavaPath.endsWith(".java") || diffEntry.getChangeType() == DiffEntry.ChangeType.DELETE){
                    return null;
                }
                String newClassContent = gitAdapter.getTagRevisionSpecificFileContent(newTag,newJavaPath);
                ASTGenerator newAstGenerator = new ASTGenerator(newClassContent);
                /*  新增类型   */
                if (diffEntry.getChangeType() == DiffEntry.ChangeType.ADD) {
                    return newAstGenerator.getClassInfo();
                }
                /*  修改类型  */
                //  获取文件差异位置,从而统计差异的行数,如增加行数,减少行数
                FileHeader fileHeader = df.toFileHeader(diffEntry);
                List<int[]> addLines = new ArrayList<int[]>();
                List<int[]> delLines = new ArrayList<int[]>();
                EditList editList = fileHeader.toEditList();
                for(Edit edit : editList){
                    if (edit.getLengthA() > 0) {
                        delLines.add(new int[]{edit.getBeginA(), edit.getEndA()});
                    }
                    if (edit.getLengthB() > 0 ) {
                        addLines.add(new int[]{edit.getBeginB(), edit.getEndB()});
                    }
                }
                String oldJavaPath = diffEntry.getOldPath();
                String oldClassContent = gitAdapter.getTagRevisionSpecificFileContent(oldTag,oldJavaPath);
                ASTGenerator oldAstGenerator = new ASTGenerator(oldClassContent);
                MethodDeclaration[] newMethods = newAstGenerator.getMethods();
                MethodDeclaration[] oldMethods = oldAstGenerator.getMethods();
                Map<String, MethodDeclaration> methodsMap = new HashMap<String, MethodDeclaration>();
                for (int i = 0; i < oldMethods.length; i++) {
                    methodsMap.put(oldMethods[i].getName().toString()+ oldMethods[i].parameters().toString(), oldMethods[i]);
                }
                for (final MethodDeclaration method : newMethods) {
                    // 如果方法名是新增的,则直接将方法加入List
                    if (!ASTGenerator.isMethodExist(method, methodsMap)) {
                        MethodInfo methodInfo = newAstGenerator.getMethodInfo(method);
                        methodInfoList.add(methodInfo);
                        continue;
                    }
                    // 如果两个版本都有这个方法,则根据MD5判断方法是否一致
                    if (!ASTGenerator.isMethodTheSame(method, methodsMap.get(method.getName().toString()+ method.parameters().toString()))) {
                        MethodInfo methodInfo =  newAstGenerator.getMethodInfo(method);
                        methodInfoList.add(methodInfo);
                    }
                }
                return newAstGenerator.getClassInfo(methodInfoList, addLines, delLines);
            }catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    
        /**
         * 多线程执行对比
         */
        private static List<ClassInfo> batchPrepareDiffMethod(final GitAdapter gitAdapter, final String branchName, final String oldBranchName, final DiffFormatter df, List<DiffEntry> diffs) {
            int threadSize = 100;
            int dataSize = diffs.size();
            int threadNum = dataSize / threadSize + 1;
            boolean special = dataSize % threadSize == 0;
            ExecutorService executorService = Executors.newFixedThreadPool(threadNum);
    
            List<Callable<List<ClassInfo>>> tasks = new ArrayList<Callable<List<ClassInfo>>>();
            Callable<List<ClassInfo>> task = null;
            List<DiffEntry> cutList = null;
            //  分解每条线程的数据
            for (int i = 0; i < threadNum; i++) {
                if (i == threadNum - 1) {
                    if (special) {
                        break;
                    }
                    cutList = diffs.subList(threadSize * i, dataSize);
                } else {
                    cutList = diffs.subList(threadSize * i, threadSize * (i + 1));
                }
                final List<DiffEntry> diffEntryList = cutList;
                task = new Callable<List<ClassInfo>>() {
                    public List<ClassInfo> call() throws Exception {
                        List<ClassInfo> allList = new ArrayList<ClassInfo>();
                        for (DiffEntry diffEntry : diffEntryList) {
                            ClassInfo classInfo = prepareDiffMethod(gitAdapter, branchName, oldBranchName, df, diffEntry);
                            if (classInfo != null) {
                                allList.add(classInfo);
                            }
                        }
                        return allList;
                    }
                };
                // 这里提交的任务容器列表和返回的Future列表存在顺序对应的关系
                tasks.add(task);
            }
            List<ClassInfo> allClassInfoList = new ArrayList<ClassInfo>();
            try {
                List<Future<List<ClassInfo>>> results = executorService.invokeAll(tasks);
                //结果汇总
                for (Future<List<ClassInfo>> future : results ) {
                    allClassInfoList.addAll(future.get());
                }
            }catch (Exception e) {
                e.printStackTrace();
            }finally {
                // 关闭线程池
                executorService.shutdown();
            }
            return allClassInfoList;
        }
    
        /**
         * 单个差异文件对比
         */
        private synchronized static ClassInfo prepareDiffMethod(GitAdapter gitAdapter, String branchName, String oldBranchName, DiffFormatter df, DiffEntry diffEntry) {
            List<MethodInfo> methodInfoList = new ArrayList<MethodInfo>();
            try {
                String newJavaPath = diffEntry.getNewPath();
                //  排除测试类
                if (newJavaPath.contains("/src/test/java/")) {
                    return null;
                }
                //  非java文件 和 删除类型不记录
                if (!newJavaPath.endsWith(".java") || diffEntry.getChangeType() == DiffEntry.ChangeType.DELETE){
                    return null;
                }
                String newClassContent = gitAdapter.getBranchSpecificFileContent(branchName,newJavaPath);
                ASTGenerator newAstGenerator = new ASTGenerator(newClassContent);
                /*  新增类型   */
                if (diffEntry.getChangeType() == DiffEntry.ChangeType.ADD) {
                    return newAstGenerator.getClassInfo();
                }
                /*  修改类型  */
                //  获取文件差异位置,从而统计差异的行数,如增加行数,减少行数
                FileHeader fileHeader = df.toFileHeader(diffEntry);
                List<int[]> addLines = new ArrayList<int[]>();
                List<int[]> delLines = new ArrayList<int[]>();
                EditList editList = fileHeader.toEditList();
                for(Edit edit : editList){
                    if (edit.getLengthA() > 0) {
                        delLines.add(new int[]{edit.getBeginA(), edit.getEndA()});
                    }
                    if (edit.getLengthB() > 0 ) {
                        addLines.add(new int[]{edit.getBeginB(), edit.getEndB()});
                    }
                }
                String oldJavaPath = diffEntry.getOldPath();
                String oldClassContent = gitAdapter.getBranchSpecificFileContent(oldBranchName,oldJavaPath);
                ASTGenerator oldAstGenerator = new ASTGenerator(oldClassContent);
                MethodDeclaration[] newMethods = newAstGenerator.getMethods();
                MethodDeclaration[] oldMethods = oldAstGenerator.getMethods();
                Map<String, MethodDeclaration> methodsMap = new HashMap<String, MethodDeclaration>();
                for (int i = 0; i < oldMethods.length; i++) {
                    methodsMap.put(oldMethods[i].getName().toString()+ oldMethods[i].parameters().toString(), oldMethods[i]);
                }
                for (final MethodDeclaration method : newMethods) {
                    // 如果方法名是新增的,则直接将方法加入List
                    if (!ASTGenerator.isMethodExist(method, methodsMap)) {
                        MethodInfo methodInfo = newAstGenerator.getMethodInfo(method);
                        methodInfoList.add(methodInfo);
                        continue;
                    }
                    // 如果两个版本都有这个方法,则根据MD5判断方法是否一致
                    if (!ASTGenerator.isMethodTheSame(method, methodsMap.get(method.getName().toString()+ method.parameters().toString()))) {
                        MethodInfo methodInfo =  newAstGenerator.getMethodInfo(method);
                        methodInfoList.add(methodInfo);
                    }
                }
                return newAstGenerator.getClassInfo(methodInfoList, addLines, delLines);
            }catch (Exception e) {
                e.printStackTrace();
            }
            return null;
        }
    }
    复制代码

      4、新增GitAdapter类:

    复制代码
    package org.jacoco.core.internal.diff;
    
    import org.eclipse.jgit.api.CreateBranchCommand;
    import org.eclipse.jgit.api.Git;
    import org.eclipse.jgit.api.errors.GitAPIException;
    import org.eclipse.jgit.lib.*;
    import org.eclipse.jgit.revwalk.RevCommit;
    import org.eclipse.jgit.revwalk.RevTree;
    import org.eclipse.jgit.revwalk.RevWalk;
    import org.eclipse.jgit.transport.UsernamePasswordCredentialsProvider;
    import org.eclipse.jgit.treewalk.AbstractTreeIterator;
    import org.eclipse.jgit.treewalk.CanonicalTreeParser;
    import org.eclipse.jgit.treewalk.TreeWalk;
    
    import java.io.*;
    import java.util.*;
    
    /**
     * Git操作类
     */
    public class GitAdapter {
        private Git git;
        private Repository repository;
        private String gitFilePath;
    
        //  Git授权
        private static UsernamePasswordCredentialsProvider usernamePasswordCredentialsProvider;
    
        public GitAdapter(String gitFilePath) {
            this.gitFilePath = gitFilePath;
            this.initGit(gitFilePath);
        }
        private void initGit(String gitFilePath) {
            try {
                git = Git.open(new File(gitFilePath));
                repository = git.getRepository();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public String getGitFilePath() {
            return gitFilePath;
        }
    
        public Git getGit() {
            return git;
        }
    
        public Repository getRepository() {
            return repository;
        }
    
        /**
         * git授权。需要设置拥有所有权限的用户
         * @param username  git用户名
         * @param password  git用户密码
         */
        public static void setCredentialsProvider(String username, String password) {
            if(usernamePasswordCredentialsProvider == null || !usernamePasswordCredentialsProvider.isInteractive()){
                usernamePasswordCredentialsProvider = new UsernamePasswordCredentialsProvider(username,password);
            }
        }
    
        /**
         * 获取指定分支的指定文件内容
         * @param branchName        分支名称
         * @param javaPath          文件路径
         * @return  java类
         * @throws IOException
         */
        public String getBranchSpecificFileContent(String branchName, String javaPath) throws IOException {
            Ref branch = repository.exactRef("refs/heads/" + branchName);
            ObjectId objId = branch.getObjectId();
            RevWalk walk = new RevWalk(repository);
            RevTree tree = walk.parseTree(objId);
            return  getFileContent(javaPath,tree,walk);
        }
    
        /**
         * 获取指定分支指定Tag版本的指定文件内容
         * @param tagRevision       Tag版本
         * @param javaPath          件路径
         * @return  java类
         * @throws IOException
         */
        public String getTagRevisionSpecificFileContent(String tagRevision, String javaPath) throws IOException {
            ObjectId objId = repository.resolve(tagRevision);
            RevWalk walk = new RevWalk(repository);
            RevCommit revCommit = walk.parseCommit(objId);
            RevTree tree = revCommit.getTree();
            return  getFileContent(javaPath,tree,walk);
        }
        
        /**
         * 获取指定分支指定的指定文件内容
         * @param javaPath      件路径
         * @param tree          git RevTree
         * @param walk          git RevWalk
         * @return  java类
         * @throws IOException
         */
        private String getFileContent(String javaPath,RevTree tree,RevWalk walk) throws IOException {
            TreeWalk treeWalk = TreeWalk.forPath(repository, javaPath, tree);
            ObjectId blobId = treeWalk.getObjectId(0);
            ObjectLoader loader = repository.open(blobId);
            byte[] bytes = loader.getBytes();
            walk.dispose();
            return new String(bytes);
        }
    
        /**
         * 分析分支树结构信息
         * @param localRef      本地分支
         * @return
         * @throws IOException
         */
        public AbstractTreeIterator prepareTreeParser(Ref localRef) throws IOException {
            RevWalk walk = new RevWalk(repository);
            RevCommit commit = walk.parseCommit(localRef.getObjectId());
            RevTree tree = walk.parseTree(commit.getTree().getId());
            CanonicalTreeParser treeParser = new CanonicalTreeParser();
            ObjectReader reader = repository.newObjectReader();
            treeParser.reset(reader, tree.getId());
            walk.dispose();
            return treeParser;
        }
        /**
         * 切换分支
         * @param branchName    分支名称
         * @throws GitAPIException GitAPIException
         */
        public void checkOut(String branchName) throws GitAPIException {
            //  切换分支
            git.checkout().setCreateBranch(false).setName(branchName).call();
        }
    
        /**
         * 更新分支代码
         * @param localRef      本地分支
         * @param branchName    分支名称
         * @throws GitAPIException GitAPIException
         */
        public void checkOutAndPull(Ref localRef, String branchName) throws GitAPIException {
            boolean isCreateBranch = localRef == null;
            if (!isCreateBranch && checkBranchNewVersion(localRef)) {
                return;
            }
            //  切换分支
            git.checkout().setCreateBranch(isCreateBranch).setName(branchName).setStartPoint("origin/" + branchName).setUpstreamMode(CreateBranchCommand.SetupUpstreamMode.SET_UPSTREAM).call();
            //  拉取最新代码
            git.pull().setCredentialsProvider(usernamePasswordCredentialsProvider).call();
        }
    
        /**
         * 判断本地分支是否是最新版本。目前不考虑分支在远程仓库不存在,本地存在
         * @param localRef  本地分支
         * @return  boolean
         * @throws GitAPIException GitAPIException
         */
        private boolean checkBranchNewVersion(Ref localRef) throws GitAPIException {
            String localRefName = localRef.getName();
            String localRefObjectId = localRef.getObjectId().getName();
            //  获取远程所有分支
            Collection<Ref> remoteRefs = git.lsRemote().setCredentialsProvider(usernamePasswordCredentialsProvider).setHeads(true).call();
            for (Ref remoteRef : remoteRefs) {
                String remoteRefName = remoteRef.getName();
                String remoteRefObjectId = remoteRef.getObjectId().getName();
                if (remoteRefName.equals(localRefName)) {
                    if (remoteRefObjectId.equals(localRefObjectId)) {
                        return true;
                    }
                    return false;
                }
            }
            return false;
        }
    }
    复制代码

      5、新增MethodInfo类:

    复制代码
    package org.jacoco.core.internal.diff;
    
    public class MethodInfo {
        /**
         * 方法的md5
         */
        public String md5;
        /**
         * 方法名
         */
        public String methodName;
        /**
         * 方法参数
         */
        public String parameters;
    
        public String getMd5() {
            return md5;
        }
    
        public void setMd5(String md5) {
            this.md5 = md5;
        }
    
        public String getMethodName() {
            return methodName;
        }
    
        public void setMethodName(String methodName) {
            this.methodName = methodName;
        }
    
        public String getParameters() {
            return parameters;
        }
    
        public void setParameters(String parameters) {
            this.parameters = parameters;
        }
    }
    复制代码

    第四步:修改org.jacoco.core.internal.flow包下的ClassProbesAdapter类:

      1、修改代码第66行visitMethod方法:

    复制代码
      @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 && isContainsMethod(name, CoverageBuilder.classInfos)) {
                methodProbes = mv;
            } else {
                // We need to visit the method in any case, otherwise probe ids
                // are not reproducible
                methodProbes = EMPTY_METHOD_PROBES_VISITOR;
            }
            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);
                    }
                }
            };
        }
    复制代码

      2、新增私有方法

    复制代码
      private boolean isContainsMethod(String currentMethod, List<ClassInfo> classInfos) {
            if (classInfos== null || classInfos.isEmpty()) {
                return true;
            }
            String currentClassName = name.replaceAll("/",".");
            for (ClassInfo classInfo : classInfos) {
                String className = classInfo.getPackages() + "." + classInfo.getClassName();
                if (currentClassName.equals(className)) {
                    for (MethodInfo methodInfo: classInfo.getMethodInfos()) {
                        String methodName = methodInfo.getMethodName();
                        if (currentMethod.equals(methodName)) {
                            return true;
                        }
                    }
                }
            }
            return false;
        }
    复制代码

     

    第五步:修改org.jacoco.report项目中org.jacoco.report.internal.html.page包下的SourceHighlighter类:

      1、修改代码第72行的render方法:

    复制代码
    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 classPath = ((SourceFileCoverageImpl) source).getPackageName() + "." + source.getName().replaceAll(".java","");
            classPath = classPath.replaceAll("/",".");
            String line;
            int nr = 0;
            while ((line = lineBuffer.readLine()) != null) {
                nr++;
                renderCodeLine(pre, line, source.getLine(nr), nr,classPath);
            }
        }
    复制代码

      2、修改代码第87行renderCodeLine方法:

    复制代码
    private void renderCodeLine(final HTMLElement pre, final String linesrc,
                final ILine line, final int lineNr, final String classPath) throws IOException {
            if (CoverageBuilder.classInfos == null || CoverageBuilder.classInfos.isEmpty()) {
                //    全量覆盖
                highlight(pre, line, lineNr).text(linesrc);
                pre.text("
    ");
            } else {
                //    增量覆盖
                boolean existFlag = true;
                for (ClassInfo classInfo : CoverageBuilder.classInfos) {
                    String tClassPath = classInfo.getPackages() + "." + classInfo.getClassName();
                    if (classPath.equals(tClassPath)) {
                        //    新增的类
                        if ("ADD".equalsIgnoreCase(classInfo.getType())) {
                            highlight(pre, line, lineNr).text("+ " + linesrc);
                            pre.text("
    ");
                        } else {
                            //    修改的类
                            boolean flag = false;
                            List<int[]> addLines = classInfo.getAddLines();
                            for (int[] ints: addLines) {
                                if (ints[0] <= lineNr &&  lineNr <= ints[1]){
                                    flag = true;
                                    break;
                                }
                            }
                            if (flag) {
                                highlight(pre, line, lineNr).text("+ " + linesrc);
                                pre.text("
    ");
                            } else {
                                highlight(pre, line, lineNr).text(" " + linesrc);
                                pre.text("
    ");
                            }
                        }
                        existFlag = false;
                        break;
                    }
                }
                if (existFlag) {
                    highlight(pre, line, lineNr).text(" " + linesrc);
                    pre.text("
    ");
                }
            }
        }
    复制代码

    使用方式:

      在org.jacoco.examples项目中,新增一个包,然后新增如下类:

      1、用于生成exec的ExecutionDataClient类:

    复制代码
    import java.io.FileOutputStream;
    import java.io.IOException;
    import java.net.InetAddress;
    import java.net.Socket;
    import org.jacoco.core.data.ExecutionDataWriter;
    import org.jacoco.core.runtime.RemoteControlReader;
    import org.jacoco.core.runtime.RemoteControlWriter;
    
    /**
    * 用于生成exec文件
    */ public class ExecutionDataClient { private static final String DESTFILE = "D:\Git\Jacoco-Test\jacoco.exec";//导出的文件路径 private static final String ADDRESS = "127.0.0.1";//配置的Jacoco的IP private static final int PORT = 9001;//Jacoco监听的端口 public static void main(final String[] args) throws IOException { final FileOutputStream localFile = new FileOutputStream(DESTFILE); final ExecutionDataWriter localWriter = new ExecutionDataWriter( localFile); //连接Jacoco服务 final Socket socket = new Socket(InetAddress.getByName(ADDRESS), PORT); final RemoteControlWriter writer = new RemoteControlWriter(socket.getOutputStream()); final RemoteControlReader reader = new RemoteControlReader(socket.getInputStream()); reader.setSessionInfoVisitor(localWriter); reader.setExecutionDataVisitor(localWriter); // 发送Dump命令,获取Exec数据 writer.visitDumpCommand(true, false); if (!reader.read()) { throw new IOException("Socket closed unexpectedly."); } socket.close(); localFile.close(); } private ExecutionDataClient() { } }
    复制代码

      2、用于根据exec文件生成覆盖率报告的ReportGenerator类:

    复制代码
    import java.io.File;
    import java.io.IOException;
    
    import org.jacoco.core.analysis.Analyzer;
    import org.jacoco.core.analysis.CoverageBuilder;
    import org.jacoco.core.analysis.IBundleCoverage;
    import org.jacoco.core.internal.diff.GitAdapter;
    import org.jacoco.core.tools.ExecFileLoader;
    import org.jacoco.report.DirectorySourceFileLocator;
    import org.jacoco.report.FileMultiReportOutput;
    import org.jacoco.report.IReportVisitor;
    import org.jacoco.report.MultiSourceFileLocator;
    import org.jacoco.report.html.HTMLFormatter;
    
    /**
    * 用于根据exec文件生成增量覆盖率报告
    */ public class ReportGenerator { private final String title; private final File executionDataFile; private final File classesDirectory; private final File sourceDirectory; private final File reportDirectory; private ExecFileLoader execFileLoader; public ReportGenerator(final File projectDirectory) { this.title = projectDirectory.getName(); this.executionDataFile = new File(projectDirectory, "jacoco.exec");   //第一步生成的exec的文件 this.classesDirectory = new File(projectDirectory, "bin");        //目录下必须包含源码编译过的class文件,用来统计覆盖率。所以这里用server打出的jar包地址即可,运行的jar或者Class目录 this.sourceDirectory = new File(projectDirectory, "src/main/java");   //源码目录 this.reportDirectory = new File(projectDirectory, "coveragereport");  //要保存报告的地址 } public void create() throws IOException { loadExecutionData(); final IBundleCoverage bundleCoverage = analyzeStructure(); createReport(bundleCoverage); } private void createReport(final IBundleCoverage bundleCoverage) throws IOException { final HTMLFormatter htmlFormatter = new HTMLFormatter(); final IReportVisitor visitor = htmlFormatter.createVisitor(new FileMultiReportOutput(reportDirectory)); visitor.visitInfo(execFileLoader.getSessionInfoStore().getInfos(),execFileLoader.getExecutionDataStore().getContents()); visitor.visitBundle(bundleCoverage, new DirectorySourceFileLocator(sourceDirectory, "utf-8", 4)); // //多源码路径 // MultiSourceFileLocator sourceLocator = new MultiSourceFileLocator(4); // sourceLocator.add( new DirectorySourceFileLocator(sourceDir1, "utf-8", 4)); // sourceLocator.add( new DirectorySourceFileLocator(sourceDir2, "utf-8", 4)); // sourceLocator.add( new DirectorySourceFileLocator(sourceDir3, "utf-8", 4)); // visitor.visitBundle(bundleCoverage,sourceLocator); visitor.visitEnd(); } private void loadExecutionData() throws IOException { execFileLoader = new ExecFileLoader(); execFileLoader.load(executionDataFile); } private IBundleCoverage analyzeStructure() throws IOException { // git登录授权 GitAdapter.setCredentialsProvider("QQ512433465", "mima512433465"); // 全量覆盖      // final CoverageBuilder coverageBuilder = new CoverageBuilder(); // 基于分支比较覆盖,参数1:本地仓库,参数2:开发分支(预发分支),参数3:基线分支(不传时默认为master) // 本地Git路径,新分支 第三个参数不传时默认比较maser,传参数为待比较的基线分支 final CoverageBuilder coverageBuilder = new CoverageBuilder("E:\Git-pro\JacocoTest","daily"); // 基于Tag比较的覆盖 参数1:本地仓库,参数2:代码分支,参数3:新Tag(预发版本),参数4:基线Tag(变更前的版本) //final CoverageBuilder coverageBuilder = new CoverageBuilder("E:\Git-pro\JacocoTest","daily","v004","v003"); final Analyzer analyzer = new Analyzer(execFileLoader.getExecutionDataStore(), coverageBuilder); analyzer.analyzeAll(classesDirectory); return coverageBuilder.getBundle(title); } public static void main(final String[] args) throws IOException { final ReportGenerator generator = new ReportGenerator(new File("D:\Git\Jacoco-Test")); generator.create(); } }
    复制代码

    参考代码:

      https://github.com/512433465/JacocoPlus

      https://github.com/fang-yan-peng/diff-jacoco

  • 相关阅读:
    Python程序员用文字加密的方式,给女程序员写情书,一周后牵手回家
    小学生在网吧用python抓取LOL英雄皮肤,步骤简单
    vuex中module的命名空间概念
    动态设置html的font-size值
    JavaScript判断各种数据类型
    vuex脑图
    作用域链和函数内部this指向问题以及bind、call、apply方法
    BOM
    jQuery_base
    js_base_note
  • 原文地址:https://www.cnblogs.com/exmyth/p/13356617.html
Copyright © 2020-2023  润新知