【问题标题】:jacoco code coverage report generator showing error : "Classes in bundle 'Code Coverage Report' do no match with execution data"jacoco 代码覆盖率报告生成器显示错误:“捆绑'代码覆盖率报告'中的类与执行数据不匹配”
【发布时间】:2015-07-30 09:35:30
【问题描述】:

我正在使用 jacoco:report 标签生成 jacoco 报告。 我收到如下错误:

[jacoco:report] Classes in bundle 'Code Coverage Report' do no match with execution data. For report generation the same class files must be used as at runtime.
[jacoco:report] Execution data for class xxxxx does not match.
[jacoco:report] Execution data for class yyyyy does not match.

ant 报告目标如下:

<target name="report">
                <jacoco:report>
                        <executiondata>
                                <file file="${jacocoexec.dir}/${jacocoexec.filename}"/>
                        </executiondata>
                        <!-- the class files and optional source files ... -->
                        <structure name="Code Coverage Report">
                                <classfiles>
                                        <fileset file="./jar/abc.jar"/>
                                </classfiles>
                                <sourcefiles>
                                      <fileset dir="./code/src"/>
                                </sourcefiles>
                        </structure>
                        <!-- to produce reports in different formats. -->
                        <html destdir="${jacoco.report.dir}"/>
                </jacoco:report>
        </target>

这样生成的abc.jar 只能使用./code/src。那为什么会出现这样的错误。有什么想法吗?

【问题讨论】:

  • 添加executiondata(${jacocoexec.filename})是如何生成的?应该有单独的目标来执行jacoco:coverage 任务。对这个目标也提出质疑。
  • 通过在运行测试用例时添加 -javaagent 参数:-javaagent:JaCoCoProject/lib/jacocoagent.jar=destfile=JaCoCoProject/jacoco.exec,output=file
  • 请分享完整的构建文件。所有这些错误都在说你检测了一个 jar,运行了测试——这些类不是来自“./jar/abc.jar”——仅仅通过名称匹配这些类是不够的。它们必须与 jacoco 看到的“classID”相匹配——这意味着即使你重新编译,这些类也是不同的。
  • @Jayan : 在相同的代码上重新编译是否会改变 jar 中的内容?
  • 是的——从 jacoco 的角度来看。它计算出二进制的哈希值——每次编译时都不同——可能是由于时间戳。

标签: java ant jacoco


【解决方案1】:

您收到与 classID 相关的错误。这是 JaCoCo docs-site 中详细描述的概念。 http://www.eclemma.org/jacoco/trunk/doc/classids.html。这是在同一 JVM 中支持多个版本的类(例如应用服务器)的关键步骤。

在此处复制其中的一部分以供查看。

什么是类 ID,它们是如何创建的?

类 ID 是 64 位整数值,例如 0x638e104737889183 in 十六进制表示法。他们的计算被认为是一个实现细节 JaCoCo 的。目前,ID 是使用原始的 CRC64 校验和创建的 类文件。

什么会导致不同的类 ID?

只有完全相同的类文件的类 ID 相同 (逐字节)。您可能会得到以下几个原因 不同的类文件。首先编译 Java 源文件将导致 如果您使用不同的工具链,则在不同的类文件中:

  • 不同的编译器供应商(例如 Eclipse 与 Oracle JDK)

  • 不同的编译器版本

  • 不同的编译器设置(例如调试与非调试)

此外,后处理类文件(混淆、AspectJ 等)通常会更改类文件。如果您只是将相同的类文件用于运行时和分析,JaCoCo 将会很好地工作。所以创建这些类文件的工具链无关紧要。

即使文件系统上的类文件相同,JaCoCo 运行时代理看到的类也可能不同。这通常发生在在 JaCoCo 代理或特殊类加载器预处理类文件之前配置另一个 Java 代理时。典型的候选人是:

  • 模拟框架
  • 应用服务器
  • 持久性框架

同一页面涵盖了可能的解决方案。

存在哪些解决方法来处理运行时修改的类?

如果您的设置中的类在运行时被修改,有一些解决方法可以让 JaCoCo 正常工作:

  • 如果您使用另一个 Java 代理,请确保首先在命令行中指定 JaCoCo 代理。这样,JaCoCo 代理应该会看到原始类文件。
  • 指定 JaCoCo 代理的 classdumpdir 选项并在生成报告时使用转储的类。请注意,只会转储已加载的类,即根本未执行的类将不会显示在您的报告中,因为未涵盖。
  • 在运行测试之前使用离线检测。这样,在进行任何运行时修改之前,JaCoCo 会检测类。请注意,在这种情况下,必须使用原始类生成报告,而不是使用插桩类生成报告。

于 2017 年 2 月 22 日编辑

如何使用离线检测: 使用Daniel Atallah提供的以下任务。

//Additional SourceSets can be added to the jacocoOfflineSourceSets as needed by 
project.ext.jacocoOfflineSourceSets = [ 'main' ]
task doJacocoOfflineInstrumentation(dependsOn: [ classes, project.configurations.jacocoAnt ]) {
    inputs.files classes.outputs.files
    File outputDir = new File(project.buildDir, 'instrumentedClasses')
    outputs.dir outputDir
    doFirst {
        project.delete(outputDir)
        ant.taskdef(
            resource: 'org/jacoco/ant/antlib.xml',
            classpath: project.configurations.jacocoAnt.asPath,
            uri: 'jacoco'
        )
        def instrumented = false
        jacocoOfflineSourceSets.each { sourceSetName ->
            if (file(sourceSets[sourceSetName].output.classesDir).exists()) {
                def instrumentedClassedDir = "${outputDir}/${sourceSetName}"
                ant.'jacoco:instrument'(destdir: instrumentedClassedDir) {
                    fileset(dir: sourceSets[sourceSetName].output.classesDir, includes: '**/*.class')
                }
                //Replace the classes dir in the test classpath with the instrumented one
                sourceSets.test.runtimeClasspath -= files(sourceSets[sourceSetName].output.classesDir)
                sourceSets.test.runtimeClasspath += files(instrumentedClassedDir)
                instrumented = true
            }
        }
        if (instrumented) {
            //Disable class verification based on https://github.com/jayway/powermock/issues/375
            test.jvmArgs += '-noverify'
        }
    }
}
test.dependsOn doJacocoOfflineInstrumentation

现在使用"gradlew test jacocoTestReport" 命令生成报告。

【讨论】:

  • 感谢您添加此答案。它对理解 jacoco 框架和相关概念很有帮助。
  • @Jayan:你的回答是正确的,但很难理解我到底要做什么才能解决这个问题。如果你能提供一些代码 sn-p 那么这对我来说会很容易。考虑到阅读本文的人对 Jacoco 的工作方式有很好的理解,您已经编写了答案。
  • @Jayan:编辑了您的答案并添加了我用于离线检测的任务。请查看。
  • 我得到错误`构建文件'C:\Projects\projname\subdir\build.gradle':45:期待''',在第45行第51列找到'\r'。:' org/jacoco/ant/antlib.xml',`知道这是什么吗?
  • 无法获取 org.gradle.api.internal.tasks.DefaultSourceSetOutput 类型的主要类的未知属性“classesDir”。
【解决方案2】:

JaCoCo 需要与执行时使用的完全相同的类文件来生成报告。由于不同的编译器和/或修改类的其他工具,类可能会有所不同。

【讨论】:

    【解决方案3】:

    我注意到,如果您要报告代码覆盖率的类在您的 JUnit 测试中被带有 PowerMockito 的 Mockito 抑制其静态初始化,就会发生这种情况。例如,如果您的测试类如下所示:

    @SuppressStaticInitializationFor(
            {"com.yourpkg.A",
            "com.yourpkg.B"})
    public class Test {
         @Test
         public void Test() { }
    }
    

    测试时的错误如下:

    Classes in bundle 'yourProject' do not match with execution data. 
    For report generation the same class files must be used as at runtime. 
    Execution data for class com/yourpkg/A does not match. 
    Execution data for class com/yourpkg/B does not match.
    

    【讨论】:

    • 我对 PowerMock 也有同样的问题。我没有使用该注释,而是使用 @RunWith(PowerMockRunner::class)@PrepareForTest(TestedClass::class) (kotlin)
    • @HendraWD 你有没有找到解决办法?我遇到了同样的问题
    【解决方案4】:

    具有深刻见解的各种答案,但在摆弄设置 2 天后,我仍然会分享对我有用的东西。

    • 我正在为 git 使用 Azure Devops。

    因此,根据代码设置指南,您需要在构建任务之后运行分析任务。这是我完全错过的一步。

    这是我的任务序列之前的样子

     - task: Gradle@2
        inputs:
          gradleWrapperFile: 'gradlew'
          tasks: '--build-cache jacocoTestReport SonarQube'
          options: '-PversionName=$(Build.BuildNumber) -PversionCode=$(Build.BuildId) -Porg.gradle.parallel=true'
          publishJUnitResults: true
          testResultsFiles: '**/TEST-*.xml'
          javaHomeOption: 'JDKVersion'
          jdkVersionOption: '1.11'
          gradleOptions: '-Xmx4096m'
          sonarQubeRunAnalysis: false
        continueOnError: true
        displayName: "Run Quality Scan and upload"
        env:
          SONAR_LOGIN: $(sonar-login)
          BRANCH_NAME: $(Build.SourceBranchName)
          
      - task: Gradle@2
        condition: in(variables['Build.SourceBranchName'], 'develop')
        inputs:
          gradleWrapperFile: 'gradlew'
          tasks: '--build-cache build publishAllPublicationsToMyDigitalRepository'
          options: '-PversionName=$(Build.BuildNumber) -PversionCode=$(Build.BuildId) -PsecureSign'
          publishJUnitResults: true
          testResultsFiles: '**/TEST-*.xml'
          javaHomeOption: 'JDKVersion'
          jdkVersionOption: '1.11'
          gradleOptions: '-Xmx4096m'
          sonarQubeRunAnalysis: false
        env:
          SDK_NAME: 'iphoneos'
        continueOnError: false
        displayName: "Build & publish library"
    

    这是重新定位后的样子

      - task: Gradle@2
        condition: in(variables['Build.SourceBranchName'], 'develop')
        inputs:
          gradleWrapperFile: 'gradlew'
          tasks: '--build-cache build publishAllPublicationsToMyDigitalRepository'
          options: '-PversionName=$(Build.BuildNumber) -PversionCode=$(Build.BuildId) -PsecureSign'
          publishJUnitResults: true
          testResultsFiles: '**/TEST-*.xml'
          javaHomeOption: 'JDKVersion'
          jdkVersionOption: '1.11'
          gradleOptions: '-Xmx4096m'
          sonarQubeRunAnalysis: false
        env:
          SDK_NAME: 'iphoneos'
        continueOnError: false
        displayName: "Build & publish library"
    
      - task: Gradle@2
        inputs:
          gradleWrapperFile: 'gradlew'
          tasks: '--build-cache jacocoTestReport SonarQube'
          options: '-PversionName=$(Build.BuildNumber) -PversionCode=$(Build.BuildId) -Porg.gradle.parallel=true'
          publishJUnitResults: true
          testResultsFiles: '**/TEST-*.xml'
          javaHomeOption: 'JDKVersion'
          jdkVersionOption: '1.11'
          gradleOptions: '-Xmx4096m'
          sonarQubeRunAnalysis: false
        continueOnError: true
        displayName: "Run Quality Scan and upload"
        env:
          SONAR_LOGIN: $(sonar-login)
          BRANCH_NAME: $(Build.SourceBranchName)
    

    所以本质上我学到了一些东西,在这里断章取意,但最重要的是。

    • 正确的任务顺序
    • 为了让 Sonar 正确找到这些文件,必须让该版本可用。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-09-09
      • 2021-06-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多