org.gradle.api.internal.changedetection.changes.DefaultTaskArtifactStateRepositoryTest.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 2013 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.internal.changedetection.changes
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.Task
import org.gradle.api.internal.TaskInternal
import org.gradle.api.internal.cache.StringInterner
import org.gradle.api.internal.changedetection.TaskArtifactState
import org.gradle.api.internal.changedetection.state.CacheBackedTaskHistoryRepository
import org.gradle.api.internal.changedetection.state.DefaultFileCollectionSnapshotterRegistry
import org.gradle.api.internal.changedetection.state.DefaultFileSystemMirror
import org.gradle.api.internal.changedetection.state.DefaultFileSystemSnapshotter
import org.gradle.api.internal.changedetection.state.DefaultGenericFileCollectionSnapshotter
import org.gradle.api.internal.changedetection.state.DefaultTaskHistoryStore
import org.gradle.api.internal.changedetection.state.FileCollectionSnapshot
import org.gradle.api.internal.changedetection.state.InMemoryCacheDecoratorFactory
import org.gradle.api.internal.changedetection.state.TaskHistoryRepository
import org.gradle.api.internal.changedetection.state.TaskHistoryStore
import org.gradle.api.internal.changedetection.state.TaskOutputFilesRepository
import org.gradle.api.internal.file.TestFiles
import org.gradle.api.tasks.incremental.InputFileDetails
import org.gradle.cache.CacheRepository
import org.gradle.cache.internal.CacheScopeMapping
import org.gradle.cache.internal.CrossBuildInMemoryCacheFactory
import org.gradle.cache.internal.DefaultCacheRepository
import org.gradle.internal.classloader.ConfigurableClassLoaderHierarchyHasher
import org.gradle.internal.event.DefaultListenerManager
import org.gradle.internal.hash.HashCode
import org.gradle.internal.hash.TestFileHasher
import org.gradle.internal.id.UniqueId
import org.gradle.internal.reflect.DirectInstantiator
import org.gradle.internal.scopeids.id.BuildInvocationScopeId
import org.gradle.internal.serialize.DefaultSerializerRegistry
import org.gradle.internal.serialize.SerializerRegistry
import org.gradle.test.fixtures.AbstractProjectBuilderSpec
import org.gradle.test.fixtures.file.TestFile
import org.gradle.testfixtures.internal.InMemoryCacheFactory
import org.gradle.util.TestUtil
class DefaultTaskArtifactStateRepositoryTest extends AbstractProjectBuilderSpec {
def gradle
final outputFile = temporaryFolder.file("output-file")
final outputDir = temporaryFolder.file("output-dir")
final outputDirFile = outputDir.file("some-file")
final outputDirFile2 = outputDir.file("some-file-2")
final emptyOutputDir = temporaryFolder.file("empty-output-dir")
final missingOutputFile = temporaryFolder.file("missing-output-file")
final missingOutputDir = temporaryFolder.file("missing-output-dir")
final inputFile = temporaryFolder.createFile("input-file")
final inputDir = temporaryFolder.createDir("input-dir")
final inputDirFile = inputDir.file("input-file2").createFile()
final missingInputFile = temporaryFolder.file("missing-input-file")
final inputFiles = [file: [inputFile], dir: [inputDir], missingFile: [missingInputFile]]
final outputFiles = [file: [outputFile], missingFile: [missingOutputFile]]
final outputDirs = [emptyDir: [emptyOutputDir], dir: [outputDir], missingDir: [missingOutputDir]]
final createFiles = [outputFile, outputDirFile, outputDirFile2] as Set
def buildScopeId = new BuildInvocationScopeId(UniqueId.generate())
TaskInternal task
def mapping = Stub(CacheScopeMapping) {
getBaseDirectory(_, _, _) >> {
return temporaryFolder.createDir("history-cache")
}
}
DefaultGenericFileCollectionSnapshotter fileCollectionSnapshotter
DefaultTaskArtifactStateRepository repository
DefaultFileSystemMirror fileSystemMirror
TaskOutputFilesRepository taskOutputFilesRepository = Stub(TaskOutputFilesRepository)
def setup() {
gradle = project.getGradle()
task = builder.task()
CacheRepository cacheRepository = new DefaultCacheRepository(mapping, new InMemoryCacheFactory())
CrossBuildInMemoryCacheFactory cacheFactory = new CrossBuildInMemoryCacheFactory(new DefaultListenerManager())
TaskHistoryStore cacheAccess = new DefaultTaskHistoryStore(gradle, cacheRepository, new InMemoryCacheDecoratorFactory(false, cacheFactory))
def stringInterner = new StringInterner()
def fileHasher = new TestFileHasher()
fileSystemMirror = new DefaultFileSystemMirror([])
fileCollectionSnapshotter = new DefaultGenericFileCollectionSnapshotter(stringInterner, TestFiles.directoryFileTreeFactory(), new DefaultFileSystemSnapshotter(fileHasher, stringInterner, TestFiles.fileSystem(), TestFiles.directoryFileTreeFactory(), fileSystemMirror))
def classLoaderHierarchyHasher = Mock(ConfigurableClassLoaderHierarchyHasher) {
getClassLoaderHash(_) >> HashCode.fromInt(123)
}
SerializerRegistry serializerRegistry = new DefaultSerializerRegistry()
fileCollectionSnapshotter.registerSerializers(serializerRegistry)
def snapshotterRegistry = new DefaultFileCollectionSnapshotterRegistry([fileCollectionSnapshotter])
TaskHistoryRepository taskHistoryRepository = new CacheBackedTaskHistoryRepository(
cacheAccess,
serializerRegistry.build(FileCollectionSnapshot),
stringInterner,
classLoaderHierarchyHasher,
TestUtil.valueSnapshotter(),
snapshotterRegistry,
TestFiles.fileCollectionFactory(),
buildScopeId)
repository = new DefaultTaskArtifactStateRepository(taskHistoryRepository, DirectInstantiator.INSTANCE, taskOutputFilesRepository)
}
def "artifacts are not up to date when cache is empty"() {
expect:
outOfDate(task)
}
def "artifacts are not up to date when any output file no longer exists"() {
given:
execute(task)
when:
outputFile.delete()
then:
outOfDate task
}
def "artifacts are not up to date when any file in output dir no longer exists"() {
given:
execute(task)
when:
outputDirFile.delete()
then:
outOfDate task
}
def "artifacts are not up to date when any output file has changed type"() {
given:
execute(task)
when:
outputFile.delete()
outputFile.createDir()
then:
outOfDate task
}
def "artifacts are not up to date when any file in output dir has changed type"() {
given:
execute(task)
when:
outputDirFile.delete()
outputDirFile.createDir()
then:
outOfDate task
}
def "artifacts are not up to date when any output file has changed hash"() {
given:
execute(task)
when:
outputFile.write("new content")
then:
outOfDate task
}
def "artifacts are not up to date when any file in output dir has changed hash"() {
given:
execute(task)
when:
outputDirFile.write("new content")
then:
outOfDate task
}
def "artifacts are not up to date when any output files added to set"() {
when:
execute(task)
TaskInternal outputFilesAddedTask = builder.withOutputFiles(outputFile, outputDir, temporaryFolder.createFile("output-file-2"), emptyOutputDir, missingOutputFile).task()
then:
outOfDate outputFilesAddedTask
}
def "artifacts are not up to date when any output files removed from set"() {
when:
execute(task)
TaskInternal outputFilesRemovedTask = builder.withOutputFiles(outputFile, emptyOutputDir, missingOutputFile).task()
then:
outOfDate outputFilesRemovedTask
}
def "artifacts are not up to date when task with different type generated any output files"() {
when:
TaskInternal task1 = builder.withOutputFiles(outputFile).task()
TaskInternal task2 = builder.withType(TaskSubType.class).withOutputFiles(outputFile).task()
execute(task1, task2)
then:
outOfDate task
}
def "artifacts are not up to date when any input files added to set"() {
final addedFile = temporaryFolder.createFile("other-input")
when:
execute(task)
TaskInternal inputFilesAdded = builder.withInputFiles(file: [inputFile, addedFile], dir: [inputDir], missingFile: [missingInputFile]).task()
then:
inputsOutOfDate(inputFilesAdded).withAddedFile(addedFile)
}
def "artifacts are not up to date when any input files removed from set"() {
when:
execute(task)
TaskInternal inputFilesRemoved = builder.withInputFiles(file: [inputFile], dir: [], missingFile: [missingInputFile]).task()
then:
inputsOutOfDate(inputFilesRemoved).withRemovedFiles(inputDir, inputDirFile)
}
def "artifacts are not up to date when any input file has changed hash"() {
given:
execute(task)
when:
inputFile.write("some new content")
then:
inputsOutOfDate(task).withModifiedFile(inputFile)
}
def "artifacts are not up to date when any input file has changed type"() {
given:
execute(task)
when:
inputFile.delete()
inputFile.createDir()
then:
inputsOutOfDate(task).withModifiedFile(inputFile)
}
def "artifacts are not up to date when any input file no longer exists"() {
given:
execute(task)
when:
inputFile.delete()
then:
inputsOutOfDate(task).withModifiedFile(inputFile)
}
def "artifacts are not up to date when any input file did not exist and now does"() {
given:
inputFile.delete()
execute(task)
when:
inputFile.createNewFile()
then:
inputsOutOfDate(task).withModifiedFile(inputFile)
}
def "artifacts are not up to date when any file created in input dir"() {
given:
execute(task)
when:
def file = inputDir.file("other-file").createFile()
then:
inputsOutOfDate(task).withAddedFile(file)
}
def "artifacts are not up to date when any file deleted from input dir"() {
given:
execute(task)
when:
inputDirFile.delete()
then:
inputsOutOfDate(task).withRemovedFile(inputDirFile)
}
def "artifacts are not up to date when any file in input dir changes hash"() {
given:
execute(task)
when:
inputDirFile.writelns("new content")
then:
inputsOutOfDate(task).withModifiedFile(inputDirFile)
}
def "artifacts are not up to date when any file in input dir changes type"() {
given:
execute(task)
when:
inputDirFile.delete()
inputDirFile.mkdir()
then:
inputsOutOfDate(task).withModifiedFile(inputDirFile)
}
def "artifacts are not up to date when any input property value changed"() {
when:
execute(builder.withProperty("prop", "original value").task())
final inputPropertiesTask = builder.withProperty("prop", "new value").task()
then:
outOfDate inputPropertiesTask
}
def "input property value can be null"() {
when:
TaskInternal task = builder.withProperty("prop", null).task()
execute(task)
then:
upToDate(task)
}
def "artifacts are not up to date when any input property added"() {
when:
execute(task)
final addedPropertyTask = builder.withProperty("prop2", "value").task()
then:
outOfDate addedPropertyTask
}
def "artifacts are not up to date when any input property removed"() {
given:
execute(builder.withProperty("prop2", "value").task())
expect:
outOfDate task
}
def "artifacts are not up to date when state has not been updated"() {
when:
repository.getStateFor(task)
then:
outOfDate task
}
def "artifacts are not up to date when output dir which used to exist has been deleted"() {
given:
// Output dir already exists before first execution of task
outputDir.createDir()
TaskInternal task1 = builder.withOutputFiles(outputDir).task()
TaskInternal task2 = builder.withPath("other").withOutputFiles(outputDir).task()
when:
TaskArtifactState state = repository.getStateFor(task1)
state.isUpToDate([])
fileSystemMirror.beforeTaskOutputsGenerated()
outputDirFile.createFile()
state.snapshotAfterTaskExecution(null)
then:
!state.upToDate
when:
fileSystemMirror.beforeTaskOutputsGenerated()
outputDir.deleteDir()
and:
// Another task creates dir
state = repository.getStateFor(task2)
then:
!state.isUpToDate([])
when:
fileSystemMirror.beforeTaskOutputsGenerated()
outputDirFile2.createFile()
state.snapshotAfterTaskExecution(null)
then:
// Task should be out-of-date
outOfDate task1
}
def "artifacts are up to date when nothing has changed since output files were generated"() {
given:
execute(task)
expect:
repository.getStateFor(task).isUpToDate([])
repository.getStateFor(task).isUpToDate([])
}
def "artifacts are up to date when output file which did not exist now exists"() {
given:
execute(task)
when:
missingOutputFile.touch()
then:
upToDate task
}
def "artifacts are up to date when output dir which was empty is no longer empty"() {
given:
execute(task)
when:
emptyOutputDir.file("some-file").touch()
then:
upToDate task
}
def "has empty task history when task has never been executed"() {
when:
TaskArtifactState state = repository.getStateFor(task)
then:
state.getExecutionHistory().getOutputFiles().isEmpty()
}
def "has task history from previous execution"() {
given:
execute(task)
when:
TaskArtifactState state = repository.getStateFor(task)
then:
state.getExecutionHistory().getOutputFiles() == [outputFile, outputDir, outputDirFile, outputDirFile2, emptyOutputDir, missingOutputDir] as Set
}
def "multiple tasks can produce files into the same output directory"() {
when:
TaskInternal task1 = task
TaskInternal task2 = builder.withPath("other").withOutputDirs(outputDir).createsFiles(outputDir.file("output2")).task()
execute(task1, task2)
then:
upToDate task1
upToDate task2
}
def "multiple tasks can produce the same file with the same contents"() {
when:
TaskInternal task1 = builder.withOutputFiles(outputFile).task()
TaskInternal task2 = builder.withPath("other").withOutputFiles(outputFile).task()
execute(task1, task2)
then:
upToDate task1
upToDate task2
}
def "multiple tasks can produce the same empty dir"() {
when:
TaskInternal task1 = task
TaskInternal task2 = builder.withPath("other").withOutputDirs(outputDir).task()
execute(task1, task2)
then:
upToDate task1
upToDate task2
}
def "does not consider existing files in output directory as produced by task"() {
when:
TestFile otherFile = outputDir.file("other").createFile()
execute(task)
otherFile.delete()
then:
TaskArtifactState state = repository.getStateFor(task)
state.isUpToDate([])
!state.getExecutionHistory().getOutputFiles().contains(otherFile)
}
def "considers existing file in output directory which is updated by the task as produced by task"() {
when:
TestFile otherFile = outputDir.file("other").createFile()
TaskArtifactState state = repository.getStateFor(task)
then:
!state.isUpToDate([])
when:
execute(task)
fileSystemMirror.beforeTaskOutputsGenerated()
otherFile.write("new content")
state.snapshotAfterTaskExecution(null)
otherFile.delete()
then:
def stateAfter = repository.getStateFor(task)
!stateAfter.upToDate
stateAfter.executionHistory.outputFiles.contains(otherFile)
}
def "file is no longer considered produced by task once it is deleted"() {
given:
execute(task)
outputDirFile.delete()
TaskArtifactState state = repository.getStateFor(task)
state.isUpToDate([])
state.snapshotAfterTaskExecution(null)
when:
outputDirFile.write("ignore me")
then:
def stateAfter = repository.getStateFor(task)
stateAfter.isUpToDate([])
!stateAfter.executionHistory.outputFiles.contains(outputDirFile)
}
def "artifacts are up to date when task does not accept any inputs"() {
when:
TaskInternal noInputsTask = builder.doesNotAcceptInput().task()
execute(noInputsTask)
then:
upToDate noInputsTask
when:
fileSystemMirror.beforeTaskOutputsGenerated()
outputDirFile.delete()
then:
outOfDate noInputsTask
}
def "artifacts are up to date when task has no input files"() {
when:
TaskInternal noInputFilesTask = builder.withInputFiles([:]).task()
execute(noInputFilesTask)
then:
upToDate noInputFilesTask
}
def "artifacts are up to date when task has no output files"() {
when:
TaskInternal noOutputsTask = builder.withOutputFiles([:]).task()
execute(noOutputsTask)
then:
upToDate noOutputsTask
}
def "task can produce into different sets of output files"() {
when:
TestFile outputDir2 = temporaryFolder.createDir("output-dir-2")
TestFile outputDirFile2 = outputDir2.file("output-file-2")
TaskInternal task1 = builder.withOutputDirs(dir: [outputDir]).createsFiles(outputDirFile).withPath('task1').task()
TaskInternal task2 = builder.withOutputDirs(dir: [outputDir2]).createsFiles(outputDirFile2).withPath('task2').task()
execute(task1, task2)
then:
def state1 = repository.getStateFor(task1)
state1.isUpToDate([])
state1.executionHistory.outputFiles == [outputDir, outputDirFile] as Set
and:
def state2 = repository.getStateFor(task2)
state2.isUpToDate([])
state2.executionHistory.outputFiles == [outputDirFile2] as Set
}
def "has no origin build ID when not executed"() {
expect:
repository.getStateFor(task).originBuildInvocationId == null
}
def "has origin build ID after executed"() {
when:
execute(task)
then:
repository.getStateFor(task).originBuildInvocationId == buildScopeId.id
}
private void outOfDate(TaskInternal task) {
final state = repository.getStateFor(task)
assert !state.isUpToDate([])
assert !state.inputChanges.incremental
}
def inputsOutOfDate(TaskInternal task) {
final state = repository.getStateFor(task)
assert !state.isUpToDate([])
final inputChanges = state.inputChanges
assert inputChanges.incremental
final changedFiles = new ChangedFiles()
inputChanges.outOfDate(new Action() {
void execute(InputFileDetails t) {
if (t.added) {
println "Added: " + t.file
changedFiles.added << t.file
} else if (t.modified) {
println "Modified: " + t.file
changedFiles.modified << t.file
} else {
assert false: "Not a valid change"
}
}
})
inputChanges.removed(new Action() {
void execute(InputFileDetails t) {
println "Removed: " + t.file
assert t.removed
changedFiles.removed << t.file
}
})
return changedFiles
}
private void upToDate(TaskInternal task) {
final state = repository.getStateFor(task)
assert state.isUpToDate([])
}
@Override
void execute(Task task) {
execute([(TaskInternal) task] as TaskInternal[])
}
void execute(TaskInternal... tasks) {
for (TaskInternal task : tasks) {
TaskArtifactState state = repository.getStateFor(task)
state.isUpToDate([])
// reset state
fileSystemMirror.beforeTaskOutputsGenerated()
super.execute(task)
state.snapshotAfterTaskExecution(null)
}
// reset state
fileSystemMirror.beforeTaskOutputsGenerated()
}
private static class ChangedFiles {
def added = []
def modified = []
def removed = []
void withAddedFile(File file) {
assert added == [file]
assert modified == []
assert removed == []
}
void withModifiedFile(File file) {
assert added == []
assert modified == [file]
assert removed == []
}
void withRemovedFile(File file) {
assert added == []
assert modified == []
assert removed == [file]
}
void withRemovedFiles(File... files) {
assert added == []
assert modified == []
assert removed == files as List
}
}
private TaskBuilder getBuilder() {
return new TaskBuilder()
}
private class TaskBuilder {
private String path = "task"
private Map inputProperties = [prop: "value"]
private Map> inputs = inputFiles
private Map> outputFiles = DefaultTaskArtifactStateRepositoryTest.this.outputFiles
private Map> outputDirs = DefaultTaskArtifactStateRepositoryTest.this.outputDirs
private Collection extends TestFile> create = createFiles
private Class extends TaskInternal> type = TaskInternal.class
TaskBuilder withInputFiles(Map> files) {
this.inputs = files
return this
}
TaskBuilder withOutputFiles(File... outputFiles) {
return withOutputFiles(default: Arrays.asList(outputFiles))
}
TaskBuilder withOutputFiles(Map> files) {
this.outputFiles = files
return this
}
TaskBuilder withOutputDirs(File... outputDirs) {
return withOutputDirs(defaultDir: Arrays.asList(outputDirs))
}
TaskBuilder withOutputDirs(Map> dirs) {
this.outputDirs = dirs
return this
}
TaskBuilder createsFiles(TestFile... outputFiles) {
create = Arrays.asList(outputFiles)
return this
}
TaskBuilder withPath(String path) {
this.path = path
return this
}
TaskBuilder withType(Class extends TaskInternal> type) {
this.type = type
return this
}
TaskBuilder doesNotAcceptInput() {
inputs = null
inputProperties = null
return this
}
TaskBuilder withProperty(String name, Object value) {
inputProperties.put(name, value)
return this
}
TaskInternal task() {
final TaskInternal task = TestUtil.createTask(type, project, path)
if (inputs != null) {
inputs.each { String property, Collection extends File> files ->
task.inputs.files files withPropertyName property
}
}
if (inputProperties != null) {
task.getInputs().properties(inputProperties)
}
if (outputFiles != null) {
outputFiles.each { String property, Collection extends File> files ->
if (files.size() == 1) {
task.getOutputs().file files[0] withPropertyName property
} else if (files.size() > 1) {
for (int idx = 0; idx < files.size(); idx++) {
def file = files[idx]
task.getOutputs().file file withPropertyName "$property.\$$idx"
}
}
}
}
if (outputDirs != null) {
outputDirs.each { String property, Collection extends File> dirs ->
if (dirs.size() == 1) {
task.getOutputs().dir dirs[0] withPropertyName property
} else if (dirs.size() > 1) {
for (int idx = 0; idx < dirs.size(); idx++) {
def dir = dirs[idx]
task.getOutputs().dir dir withPropertyName "$property.\$$idx"
}
}
}
}
task.doLast(new Action
© 2015 - 2025 Weber Informatics LLC | Privacy Policy