Search code examples
unit-testinggradlejacocogradle-2

Gradle test task - Gradle 1.6 and with Gradle 2.3 or later


I have a Java project.

PS: In my project, I don't have any java program/source code in src/test/java.
- This folder just contains a blank.txt file.

I have two different Gradle versions:

  1. Gradle 1.6 with Java 7 (as Java 8 is NOT compatible with Gradle 1.6 or any version < less than 1.10 version if I'm correct).

  2. The other version is: Gradle 2.3 with Java 8.

Using both of the above mentioned Gradle 1.6 + Java7 OR Gradle 2.3 + Java 8 versions my project build successfully.

Though, I noticed one thing: That while running the build, it calls "test" task automatically (as per the Gradle design, test task runs for free); I found during Gradle 1.6 + Java7 run --- I see the following output.

:jar
:assemble
:compileTestJava UP-TO-DATE
:processTestResources
:testClasses
:test
:check

As you'll notice, it says I don't have any test source code (i.e. src/test/java doesn't contain any source code OR there's nothing new for Gradle to compile this time may be nothing changed since last time gradle ran the build) and that's why compileTestJava task is showing UP-TO-DATE in front of it.

But, :test task is showing that it ran successfully. I have used jacoco (code coverage) section within test { .. } task, then it actually ran that part (as there is no UP-TO-DATE in front of test task). Jacoco section is NOT defined in my project's build.gradle but actually it's coming from a top level / GRADLE_HOME/init.d/some-common-top-level.gradle file (where test { ... has jacoco { ... } .. } section in it).

As I mentioned above, test task didn't say UP-TO-DATE, therefore, after Gradle build process was complete, I can see it created the following folder/files structure inside build/tmp/expandedArchives/org.jacoco.... folder:

$ ls -ltr build/tmp/expandedArchives/
total 4
drwxr-xr-x+ 1 e020001 Domain Users 0 Jul  7 20:45 org.jacoco.agent-0.7.2.201409121644.jar_778m6tp3jrtvcetasufl59dmau

$ ls -ltr build/tmp/expandedArchives/org.jacoco.agent-0.7.2.201409121644.jar_778m6tp3jrtvcetasufl59dmau/
total 272
drwxr-xr-x+ 1 e020001 Domain Users      0 Jul  7 20:58 META-INF
-rwxr-xr-x  1 e020001 Domain Users   2652 Jul  7 20:58 about.html
-rwxr-xr-x  1 e020001 Domain Users 272311 Jul  7 20:58 jacocoagent.jar
drwxr-xr-x+ 1 e020001 Domain Users      0 Jul  7 20:58 org

The same is NOT happening when I'm running Gradle 2.3 and Java8.

Build is successful but I'm not getting build/tmp/expandedArchives/org.jacoco.... folder containing jacocoagent.jar file.

Any idea, why Gradle 2.3 is not creating this jacoco specific .jar file.

With Gradle 2.3+Java8, the following output shows UP-TO-DATE in front of both :compileTestJava and :test tasks (which was not the case with Gradle 1.6 for test task).

I ran "gradle clean build".

:compileTestJava UP-TO-DATE
:compileTestGroovy UP-TO-DATE
:processTestResources
:testClasses
:test UP-TO-DATE
:check

I need Gradle 2.3 to generate this jacocoagent.jar under build/tmp/expandedArchives/org.jacoco..... folder so that I can use it in a downstream Jenkins job (which runs non-Unit tests) as this project does have some Integration tests and I'm fetching the jacocoagent.jar from the parent main build job (which runs gradle clean build including test task) in downstream job so that I can pass it to TOMCAT JVM while starting Tomcat (so that I can get jacocoIT.exec code coverage for IT tests). But, after I switched to Gradle 2.3, all projects where I don't have src/test/java ... now jacocoagent.jar is not getting created and the copy artifact plugin fails while trying to copy the .jar file from parent job.

One more point:
With Gradle 1.6 + Java7, if I run gradle clean build, it successfully creates that jacocoagent.jar inside build/tmp/expandedArchives/org.jacoco..... folder but it works this way, only when I run gradle clean build or "gradle clean; gradle test".

If I run gradle clean build, and then remove build/tmp folder, and now just run: gradle test, it shows me UP-TO-DATE in front of both :compileTestJava and :test tasks and doesn't create build/tmp/expandedArchives/org.jacoco.... folder containing jacocoagent.jar file.

For more info, I'm attaching the profile run (i.e. using --profile option) while running gradle test task for Gradle 1.6 + java 7.

I see that, in the profile html file that when test task is run, it first calls compileJava as per Gradle process logic and then test task and it's also calling depedencies --- :jacocoAgent (as per the dependency resolution tab): enter image description here



But,

with Gradle 2.3 + Java8, the dependency Resolution / order and Task execution step is not same (or in the order as compared to Gradle 1.6) for generating or showing any reference to jacocoAgent dependency as it's not even calling it. enter image description here



Running Gradle1.6 +Java7 test task with -i (or --info) option shows why it ran test task even though I had no test source code, see the reason why:

Note: Recompile with -Xlint:unchecked for details.
:processResources
Skipping task ':processResources' as it has no source files.
:processResources UP-TO-DATE
:classes
Skipping task ':classes' as it has no actions.
:compileTestJava
Skipping task ':compileTestJava' as it has no source files.
:compileTestJava UP-TO-DATE
:processTestResources
Executing task ':processTestResources' due to:
  No history is available.
:testClasses
Skipping task ':testClasses' as it has no actions.
:test
file or directory '/my/workspace/project/build/classes/test', not found
Executing task ':test' due to:
  No history is available.
file or directory '/my/workspace/project/build/classes/test', not found
Finished generating test XML results (0.001 secs)
Generating HTML test report...
Finished generating test html results (0.012 secs)

BUILD SUCCESSFUL

Solution

  • Summary:

    With Gradle 2.3, if there are no valid .java/.groovy (or etc) test code, then test task won't even run and thus there'll be no jacocoagent.jar created somewhere deep in build/tmp/exapandedArchives/org.jacoco.xxx.... folder.

    Solution was to include the following (in top level $GRADLE_HOME/init.d/some-global-file.gradle) inside allprojects { .... } section. All we are doing is, if src/test/java (standard) or any legacy folder structure (src/java if your project structure is like this) doesn't have any valid test source code, then we can add a dummy test file (DummyTestXYZ.java or groovy) and let test task run which will generate jacocoagent.jar (which we can use / tie in Tomcat options for generating jacoco report for non-unit aka integration tests). This way, if your main build job calls a downstream/child job to run your IT tests, it won't fail as it can fetch jacocoagent.jar (from main build job's workspace) as test task will create jacocoagent.jar in build/tmp/expandedArchives/org.jacoco.xx.x.xx..x folder (that you can get using Copy Artifact plugin in Jenkins).

    PS: Change the if statement logic acc. to your own folder setup i.e. in which folder you'd want to create the DummyTestXYZ.java file. In our case, all new projects were using src/test/java (standard folder structure as per Maven/Gradle standard) and during the new project creation, we are adding valid sample unit tests checked-in to the source control. Thus, in the logic below, we are actually ignoring to create this DummyTestXYZ.java in case src/test/java exists and creating this file only if src/test/java folder doesn't exist in the project (i.e. this is a project which has legacy folder structure) + test/java (legacy folder for storing JUnit unit tests) has no .java programs and/or if test/java doesn't exist then create it first and then create the dummy file. I know, we could have uploaded jacocoagent.jar at some location on Jenkins server and use that file while starting Tomcat for getting code coverage for IT tests. The dummy test file we added requires junit:junit:4.10 or 4.11 library version for the :compileTestJava task to succeed.

       compileJava {
        doLast {
            def dirName = "${projectDir}/test/java"
    
            if(!file( "${projectDir}/src/test/java" ).exists()) 
                if(!file( dirName ).exists()) 
                    new File( dirName ).mkdirs()
    
            if(file( dirName ).exists()) {
                def javaCnt = new FileNameByRegexFinder().getFileNames(dirName, /.*\.java/).size()
                if(javaCnt == 0) {
                    def f = new File( dirName , 'DummyTestXYZ.java' )
                    def w = f.newPrintWriter()
                    w.println('import org.junit.Test;')
                    w.println('')
                    w.println('public class DummyTestXYZ {')
                    w.println('@Test' )
                    w.println('public void test() {')
                    w.println('}')
                    w.println('}')
                    w.close()
                }
            }
        }
       }
    
       test {
         doFirst {
                  testResultsDirName = "test-results/UT"
                  testReportDirName = "tests/UT"
         }         
         maxParallelForks = 5
         forkEvery = 50
         //ignoreFailures = true
    
         // Following Jacoco section is required only in Jenkins
         // But a developer can uncomment them if they want this feature to work for their 
         // Desktop local Gradle builds.
         jacoco {
                //Following vars works only with versions >= 1.7 version of Gradle
                destinationFile = file("$buildDir/jacoco/UT/jacocoUT.exec")
         }
    
         doLast {
            if (file("${projectDir}/test/java/DummyTestXYZ.java").exists()) {
                println "++"
                println "++"
                println "++"
                println "======================================================="
                println "DEV Team – Please add valid Unit tests in this project."
                println "======================================================="
                println "++"
                println "++"
                println "++"
                sleep(30 * 1000)
                new File("${projectDir}/build/classes/test").deleteDir()
                new File("${buildDir}/jacoco/UT").deleteDir()
                new File("${buildDir}/test-results/UT").deleteDir()
                delete "${projectDir}/test/java/DummyTestXYZ.java"
             }
    
    
         }
       }
       //Do the same (as above test code) for any other similar test tasks like integartionTest, acceptanceTest etc..
    
      jacocoTestReport {
         //cleaning any compile time generated (for ex: JiBx classes files) so that jacoco task won't fail for not finding the actual source files (.java/.groovy for the compile time generated .class files)
         doFirst {
             delete fileTree (dir: "${buildDir}/classes", include: "**/JiBX_*.class")
         }
    
         group = "Reporting"
         description = "Generate Jacoco coverage reports after running tests."
         //ignoreFailures = true
    
         executionData = fileTree(dir: 'build/jacoco', include: '**/*.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"
           }
         }
    
         sourceDirectories = files(['src/java','src/main/java', 'src/main/groovy'])
         classDirectories =  files('build/classes/main')
    
         doLast {
            if (file("${projectDir}/test/java/DummyTestXYZ.java").exists()) {
                delete "${projectDir}/test/java/DummyTestXYZ.java"
             }
         }
       }