【问题标题】:jacoco+gradle taskdef A class needed by class org.jacoco.ant.InstrumentTask canno t be foundjacoco+gradle taskdef org.jacoco.ant.InstrumentTask 类需要的类找不到
【发布时间】:2015-02-22 09:50:14
【问题描述】:

我正在尝试让 jacoco+gradle 一起工作。看看这个论坛,似乎有些人已经成功地做到了。但是当我尝试时,我遇到了一个奇怪的异常。

我做了什么:

1.下载gradle 2.2.1并配置环境变量等

2.从http://www.eclemma.org/jacoco/下载jacoco 0.7.1

3.添加:

 apply plugin: 'jacoco'

 buildTypes {
         debug
{
         testCoverageEnabled true
...

在 build.gradle 中

4.运行 gradle build

5.我收到一条错误消息,提示找不到 jacoco 代理 jar,等等。错误消息显示它试图搜索 C:\Program Files (x86)\Android\android-sdk\extras\android\m2repository 下的文件。 ..等等

6.我手动解压了jacoco的jar文件,放到提到错误信息的地方,错误信息消失了。

7.然后我运行了 gradle build。运行内置的 instrumentDebug 任务时出现以下新错误:

Caused by: : taskdef A class needed by class org.jacoco.ant.InstrumentTask cannot be found: org/jacoco/core/runtime/IExecutionDataAccessorGenerator using the classloader AntClassLoader[C:\Program Files (x86)\Android\android-sdk\extras\android\m2repository\org\jacoco\org.jacoco.ant\0.7.1.201405082137\org.jacoco.ant-0.7.1.201405082137.jar]
    at org.apache.tools.ant.taskdefs.Definer.addDefinition(Definer.java:612)

    at org.apache.tools.ant.taskdefs.Definer.execute(Definer.java:237)
    at org.apache.tools.ant.UnknownElement.execute(UnknownElement.java:292)
    at org.apache.tools.ant.dispatch.DispatchUtils.execute(DispatchUtils.java:106)
    at org.gradle.api.internal.project.ant.BasicAntBuilder.nodeCompleted(BasicAntBuilder.java:77)
    at org.gradle.api.internal.project.ant.BasicAntBuilder.doInvokeMethod(BasicAntBuilder.java:92)
    at com.android.build.gradle.internal.coverage.JacocoInstrumentTask.instrument(JacocoInstrumentask.groovy:51)
    at org.gradle.internal.reflect.JavaMethod.invoke(JavaMethod.java:63)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.doExecute(AnnotationProcessingTaskFactory.java:218)
    at org.gradle.api.internal.project.taskfactory.AnnotationProcessingTaskFactory$StandardTaskAction.execute(AnnotationProcessingTaskFactory.java:211)


Caused by: java.lang.ClassNotFoundException: 

    at org.jacoco.core.runtime.IExecutionDataAccessorGenerator
    at org.apache.tools.ant.AntClassLoader.findClassInComponents(AntClassLoader.java:1366)
    at org.apache.tools.ant.AntClassLoader.findClass(AntClassLoader.java:1315)
    at org.apache.tools.ant.AntClassLoader.loadClass(AntClassLoader.java:1068)...

当我使用进程监视器(我的开发机器是 Win 7)时,我看到无法访问 org.jacoco.core-0.7.1.201405082137.jar,org.jacoco.ant.InstrumentTask 类驻留在其中。所以我认为 gradle 没有正确地将文件路径传递给 org.apache.tools.ant.AntClassLoader。

我尝试了以下方法,但都没有奏效:

  1. 将所有 jacoco jar 文件添加到 CLASSPATH 环境变量中。
  2. 将 jacoco jar 文件添加到 ant libs、gradle libs、gradle libs/plugins、文件夹。
  3. 查看gradle 2.2.1的源码。此刻一头雾水……

有人知道如何解决这个问题吗?

提前致谢!

编辑:

主要目的是:

1.让 jacoco 构建一个检测的 apk 文件

2。手动测试apk文件(非自动测试)

3.让 jacoco 生成覆盖率报告

更新: 我刚刚发现在执行 InstrumentDebug 任务时,gradle 使用以下命令启动了一个新进程:

"C:\Program Files\Java\jdk1.8.0_25\bin\java.exe" -XX:MaxPermSize=512m -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country=CN -Duser.language= zh -Duser.variant -cp D:\gradle-2.2.1\gradle-2.2.1\lib\gradle-launcher-2.2.1.jar org.gradle.launcher.daemon.bootstrap.GradleDaemon 2.2.1 D:\ gradle-2.2.1\gradle-2.2.1\daemon 120000 744501ac-32c1-4930-82bd-59e0a9e2b92d -XX:MaxPermSize=512m -Xmx2048m -Dfile.encoding=UTF-8 -Duser.country=CN -Duser.language= zh -Duser.variant

如您所见,类路径是硬编码的,它会覆盖环境变量中定义的内容。所以找不到需要的jacoco核心库,导致了这个问题。我现在正在尝试查看此过程从哪里开始以及是否有办法更改 -cp 选项。

更新 2: 我终于发现这个过程是由 gradle-launcher-2.2.1.jar 启动的。请参见下面的代码。我现在正在尝试查看是否可以更改 DefaultModuleRegistry 并使 registry.getGradleHome() == null。 同时有成功使用过jacoco+gradle的朋友可以告诉我你用的是哪个版本的gradle吗?

public DaemonStartupInfo startDaemon()
{
DefaultModuleRegistry registry = new DefaultModuleRegistry();
Set<File> bootstrapClasspath = new LinkedHashSet();
bootstrapClasspath.addAll(registry.getModule("gradle-launcher").getImplementationClasspath().getAsFiles());
if (registry.getGradleHome() == null) 
{
  bootstrapClasspath.addAll(registry.getFullClasspath());
}
if (bootstrapClasspath.isEmpty()) {
  throw new IllegalStateException("Unable to construct a bootstrap classpath when starting the daemon");
}    
new JvmVersionValidator().validate(this.daemonParameters);
List<String> daemonArgs = new ArrayList();
daemonArgs.add(this.daemonParameters.getEffectiveJavaExecutable());
List<String> daemonOpts = this.daemonParameters.getEffectiveJvmArgs();
LOGGER.debug("Using daemon opts: {}", daemonOpts);
daemonArgs.addAll(daemonOpts);
daemonArgs.add("-cp");
daemonArgs.add(CollectionUtils.join(File.pathSeparator, bootstrapClasspath));
daemonArgs.add(GradleDaemon.class.getName());
daemonArgs.add(GradleVersion.current().getVersion());
daemonArgs.add(this.daemonDir.getBaseDir().getAbsolutePath());
daemonArgs.add(String.valueOf(this.daemonParameters.getIdleTimeout()));
daemonArgs.add(this.daemonParameters.getUid());

...
}

【问题讨论】:

  • 您是否遵循 Gradle 指南(第 34 章):gradle.org/docs/current/userguide/jacoco_plugin.html。我还没有尝试过 Gradle 2.2.1,但是,我的问题是,您是在尝试使用“单元测试”还是使用“集成/接受/Selenium/etc GUI 测试”来获取主要源代码的代码覆盖率?如果它正在使用您的 UNIT 测试,那么当您运行“gradle clean build”时,Gradle 将免费执行此操作。如果您想使用非单元测试对主要源代码进行代码覆盖,那么您必须将 jacocoagent 附加到目标 JVM(例如:在目标服务器的 Tomcat JVM 中,其中运行应用程序的 .war)。
  • 另外,粘贴您的 build.gradle。
  • @ArunSangal,感谢您的回复。我已经更新了我的目的。我现在被困在#1。我的 build.gradle 没有什么特别之处。我刚刚添加了第 3 步提到的内容。

标签: gradle jacoco


【解决方案1】:

看看这是否有帮助。 我没有使用 Gradle 2.2.1,但这就是我在全局 Gradle 文件中的内容(即 $GRADLE_HOME/init.d 级别文件中的文件)。文件名可以是任何带有 .gradle 扩展名的名称。

allprojects {
   apply plugin: 'java'
   apply plugin: 'pmd'
   apply plugin: 'findbugs'
   apply plugin: 'checkstyle'
   apply plugin: 'jacoco'

   //NOTE1: The following soureSet section is NOT required, if your folder structure follows what Gradle says where your main source should reside, where test (Unit tests) should reside, and where other like integrationTest (integration tests code reside). If your project structure doesn't follow the Gradle defined structure, then you can define that as my source code is not under src/main/java but is under src/java. The use of "sourceSet" section in this global file is only helping to use some conventional values in this global level file for ex: see integrationTest task and jacocoTestReport task below (you can't use those values if sourceSet is NOT defined in this file and if your project doesn't following the Gradle defined structure).

  //NOTE2: Here in the global level Gradle file, I'm using values for sources for main, test, integrationTest etc as "dont_change_me" as I don't know what all projects (which will use this Gradle's global level file), will have what source code structure. The main / actual values of the sources for main, test and integrationTest task MUST be defined in the PROJECT's build.gradle file in sourceSets { main { java { srcDir 'src/java' } } } way.


   sourceSets {
      main {
         java {
            srcDir 'dont_change_me'
         }
         resources {
            srcDir 'dont_change_me'
         }
      }
      test {
         java {
            srcDir 'dont_change_me'
         }
         resources {
            srcDir 'dont_change_me'
         }
      }
      integrationTest {
         java {
            srcDir 'dont_change_me'
         }
         resources {
            srcDir 'dont_change_me'
         }
      }
      acceptanceTest {
         java {
            srcDir 'dont_change_me'
         }
         resources {
            srcDir 'dont_change_me'
         }
      }

   }

   //...more code here
   //...more code here

   // The following is necessary to get code coverage info out. Compile with debug mode.
   tasks.withType(Compile) {
     options.debug = true
     options.compilerArgs = ["-g"]
   }

   jacoco {
        //toolVersion = "0.6.2.201302030002"
        //toolVersion = "0.7.0.201403182114"
        //toolVersion = "0.7.1.201404171759"

        //This is latest than above, you may find later versions in online Maven repository.
        toolVersion = "0.7.2.201409121644"

        //OK I don't need the following folder to be created as I'll define my own.
        // reportsDir = file("$buildDir/customJacocoReportDir")

   }

   //The following section is for UNIT tests (as build task in Gradle calls test task for free)
   test {
     maxParallelForks = 5
     forkEvery = 50
     ignoreFailures = true

     // I want my reports (html) files to be created in a user defined folder UT(Unit test in build/reports/UT folder) and xml files (in user defined folder UT folder) under build/test-results/UT folder.
     testReportDir = file("$buildDir/reports/tests/UT")
     testResultsDir = file("$buildDir/test-results/UT")

     //Following jacoco session will RUN in GRADLE's JVM session (during build / test time). This is different JVM than what many think of a runtime/Tomcat JVM session where we run a .war/.ear/etc file of an app to run that app and if you want to get code coverage of your main source code using non-unit tests from a Tomcat JVM, then see next task (integrationTest) as the following jacoco section in this "test" task is just for UNIT tests running in Gradle JVM session on a machine.
     jacoco {
        //NOTE: The following vars works ONLY with Gradle <= 1.6 version

        // Create jacoco .exec file for Unit test in a user defined location
        destPath = file("$buildDir/jacoco/UT/jacocoUT.exec")
        //The following line is not that usesful acc. to my experience so commented it.
        //classDumpPath = file("$buildDir/jacoco/UT/classpathdumps")



        //NOTE: Following vars works only with versions >= 1.7 version of Gradle
        //destinationFile = file("$buildDir/jacoco/UT/jacocoUT.exec")
        //  classDumpFile = file("$buildDir/jacoco/UT/classpathdumps")
     }
   }


   task integrationTest( type: Test) {

     //Always run tests
     outputs.upToDateWhen { false }

     //Ignore the failures if any during tests and don't mark the Gradle task as failed.
     //You can comment this line if you want your gradle task to fail as soon as it finds any failing tests.
     ignoreFailures = true

     //This is telling Gradle that where it'll find class files from integration tests source code
     testClassesDir = sourceSets.integrationTest.output.classesDir
     //What path to use in classpath for integration tests
     classpath = sourceSets.integrationTest.runtimeClasspath

     //My custom location where I want my html reports files and xml result times of integration tests
     testReportDir = file("$buildDir/reports/tests/IT")
     testResultsDir = file("$buildDir/test-results/IT")

     //Jacoco section in IT tests is NOT required here. Why as it'll never generage a coverage report this way as this way of using jacoco section in integrationTest task is telling Gradle to use jacoco in Gradle JVM and for getting code coverage you have to run jacoco/jacocoagent.jar in Target JVM (which is Tomcat or similar) by introducing jacocoagent.jar and other parameters for jacoco to Tomcat using one of Tomcat's -Dxxx option (see Jacoco help on how to do this). As the following is not required, I'm commenting the following jacoco code(otherwise if used, it'll always give you 0% coverage).
        //jacoco {
           //This works with 1.6
           //  destPath = file("$buildDir/jacoco/IT/jacocoIT.exec")
           //  classDumpPath = file("$buildDir/jacoco/IT/classpathdumps")

           //Following works only with versions >= 1.7 version of Gradle
           //destinationFile = file("$buildDir/jacoco/IT/jacocoIT.exec")
           //  classDumpFile = file("$buildDir/jacoco/IT/classpathdumps")
        //}
  }


  jacocoTestReport {
      group = "Reporting"
      description = "Generate Jacoco coverage reports after running tests."
      ignoreFailures = true

      //Use any .exec file found before generating coverage report i.e. it'll give you combined coverage report if you have both jacocoUT.exec and jacocoIT.exec or other .exec files in build/jacoco/xx folders.
      executionData = fileTree(dir: 'build/jacoco', include: '**/*.exec')

      //executionData = files('build/jacoco/UT/jacocoUT.exec', 'build/jacoco/IT/jacocoIT.exec')
      //executionData = files(['build/jacoco/UT/jacocoUT.exec', 'build/jacoco/IT/jacocoIT.exec'])

      reports {
             xml{
                 enabled true
                 //Following value is a file
                 destination "${buildDir}/reports/jacoco/xml/jacoco.xml"
             }
             csv.enabled false
             html{
                 enabled true
                 //Following value is a folder
                 destination "${buildDir}/reports/jacoco/html"
             }
      }

      //The following is an example of using Gradle conventional way of saying where is my main source code directory
      //sourceDirectories = files(sourceSets.main.allJava.srcDirs)

      sourceDirectories = files(['src/java', 'src/groovy'])
      classDirectories =  files('build/classes/main')
  }

}

在 Project 的 build.gradle 中,如果您的项目结构与 Gradle 所说的不同,您可以定义 sourceSets 部分(带有实际值)。您定义依赖项(即,对于编译,您需要这个 .jar,对于测试或集成测试,您需要来自 main/test 等的这个 .jar 或 .class)。

然后,如果您运行 gradle clean build,您将在 build/jacoco/UT 文件夹下以 .exec 文件的形式获得 UT(单元测试)的 jacoco 代码覆盖率数据。在 build/reports/... html 文件夹下,您会找到 jacoco main index.html,它会显示代码覆盖率报告。

如果您想要来自非单元测试(即集成测试等)的代码覆盖率报告,请将以下参数附加到目标 JVM。我使用的是 Tomcat,所以我附上了它,其中 testType 是一个变量,我用它来查找/判断我是在运行 IT(集成测试)、AT(验收测试)还是 ST(Selenium GUI 测试)。

export PROJ_EXTRA_JVM_OPTS="-javaagent:tomcat/jacocoagent.jar=destfile=build/jacoco/${testType}/jacoco${testType}.exec,append=false"

在我使用的 startTomcat.sh 脚本中,您会注意到我正在使用上述变量并将其传递给 Tomcat 的 JVM,因为它是运行我项目的主要 .war/.ear 文件的 JVM我希望我的代码覆盖数据的源代码类文件使用集成/验收/其他非 UNIT 类型测试:

## Tomcat command - JDK 1.6/Tomcat 6.0
TOMCAT_CMD="$JAVA_HOME/bin/java $TOMCAT_JVM_ARGS \
$OPTIT_JVM_ARGS \

$JPROF_JVM_ARGS \
$PROJ_EXTRA_JVM_OPTS \
org.apache.catalina.startup.Bootstrap $TOMCAT_CFG_FILE_ARGS start"

上述变量需要存在于 Tomcat 启动脚本中,即当 Tomcat 启动时,它应该在其 JVM 会话中获取上述变量。完成此操作后,您必须运行“gradle integrationTest”然后停止您的 Tomcat 会话(然后它会将代码覆盖率数据刷新到 jacocoIT.exec 文件),然后如果您运行“gradle jacocoTestReport”,它将读取jacocoIT.exec 文件并为您的集成/接受/硒测试试图覆盖的主要源代码生成 jacoco 代码覆盖率报告。

【讨论】:

  • 感谢您的回复。我已经更新了原来的用途。
猜你喜欢
  • 1970-01-01
  • 2015-03-12
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-07-11
  • 2010-10-03
  • 1970-01-01
  • 2011-04-25
相关资源
最近更新 更多