Execute gradle task after test - gradle

After executing test task in Gradle build I want to always execute additional task which should have access to test result. Something like:
task('afterTest') {
doLast {
if (testSuccessful) {
//
} else {
//
}
}
}

There are two parts in your question: 1) make a custom task executed always after test task is executed, 2) make the test "result" available in this custom task.
1) First part is very simple, you just have to use the dedicated Task.finalizedBy method to create a "finalized by" dependency between test task and your custom task. (see Finalizer tasks)
2) Second part is a bit tricky, as there is no simple way provided by Gradle, as far as I know, to get the "result" (SUCCESS or FAILURE) of test task. But you could use API exposed by the Test Task to store the number of test in error into a variable and test this counter in your custom task : here is a working example:
ext{
// counter of test cases in error
nbTestOnError = 0
}
test {
// use "afterTest" hook to increment nbTestOnError counter
afterTest { desc , result ->
if (result.getResultType().equals(TestResult.ResultType.FAILURE)){
nbTestOnError++
}
}
}
task('afterTest') {
doLast {
// implement your logic depending on counter value
if (nbTestOnError > 0) {
// do something if test failed
} else{
// do something when all tests passed
}
}
}
// create "finalized by" dependency
test.finalizedBy afterTest
EDIT : based on important remark in comment from #lance-java : in order to support up-to-date check , you could configure your custom task to be "skipped" if test task is not executed. A simple way would be to use Task upToDateWhen feature (see here) :
task('afterTest') {
// consider this task "UP TO DATE" if `test` task did not execute
outputs.upToDateWhen {
!test.didWork
}
doLast {
//...
}
}

As I said in the other thread, it's best to use the file system to pass values from one task to another. This way the value will be available even if the test task was up to date and skipped. Eg:
test {
outputs.file "$buildDir/test.properties"
ext.errorCount = 0
afterTest { desc , result ->
if (result.resultType.name() == "FAILURE") {
errorCount++
}
}
doLast {
file("$buildDir/test.properties").text = "errorCount=$errorCount"
}
finalizedBy 'afterTest'
ignoreFailures = true
}
task afterTest {
dependsOn 'test'
inputs.file "$buildDir/test.properties"
doLast {
def props = new Properties()
props.load(file("$buildDir/test.properties"))
def errorCount = Integer.parseInt(props['errorCount'])
if (errorCount) {
// doStuff
throw new TaskExecutionException("$errorCount tests failed")
}
}
}

Building on the existing answers, use org.gradle.api.tasks.testing.Test.afterSuite to save the test result to a file:
test {
afterSuite { descriptor, result ->
if (descriptor.parent == null) {
file("$buildDir/test.result").text = result.resultType
}
}
}
afterTest.onlyIf {
file("$buildDir/test.result").text == 'SUCCESS'
}
where the afterTest task is executed only when the test suite succeeds.

Related

Executing task on its own is working but not when executed from another one

I have three Gradle tasks if I execute them one by one on its own then its working. But when I execute them from another task then its not working. Here is how my task looks like
import com.github.gradle.node.npm.task.NpmTask
plugins {
id("com.github.node-gradle.node") version "3.4.0"
}
// Executing this task on its own is working
tasks.register<NpmTask>("buildFrontEnd") {
workingDir.set(file("${projectDir}/frontend"))
args.set(listOf("run", "build"))
}
// Executing this task on its own is working
tasks.register<Delete>("cleanFrontEnd") {
delete(
fileTree("${projectDir}/backend/main/resources/static/js"),
)
}
// Executing this task on its own is working
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
copy {
from("${projectDir}/frontend/dist/css")
into("${projectDir}/backend/main/resources/static/css")
}
copy {
from("${projectDir}/frontend/dist/js")
into("${projectDir}/backend/main/resources/static/js")
}
}
// This tasks is not executing "copyFrontEnd"
tasks.register("frontEndBuild") {
dependsOn("buildFrontEnd", "cleanFrontEnd", "copyFrontEnd")
tasks.findByName("copyFrontEnd")?.mustRunAfter("buildFrontEnd", "cleanFrontEnd")
// Tried this too but it is not working
// tasks.findByName("cleanFrontEnd")?.mustRunAfter("buildFrontEnd")
// tasks.findByName("copyFrontEnd")?.mustRunAfter("cleanFrontEnd")
}
This is the output
> Task :cleanFrontEnd
> Task :copyFrontEnd NO-SOURCE
> Task :frontEndBuild
BUILD SUCCESSFUL in 20s
2 actionable tasks: 2 executed
For :copyFrontEnd it is saying NO-SOURCE but if that is the case then why its working when executing on its own? Is there anyway to fix this.
When you register a task, the bit inside the { } block configures the task. This block will always run whenever the task is required.
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
// these copy actions will ALWAYS run whenever Gradle configures this task
copy {
from("${projectDir}/frontend/dist/css")
into("${projectDir}/backend/main/resources/static/css")
}
copy {
from("${projectDir}/frontend/dist/js")
into("${projectDir}/backend/main/resources/static/js")
}
}
Because you've used register when creating the task, Gradle won't execute the task configuration block won't unless the task is required. But when it is, those copy actions will run immediately.
To add task actions, which will actually do the work of the task, add them using doLast { } or doFirst { }.
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
doLast {
copy {
from("${projectDir}/frontend/dist/css")
into("${projectDir}/backend/main/resources/static/css")
}
copy {
from("${projectDir}/frontend/dist/js")
into("${projectDir}/backend/main/resources/static/js")
}
}
}
Now those copy actions will only run when the task runs.
This still won't work however...
NO-SOURCE
Task did not need to execute its actions.
Task has inputs and outputs, but no sources
https://docs.gradle.org/current/userguide/more_about_tasks.html#sec:task_outcomes
When you register a Copy task, you need to specify what the source is.
tasks.register<Copy>("copyFrontEnd") {
into("$projectDir")
// from(...) // no from, no source - Gradle thinks "this task doesn't need to run"
}
In your case, you can specify the source as "${projectDir}/frontend/dist/css" and get rid of the two separate copy actions.
tasks.register<Copy>("copyFrontEnd") {
from("${projectDir}/frontend/dist/")
into("${projectDir}/backend/main/resources/static/css")
}
You might want to fiddle around with includes to only copy the css/ and js/ directories - https://docs.gradle.org/current/userguide/working_with_files.html#sec:copying_directories_example
Typesafe references
This last bit is unrelated, but I thought I'd include it as it can help make your buildscript clearer.
While you can reference task by their names, it's nicer to reference them by a variable. When you register a task, it returns a handle to that task.
You can use that handle to specify task dependencies.
val buildFrontEndTask: TaskProvider<NpmTask> = tasks.register<NpmTask>("buildFrontEnd") {
// ...
}
val cleanFrontEndTask: TaskProvider<Delete> = tasks.register<Delete>("cleanFrontEnd") {
// ...
}
val copyFrontEndTask: TaskProvider<Copy> = tasks.register<Copy>("copyFrontEnd") {
// ...
}
tasks.register("frontEndBuild") {
// here you can set the task dependencies based on the task providers
dependsOn(buildFrontEndTask, cleanFrontEndTask, copyFrontEndTask)
}

Gradle : use a task to disable another task

I have three gradle tasks (this is kotlin code inside a plugin):
project.task<Task>("checkNeeded") {
doLast {
if (someTest()) {
listOf("SomeCopy", "SomeAction")
.map { project.tasks[it] as AbstractTask }
.forEach { it.isEnabled = false }
}
}
}
project.task<Copy>("SomeCopy") {
dependsOn("checkNeeded")
from(wherever)
into(whatever)
}
project.task<Task>("SomeAction") {
dependsOn("checkNeeded")
doLast {
/* something magical */
}
}
So, both tasks SomeCopy and someAction depend on checkNeeded. The role of checkNeeded is to disable these two task if it finds that they are not needed.
However, this crashes with the following exception: Cannot call Task.setEnabled(boolean) on task ':SomeCopy' after task has started execution.
So, how can I have a task guaranteed to run before other tasks that can disable other tasks if they are not needed?
I ended up fixing this issue with onlyIf:
var needed by Delegates.notNull<Boolean>()
project.task<Task>("checkNeeded") {
doLast {
needed = !someTest()
}
}
project.task<Copy>("SomeCopy") {
dependsOn("checkNeeded")
onlyIf { needed }
from(wherever)
into(whatever)
}
project.task<Task>("SomeAction") {
dependsOn("checkNeeded")
onlyIf { needed }
doLast {
/* something magical */
}
}
Note that the checkNeeded task is not really necessary, but I like to have it to know, in the process, when the check is made. I could have done:
val needed by lazy { !someTest() }
project.task<Copy>("SomeCopy") {
onlyIf { needed }
from(wherever)
into(whatever)
}
project.task<Task>("SomeAction") {
onlyIf { needed }
doLast {
/* something magical */
}
}
The dependsOn does set the order. It may make sense to move the if outside the checkNeeded task and place it either in the build script (it will be executed in the configuration phase) or in the afterEvaluate {...} block (it will still be executed in the configuration phase, but later)
Another option is to execute checkNeeded task explicitly before running one of the SomeCopy or SomeAction

Execute gradle test task if another test task fails?

I am using TestNG and Gradle, what I am trying to achieve is, if the task that runs the tests fails, run another task which is also of type test which actually sends the test report. If all tests pass, don't do anything, I know about finalizedBy, but that sends the test report either way.
I tried something like this, but no luck.
task uiTest(type: Test) {
useTestNG() {
environment "DOMAIN", "${DOMAIN}"
useDefaultListeners = true
suites "src/test/resources/ui-tests/ThemeA/chrome67.xml"
}
reports {
html {
enabled true
}
reports.html.destination = "build/reports/TestReports/uiTestThemeA"
}
testLogging {
showStandardStreams = true
exceptionFormat "full"
events = ["started", "passed", "skipped", "failed"] //, "standard_out", "standard_error"]
showExceptions = true
showStackTraces = true
}
}
task testing(dependsOn: uiTest, type: Test) {
boolean state = uiTestThemeA.state.executed
if(!state) {
println name
useTestNG() {
suites "src/test/resources/ui-tests/sendReport.xml"
}
}
}
If I understand you correctly, then the following should do what you need. Replace your testing task with the following one and configure your uiTest task to be finalized by the testing task:
task testing(type: Test) {
onlyIf { uiTest.state.failure }
useTestNG() {
suites "src/test/resources/ui-tests/sendReport.xml"
}
}
uiTest {
finalizedBy testing
}
Some notes:
the onlyIf statement does the magic you are looking for
this assumes you execute uiTest

refer to Gradle task which is defined later in build

Our build has some generic logic to create tasks from configuration data. There are also several Gradle files on whose execution order I do not want to depend.
Now I need to add a task dependency to a task without knowing if that task will be defined before my part of the build script runs (at configuration time) or after my part.
[Edit: as #canillas writes I can say myTask.dependsOn "otherTask" and "lazily" depend on yet undefined "otherTask", but I need it the other way around.]
Since I can't write tasks["otherTask"].dependsOn myNewTask before "otherTask" is defined, I made the following helper function:
void safelyDoWithTask(String taskName, Closure func) {
def task = tasks.findByName(taskName)
if (task != null) {
func(task)
} else {
project.tasks.whenTaskAdded { task_ ->
if (taskName.equals(task_.getName())) {
func(task_)
}
}
}
}
safelyDoWithTask('otherTask', { it.dependsOn myNewTask })
Now I wonder if there is more canonical way to achieve this?
Consider the following:
// section 1
def dynamicDependencies = [:]
dynamicDependencies['otherTask'] = 'myNewTask'
// section 2
afterEvaluate { project ->
// taskA dependsOn taskB
dynamicDependencies.each { taskA, taskB ->
def task = project.tasks.findByName(taskA)
if (task) { task.dependsOn "${taskB}" }
}
}
// section 3
task myNewTask() {
doLast {
println 'myNewTask !'
}
}
task otherTask() {
doLast {
println 'otherTask !'
}
}
The gist is:
section 1 defines our dependency info in a custom map
section 2 uses the afterEvaluate hook to process the map
because the above is decoupled from the task definitions, we can simply put them in section 3 (or wherever)

useTestNG in gradle not picking dynamic suites being passed through project extention property

I have defined a test Task that uses the TestNG to run testcases and am passing a list of testNG suites file dynamically for it to run. However it does not seem to pick the file list dynamically.
task runTestNG(type: Test) {
doFirst {
if(project(':testNG').hasProperty("testJerseyFiles")) {
ext.ngFiles = project.property("testJerseyFiles")
List testFiles = ngFiles as List
if(testFiles != null && testFiles.size() != 0 ) {
println "++++++++If : runTestNG++++++++++++++++++++++++++++"
testFiles.each { test ->
println "${test}"
}
}
} else {
println "++++++++Else : runTestNG++++++++++++++++++++++++++++"
}
}
useTestNG() {
if(project(':testNG').hasProperty("testJerseyFiles")) {
ext.ngFiles = project.property("testJerseyFiles")
List testFiles = ngFiles as List
if(testFiles != null && testFiles.size() != 0 ) {
listeners << 'org.testng.reporters.XMLReporter'
ignoreFailures = true
testFiles.each { test ->
println "******* ${test} ***************"
suites new File ("${test}")
//options.suites(new File ("${test}"))
}
}
} else {
// println "++++++++Else : runTestNG++++++++++++++++++++++++++++"
}
}
When I run this test from another task which is setting the property project(':testNG').ext.testJerseyFiles than I get the following
++++++++If : runTestNG++++++++++++++++++++++++++++
output/suiteDir/Jersey_setNo1.xml
Running test: test method init(test1.Test1)
However the setNo1.xml has a list of a completely different class. It does not seem to pick the file in the "useTestNG" suite option however it is clearly printing out the variable in the "doFirst" closure. Not sure what I am doing wrong.
The more research I do it seems that the suite parameter for the useTestNG is setup at configuration time and since the project.ext.property is being set at execution time it always find the ".hasProperty" to be empty and skips the if clause. Which causes it to go to the test list whatever it finds in the classpath.
Any way to make this suite pick up a list of suite dynamically?

Resources