How to include a FileTree in a test task

I am trying to change an existing project that uses Ant.junit to run junit tests inside a general task(not of type test). The task unzipz a jar and moves all the classes to a target directory, from which files to be tested are filtered into a FileTree. Then the Ant.junit() {…} runs the tests in the FileTree(filteredTests) using the following:

batchtest(todir: "${testDir}") {
      ant.filelist(dir: "${targetDir}/build", files: 
      filteredTests.join(","))
}

I am trying to remove the Ant.junit and use a gradle test task where I can include the fileTree. Is this possible?

Reason: I am doing this because I am trying to parallelize the tests using a set number of forks. Gradle test task has the maxParallelForks option.

Things tried:

  • I tried to change the task to a test task, but it does not like that. My understanding is the test task works only when the project structure is present and then the test names are enough to run them. But it behaves differently during the unzipping.
  • I have also tried to do the following :
task runFilteredTests(type: Test, description: 'Run filtered tests') {
  dependsOn "filterTests"
  doFirst {
    setTestClassesDirs(filteredTests) 
    filter {
      setIncludePatterns(filteredTests.getFiles() as String[])
    }
  }
}

without the setTestClassesDirs(filteredTests), it gives me an error that there is no source so it is skipping the task. But when I send in the FileTree into the method setTestClassesDirs(filteredTests). Intellij shows cannot infer argument types. I assumed it should work since FileTree extends FileCollection

Post on stack overflow with other details of what I am trying to do: https://stackoverflow.com/questions/57279384/running-junit-tests-from-a-filetree-in-gradle

Let’s say your xml was something like

<tests>
   <test>foo.bar.Test1</test>
   <test>foo.bar.Test2</test>
</tests>

You could do something like

task unzipTestClasses(type:Copy) {
   from zipTree('path/to/testClasses.zip')
   into "$buildDir/testClasses" 
}   
task runTestClasses(type:Test) {
   dependsOn 'unzipTestClasses' 
   testClassesDirs = "$buildDir/testClasses"  
   def root = new XmlSlurper().parse(file('path/to.xml'))
   include root.tests.test.collect { "${it.text().replace('.', '/')}.class" } 
}      
1 Like

Thing is I do not want to run all the tests that are mentioned in the testplan.
Currently the tests are filtered based on a property that is sent in:

 project.ant.properties['testplan.classes'] = "$buildDir/testClasses".toString()
       ant.importBuild "${targetTestDir}/testplans.xml"
       filteredTests = ant.references[testPlan].collect { it }

my guess is the ant.references filters the classes under the tag mentioned in the testPlan property. eg:

<files id=testPlantag1>  
       <file name="foo.bar.Test1"/>
       <file name="foo.bar.Test2">
</files>
<files id=testPlanTag2>
...
</files>

My initial thought was to see if there is a way to directly include the fileTree(filteredTests) that is generated here, in a separate test task. But may be I can just filter the classes and include it like you mentioned.

Don’t know what would be the correct syntax, but want to try something like this:

task runTestClasses(type:Test) {
   dependsOn 'unzipTestClasses' 
   testClassesDirs = "$buildDir/testClasses"  
   def root = new XmlSlurper().parse(file('path/to.xml'))
   root.findAll { r ->
   r.files['@id'].equals(${testPlan})
    }.each { r ->
   include r.files.file['@name'].collect { "${it.text().replace('.', '/')}.class" } 
   }
}

I’d just create a separate task for each <files> element

Eg:

def root = new XmlSlurper().parse(file('path/to.xml'))
root.files.each { el ->
   tasks.create("test${el.@id.capitalize()}", Test) {
      dependsOn 'unzipTestClasses' 
      include el.file.collect { "${it.@name.replace('.','/'}.class"} 
   } 
} 

Then you could run

gradle testTestPlantag1
1 Like

The thing is I am running this in Bamboo which calls a task say runEntireBuild() that depends on a bunch of tasks including the one that runs the tests.

task runEntireBuild() {
    dependsOn taskA
    dependsOn taskB
    dependsOn taskC
    dependsOn runTestClasses
    runTestClasses mustRunAfter taskC
}

The property for the testplan is passed in through the Bamboo config and based on that property the tests are filtered and run. In Bamboo Config the task runEntireBuild is called.

In your example, the test task will be created on the fly, but the runEntireBuild will not know which task to depend on. Can the dependsOn in runEntireBuild have the generated name? Also how would the name be used for the mustRunAfter

You can do

task runEntireBuild {...}
def root = new XmlSlurper().parse(file('path/to.xml'))
root.files.each { el ->
   tasks.create("test${el.@id.capitalize()}", Test) {
      dependsOn 'unzipTestClasses' 
      include el.file.collect { "${it.@name.replace('.','/'}.class"} 
   } 
   runEntireBuild.dependsOn "test${el.@id.capitalize()}"
} 
1 Like

I need the test task to run after all the other tasks in runEntireBuild. In your example it will run the tests before the setup tasks present in the runEntireBuild task.
So I need something like test${f.@id.capitalize()}.dependsOn “runEntireBuild” or a way to specify the test task in the runEntireBuild task like this:

task runEntireBuild() {
    dependsOn "taskA"
    dependsOn "taskB"
    dependsOn "taskC"
    dependsOn "test${f.@id.capitalize()}"
    test${f.@id.capitalize()} mustRunAfter taskC
}

Thanks :slight_smile: I’ll try these out

def root = new XmlSlurper().parse(file('path/to.xml'))
root.files.each { el ->
   tasks.create("test${el.@id.capitalize()}", Test) {
      dependsOn 'unzipTestClasses' 
      include el.file.collect { "${it.@name.replace('.','/'}.class"} 
      mustRunAfter 'taskC' 
   } 
} 
2 Likes

I had a couple issues:

  • My xml gets generated during the build process, so during the compile phase it cannot find the file and fails on that.

  • To avoid the previous issue, I tried to put that block of code inside a task to be executed during the execution phase:

      task createTasks << {    
         def root = new XmlSlurper().parse(file('path/to.xml'))
          root.files.each { el ->
             tasks.create("test${el.@id.capitalize()}", Test) {
                dependsOn 'unzipTestClasses' 
                include el.file.collect { "${it.@name.replace('.','/'}.class"} 
                mustRunAfter 'taskC' 
             } 
          }
      }
    

But that failed with the error that the task I was trying to run was not present in the root directory.

I am going to research and see when does the xml get created exactly and also try to find a way to register the task into a specific location(the root) if that is possible. Will update here if I have something.

Gradle has a configuration phase where tasks are created/initialized then an execution phase where they are executed. You can’t create extra tasks in the execution phase sorry. So back to the drawing board

1 Like

To start from scratch, I tried to just take the fileTree of the tests that need to be run and put that as a filter in the test task, just like I did in the post originally. What is the issue with that? Is there no way to run the tests in a fileTree in gradle?
filter {
setIncludePatterns(filteredTests.getFiles() as String)
}
Why does this not work?