org.gradle.api.tasks.NestedInputIntegrationTest.groovy Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of gradle-api Show documentation
Show all versions of gradle-api Show documentation
Gradle 6.9.1 API redistribution.
/*
* Copyright 2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gradle.api.tasks
import org.gradle.api.file.DirectoryProperty
import org.gradle.api.file.RegularFileProperty
import org.gradle.integtests.fixtures.AbstractIntegrationSpec
import org.gradle.integtests.fixtures.DirectoryBuildCacheFixture
import org.gradle.integtests.fixtures.ToBeFixedForConfigurationCache
import org.gradle.integtests.fixtures.UnsupportedWithConfigurationCache
import org.gradle.internal.reflect.validation.ValidationMessageChecker
import org.gradle.test.fixtures.file.TestFile
import spock.lang.Issue
import static org.gradle.integtests.fixtures.ToBeFixedForConfigurationCache.Skip.INVESTIGATE
class NestedInputIntegrationTest extends AbstractIntegrationSpec implements DirectoryBuildCacheFixture, ValidationMessageChecker {
def setup() {
expectReindentedValidationMessage()
}
def "nested #type.simpleName input adds a task dependency"() {
buildFile << """
class TaskWithNestedProperty extends DefaultTask {
@Nested
Object bean
}
class NestedBeanWithInput {
@Input${kind}
@PathSensitive(PathSensitivity.NONE)
final ${type.name} input
NestedBeanWithInput(${type.name} input) {
this.input = input
}
}
class GeneratorTask extends DefaultTask {
@Output${kind}
final ${type.name} output = project.objects.${factory}()
@TaskAction
void doStuff() {
output${generatorAction}
}
}
task generator(type: GeneratorTask) {
output.set(project.layout.buildDirectory.${lookup}('output'))
}
task consumer(type: TaskWithNestedProperty) {
bean = new NestedBeanWithInput(project.objects.${factory}())
bean.input.set(generator.output)
}
"""
when:
run 'consumer'
then:
executedAndNotSkipped(':generator', ':consumer')
where:
kind | type | factory | lookup | generatorAction
'File' | RegularFileProperty | 'fileProperty' | 'file' | '.getAsFile().get().text = "Hello"'
'Directory' | DirectoryProperty | 'directoryProperty' | 'dir' | '''.file('output.txt').get().getAsFile().text = "Hello"'''
}
def "nested FileCollection input adds a task dependency"() {
buildFile << """
class TaskWithNestedProperty extends DefaultTask {
@Nested
Object bean
}
class NestedBeanWithInput {
@InputFiles
FileCollection input
}
class GeneratorTask extends DefaultTask {
@OutputFile
final RegularFileProperty outputFile = project.objects.fileProperty()
@TaskAction
void doStuff() {
outputFile.getAsFile().get().text = "Hello"
}
}
task generator(type: GeneratorTask) {
outputFile = project.layout.buildDirectory.file('output')
}
task consumer(type: TaskWithNestedProperty) {
bean = new NestedBeanWithInput(input: files(generator.outputFile))
}
"""
when:
run 'consumer'
then:
executedAndNotSkipped(':generator', ':consumer')
}
@Issue("https://github.com/gradle/gradle/issues/3811")
def "nested input using output file property of different task adds a task dependency"() {
buildFile << """
class TaskWithNestedProperty extends DefaultTask {
@Nested
Object bean
}
class NestedBeanWithInput {
@InputFile
final RegularFileProperty file
NestedBeanWithInput(RegularFileProperty file) {
this.file = file
}
}
class GeneratorTask extends DefaultTask {
@OutputFile
final RegularFileProperty outputFile = project.objects.fileProperty()
@TaskAction
void doStuff() {
outputFile.getAsFile().get().text = "Hello"
}
}
task generator(type: GeneratorTask) {
outputFile = project.layout.buildDirectory.file('output')
}
task consumer(type: TaskWithNestedProperty) {
bean = new NestedBeanWithInput(generator.outputFile)
}
"""
when:
run 'consumer'
then:
executedAndNotSkipped(':generator')
executedAndNotSkipped(':consumer')
}
@UnsupportedWithConfigurationCache(because = "task references another task")
def "re-configuring #change in nested bean during execution time is detected"() {
def fixture = new NestedBeanTestFixture()
buildFile << fixture.taskWithNestedProperty()
buildFile << """
task configureTask {
doLast {
taskWithNestedProperty.bean = secondBean
}
}
taskWithNestedProperty.dependsOn(configureTask)
"""
fixture.prepareInputFiles()
when:
fixture.runTask()
then:
executedAndNotSkipped(fixture.task)
when:
fixture.changeFirstBean(change)
fixture.runTask()
then:
skipped(fixture.task)
when:
fixture.changeSecondBean(change)
fixture.runTask()
then:
executedAndNotSkipped(fixture.task)
where:
change << ['inputProperty', 'inputFile', 'outputFile']
}
@UnsupportedWithConfigurationCache(because = "task references another task")
def "re-configuring a nested bean from #from to #to during execution time is detected"() {
def fixture = new NestedBeanTestFixture()
buildFile << fixture.taskWithNestedProperty()
buildFile << """
taskWithNestedProperty.bean = ${from}
task configureTask {
doLast {
taskWithNestedProperty.bean = ${to}
}
}
taskWithNestedProperty.dependsOn(configureTask)
"""
fixture.prepareInputFiles()
when:
fixture.runTask()
then:
executedAndNotSkipped(fixture.task)
when:
fixture.changeFirstBean('inputProperty')
fixture.runTask()
then:
if (to == 'null') {
skipped(fixture.task)
} else {
executedAndNotSkipped(fixture.task)
}
where:
from | to
'firstBean' | 'null'
'null' | 'firstBean'
}
def "re-configuring #change in nested bean after the task started executing has no effect"() {
def fixture = new NestedBeanTestFixture()
fixture.prepareInputFiles()
buildFile << fixture.taskWithNestedProperty()
buildFile << """
taskWithNestedProperty.doLast {
bean = secondBean
}
"""
when:
fixture.runTask()
then:
executedAndNotSkipped(fixture.task)
when:
fixture.changeFirstBean(change)
fixture.runTask()
then:
executedAndNotSkipped(fixture.task)
when:
fixture.changeSecondBean(change)
fixture.runTask()
then:
skipped(fixture.task)
where:
change << ['inputProperty', 'inputFile', 'outputFile']
}
def "re-configuring a nested bean from #from to #to after the task started executing has no effect"() {
def fixture = new NestedBeanTestFixture()
fixture.prepareInputFiles()
buildFile << fixture.taskWithNestedProperty()
buildFile << """
taskWithNestedProperty.bean = ${from}
taskWithNestedProperty.doLast {
bean = ${to}
}
"""
when:
fixture.runTask()
then:
executedAndNotSkipped(fixture.task)
when:
fixture.changeFirstBean('inputProperty')
fixture.runTask()
then:
if (from == 'null') {
skipped(fixture.task)
} else {
executedAndNotSkipped(fixture.task)
}
where:
from | to
'firstBean' | 'null'
'null' | 'firstBean'
}
class NestedBeanTestFixture {
def firstInputFile = 'firstInput.txt'
def firstOutputFile = 'build/firstOutput.txt'
def secondInputFile = 'secondInput.txt'
def secondOutputFile = 'build/secondOutput.txt'
def task = ':taskWithNestedProperty'
def inputProperties = [
first: 'first',
second: 'second'
]
def inputFiles = [
first: file(firstInputFile),
second: file(secondInputFile)
]
def outputFiles = [
first: file(firstOutputFile),
second: file(secondOutputFile)
]
def changes = [
inputProperty: { String property ->
inputProperties[property] = inputProperties[property] + ' changed'
},
inputFile: { String property ->
inputFiles[property] << ' changed'
},
outputFile: { String property ->
outputFiles[property] << ' changed'
}
]
def changeFirstBean(String change) {
changes[change]('first')
}
def changeSecondBean(String change) {
changes[change]('second')
}
def prepareInputFiles() {
file(firstInputFile).text = "first input file"
file(secondInputFile).text = "second input file"
}
def runTask() {
result = executer.withTasks(task, '-PfirstInput=' + inputProperties.first, '-PsecondInput=' + inputProperties.second).run()
}
String taskWithNestedProperty() {
"""
class TaskWithNestedProperty extends DefaultTask {
@Nested
@Optional
Object bean
@OutputFile
final RegularFileProperty outputFile = project.objects.fileProperty()
@TaskAction
void writeInputToFile() {
outputFile.getAsFile().get().text = bean == null ? 'null' : bean.toString()
if (bean != null) {
bean.doStuff()
}
}
}
class NestedBean {
@Input
String firstInput
@InputFile
File firstInputFile
@OutputFile
File firstOutputFile
String toString() {
firstInput
}
void doStuff() {
firstOutputFile.text = firstInputFile.text
}
}
class OtherNestedBean {
@Input
String secondInput
@InputFile
File secondInputFile
@OutputFile
File secondOutputFile
String toString() {
secondInput
}
void doStuff() {
secondOutputFile.text = secondInputFile.text
}
}
def firstString = providers.gradleProperty('firstInput').orNull
def firstBean = new NestedBean(firstInput: firstString, firstOutputFile: file("${firstOutputFile}"), firstInputFile: file("${firstInputFile}"))
def secondString = providers.gradleProperty('secondInput').orNull
def secondBean = new OtherNestedBean(secondInput: secondString, secondOutputFile: file("${secondOutputFile}"), secondInputFile: file("${secondInputFile}"))
task taskWithNestedProperty(type: TaskWithNestedProperty) {
bean = firstBean
outputFile.set(project.layout.buildDirectory.file('output.txt'))
}
"""
}
}
def "execution fails when a nested property throws an exception where property is a #description"() {
buildFile << """
import javax.inject.Inject
import org.gradle.api.provider.ProviderFactory
abstract class TaskWithFailingNestedInput extends DefaultTask {
@Inject
abstract ProviderFactory getProviders()
@Nested
Object getNested() {
$propertyValue
}
@Input
String input = "Hello"
@OutputFile
File outputFile
@TaskAction
void doStuff() {
outputFile.text = input
}
}
task myTask(type: TaskWithFailingNestedInput) {
outputFile = file('build/output.txt')
}
"""
expect:
fails "myTask"
failure.assertHasDescription("Execution failed for task ':myTask'.")
failure.assertHasCause("BOOM")
where:
description | propertyValue
"Java type" | "throw new RuntimeException(\"BOOM\")"
"Provider" | "return providers.provider { throw new RuntimeException(\"BOOM\") }"
"Provider in a collection" | "return [providers.provider { throw new RuntimeException(\"BOOM\") }]"
}
def "null on nested bean is validated #description"() {
buildFile << nestedBeanWithStringInput()
buildFile << """
class TaskWithAbsentNestedInput extends DefaultTask {
@Nested
$property
@Input
String input = "Hello"
@OutputFile
File outputFile
@TaskAction
void doStuff() {
outputFile.text = input
}
}
task myTask(type: TaskWithAbsentNestedInput) {
outputFile = file('build/output.txt')
}
"""
expect:
fails "myTask"
failure.assertHasDescription("A problem was found with the configuration of task ':myTask' (type 'TaskWithAbsentNestedInput').")
failureDescriptionContains(missingValueMessage { type('TaskWithAbsentNestedInput').property('nested') })
where:
description | property
"for plain Java property" | "NestedBean nested"
"for Provider property" | "Provider nested = project.providers.provider { null }"
}
def "null on optional nested bean is allowed #description"() {
buildFile << nestedBeanWithStringInput()
buildFile << """
class TaskWithAbsentNestedInput extends DefaultTask {
@Nested
@Optional
$property
@Input
String input = "Hello"
@OutputFile
File outputFile
@TaskAction
void doStuff() {
outputFile.text = input
}
}
task myTask(type: TaskWithAbsentNestedInput) {
outputFile = file('build/output.txt')
}
"""
expect:
succeeds "myTask"
where:
description | property
"for plain Java property" | "NestedBean nested"
"for Provider property" | "Provider nested = project.providers.provider { null }"
}
def "changes to nested bean implementation are detected"() {
buildFile << """
class TaskWithNestedInput extends DefaultTask {
@Nested
Object nested
@OutputFile
File outputFile
@TaskAction
void doStuff() {
outputFile.text = nested.input
}
}
class NestedBean {
@Input
input
}
class OtherNestedBean {
@Input
input
}
boolean useOther = providers.gradleProperty('useOther').present
task myTask(type: TaskWithNestedInput) {
outputFile = file('build/output.txt')
nested = useOther ? new OtherNestedBean(input: 'string') : new NestedBean(input: 'string')
}
"""
def task = ':myTask'
when:
run task
then:
executedAndNotSkipped(task)
when:
run task
then:
skipped task
when:
run task, '-PuseOther=true'
then:
executedAndNotSkipped task
}
def "elements of nested iterable cannot be #description"() {
buildFile << """
class TaskWithNestedIterable extends DefaultTask {
@Nested
@Optional
Iterable
© 2015 - 2025 Weber Informatics LLC | Privacy Policy