From 70a0747828d73eef58481fc5e2f2ff1d9925e885 Mon Sep 17 00:00:00 2001 From: wick Date: Sat, 3 Feb 2024 15:35:00 +0800 Subject: [PATCH] =?UTF-8?q?feat(agent):=20agent=E5=A2=9E=E5=8A=A0=E6=95=B0?= =?UTF-8?q?=E6=8D=AE=E4=B8=8A=E6=8A=A5=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - AgentOptions 中增加参数 debug 和 host,用于配置覆盖率数据上报信息,debug默认为 true,表示不上报 - Agent 中修改 Runtime.addShutdownHook方法,增加数据上报逻辑 - 增加简单日志工具类 AgentLogger - 修改代码中的错别字 --- org.jacoco.agent.rt/pom.xml | 10 + .../org/jacoco/agent/rt/internal/Agent.java | 97 +++- .../jacoco/agent/rt/internal/AgentLogger.java | 65 +++ .../org/jacoco/core/analysis/Analyzer.java | 491 +++++++++--------- .../jacoco/core/analysis/CoverageBuilder.java | 2 +- .../core/internal/analysis/ClassAnalyzer.java | 5 +- .../core/internal/diff/CodeDiffUtil.java | 195 +++---- .../internal/flow/ClassProbesAdapter.java | 8 +- .../org/jacoco/core/runtime/AgentOptions.java | 44 +- 9 files changed, 556 insertions(+), 361 deletions(-) create mode 100644 org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/AgentLogger.java diff --git a/org.jacoco.agent.rt/pom.xml b/org.jacoco.agent.rt/pom.xml index 6eb61ab8..4a6cf0ec 100644 --- a/org.jacoco.agent.rt/pom.xml +++ b/org.jacoco.agent.rt/pom.xml @@ -36,6 +36,16 @@ ${project.groupId} org.jacoco.core + + com.konghq + unirest-java-core + 4.2.4 + + + commons-logging + commons-logging + 1.2 + diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java index efb74846..fd3f05c8 100644 --- a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java +++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/Agent.java @@ -12,17 +12,10 @@ *******************************************************************************/ package org.jacoco.agent.rt.internal; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.net.InetAddress; -import java.util.concurrent.Callable; - +import kong.unirest.core.HttpResponse; +import kong.unirest.core.Unirest; import org.jacoco.agent.rt.IAgent; -import org.jacoco.agent.rt.internal.output.FileOutput; -import org.jacoco.agent.rt.internal.output.IAgentOutput; -import org.jacoco.agent.rt.internal.output.NoneOutput; -import org.jacoco.agent.rt.internal.output.TcpClientOutput; -import org.jacoco.agent.rt.internal.output.TcpServerOutput; +import org.jacoco.agent.rt.internal.output.*; import org.jacoco.core.JaCoCo; import org.jacoco.core.data.ExecutionDataWriter; import org.jacoco.core.runtime.AbstractRuntime; @@ -30,6 +23,14 @@ import org.jacoco.core.runtime.AgentOptions; import org.jacoco.core.runtime.AgentOptions.OutputMode; import org.jacoco.core.runtime.RuntimeData; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.net.InetAddress; +import java.util.Objects; +import java.util.concurrent.Callable; + /** * The agent manages the life cycle of JaCoCo runtime. */ @@ -55,6 +56,82 @@ public class Agent implements IAgent { Runtime.getRuntime().addShutdownHook(new Thread() { @Override public void run() { + AgentLogger.info("Jacocoagent Shutdown hook running."); + // 非 debug 模式,直接上报数据到指定服务 + if (!options.getDebug() && options.getHost() != null + && !Objects.equals(options.getHost(), "")) { + AgentLogger.info( + "Not in debug mode, push RuntimeData to server."); + // 直接在本地生成 exec 文件 + File execFile = new File("output.exec"); + try { + FileOutputStream fos = new FileOutputStream( + execFile); + ExecutionDataWriter writer = new ExecutionDataWriter( + fos); + RuntimeData runtimeData = singleton.getData(); + runtimeData.collect(writer, writer, false); + fos.flush(); + AgentLogger.info( + "Generate exec file success, exec file path is : " + + execFile.getAbsolutePath()); + } catch (Exception e) { + AgentLogger.severe("Failed to generate exec file: " + + e.getMessage()); + } + try { + AgentLogger.info( + "Server host is: " + options.getHost()); + // 调用 cov 服务接口,上传数据 + String reportApiPath = "/coverage/rpc/jacocoagent/report"; + HttpResponse response = Unirest + .post(options.getHost() + reportApiPath) + .field("ip", + InetAddress.getLocalHost() + .getHostAddress()) + .field("file", execFile).asString(); + if (response.getStatus() == 200) { + AgentLogger.info( + "Push data to server success, request url is : " + + options.getHost() + + reportApiPath + + ", request body is :" + + " {ip:" + + InetAddress.getLocalHost() + .getHostAddress() + + ", file:" + + execFile.getAbsolutePath() + + "}"); + } else { + AgentLogger.severe( + "Failed to push data to server, request url is : " + + options.getHost() + + reportApiPath + + ", request body is :" + + " {ip:" + + InetAddress.getLocalHost() + .getHostAddress() + + ", file:" + + execFile.getAbsolutePath() + + "}" + ", response status is " + + response.getStatus() + + ", response body is : " + + response.getBody()); + } + } catch (Exception e) { + AgentLogger.severe( + "Failed to push data to server, on exception: " + + e.getMessage()); + } finally { + // 删除 exec 文件 + if (execFile.exists()) { + execFile.delete(); + } + } + } else { + AgentLogger.info( + "In debug mode, skip pushing data to server."); + } agent.shutdown(); } }); diff --git a/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/AgentLogger.java b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/AgentLogger.java new file mode 100644 index 00000000..3f95fc16 --- /dev/null +++ b/org.jacoco.agent.rt/src/org/jacoco/agent/rt/internal/AgentLogger.java @@ -0,0 +1,65 @@ +/******************************************************************************* + * 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.agent.rt.internal; + +import java.io.File; +import java.util.logging.FileHandler; +import java.util.logging.Logger; +import java.util.logging.SimpleFormatter; + +/** + * @author: wick + * @date: 2024/2/3 15:01 + * @description: agent日志工具类 + */ + +public class AgentLogger { + private static final Logger logger; + private static final String defaultLogFileName = "javaagent.log"; + + /** + * 日志锁文件 + */ + private static final File LOG_LOCK_FILE = new File("javaagent.log.lck"); + + // 静态初始化块配置 Logger 和 FileHandler + static { + // 移除日志锁文件 + if (LOG_LOCK_FILE.exists()) { + LOG_LOCK_FILE.delete(); + } + logger = Logger.getLogger("AgentLogger"); + try { + FileHandler fileHandler = new FileHandler(defaultLogFileName, true); + fileHandler.setFormatter(new SimpleFormatter()); + logger.addHandler(fileHandler); + logger.setUseParentHandlers(false); + } catch (Exception e) { + logger.warning("An error occurred initializing th AgentLogger: " + + e.getMessage()); + } + } + + public static void info(String msg) { + logger.info(msg); + } + + public static void severe(String msg) { + logger.severe(msg); + } + + public static void warning(String msg) { + logger.warning(msg); + } +} diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java b/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java index 34e4a363..85ea8638 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/Analyzer.java @@ -49,258 +49,277 @@ import org.objectweb.asm.Opcodes; */ public class Analyzer { - private final ExecutionDataStore executionData; + private final ExecutionDataStore executionData; - private final ICoverageVisitor coverageVisitor; + private final ICoverageVisitor coverageVisitor; - private final StringPool stringPool; + private final StringPool stringPool; - private List classInfos; + private List classInfos; - /** - * Creates a new analyzer reporting to the given output. - * - * @param executionData execution data - * @param coverageVisitor the output instance that will coverage data for every analyzed - * class - */ - public Analyzer(final ExecutionDataStore executionData, - final ICoverageVisitor coverageVisitor) { - this.executionData = executionData; - this.coverageVisitor = coverageVisitor; - this.stringPool = new StringPool(); - } + /** + * Creates a new analyzer reporting to the given output. + * + * @param executionData + * execution data + * @param coverageVisitor + * the output instance that will coverage data for every analyzed + * class + */ + public Analyzer(final ExecutionDataStore executionData, + final ICoverageVisitor coverageVisitor) { + this.executionData = executionData; + this.coverageVisitor = coverageVisitor; + this.stringPool = new StringPool(); + } - /** - * Creates an ASM class visitor for analysis. - * - * @param classid id of the class calculated with {@link CRC64} - * @param className VM name of the class - * @return ASM visitor to write class definition to - */ - private ClassVisitor createAnalyzingVisitor(final long classid, - final String className) { - final ExecutionData data = executionData.get(classid); - final boolean[] probes; - final boolean noMatch; - // data为空说明exec文件没有探针信息,说明执行测试的类和进行report的类不一致 - if (data == null) { - probes = null; - noMatch = executionData.contains(className); - } else { - probes = data.getProbes(); - noMatch = false; - } - final ClassCoverageImpl coverage = new ClassCoverageImpl(className, - classid, noMatch); - final ClassAnalyzer analyzer = new ClassAnalyzer(coverage, probes, - stringPool, this.classInfos) { - @Override - public void visitEnd() { - super.visitEnd(); - // 这里有个模板方法模式的钩子方法,这里先定义,等后面类的方法解析完再调用此方法 - coverageVisitor.visitCoverage(coverage); - } - }; - return new ClassProbesAdapter(analyzer, false); - } + /** + * Creates an ASM class visitor for analysis. + * + * @param classid + * id of the class calculated with {@link CRC64} + * @param className + * VM name of the class + * @return ASM visitor to write class definition to + */ + private ClassVisitor createAnalyzingVisitor(final long classid, + final String className) { + final ExecutionData data = executionData.get(classid); + final boolean[] probes; + final boolean noMatch; + // data为空说明exec文件没有探针信息,说明执行测试的类和进行report的类不一致 + if (data == null) { + probes = null; + noMatch = executionData.contains(className); + } else { + probes = data.getProbes(); + noMatch = false; + } + final ClassCoverageImpl coverage = new ClassCoverageImpl(className, + classid, noMatch); + final ClassAnalyzer analyzer = new ClassAnalyzer(coverage, probes, + stringPool, this.classInfos) { + @Override + public void visitEnd() { + super.visitEnd(); + // 这里有个模板方法模式的钩子方法,这里先定义,等后面类的方法解析完再调用此方法 + coverageVisitor.visitCoverage(coverage); + } + }; + return new ClassProbesAdapter(analyzer, false); + } - private void analyzeClass(final byte[] source) { - final long classId = CRC64.classId(source); - final ClassReader reader = InstrSupport.classReaderFor(source); - if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) { - return; - } - if ((reader.getAccess() & Opcodes.ACC_SYNTHETIC) != 0) { - return; - } - if (this.coverageVisitor instanceof CoverageBuilder) { - this.classInfos = ((CoverageBuilder) this.coverageVisitor).getClassInfos(); - } - // 字段不为空说明是增量覆盖 - if (null != this.classInfos - && !this.classInfos.isEmpty()) { - // 如果没有匹配到增量代码就无需解析类 - if (!CodeDiffUtil.checkClassIn(reader.getClassName(), this.classInfos)) { - return; - } - } - final ClassVisitor visitor = createAnalyzingVisitor(classId, - reader.getClassName()); - // 重点,开始解析类里面的方法,逐个方法遍历 - reader.accept(visitor, 0); + private void analyzeClass(final byte[] source) { + final long classId = CRC64.classId(source); + final ClassReader reader = InstrSupport.classReaderFor(source); + if ((reader.getAccess() & Opcodes.ACC_MODULE) != 0) { + return; + } + if ((reader.getAccess() & Opcodes.ACC_SYNTHETIC) != 0) { + return; + } + if (this.coverageVisitor instanceof CoverageBuilder) { + this.classInfos = ((CoverageBuilder) this.coverageVisitor) + .getClassInfos(); + } + // 字段不为空说明是增量覆盖 + if (null != this.classInfos && !this.classInfos.isEmpty()) { + // 如果没有匹配到增量代码就无需解析类 + if (!CodeDiffUtil.checkClassIn(reader.getClassName(), + this.classInfos)) { + return; + } + } + final ClassVisitor visitor = createAnalyzingVisitor(classId, + reader.getClassName()); + // 重点,开始解析类里面的方法,逐个方法遍历 + reader.accept(visitor, 0); - } + } - /** - * Analyzes the class definition from a given in-memory buffer. - * - * @param buffer class definitions - * @param location a location description used for exception messages - * @throws IOException if the class can't be analyzed - */ - public void analyzeClass(final byte[] buffer, final String location) - throws IOException { - try { - analyzeClass(buffer); - } catch (final RuntimeException cause) { - throw analyzerError(location, cause); - } - } + /** + * Analyzes the class definition from a given in-memory buffer. + * + * @param buffer + * class definitions + * @param location + * a location description used for exception messages + * @throws IOException + * if the class can't be analyzed + */ + public void analyzeClass(final byte[] buffer, final String location) + throws IOException { + try { + analyzeClass(buffer); + } catch (final RuntimeException cause) { + throw analyzerError(location, cause); + } + } - /** - * Analyzes the class definition from a given input stream. The provided - * {@link InputStream} is not closed by this method. - * - * @param input stream to read class definition from - * @param location a location description used for exception messages - * @throws IOException if the stream can't be read or the class can't be analyzed - */ - public void analyzeClass(final InputStream input, final String location) - throws IOException { - final byte[] buffer; - try { - buffer = InputStreams.readFully(input); - } catch (final IOException e) { - throw analyzerError(location, e); - } - analyzeClass(buffer, location); - } + /** + * Analyzes the class definition from a given input stream. The provided + * {@link InputStream} is not closed by this method. + * + * @param input + * stream to read class definition from + * @param location + * a location description used for exception messages + * @throws IOException + * if the stream can't be read or the class can't be analyzed + */ + public void analyzeClass(final InputStream input, final String location) + throws IOException { + final byte[] buffer; + try { + buffer = InputStreams.readFully(input); + } catch (final IOException e) { + throw analyzerError(location, e); + } + analyzeClass(buffer, location); + } - private IOException analyzerError(final String location, - final Exception cause) { - final IOException ex = new IOException( - String.format("Error while analyzing %s.", location)); - ex.initCause(cause); - return ex; - } + private IOException analyzerError(final String location, + final Exception cause) { + final IOException ex = new IOException( + String.format("Error while analyzing %s.", location)); + ex.initCause(cause); + return ex; + } - /** - * Analyzes all classes found in the given input stream. The input stream - * may either represent a single class file, a ZIP archive, a Pack200 - * archive or a gzip stream that is searched recursively for class files. - * All other content types are ignored. The provided {@link InputStream} is - * not closed by this method. - * - * @param input input data - * @param location a location description used for exception messages - * @return number of class files found - * @throws IOException if the stream can't be read or a class can't be analyzed - */ - public int analyzeAll(final InputStream input, final String location) - throws IOException { - final ContentTypeDetector detector; - try { - detector = new ContentTypeDetector(input); - } catch (final IOException e) { - throw analyzerError(location, e); - } - switch (detector.getType()) { - // 编译后的类 - case ContentTypeDetector.CLASSFILE: - analyzeClass(detector.getInputStream(), location); - return 1; - case ContentTypeDetector.ZIPFILE: - return analyzeZip(detector.getInputStream(), location); - case ContentTypeDetector.GZFILE: - return analyzeGzip(detector.getInputStream(), location); - case ContentTypeDetector.PACK200FILE: - return analyzePack200(detector.getInputStream(), location); - default: - return 0; - } - } + /** + * Analyzes all classes found in the given input stream. The input stream + * may either represent a single class file, a ZIP archive, a Pack200 + * archive or a gzip stream that is searched recursively for class files. + * All other content types are ignored. The provided {@link InputStream} is + * not closed by this method. + * + * @param input + * input data + * @param location + * a location description used for exception messages + * @return number of class files found + * @throws IOException + * if the stream can't be read or a class can't be analyzed + */ + public int analyzeAll(final InputStream input, final String location) + throws IOException { + final ContentTypeDetector detector; + try { + detector = new ContentTypeDetector(input); + } catch (final IOException e) { + throw analyzerError(location, e); + } + switch (detector.getType()) { + // 编译后的类 + case ContentTypeDetector.CLASSFILE: + analyzeClass(detector.getInputStream(), location); + return 1; + case ContentTypeDetector.ZIPFILE: + return analyzeZip(detector.getInputStream(), location); + case ContentTypeDetector.GZFILE: + return analyzeGzip(detector.getInputStream(), location); + case ContentTypeDetector.PACK200FILE: + return analyzePack200(detector.getInputStream(), location); + default: + return 0; + } + } - /** - * Analyzes all class files contained in the given file or folder. Class - * files as well as ZIP files are considered. Folders are searched - * recursively. - * - * @param file file or folder to look for class files - * @return number of class files found - * @throws IOException if the file can't be read or a class can't be analyzed - */ - public int analyzeAll(final File file) throws IOException { - int count = 0; - // 如果是文件夹递归找到文件再进行解析 - if (file.isDirectory()) { - for (final File f : file.listFiles()) { - count += analyzeAll(f); - } - } else { - final InputStream in = new FileInputStream(file); - try { - // 对编译后的class类进行分析即 - count += analyzeAll(in, file.getPath()); - } finally { - in.close(); - } - } - return count; - } + /** + * Analyzes all class files contained in the given file or folder. Class + * files as well as ZIP files are considered. Folders are searched + * recursively. + * + * @param file + * file or folder to look for class files + * @return number of class files found + * @throws IOException + * if the file can't be read or a class can't be analyzed + */ + public int analyzeAll(final File file) throws IOException { + int count = 0; + // 如果是文件夹递归找到文件再进行解析 + if (file.isDirectory()) { + for (final File f : file.listFiles()) { + count += analyzeAll(f); + } + } else { + final InputStream in = new FileInputStream(file); + try { + // 对编译后的class类进行分析即 + count += analyzeAll(in, file.getPath()); + } finally { + in.close(); + } + } + return count; + } - /** - * Analyzes all classes from the given class path. Directories containing - * class files as well as archive files are considered. - * - * @param path path definition - * @param basedir optional base directory, if null the current - * working directory is used as the base for relative path - * entries - * @return number of class files found - * @throws IOException if a file can't be read or a class can't be analyzed - */ - public int analyzeAll(final String path, final File basedir) - throws IOException { - int count = 0; - final StringTokenizer st = new StringTokenizer(path, - File.pathSeparator); - while (st.hasMoreTokens()) { - count += analyzeAll(new File(basedir, st.nextToken())); - } - return count; - } + /** + * Analyzes all classes from the given class path. Directories containing + * class files as well as archive files are considered. + * + * @param path + * path definition + * @param basedir + * optional base directory, if null the current + * working directory is used as the base for relative path + * entries + * @return number of class files found + * @throws IOException + * if a file can't be read or a class can't be analyzed + */ + public int analyzeAll(final String path, final File basedir) + throws IOException { + int count = 0; + final StringTokenizer st = new StringTokenizer(path, + File.pathSeparator); + while (st.hasMoreTokens()) { + count += analyzeAll(new File(basedir, st.nextToken())); + } + return count; + } - private int analyzeZip(final InputStream input, final String location) - throws IOException { - final ZipInputStream zip = new ZipInputStream(input); - ZipEntry entry; - int count = 0; - while ((entry = nextEntry(zip, location)) != null) { - count += analyzeAll(zip, location + "@" + entry.getName()); - } - return count; - } + private int analyzeZip(final InputStream input, final String location) + throws IOException { + final ZipInputStream zip = new ZipInputStream(input); + ZipEntry entry; + int count = 0; + while ((entry = nextEntry(zip, location)) != null) { + count += analyzeAll(zip, location + "@" + entry.getName()); + } + return count; + } - private ZipEntry nextEntry(final ZipInputStream input, - final String location) throws IOException { - try { - return input.getNextEntry(); - } catch (final IOException e) { - throw analyzerError(location, e); - } - } + private ZipEntry nextEntry(final ZipInputStream input, + final String location) throws IOException { + try { + return input.getNextEntry(); + } catch (final IOException e) { + throw analyzerError(location, e); + } + } - private int analyzeGzip(final InputStream input, final String location) - throws IOException { - GZIPInputStream gzipInputStream; - try { - gzipInputStream = new GZIPInputStream(input); - } catch (final IOException e) { - throw analyzerError(location, e); - } - return analyzeAll(gzipInputStream, location); - } + private int analyzeGzip(final InputStream input, final String location) + throws IOException { + GZIPInputStream gzipInputStream; + try { + gzipInputStream = new GZIPInputStream(input); + } catch (final IOException e) { + throw analyzerError(location, e); + } + return analyzeAll(gzipInputStream, location); + } - private int analyzePack200(final InputStream input, final String location) - throws IOException { - InputStream unpackedInput; - try { - unpackedInput = Pack200Streams.unpack(input); - } catch (final IOException e) { - throw analyzerError(location, e); - } - return analyzeAll(unpackedInput, location); - } + private int analyzePack200(final InputStream input, final String location) + throws IOException { + InputStream unpackedInput; + try { + unpackedInput = Pack200Streams.unpack(input); + } catch (final IOException e) { + throw analyzerError(location, e); + } + return analyzeAll(unpackedInput, location); + } } diff --git a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java index 71a52615..d265a075 100644 --- a/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java +++ b/org.jacoco.core/src/org/jacoco/core/analysis/CoverageBuilder.java @@ -43,7 +43,7 @@ public class CoverageBuilder implements ICoverageVisitor { /** * 新增代码类 */ - public List classInfos; + public List classInfos; /** * Create a new builder. diff --git a/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java b/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java index 8fe52976..e4383f4c 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/analysis/ClassAnalyzer.java @@ -70,9 +70,9 @@ public class ClassAnalyzer extends ClassProbesVisitor this.filter = Filters.all(); } - public ClassAnalyzer(final ClassCoverageImpl coverage, - final boolean[] probes, final StringPool stringPool,List classInfos) { + final boolean[] probes, final StringPool stringPool, + List classInfos) { this.coverage = coverage; this.probes = probes; this.stringPool = stringPool; @@ -80,7 +80,6 @@ public class ClassAnalyzer extends ClassProbesVisitor this.classInfos = classInfos; } - public List getClassInfos() { return classInfos; } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/diff/CodeDiffUtil.java b/org.jacoco.core/src/org/jacoco/core/internal/diff/CodeDiffUtil.java index 97447546..825f2f89 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/diff/CodeDiffUtil.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/diff/CodeDiffUtil.java @@ -25,108 +25,109 @@ import java.util.stream.Stream; * @Author: duanrui * @CreateDate: 2021/1/12 15:17 * @Version: 1.0 - *

- * Copyright: Copyright (c) 2021 + *

+ * Copyright: Copyright (c) 2021 */ public class CodeDiffUtil { - private final static String OPERATE_ADD = "ADD"; + private final static String OPERATE_ADD = "ADD"; - /** - * 检测类是否在差异代码中 - * - * @param className - * @return Boolean - */ - public static Boolean checkClassIn(String className, List classInfos) { - if (null == classInfos - || classInfos.isEmpty() || null == className) { - return Boolean.FALSE; - } - // 这里要考虑匿名内部类的问题 - return classInfos.stream() - .anyMatch(c -> className.equals(c.getClassFile()) - || className.split("\\$")[0].equals(c.getClassFile())); - } + /** + * 检测类是否在差异代码中 + * + * @param className + * @return Boolean + */ + public static Boolean checkClassIn(String className, + List classInfos) { + if (null == classInfos || classInfos.isEmpty() || null == className) { + return Boolean.FALSE; + } + // 这里要考虑匿名内部类的问题 + 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 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())) { - 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 className + * @param methodName + * @return Boolean + */ + public static Boolean checkMethodIn(String className, String methodName, + String desc, List 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())) { + 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 params, String desc) { - // 解析ASM获取的参数 - Type[] argumentTypes = Type.getArgumentTypes(desc); - // 说明是无参数的方法,匹配成功 - if (params.size() == 0 && argumentTypes.length == 0) { - return Boolean.TRUE; - } - String[] diffParams = params.toArray(new String[params.size()]); - // 只有参数数量完全相等才做下一次比较,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].contains(arg)) { - return Boolean.FALSE; - } - } - // 只有个数和类型全匹配到才算匹配 - return Boolean.TRUE; - } - return Boolean.FALSE; - } + /** + * 匹配参数 + * + * @param params + * 格式:String a + * @param desc + * 转换后格式: java.lang.String + * @return + */ + public static Boolean checkParamsIn(List params, String desc) { + // 解析ASM获取的参数 + Type[] argumentTypes = Type.getArgumentTypes(desc); + // 说明是无参数的方法,匹配成功 + if (params.size() == 0 && argumentTypes.length == 0) { + return Boolean.TRUE; + } + String[] diffParams = params.toArray(new String[params.size()]); + // 只有参数数量完全相等才做下一次比较,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].contains(arg)) { + return Boolean.FALSE; + } + } + // 只有个数和类型全匹配到才算匹配 + return Boolean.TRUE; + } + return Boolean.FALSE; + } } diff --git a/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java b/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java index 4ddb8290..6919bf05 100644 --- a/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java +++ b/org.jacoco.core/src/org/jacoco/core/internal/flow/ClassProbesAdapter.java @@ -74,13 +74,13 @@ public class ClassProbesAdapter extends ClassVisitor signature, exceptions); if (null != mv) { List classInfos = null; - if(cv instanceof ClassAnalyzer){ + if (cv instanceof ClassAnalyzer) { classInfos = ((ClassAnalyzer) cv).getClassInfos(); } // 增量代码,有点绕,由于参数定义成final,无法第二次指定,代码无法简化 - if (null != classInfos - && !classInfos.isEmpty()) { - if (CodeDiffUtil.checkMethodIn(this.name, name, desc,classInfos)) { + if (null != classInfos && !classInfos.isEmpty()) { + if (CodeDiffUtil.checkMethodIn(this.name, name, desc, + classInfos)) { methodProbes = mv; } else { methodProbes = EMPTY_METHOD_PROBES_VISITOR; diff --git a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java index 1ae33086..b6f8068e 100644 --- a/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java +++ b/org.jacoco.core/src/org/jacoco/core/runtime/AgentOptions.java @@ -12,18 +12,12 @@ *******************************************************************************/ package org.jacoco.core.runtime; -import static java.lang.String.format; - import java.io.File; -import java.util.Arrays; -import java.util.Collection; -import java.util.HashMap; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Properties; +import java.util.*; import java.util.regex.Pattern; +import static java.lang.String.format; + /** * Utility to create and parse options for the runtime agent. Options are * represented as a string in the following format: @@ -189,10 +183,24 @@ public final class AgentOptions { */ public static final String JMX = "jmx"; + /** + * 是否启用 debug 模式,如果是 debug 模式,则不会上报覆盖率数据,否则根据 host 配置推送覆盖率数据到 cov 服务器 + */ + public static final String DEBUG = "debug"; + /** + * 测试平台域名,用来发送覆盖率数据 + */ + public static final String HOST = "host"; + + /** + * 默认启用 debug 模式 + */ + public static final Boolean DEFAULT_DEBUG = true; + private static final Collection VALID_OPTIONS = Arrays.asList( DESTFILE, APPEND, INCLUDES, EXCLUDES, EXCLCLASSLOADER, INCLBOOTSTRAPCLASSES, INCLNOLOCATIONCLASSES, SESSIONID, DUMPONEXIT, - OUTPUT, ADDRESS, PORT, CLASSDUMPDIR, JMX); + OUTPUT, ADDRESS, PORT, CLASSDUMPDIR, JMX, DEBUG, HOST); private final Map options; @@ -268,6 +276,22 @@ public final class AgentOptions { return getOption(DESTFILE, DEFAULT_DESTFILE); } + public Boolean getDebug() { + return getOption(DEBUG, DEFAULT_DEBUG); + } + + public void setDebug(final boolean debug) { + setOption(DEBUG, debug); + } + + public String getHost() { + return getOption(HOST, null); + } + + public void setHost(final String host) { + setOption(HOST, host); + } + /** * Sets the output file location. * -- Gitee