Gradle Plugin Test UpToDateWhen method - gradle

I'm writing a gradle plugin that defines an upToDateWhen closure to skip the task when certain criteria is met. I'm having trouble figuring out how to wrap a test around this method. Currently it looks like:
class MyCoolTask extends DefaultTask {
MyCoolTask() {
outputs.upToDateWhen {
if (somecondition)
return true
else
return false
}
}
}
My test looks like this:
class MyCoolTaskTest {
#Test
void testUpToDateCheck() {
project = ProjectBuilder.builder().build()
project.apply plugin: 'myCoolPlugin'
project.myCoolTask.execute()
// But then how do you do a subsequent run and ensure that the task did not execute?
project.myCoolTask.execute() // running this a second time does not work.
project.myCoolTask.outputs.upToDateWhen() // Throws a syntax error
}
}
Any insight that could be offered would be great! Thanks!

ProjectBuilder is meant for low-level tests that configure the build but don't execute any tasks. You can either factor out the contents of outputs.upToDateWhen { ... } into a method/class and test that, and/or write an acceptance test that executes a real build using the Gradle tooling API.

Related

How to get Gradle TestKit test project's `sourceSets`?

I'm using Gradle's TestKit and want to ensure that a plugin is adding the correct paths to sourceSets.main.proto.
How do I access that info?
I haven't found a way to access the Project object from TestKit (and I'm suspecting it's impossible to do so).
One way to get at sourceSets.main.proto would be to create a task within the test data build.gradle file. For example:
tasks.register('assertMainProtoSrcDirsIncludesProtopPath') {
doLast {
assert sourceSets.main.proto.srcDirs.contains(file("${rootDir}/${protop.path}"))
}
}
TestKit can then be used to call that task:
#Test
#DisplayName("`sourceSets.main.proto.srcDirs` should include `protop.path`.")
public void sourceSetsMainProtoSrcDirsShouldIncludeProtopPath(final SoftAssertions softly) {
final BuildResult buildResult = gradleRunner
.withPluginClasspath()
.withProjectDir(rootDir.toFile())
.withArguments("--info", "--stacktrace", ":assertMainProtoSrcDirsIncludesProtopPath")
.build();
softly.assertThat(Objects.requireNonNull(buildResult.task(":assertMainProtoSrcDirsIncludesProtopPath")).getOutcome())
.isEqualByComparingTo(SUCCESS);
}

How to include/exclude junit5 tags in gradle cmd?

I want to execute tagged JUnit 5 tests, eg. only slow tests, with gradle.
I want to do the same like this in maven:
mvn test -Dgroups="slow"
But what is the equivalent in gradle? Or is there anything at all?
To execute all JUnit 5 tests which are marked with #Tag("slow"). I know it's quite simple to create a dedicated task like this:
tasks.withType(Test::class.java).configureEach {
useJUnitPlatform() {
includeTags("slow")
}
}
But I have a lot of different tags and I don't want to have a task for each single tag. Or worse, having one task for all permutations.
Another possibility would be to pass self defined properties to the task like this
tasks.withType(Test::class.java).configureEach {
val includeTagsList = System.getProperty("includeTags", "")!!.split(",")
.map { it.trim() }
.filter { it.isNotBlank() }
if (includeTagsList.isNotEmpty()) {
includeTags(*includeTagsList.toTypedArray())
}
}
The last time I checked, Gradle didn't have built-in support for configuring JUnit Platform include and exclude tags via the command line, so you'll have to go with your second approach.
But... there's no need to split, map, and filter the tags: just use a tag expression instead: https://junit.org/junit5/docs/current/user-guide/#running-tests-tag-expressions
You can create a separate task and to choose do you want to run the task or to skip it.
For example add this code in build.gradle:
def slowTests= tasks.register("slowTests", Test) {
useJUnitPlatform {
includeTags "slow"
}
}
Now if you want to run only slow tests:
./gradlew clean build -x test slowTests

Gradle task check if property is defined

I have a Gradle task that executes a TestNG test suite.
I want to be able to pass a flag to the task in order to use a special TestNG XML suite file (or just use the default suite if the flag isn't set).
gradle test
... should run the default standard suite of tests
gradle test -Pspecial
... should run the special suite of tests
I've been trying something like this:
test {
if (special) {
test(testng_special.xml);
}
else {
test(testng_default.xml);
}
}
But I get a undefined property error. What is the correct way to go about this?
if (project.hasProperty('special'))
should do it.
Note that what you're doing to select a testng suite won't work, AFAIK: the test task doesn't have any test() method. Refer to https://discuss.gradle.org/t/how-to-run-acceptance-tests-with-testng-from-gradle/4107 for a working example:
test {
useTestNG {
suites 'src/main/resources/testng.xml'
}
}
This worked for me:
test {
if (properties.containsKey('special')) {
test(testng_special.xml);
}
else {
test(testng_default.xml);
}
}
Here are 3 solutions for Kotlin DSL (build.gradle.kts):
val prop = project.properties["myPropName"] ?: "myDefaultValue"
val prop = project.properties["myPropName"] ?: error("Property not found")
if (project.hasProperty("special")) {
val prop = project.properties["myPropName"]
}
Note that you can omit the project. prefix as it is implicit in Gradle build files.
From Gradle Documentation:
-P, --project-prop
Sets a project property of the root project, for example -Pmyprop=myvalue
So you should use:
gradle test -Pspecial=true
with a value after the property name

Configuring a Gradle task based on task to be executed

I'm seemingly in a bit of a chicken/egg problem. I have the test task that Gradle defines with the Java plugin. I set the JUnit categories to run using a property. My team has expressed interest in a task that will run tasks in a specific category instead of having to use -P to set a property on the command line.
I can't come up with a way to pull this off since the new task would need to configure the test task only if it's executed. Since the categories to run need to be a input parameter for the test task to make sure the UP-TO-DATE check functions correctly, they need to be set during the configuration phase and can't wait for the execution phase.
Does anyone know how to make a setup like this work? Maybe I'm approaching it from the wrong angle entirely.
Edit 1
Current build.gradle
apply plugin: 'java'
def defaultCategory = 'checkin'
test {
def category = (project.hasProperty('category') ? project['category'] : defaultCategory)
inputs.property('category', category)
useJUnit()
options {
includeCategories category
}
}
What I'd like, but doesn't work:
apply plugin: 'java'
def defaultCategory = 'checkin'
test {
def category = (project.hasProperty('category') ? project['category'] : defaultCategory)
inputs.property('category', category)
useJUnit()
options {
includeCategories category
}
}
task nightly(dependsOn: 'build') {
defaultCategory = 'nightly'
}
task weekly(dependsOn: 'build') {
defaultCategory = 'weekly'
}
Since both tasks are configured regardless of whether they will be run, they become useless. I can't defer setting the defaultCategory value until the execution phase because it's value is needed to configure the task inputs of the test task and because the value is required to be able to run the test task, which runs before the build task.
I don't know if I'd call this solution elegant, but it has proven to be effective:
task nightly(dependsOn: 'build')
test {
useJUnit()
gradle.taskGraph.whenReady {
if (gradle.taskGraph.hasTask(":${project.name}:nightly")) {
options {
includeCategories 'nightly'
}
}
}
}

Custom conditional configuration for Gradle project

Having an extract from https://github.com/gradle/gradle/blob/master/build.gradle:
ext {
isDevBuild = {
gradle.taskGraph.hasTask(developerBuild)
}
}
task developerBuild {
description = 'Builds distributions and runs pre-checkin checks'
group = 'build'
dependsOn testedDists
}
When I used this approach to create custom configuration in my project I discovered that:
isDevBuild === true
i.e. it's always true because task 'developerBuild' is inside my build.gradle project, and hence in graph. They have a couple of "different" configs (isCIBuild, isCommitBuild, isFinalReleaseBuild, ...) so I suppose I got something wrong here.
Can someone explain how to make this configs conditional based on some external parameter?
taskGraph.hasTask() tells if a task is in the task execution graph, that is whether it will get executed. Because the task execution graph is only created after the configuration phase, this method has to be called from a whenReady callback (or in the execution phase):
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(developerBuild)) {
// do conditional configuration
}
}
To make this more readable, we can introduce a new method:
def onlyFor(task, config) {
gradle.taskGraph.whenReady { graph ->
if (graph.hasTask(task)) {
project.configure(project, config)
}
}
}
Now we can write:
onlyFor(developerBuild) { ... }
onlyFor(ciBuild) { ... }
Another, simpler way to solve this problem is to check whether a particular task name is contained in gradle.startParameter.taskNames. However, this has two limitations: First, it compares task names, which can make a difference in multi-project builds. Second, it will only find tasks that have been specified directly (e.g. on the command line), but not dependencies thereof.
PS.: In your code, isDevBuild always holds because a (non-null) closure is true according to Groovy truth. (In contrast to isDevBuild(), isDevBuild won't call the closure.)

Resources