Run gradle test from another gradle task - spring

I created Spring Boot project which uses gradle build system. I want to run one separate test class by custom gradle task to be able depend on it in other tasks. Now I can do it with this code:
import org.apache.tools.ant.taskdefs.condition.Os
def gradleWrapper = Os.isFamily(Os.FAMILY_WINDOWS) ? 'gradlew.bat' : './gradlew'
task runMyTest(type: Exec) {
workingDir "$rootDir"
commandLine gradleWrapper, ':test', '--tests', 'com.example.MyTest'
}
Obviously, this is not a very beautiful solution, because it launches an additional Gradle daemon. I tried before another solution:
task runMyTest(type: Test, dependsOn: testClasses) {
include 'com.example.MyTest'
}
But it is not working (do not execute my test class).
UPD: I tried yet another solution:
task runMyTest(type: Test) {
filter {
includeTestsMatching "com.example.MyTest"
}
}
It fails with this error message:
Execution failed for task ':runMyTest'.
> No tests found for given includes: [com.example.MyTest](filter.includeTestsMatching)
However, obviously, my test exists, since running the test through the command line produces the correct result.
UPD2: I missed useJUnitPlatform() inside my test task. It was in the default test task (written to my build.gradle by Spring Boot initializer), but not in the custom task.

You can do it using a TestFilter.
Using includeTestsMatching you can specify your class.
If you need to specify a single test method, you can use includeTest "com.example.MyTest", "someTestMethod".
task runMyTest(type: Test) {
useJUnitPlatform()
filter {
includeTestsMatching "com.example.MyTest"
}
}

Related

Gradle execute task while another task is running

I'm working in a spring boot project to automate integration tests with gradle. I started working recently in a new enterprise, and my colleagues run integration tests as follows:
In the build.gradle file there is an integrationTest task
sourceSets {
integrationTest {
java {
compileClasspath = test.output + main.output + compileClasspath
runtimeClasspath = test.output + main.output + runtimeClasspath
}
resources.srcDir file('src/test/resources')
}
}
configurations {
mapstruct
integrationTestCompile.extendsFrom testCompile
integrationTestRuntime.extendsFrom testRuntime
}
compileJava {
options.compilerArgs = [
'-Amapstruct.defaultComponentModel=spring'
]
}
test {
ignoreFailures = true
}
task integrationTest(type: JavaExec) {
classpath = sourceSets.integrationTest.runtimeClasspath
}
After launching the task the application starts running in a designated port, then they open postman, import a collection and run the tests.
My job is to find a way to skip the extra clicks, i.e. run the postman collections automatically.
The first idea was to use the postman-runner gradle plugin but I cannot add it to the project due to connectivity issues of enterprise computers.
The second idea which I am currently working on, is to run newman on a powershell script and save the output.
The problem is that in gradle you can execute a task once another task is finished, but the integrationTest task never finishes. It launches the application in a port and keeps listening for requests. Is there a way to run the other task, which executes the powershell script, after the application has started running on the port and while it is waiting for requests ?
Thank you!
Simple answer: By design, it is not possible in Gradle to execute a task while another task is still running.
However, there might still be a solution for your problem. A task in Gradle is just a concept of something that needs to be done as a part of your build. But it does not necessarily represent a single process. Gradle might run a process using two tasks, one to start the process and another one to stop the process. The following Gradle "pseudo-code" shows an example of this idea.
def process
task start {
doFirst {
process = startProcess()
}
finalizedBy 'stop'
}
task stop {
dependsOn 'start'
doFirst {
process.stop()
}
}
This example even uses Gradle functionality to ensure that each time start is executed, stop will be executed later on and vice-versa. The Gradle Docker plugin uses a similar concept with its task types DockerStartContainer and DockerStopContainer.
Regarding your use case, you could use one task to start the application listening to a port, another task that runs the actual tests (using Postman) and another task that stops the application once the tests are finished:
task startApp {
doFirst {
println 'Starting app'
}
finalizedBy 'stopApp'
}
task integrationTest {
doFirst {
println 'Running integration tests'
}
dependsOn 'startApp'
finalizedBy 'stopApp'
}
task stopApp {
doFirst {
println 'Stopping app'
}
dependsOn 'startApp'
}
This second example is a valid build.gradle file that may be used as a template. When running gradle integrationTest, you will see that the tasks are executed in the correct order. Now all that is missing is the actual functionality of the tasks. Sadly, the JavaExec task type provided by Gradle does not support this kind of async execution, so you might need a third-party plugin or create your own task type (maybe based on ProcessBuilder). You might even wrap your application in a Docker container and use the plugin mentioned above.

Passing parameters to dependable task of custom task

There is task which can be executed with parameter like this:
./gradlew taskX -Pkey=value
And plugin with custom task which should execute taskX:
class CustomPlugin : Plugin<Project> {
override fun apply(project: Project) {
project.tasks.register("custom", CustomTask::class.java)
.configure {
it.description = "Description"
it.group = "Group"
val taskX = project.getTasksByName("taskX", true).first()
it.dependsOn(taskX)
}
}
}
I would expect something like this for example:
it.dependsOn(taskX, "key=value")
How to pass parameters to dependsOn?
Simple answer: You can't. Task dependencies only express what needs to be done beforehand, not how it needs to be done.
Let me show you a simple example, why something like this is not possible in the Gradle task system:
First, we need to know that in Gradle, every task will be executed only once in a single invocation (often called build). Now imagine a task that needs to be run before two tasks that are unrelated to each other. A good real world example is the task compileJava from the Java plugin that both the test task and the jar task depend on. If dependsOn would support parameters, it could happen that two tasks depend on a single task with different parameters. What parameters should be used in this case?
As a solution, you may configure the other task directly in your plugin. If you want to pass the parameter only if your custom task is run, you may need to add another task that runs as a setup and applies the required configuration to the actual task:
task setup {
doFirst {
// apply configuration
}
}
taskX.mustRunAfter setup
task custom {
dependsOn setup
dependsOn taskX
}
This example uses Groovy, but it should be possible to translate it to Kotlin and use it in your plugin.
Edit regarding actual parameter
To be honest, I am not that familiar with the Android Gradle plugin, but if I get this documentation right, the project property android.testInstrumentationRunnerArguments.annotation is just an alternative to using the following code in the build script:
android {
defaultConfig {
testInstrumentationRunnerArgument 'annotation', '<some-value>'
}
}
You may try to define the following task and then run it using ./gradlew customTest
task customTest {
doFirst {
android.defaultConfig.testInstrumentationRunnerArgument 'annotation', '<some-value>'
}
finalizedBy 'connectedAndroidTest'
}

How to get the exact copy of original Run task in Gradle?

Does anybody know what the simplest way to register several run tasks that represent exact copy of original one with changed app’s arguments ? Or may be how to supply run task with an optional argument that would represent app’s arguments.
Basically, I want my build to contain some pre-defined options how to run an application and don’t want to declare new JavaExec that requires its manual configuring while I have already had ready-to-go run task supplied by default.
gradle run --args='--mode=middle' ---> gradle run || gradle runDefault || gradle run default
gradle run --args='--mode=greed' ---> gradle runGreed || gradle run greed
gradle run --args='--mode=lavish' ---> gradle runLavish || gradle run lavish
As for now I came up only with the option that suggests implementing my own JavaExec_Custom task class with supplied arguments property. And it seems to be too complex and boilerplating as for my goal.
You can create tasks that modify the configuration of the actual run task in a doFirst or doLast closure:
// Groovy-DSL
task runGreed {
doFirst {
run.args '--mode=greed'
}
finalizedBy run
}
// Kotlin-DSL
register("runGreed") {
doFirst {
run.get().args = listOf("--mode=greed")
}
finalizedBy(run)
}
You can use a property for the args
task run(type: JavaExec) {
args property('run.args').split(' ')
...
}
Usage
gradle run -Prun.args='foo bar baz'

Depend on multiple gradle tasks in multi project build

Currently I have task which starts Google Cloud server, runs tests and stops server. It is defined at root project:
buildscript {...}
allprojects {...}
task startServer (dependsOn: "backend:appengineStart") {}
task testPaid (dependsOn: "app:connectedPaidDebugAndroidTest") {}
task stopServer (dependsOn: "backend:appengineStop") {}
task GCEtesting {
dependsOn = ["startServer",
"testPaid",
"stopServer"]
group = 'custom'
description 'Starts GCE server, runs tests and stops server.'
testPaid.mustRunAfter 'startServer'
stopServer.mustRunAfter 'testPaid'
}
I tried multiple ways to write it something like this, short with only one task. I didn't get how to refer to task from another project and call mustRunAfter on it. This doesn't work (I also tried to refer from Project.tasks.getByPath, root.tasks, etc):
task GCEtesting {
dependsOn = ["backend:appengineStart",
"app:connectedPaidDebugAndroidTest",
"backend:appengineStop"]
group = 'custom'
description 'Starts GCE server, runs tests and stops server.'
"app:connectedPaidDebugAndroidTest".mustRunAfter "backend:appengineStart"
"backend:appengineStop".mustRunAfter "app:connectedPaidDebugAndroidTest"
}
Is it possible? What is correct syntax to make this work?
It looks like your problem is that you're treating dependsOn as meaning "invoke this task". It actually means "ensure the result of the depended on task is available before the dependent task starts". That's why your first solution didn't work: statements like testPaid.mustRunAfter only affect the actions of the testPaid task itself, not its dependencies.
Anyway, you can get the behaviour you want using dependsOn and finalizedBy, but they have to be declared in the build file of the app subproject.
app/build.gradle:
task connectedPaidDebugAndroidTest {
//
//...
//
dependsOn 'backend:appengineStart' // Ensure appengineStart is run at some point before this task
finalizedBy 'backend:appendginStop' // Ensure appengineStop is run at some point after this task
}
Then, you can run your tests simply with gradle app:connectedPaidDebugAndroidTest. If you really want to define a task in the root project to run the tests, then that's easy too:
build.gradle:
task GCEtesting {
dependsOn = "app:connectedPaidDebugAndroidTest"
}

How to make gradle not to mark build failed if no tests are found

As the title says, how can I make gradle not to fail a test task if no tests are found? I ran into this problem when I was using the --tests command line option with a multi-subproject project. For instance, this command below will run all tests in class FooTest from subproject A:
gradle test --tests com.foo.bar.FooTest
However, this command fails because of something like this:
Execution failed for task ':B:test'.
> No tests found for given includes: [com.foo.bar.FooTest]
BTW, I know something like below will succeed. But is it possible to make it succeed even with the test task? It's kind of annoying to type a test task name longer than test.
gradle :A:test --tests com.foo.bar.FooTest
The behavior you described is the current Gradle behavior, there is already a ticket on Gradle forum, see https://discuss.gradle.org/t/multi-module-build-fails-with-tests-filter/25835
Based on the solution described in this ticket, you can do something like that to disable the 'failIfNoTest' default behavior:
In your root project build (or better: in an InitScript in your Gradle USER_HOME dir, to make this behavior available for all your local projects)
gradle.projectsEvaluated {
subprojects {
// TODO: filter projects that does not have test task...
test {
filter {
setFailOnNoMatchingTests(false)
}
}
}
}
Then you can execute the following command without having errors if the given test doesn't exist in all sub-projects:
gradle test --tests com.foo.bar.FooTest
it seems that currently only a workaround like this is possible:
test {
afterSuite { desc, result ->
if (!desc.parent) {
if (result.testCount == 0) {
throw new IllegalStateException("No tests were found. Failing the build")
}
}
}
}
I have filed an issue with Gradle to introduce this as a simple config option: https://github.com/gradle/gradle/issues/7452
You can also run the tests only for the current project with
gradle :test --tests com.foo.bar.FooTest
Note the colon before the test task.

Resources