白盒测试 解决 jacoco 合并体积无限膨胀的问题

tlhymm42 · 2024年03月21日 · 最后由 陈先生 回复于 2024年03月26日 · 4899 次阅读

背景

目前想每隔 10 分钟自动收集一次测试环境所有特性分支的当前覆盖率,并做合并操作,提高覆盖率收集效率和准确性。
但实际在合并后覆盖率的文件会持续膨胀,如果频繁合并会导致磁盘迅速变满。

原因分析

分析 merge 逻辑

合并入口:
org.jacoco.core.tools.ExecFileLoader#save

实际写入:
org.jacoco.core.data.ExecutionDataWriter#visitClassExecution

分析发现,当 append=true,则读取原来的覆盖率文件,并合并到目标覆盖率文件中,所以文件体积会一直变大。

解决办法

在合并前,先读取一次原来的文件,提前做合并操作,待合并完后,不已追加写的方式写覆盖率文件,而是将覆盖率重新写入,这样写入的覆盖率就是合并后的了。
新建一个类,继承 ExecFileLoader,并覆盖 save 方法

public void save(final File file, boolean append) throws IOException {
    if (file.exists()) {
        final FileInputStream in = new FileInputStream(file);
        final ExecutionDataReader reader = new ExecutionDataReader(in);

        // 1. 读取目标覆盖率文件
        ExecutionDataStore destExecDataStore = new ExecutionDataStore();
        reader.setSessionInfoVisitor(new SessionInfoStore());
        reader.setExecutionDataVisitor(destExecDataStore);
        reader.read();
        in.close();
        // 2. 合并 新的覆盖率文件 和 目标覆盖文件
        this.executionData.merge(destExecDataStore.getContents());
    }
    // 3. 不通过追加写的方式写入覆盖率
    final FileOutputStream fileStream = new FileOutputStream(file, false);
    // Avoid concurrent writes from other processes:
    fileStream.getChannel().lock();
    final OutputStream bufferedStream = new BufferedOutputStream(
            fileStream);
    try {
        save(bufferedStream);
    } finally {
        bufferedStream.close();
    }
}

merge 方法:

public void merge(Collection<ExecutionData> executionDataList) {
    Map<Long, ExecutionData> mergeEntries = new HashMap<Long, ExecutionData>();
    // executionDataList 为待合入的覆盖率文件
    for (ExecutionData executionData : executionDataList) {
        mergeEntries.put(executionData.getId(), executionData);
    }
    // 
    for (Long id : mergeEntries.keySet()) {
        // this.entries 是后续实际要写入的覆盖率数据
        // 1. 如果类id一致,表示类没有变动,则合并覆盖率
        if (this.entries.containsKey(id)) {
            ExecutionData merge = mergeEntries.get(id);
            ExecutionData entry = this.entries.get(id);
            entry.mergeProbes(merge.getProbes());
            this.entries.put(id, entry);
        } else {
            // 2. 如果不存在,则有可能类被改过,将旧的覆盖率合并到新的
            this.entries.put(id, mergeEntries.get(id));
        }
    }
}

mergeProbes 方法

public void mergeProbes(boolean[] probes) {
    for (int i = 0; i < this.probes.length; i++) {
        if (!this.probes[i] && probes[i]) {
            this.probes[i] = probes[i];
        }
    }
}

最后,记得在 org.jacoco.cli.internal.commands.Merge 中,将 ExecFileLoader 替换为我们优化后的类

如此,无论是代码、覆盖率有无改动,每次合并都只会合并增量的部分,合并后的文件大小基本不会发生太大改变。

共收到 3 条回复 时间 点赞

合并只是合并探针数据把,通过 classId 和探针数组是否一致判断,文件大小应该不会变化才对

陈先生 回复

我开始也以为是这样,但是实际发现不是,每次都会把新的覆盖率追加写到旧的覆盖率文件上.

tlhymm42 回复

还真是这样子,默认追加有点傻

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册