1

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
Jean-François Fabre
  • 137,073
  • 23
  • 153
  • 219
AKS
  • 16,482
  • 43
  • 166
  • 258
  • can you run Gradle 1.6 + Java7 with the "-i" flag enabled. that tells you why gradle runs this task and does consider the test task not to be up-to-date. I guess there were changes made – Rene Groeschke Jul 08 '15 at 04:17
  • I ran "rm -fr .gradle" (to remove any local gradle cache), ran "gradle clean". Now, I ran gradle test -i (for showing --info). It shows: :compileTestJava Skipping task ':compileTestJava' as it has no source files.:compileTestJava UP-TO-DATE. Then, :processTestResources Executing task ':processTestResources' due to: No history is available. Then, :testClasses Skipping task ':testClasses' as it has no actions. Then, :test file or directory '/production/home/c400093/giga/OCMPayerOnline/build/classes/test', not found – AKS Jul 08 '15 at 04:43
  • Executing task ':test' due to: No history is available. file or directory '/my/workspace/project/build/classes/test', not found which is as expected. – AKS Jul 08 '15 at 04:46
  • ah I see. does it help if you do add this to your build: `test {doFirst { file("build/classes/test").mkdirs()}}` – Rene Groeschke Jul 08 '15 at 04:47
  • Yea, I agree with you as why compileTestJava says UP-TO-DATE (which is expected as I don't have any test code) but test task still runs (without showing UP-TO-DATE) with Gradle1.6+Java7. I pasted the reason above with -i output – AKS Jul 08 '15 at 04:47
  • @ReneGroeschke I added that piece of code in doFirst to create build/classes/test folder ... but it still says this with Gradle 2.3+Java8 with -i option: reason for not running test task is "no source files" ====> :test (Thread[main,5,main]) started. :test file or directory '/my/workspace/project/build/classes/test', not found Skipping task ':test' as it has no source files. :test UP-TO-DATE :test (Thread[main,5,main]) completed. Took 0.002 secs. => AS IT DIDN"T RUN test task / skipped it totally, it didn't create build/classes/test folder at all. Wondering if doFirst would be called or not. – AKS Jul 08 '15 at 05:06
  • @ReneGroeschke with Gradle 1.6+Java7 ... test task says it's gonna execute test task anyway ... reason is: -----> Executing task ':test' due to: No history is available. ........i.e. NO HISTORY is available. Any idea why this is not happening when I'm using Gradle2.3 + Java8? – AKS Jul 08 '15 at 05:21
  • my fault. creating the desired directory in the test task is to late. only workaround I see at the moment is to ensure this directory is created in a separate task. – Rene Groeschke Jul 08 '15 at 05:25
  • I did compileJava { doLast { file("build/classes/test").mkdir() } } --- it created the build/classes/test folder, but :test task still says ... UP-TO-DATE (as it's finding the main reason as "NO SOURCE FILES" in test source code folder. IF it will detect/get that "No History available" message like we got in Gradle 1.6, then I guess it will work fine). Even If I create a blank.java file (containing nothing in it).. :test task still shows -- UP-TO-DATE (no source files ... as it seems like Gradle 2.3 is intelligent enought to find that blank .java files are NULL thus no source code files). – AKS Jul 08 '15 at 05:37

2 Answers2

0

you can force the test task to be executed no matter what the status of inputs and outputs are:

test{
    outputs.upToDateWhen{false}
}

for earlier gradle versions you can ensure the class directory exists by

task createTestClassesDir << {sourceSets.test.output.classesDir.mkdirs()}
test.dependsOn createTestClassesDir
Rene Groeschke
  • 27,999
  • 10
  • 69
  • 78
  • Thx Rene. I tried that, it didn't work with Gradle 2.3 + Java8. All this says is, no matter what, just run the task (test). As with Gradle 2.3, running test task (after a clean) already shows UP-TO-DATE. I'll post the output of -i option that you wanted. I just updated my question with --profile output from both Gradle 1.6+Java7 ..and Gradle 2.3+Java8. I'm trying to get Gradle2.3 + Java8 to call those :testRuntime and :jacocoAgent dependencies... so that it can create that build/tmp/exapandedArchives/org.jacoco... folder containing the file. – AKS Jul 08 '15 at 04:26
  • I tried the above createTestClassDir workaround, it created build/classes/test folder, but :test task still says: Skipping task ':test' as it has no source files. :test UP-TO-DATE :test ...Took 0.001 secs. – AKS Jul 08 '15 at 05:45
  • I have a solution, I'll share as soon it's verified. As in Gradle 2.3, we must have valid source test java / groovy code for ":test" task to actually run (which will also create jacocoagent.jar in build/tmp/expandedArchives/org.jacoco.... folder provided you have jacoco { .. } section defined in test task), the idea is, we need to create a valid DummyTest.java file inside src/test/java folder, create src/test/java folder in case it doesn exist otherwise, just create the dummy java file inside compileJava { doLast { ..section.. } } , then in test { doLast { ...section ..} }, we need to do more. – AKS Jul 09 '15 at 18:05
  • In test { ... doLast {... section ... } }, we have to delete DummyTest.java file, delete src/test/java folder if it was created by our workaround solution (using a FLAG variable would help otherwise, we won't delete this folder), then delete build/classes/test folder and finally build/jacoco/ jacoco.exec file for Unit tests if any (depending upon the FLAG if reqd). This way, test task will run for sure for any project which don't have any valid java file inside src/test/java or don't even have src/test/java at all. In my case, we create jacocoUT.exe (for Unit tests) inside build/jacoco/UT dir. – AKS Jul 09 '15 at 18:07
0

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"
         }
     }
   }
AKS
  • 16,482
  • 43
  • 166
  • 258