All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.gradle.execution.taskgraph.DefaultTaskExecutionPlanParallelTaskHandlingTest.groovy Maven / Gradle / Ivy

/*
 * Copyright 2014 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.execution.taskgraph

import org.gradle.api.DefaultTask
import org.gradle.api.Project
import org.gradle.api.Task
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.ParallelizableTask
import org.gradle.initialization.BuildCancellationToken
import org.gradle.internal.nativeintegration.filesystem.FileSystem
import org.gradle.test.fixtures.AbstractProjectBuilderSpec
import org.gradle.test.fixtures.ConcurrentTestUtil
import org.gradle.test.fixtures.file.TestFile
import org.gradle.testfixtures.internal.NativeServicesTestFixture
import org.gradle.util.Requires
import org.gradle.util.TestPrecondition

import static org.gradle.util.TestUtil.createChildProject
import static org.gradle.util.TestUtil.createRootProject

class DefaultTaskExecutionPlanParallelTaskHandlingTest extends AbstractProjectBuilderSpec {

    FileSystem fs = NativeServicesTestFixture.instance.get(FileSystem)

    DefaultTaskExecutionPlan executionPlan = new DefaultTaskExecutionPlan(Stub(BuildCancellationToken), true)
    ProjectInternal root = createRootProject(temporaryFolder.testDirectory)

    List startedTasks = []
    List blockedThreads = []

    void cleanup() {
        completeAllStartedTasks()
        allBlockedThreadsFinish()
    }

    private void addToGraphAndPopulate(Task... tasks) {
        executionPlan.addToTaskGraph(Arrays.asList(tasks))
        executionPlan.determineExecutionPlan()
    }

    void startTasks(int num) {
        num.times { startedTasks << executionPlan.getTaskToExecute() }
    }

    void noMoreTasksCurrentlyAvailableForExecution() {
        blockedThreads << blockedThread { executionPlan.taskComplete(executionPlan.getTaskToExecute()) }
    }

    void completeAllStartedTasks() {
        startedTasks.each { executionPlan.taskComplete(it) }
        startedTasks.clear()
    }

    void allBlockedThreadsFinish() {
        blockedThreads*.join()
        blockedThreads.clear()
    }

    TestFile file(String path) {
        temporaryFolder.file(path)
    }

    @ParallelizableTask
    static class Parallel extends DefaultTask {}

    static class ParallelChild extends Parallel {}

    Thread blockedThread(Runnable target) {
        def thread = new Thread(target)

        thread.start()
        ConcurrentTestUtil.poll(3, 0.01) {
            assert thread.state == Thread.State.WAITING
        }
        thread
    }

    void requestedTasksBecomeAvailableForExecution() {
        allBlockedThreadsFinish()
    }

    def "tasks arent parallelized unless toggle is on"() {
        given:
        executionPlan = new DefaultTaskExecutionPlan(Stub(BuildCancellationToken), false)
        Task a = root.task("a")
        Task b = root.task("b")

        when:
        addToGraphAndPopulate(a, b)

        then:
        startTasks(1)

        and:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "two dependent parallelizable tasks are not executed in parallel"() {
        given:
        Task a = root.task("a", type: Parallel)
        Task b = root.task("b", type: Parallel).dependsOn(a)

        when:
        addToGraphAndPopulate(b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "two parallelizable tasks with must run after ordering are not executed in parallel"() {
        given:
        Task a = root.task("a", type: Parallel)
        Task b = root.task("b", type: Parallel).mustRunAfter(a)

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "task that extend a parallelizable task are not parallelizable by default"() {
        given:
        Task a = root.task("a", type: ParallelChild)
        Task b = root.task("b", type: ParallelChild)

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "task is not available for execution until all of its dependencies that are executed in parallel complete"() {
        given:
        Task a = root.task("a", type: Parallel)
        Task b = root.task("b", type: Parallel)
        Task c = root.task("c", type: Parallel).dependsOn(a, b)

        when:
        addToGraphAndPopulate(c)
        startTasks(2)

        then:
        noMoreTasksCurrentlyAvailableForExecution()

        when:
        completeAllStartedTasks()

        then:
        requestedTasksBecomeAvailableForExecution()
    }

    def "a parallelizable task with custom actions is not run in parallel"() {
        given:
        Task a = root.task("a", type: Parallel)
        Task b = root.task("b", type: Parallel).doLast {}

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "DefaultTask is parallelizable"() {
        given:
        Task a = root.task("a")
        Task b = root.task("b")

        when:
        addToGraphAndPopulate(a, b)

        then:
        startTasks(2)
    }

    def "Delete tasks are not parallelizable"() {
        given:
        Task clean = root.task("clean", type: Delete)
        Task parallelizable = root.task("parallelizable", type: Parallel)

        when:
        addToGraphAndPopulate(clean, parallelizable)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    @ParallelizableTask
    static class ParallelWithOutputFile extends DefaultTask {
        @OutputFile
        File outputFile
    }

    Task taskWithOutputFile(Project project = root, String taskName, File file) {
        project.task(taskName, type: ParallelWithOutputFile) {
            outputFile = file
        }
    }

    def "two parallelizable tasks that have the same file in outputs are not executed in parallel"() {
        given:
        Task a = taskWithOutputFile("a", file("output"))
        Task b = taskWithOutputFile("b", file("output"))

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    @ParallelizableTask
    static class ParallelWithOutputDirectory extends DefaultTask {
        @OutputDirectory
        File outputDirectory
    }

    Task taskWithOutputDirectory(Project project = root, String taskName, File directory) {
        project.task(taskName, type: ParallelWithOutputDirectory) {
            outputDirectory = directory
        }
    }

    def "a task that writes into a directory that is an output of currently running task is not started"() {
        given:
        Task a = taskWithOutputDirectory("a", file("outputDir"))
        Task b = taskWithOutputFile("b", file("outputDir").file("outputSubdir").file("output"))

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "a task that writes into an ancestor directory of a file that is an output of currently running task is not started"() {
        given:
        Task a = taskWithOutputFile("a", file("outputDir").file("outputSubdir").file("output"))
        Task b = taskWithOutputDirectory("b", file("outputDir"))

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    @Requires(TestPrecondition.SYMLINKS)
    def "a task that writes into a symlink that overlaps with output of currently running task is not started"() {
        given:
        def taskOutput = file("outputDir").createDir()
        def symlink = file("symlink")
        fs.createSymbolicLink(symlink, taskOutput)

        and:
        Task a = taskWithOutputDirectory("a", taskOutput)
        Task b = taskWithOutputFile("b", symlink.file("fileUnderSymlink"))

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    @Requires(TestPrecondition.SYMLINKS)
    def "a task that writes into a symlink of a shared output dir of currently running task is not started"() {
        given:
        def taskOutput = file("outputDir").createDir()
        def symlink = file("symlink")
        fs.createSymbolicLink(symlink, taskOutput)

        // Deleting any file clears the internal canonicalisation cache.
        // This allows the created symlink to be actually resolved.
        // See java.io.UnixFileSystem#cache.
        file("tmp").createFile().delete()

        and:
        Task a = taskWithOutputDirectory("a", taskOutput)
        Task b = taskWithOutputDirectory("b", symlink)

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()

        cleanup:
        assert symlink.delete()
    }

    def "tasks from two different projects that have the same file in outputs are not executed in parallel"() {
        given:
        Task a = taskWithOutputFile(createChildProject(root, "a"), "a", file("output"))
        Task b = taskWithOutputFile(createChildProject(root, "b"), "b", file("output"))

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }

    def "a task from different project that writes into a directory that is an output of currently running task is not started"() {
        given:
        Task a = taskWithOutputDirectory(createChildProject(root, "a"), "a", file("outputDir"))
        Task b = taskWithOutputFile(createChildProject(root, "b"), "b", file("outputDir").file("outputSubdir").file("output"))

        when:
        addToGraphAndPopulate(a, b)
        startTasks(1)

        then:
        noMoreTasksCurrentlyAvailableForExecution()
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy