我fork的jacoco源码改造好:https://github.com/exmyth/jacoco

入口:https://github.com/exmyth/jacoco/blob/master/org.jacoco.examples/src/org/jacoco/examples/report/DiffReportGenerator.java

DiffReport需要设置成你自己仓库的账号密码

https://github.com/exmyth/jacoco/blob/master/org.jacoco.examples/src/org/jacoco/examples/report/DiffReport.java

org.jacoco.example生成jar方法参考:https://www.cnblogs.com/exmyth/p/13362220.html

如果想自己动手,参考步骤如下:

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

第一步:拉取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("\n");
} 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("\n");
} 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("\n");
} else {
highlight(pre, line, lineNr).text(" " + linesrc);
pre.text("\n");
}
}
existFlag = false;
break;
}
}
if (existFlag) {
highlight(pre, line, lineNr).text(" " + linesrc);
pre.text("\n");
}
}
}

使用方式:

  在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

jacoco-实战篇-增量覆盖率的更多相关文章

  1. 二、Redis基本操作——String(实战篇)

    小喵万万没想到,上一篇博客,居然已经被阅读600次了!!!让小喵感觉压力颇大.万一有写错的地方,岂不是会误导很多筒子们.所以,恳请大家,如果看到小喵的博客有什么不对的地方,请尽快指正!谢谢! 小喵的唠 ...

  2. AngularJS in Action读书笔记6(实战篇)——bug hunting

    这一系列文章感觉写的不好,思维跨度很大,原本是由于与<Angularjs in action>有种相见恨晚而激发要写点读后感之类的文章,但是在翻译或是阐述的时候还是会心有余而力不足,零零总 ...

  3. ROS2.9.27架设网吧软路由实战篇之端口映射与回流

    转载:http://blog.csdn.net/zm2714/article/details/7924280 上一篇:ROS2.9.27架设网吧软路由实战篇之连通网络,主要讲述了网吧架设软路由ROS2 ...

  4. 2天驾驭DIV+CSS (实战篇)(转)

     这是去年看到的一片文章,感觉在我的学习中,有不少的影响.于是把它分享给想很快了解css的兄弟们.本文是实战篇. 基础篇[知识一] “DIV+CSS” 的叫法是不准确的[知识二] “DIV+CSS” ...

  5. javamail模拟邮箱功能发送电子邮件-基础实战篇(javamail API电子邮件实例)

    引言: JavaMail 是一种可选的.能用于读取.编写和发送电子消息的包 JavaMail jar包下载地址:http://java.sun.com/products/javamail/downlo ...

  6. javamail模拟邮箱功能发送电子邮件-中级实战篇【新增附件发送方法】(javamail API电子邮件实例)

    引言: JavaMail jar包下载地址:http://java.sun.com/products/javamail/downloads/index.html 此篇是紧随上篇文章而封装出来的,阅读本 ...

  7. javamail模拟邮箱功能--邮件删除-中级实战篇【邮件标记方法】(javamail API电子邮件实例)

    前言: JavaMail jar包下载地址:http://java.sun.com/products/javamail/downloads/index.html 本章可能是讲解javamail的最后一 ...

  8. Systemd 入门教程:实战篇

    Systemd 入门教程:实战篇 上一篇文章,介绍了 Systemd 的主要命令,这篇文章主要介绍如何使用 Systemd 来管理我们的服务,以及各项的含义: 一.开机启动 对于那些支持 System ...

  9. 工作经常使用的SQL整理,实战篇(二)

    原文:工作经常使用的SQL整理,实战篇(二) 工作经常使用的SQL整理,实战篇,地址一览: 工作经常使用的SQL整理,实战篇(一) 工作经常使用的SQL整理,实战篇(二) 工作经常使用的SQL整理,实 ...

  10. 工作经常使用的SQL整理,实战篇(三)

    原文:工作经常使用的SQL整理,实战篇(三) 工作经常使用的SQL整理,实战篇,地址一览: 工作经常使用的SQL整理,实战篇(一) 工作经常使用的SQL整理,实战篇(二) 工作经常使用的SQL整理,实 ...

随机推荐

  1. replace 和 replaceAll

    replace 匹配中的第一次:replaceAll 替换所有匹配的内容: let str = 'aj123dshf12aaaaaaaaaaa3uausdjd123suas123fus12' let ...

  2. MySQL下载安装教程

    下载 https://www.mysql.com/downloads/

  3. 《Programming from the Ground Up》读后感

    之所以看这本书,是想了解一些跟汇编相关的知识,打开这本书后就被作者的观点--"If you don't understand something the first time, reread ...

  4. .NET无侵入式对象池解决方案

    Pooling(https://github.com/inversionhourglass/Pooling),编译时对象池组件,在编译时将指定类型的new操作替换为对象池操作,简化编码过程,无需开发人 ...

  5. 在mac上配置nginx 并将前端的打包文件运行

    .markdown-body { line-height: 1.75; font-weight: 400; font-size: 16px; overflow-x: hidden; color: rg ...

  6. 听说过Paas、Saas和Iaas,那你听说过Apaas吗?

    互联网行业就喜欢搞一些单词的缩写,在云计算行业,前者有SaaS.PaaS.IaaS,最近两三年APaaS的概念又开始被关注.APaaS到底是什么意思,有什么用,与前三者的区别是什么?本文将对这些问题进 ...

  7. Machine Learning Week_9 Anomaly Detection and Recommend System

    1. Anomaly Detection I'd like to tell you about a problem called Anomaly Detection. This is a reason ...

  8. SpringBoot读取properties文件配置项

    使用SpringBoot开发过程中,难免需要配置相关数据项,然后在Java代码中@Autowired注入并使用. 我们应该如何读取properties文件中的配置项呢? 基于SpringBoot项目, ...

  9. 高性能的Reactor和Proactor模式学习

    0.引言 在上一篇的笔记中,我们学习了操作系统提供的高效I/O管理技术,主要用于解决服务器在高并发场景下的资源浪费和瓶颈问题.但是在实际的代码编写中,要是我们都全部调用底层的I/O多路复用接口来编写网 ...

  10. docker实现redis集群

    1.主从模式(Master-Slave) 1.1主从复制原理 主从复制是redis的一种基本的集群方式,它通过将一个Redis节点(主节点)的数据复制到一个或多个其他Redis节点来实现数据的冗余和备 ...