How to re-run failed TestNG tests using Gradle - gradle

I know what TestNG generates testng-failed.xml with information about failed test.
I'd like to use it in Gradle to created new task for running failed tests:
task secondTry(type: Test) {
onlyIf {
file("build/reports/tests/test/testng-failed.xml").exists()
}
println file("build/reports/tests/test/testng-failed.xml").exists()
testClassesDir = sourceSets.test.output.classesDir
classpath = sourceSets.test.runtimeClasspath
useTestNG() {
suites("build/reports/tests/test/testng-failed.xml")
}
}
Aftrer running task secondTry I got true for println file("build/reports/tests/test/testng-failed.xml").exists(), but failed tests are not run and task is skipped: secondTry SKIPPED
Does anyone experience in such cases, how to make tests run?
UPDATE:
I've tried to modify task a bit, so just run testng-failed.xml:
task secondTry(type: Test) {
useTestNG() {
useDefaultListeners = true
reports.html.enabled = false
options.suites("build/reports/tests/test/testng-failed.xml")
}
}
As result, build is successfully executed, including secondTry task, but failed tests still are not run.

This can be achieved by a TestNG's feature called RetryAnalyzer. With few little tweaks, we can tell TestNG that how many times a test should get retried until it gets passed and what kind of exceptions/errors would trigger the retrying of the failed test methods.

The idiomatic way of doing this in TestNG is to implement the retry method in IRetryAnalyzer interface and then annotate the required test methods or class that you expect flaky tests to be in. TestNG will automatically run them again as part of your test suite. You can refer to the TestNG docs for this.
Here is a working example of this in Kotlin language, Here the failed test methods would be run again until they meet the specified criteria.
RerunTests.kt
import framework.core.retry.FailureRunner
import org.testng.Assert
import org.testng.annotations.Test
#Test(groups = ["rerun"], retryAnalyzer = FailureRunner::class)
class RerunTests {
fun foobar() {
Assert.assertFalse(true)
}
fun foo() {
Assert.assertEquals(2, 2)
}
}
And here is a sample implementation of a class that implements the required interface
FailureRunner.kt
import org.testng.IRetryAnalyzer
import org.testng.ITestResult
import org.testng.Reporter
class FailureRunner : IRetryAnalyzer {
private var retryCounter = 0
private var maxRetryCount = System.getProperty("maxFailureRetries").toInt()
override fun retry(result: ITestResult?): Boolean {
val testName = result?.testName
return if (retryCounter < maxRetryCount) {
Reporter.log("Current retry count <$retryCounter> is less than the max no of failure retries <$maxRetryCount>")
Reporter.log("Retrying $testName")
++retryCounter
true
} else {
Reporter.log("Retry count exceeded max limit for $testName.")
false
}
}
}

The Test Retry Gradle plugin is designed to retry failed tests, and works with TestNG, JUnit 4 & 5, and Spock. It will rerun each failed test a certain number of times, with the option of failing the build if too many failures have occurred overall.
plugins {
id 'org.gradle.test-retry' version '1.2.1'
}
test {
retry {
maxRetries = 3
maxFailures = 20 // Optional attribute
}
}

Related

Rerun runnerclass is running before the original runner class in cucumber

CucumberSerenityBDDAPIAutoRunner
CucumberSerenityBDDUIAutoRunner
CucumberSerenityBDDUIReRunner
Are my runner classes lying in one folder. I have chosen alphabetical increasing order to run the rerunner class at the end.
#RunWith(CucumberWithSerenity.class)
#CucumberOptions(features= “src/test/resources/features/ui/“, glue = “com.xxxx.xxxx.xxxx.features”, plugin = {“pretty”,“rerun:target/failedrerun.txt”}, monochrome = true, tags = “#envr=stagging and #UI”)
public class CucumberSerenityBDDUIAutoRunner {
#WithTag(“envr:stagging”)
public void run_stagging_tests(){}
#WithTag(“envr:prod”)
public void run_production_tests(){}
}
#RunWith(CucumberWithSerenity.class)
#CucumberOptions(features= {“#target/failedrerun.txt”}, glue = “com.xxxx.xxxx.xxxx.features”, plugin = “pretty”, monochrome = true, tags = “#envr=stagging and #UI”)
public class CucumberSerenityBDDUIReRunner {
#WithTag(“envr:stagging”)
public void run_stagging_tests(){}
#WithTag(“envr:prod”)
public void run_production_tests(){}
}
These are my two concreate runner classes.
Error which I am receing from the gradle run is below since the rerunner is running before the original runner:
com.xxxx.xxxx.xxxx.features.CucumberSerenityBDDUIReRunner > initializationError FAILED
io.cucumber.core.exception.CucumberException: Failed to parse ‘target/failedrerun.txt’
How can i choose the correct sequence?
As a workaround I have created a seperate gradle task to run the rerunner classes at the end and removed the CucumberSerenityBDDUIReRunner class from source.
configurations {
cucumberRuntime {
extendsFrom implementation
}
}
task reRun(type: JavaExec, dependsOn:testClasses) {
systemProperties System.getProperties()
systemProperty "cucumber.options", System.getProperty("cucumber.options")
mainClass = "net.serenitybdd.cucumber.cli.Main"
classpath = configurations.cucumberRuntime + sourceSets.main.output + sourceSets.test.output
args += [
'--plugin', 'pretty',
'#target/failedrerun.txt',
'--glue', 'com.abc.def.ijk.features']
}
Now I run gradle --info clean test reRun aggregate -Denvironment='stagging' -Dtags='envr:stagging' -Dcucumber.options='--tags #envr=stagging' from commandLine.
This time the
CucumberSerenityBDDAPIAutoRunner
and
CucumberSerenityBDDUIAutoRunner
Classes as a part of test task and rerun will run the failed test cases link generated in #target/failedrerun.txt file.
Serenity report only takes the latest test case run in account( i.e
the rerun one if it has run as a part of reRun task)
P.S: we can put after reRun task in build.gradle file
tasks.test {
finalizedBy reRun
}
then the old gradle --info clean test aggregate -Denvironment='stagging' -Dtags='envr:stagging' -Dcucumber.options='--tags #envr=stagging' command will also work.

Generate Jacoco report for integration tests

I've created a new test suite called integrationTest using the jvm-test-suite plugin.
Now I want to generate a jacoco report just for the integration tests.
I create a new gradle task like this:
tasks.create<JacocoReport>("jacocoIntegrationTestReport") {
group = "verification"
description = "Generates code coverage report for the integrationTest task."
executionData.setFrom(fileTree(buildDir).include("/jacoco/integrationTest.exec"))
reports {
xml.required.set(true)
html.required.set(true)
}
}
But the generated HTML/XML report is empty. I have run the integration tests before executing the task and the file integrationTest.exec exists.
Thanks
It seems the important part of the new JaCoCo report task configuration is to wire the execution data via the integrationTest task instead of the exec-file path. The official docs (see last example here) also imply that the source set must be wired as well.
Here is the full build script (Gradle 7.6) that produces a report with command:
./gradlew :app:integrationTest :app:jacocoIntegrationTestReport
// build.gradle.kts
plugins {
application
jacoco
}
repositories {
mavenCentral()
}
testing {
suites {
val integrationTest by creating(JvmTestSuite::class) {
dependencies {
implementation(project(":app"))
}
}
}
}
tasks.register<JacocoReport>("jacocoIntegrationTestReport") {
executionData(tasks.named("integrationTest").get())
sourceSets(sourceSets["integrationTest"])
reports {
xml.required.set(true)
html.required.set(true)
}
}

How to get Gradle to fail tests on certain console output

I am trying to configure Gradle to fail a test upon detecting a particular console output, but only if that test did not already fail.
For better context, I am trying to fail a test whenever the word "LEAK" is logged to the console, which Netty will automatically log.
My current solution looks like this:
ext {
testsWithLeak = [:]
}
test {
useJUnitPlatform()
onOutput { test, output ->
if (output.message.contains("LEAK")) {
testsWithLeak[test] = output.message
}
}
afterTest { test, result ->
if (result.getResultType().equals(TestResult.ResultType.FAILURE)) {
testsWithLeak.remove(test);
}
}
finalizedBy 'checkLeaks'
}
task checkLeaks {
dependsOn 'test'
doLast {
testsWithLeak.each{entry ->
throw new GradleException("ERROR: $entry.key produced leak: $entry.value")
}
}
}
This mostly works, and will correctly fail a build upon detecting a "LEAK" message. The problem is that since the test itself is considered a SUCCESS, it appears to be cached and not run again of subsequent builds. I tried calling cleanTest as part of throwing a GradleException, but that did not help.
Note that I use the testsWithLeak variable because I only want to throw this exception if the test did not already fail. A test may fail due to other assertions in which case I do not want to check for leaks (as the GradleException may mask the underlying assertion failure).
Also note that throwing the GradleException as part of the afterTest closure does not appear to fail the build.
How can I:
Fail a build when a test logs this message
Only throw this exception when the test did not otherwise fail
Ensure the build will correctly fail on subsequent attempts (without relying on commandline options)
The test is considered up to date because the test inputs (the class files) and the test outputs (the xml report) have not changed since the previous run. You can do the following so that tests are never up to date
test {
outputs.upToDateWhen {false}
...
}
See
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskInputs.html
https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/TaskOutputs.html
Your testsWithLeak map will not persist between gradle invocations. I suggest you write to a file under $buildDir and add a TaskOutputs.upToDateWhen {...} predicate to check the file exists. Your checkLeaks tasks should also be driven by the file and not a project variable.
Eg:
test {
ext {
testsWithLeak = new java.util.Properties()
leakFile = file("$buildDir/testsWithLeak.props")
}
outputs.upToDateWhen {!leakFile.exists()}
onOutput {...}
afterTest {...}
doFirst {
delete leakFile
}
doLast {
if (testsWithLeak) {
leakFile.withWriter { testsWithLeak.store(it)}
}
}
}

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

Grouping JUnit tests with Gradle

I created a new Groovy project of integration tests and want to be able to be able to group tests together for different components. For example, some tests are only valid for a docker image, while others are for testing a webapp running locally. I'd also like to get reports for the results of the tests and also be able to run the tests over and over if needed.
The first step to grouping tests in JUnit is to create an interface to name the group. Create this in a package your test classes can reach.
If your Test Class looks like this:
package com.example.test.api.get.cert
class ByIssuedBeforeTest {
//awesome tests
}
Then create an empty interface like this:
package com.example.test
interface DockerTest {}
Now change your Test Class to look like this:
package com.example.test.api.get.cert
import org.junit.experimental.categories.Category
#Category(DockerTest.class)
class ByIssuedBeforeTest {
//awesome tests
}
You can also annotate individual tests instead of the whole class.
Now in your build.gradle file:
apply plugin: 'java'
sourceSets {
main {
java {
srcDirs = ["src/main/java", "src/main/groovy"]
}
}
test {
java {
srcDirs = ["src/test/java", "src/test/groovy"]
}
}
}
test {
outputs.upToDateWhen { false }
reports {
junitXml.enabled=true
html.enabled=true
}
}
task nondockerTest(type: Test) {
outputs.upToDateWhen { false }
useJUnit {
excludeCategories 'com.example.test.DockerTest'
}
}
task dockerTest(type: Test) {
outputs.upToDateWhen { false }
useJUnit {
includeCategories 'com.example.test.DockerTest'
}
}
The java plugin for gradle gives you the test task. In the test task we add the outputs line in order to keep the test task from ever being labeled as "UP-TO-DATE", in essence forcing gradle to always run the task when we call it instead of caching the result.
The report block enables JUnit report files to be created. They will be placed in the build/reports/tests directory. If you ran the nonDockerTest it would be build/reports/tests/nondockerTest/index.html.
The useJUnit block tells Gradle to use JUnit to run the tests as well as which category of tests to run (if using includeCategories) or category to not run (if using excludeCategories).
gradle test to run all the test.
gradle nondockerTest to run the tests not labeled as with the DockerTest category.
gradle dockerTest to run just tests labeled with the DockerTest category.
Each of these tasks will create reports in the /build/reports/tests directory.

Resources