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

org.gradle.integtests.composite.CompositeBuildOperationsIntegrationTest.groovy Maven / Gradle / Ivy

There is a newer version: 8.11.1
Show newest version
/*
 * 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.integtests.composite

import org.gradle.api.internal.tasks.execution.ExecuteTaskBuildOperationType
import org.gradle.execution.taskgraph.NotifyTaskGraphWhenReadyBuildOperationType
import org.gradle.initialization.BuildIdentifiedProgressDetails
import org.gradle.initialization.ConfigureBuildBuildOperationType
import org.gradle.initialization.LoadBuildBuildOperationType
import org.gradle.integtests.fixtures.build.BuildTestFile
import org.gradle.integtests.fixtures.executer.GradleContextualExecuter
import org.gradle.internal.operations.trace.BuildOperationRecord
import org.gradle.internal.taskgraph.CalculateTaskGraphBuildOperationType
import org.gradle.internal.taskgraph.CalculateTreeTaskGraphBuildOperationType
import org.gradle.launcher.exec.RunBuildBuildOperationType
import org.gradle.operations.lifecycle.FinishRootBuildTreeBuildOperationType
import org.gradle.operations.lifecycle.RunRequestedWorkBuildOperationType
import org.gradle.test.precondition.Requires
import org.gradle.test.preconditions.IntegTestPreconditions
import org.junit.Assume

import java.util.regex.Pattern

import static org.gradle.util.internal.TextUtil.getPlatformLineSeparator

class CompositeBuildOperationsIntegrationTest extends AbstractCompositeBuildIntegrationTest {
    BuildTestFile buildB

    def setup() {
        buildB = multiProjectBuild("buildB", ['b1', 'b2']) {
            buildFile << """
                allprojects {
                    apply plugin: 'java'
                }
"""
        }
        includedBuilds << buildB
    }

    def "generates build operations for tasks in included builds"() {
        given:
        dependency 'org.test:buildB:1.0'

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:jar"

        and:
        List allOps = operations.all(ExecuteTaskBuildOperationType)

        allOps.find { it.details.buildPath == ":buildB" && it.details.taskPath == ":jar" }
        allOps.find { it.details.buildPath == ":" && it.details.taskPath == ":jar" }

        for (BuildOperationRecord operationRecord : allOps) {
            assertChildrenNotIn(operationRecord, operationRecord, allOps)
        }
    }

    // Also covered by tests in configuration cache project
    @Requires(IntegTestPreconditions.NotConfigCached)
    def "generates build lifecycle operations for included builds with #display"() {
        given:
        dependency "org.test:${dependencyName}:1.0"

        buildB.settingsFile << settings << "\n"

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:jar"

        and:
        def root = operations.root(RunBuildBuildOperationType)

        def loadOps = operations.all(LoadBuildBuildOperationType)
        loadOps.size() == 2
        loadOps[0].displayName == "Load build"
        loadOps[0].details.buildPath == ":"
        loadOps[0].parentId == root.id
        loadOps[1].displayName == "Load build (:buildB)"
        loadOps[1].details.buildPath == ":buildB"
        loadOps[1].parentId == loadOps[0].id

        def buildIdentifiedEvents = operations.progress(BuildIdentifiedProgressDetails)
        buildIdentifiedEvents.size() == 2
        buildIdentifiedEvents[0].details.buildPath == ':'
        buildIdentifiedEvents[1].details.buildPath == ":buildB"

        def configureOps = operations.all(ConfigureBuildBuildOperationType)
        configureOps.size() == 2
        configureOps[0].displayName == "Configure build"
        configureOps[0].details.buildPath == ":"
        configureOps[0].parentId == root.id
        configureOps[1].displayName == "Configure build (:buildB)"
        configureOps[1].details.buildPath == ":buildB"
        configureOps[1].parentId == configureOps[0].id

        def treeTaskGraphOps = operations.all(CalculateTreeTaskGraphBuildOperationType)
        treeTaskGraphOps.size() == 1
        treeTaskGraphOps[0].displayName == "Calculate build tree task graph"
        treeTaskGraphOps[0].parentId == root.id

        def taskGraphOps = operations.all(CalculateTaskGraphBuildOperationType)
        taskGraphOps.size() == 2
        taskGraphOps[0].displayName == "Calculate task graph"
        taskGraphOps[0].details.buildPath == ":"
        taskGraphOps[0].parentId == treeTaskGraphOps[0].id
        taskGraphOps[1].displayName == "Calculate task graph (:buildB)"
        taskGraphOps[1].details.buildPath == ":buildB"
        taskGraphOps[1].parentId == treeTaskGraphOps[0].id

        def runMainTasks = operations.only(RunRequestedWorkBuildOperationType)
        runMainTasks.parentId == root.id

        def runTasksOps = operations.all(Pattern.compile("Run tasks.*"))
        runTasksOps.size() == 2
        // Build operations are run in parallel, so can appear in either order
        [runTasksOps[0].displayName, runTasksOps[1].displayName].sort() == ["Run tasks", "Run tasks (:buildB)"]
        runTasksOps[0].parentId == runMainTasks.id
        runTasksOps[1].parentId == runMainTasks.id

        def graphNotifyOps = operations.all(NotifyTaskGraphWhenReadyBuildOperationType)
        graphNotifyOps.size() == 2
        graphNotifyOps[0].displayName == "Notify task graph whenReady listeners (:buildB)"
        graphNotifyOps[0].details.buildPath == ":buildB"
        graphNotifyOps[0].parentId == treeTaskGraphOps[0].id
        graphNotifyOps[1].displayName == 'Notify task graph whenReady listeners'
        graphNotifyOps[1].details.buildPath == ':'
        graphNotifyOps[1].parentId == treeTaskGraphOps[0].id

        where:
        settings                     | dependencyName | display
        ""                           | "buildB"       | "default root project name"
        "rootProject.name='someLib'" | "someLib"      | "configured root project name"
    }

    // Also covered by tests in configuration cache project
    @Requires(IntegTestPreconditions.NotConfigCached)
    def "generates build lifecycle operations for multiple included builds"() {
        given:
        def buildC = multiProjectBuild("buildC", ["someLib"]) {
            buildFile << """
                allprojects {
                    apply plugin: 'java'
                }
            """
        }
        includedBuilds << buildC
        dependency "org.test:buildB:1.0"
        dependency "org.test:buildC:1.0"
        dependency buildB, "org.test:buildC:1.0"

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:jar", ":buildC:jar"

        and:
        def root = operations.root(RunBuildBuildOperationType)

        def treeTaskGraphOps = operations.all(CalculateTreeTaskGraphBuildOperationType)
        treeTaskGraphOps.size() == 1
        treeTaskGraphOps[0].displayName == "Calculate build tree task graph"
        treeTaskGraphOps[0].parentId == root.id

        def taskGraphOps = operations.all(CalculateTaskGraphBuildOperationType)
        taskGraphOps.size() == 3
        taskGraphOps[0].displayName == "Calculate task graph"
        taskGraphOps[0].details.buildPath == ":"
        taskGraphOps[0].parentId == treeTaskGraphOps[0].id
        taskGraphOps[1].displayName == "Calculate task graph (:buildB)"
        taskGraphOps[1].details.buildPath == ":buildB"
        taskGraphOps[1].parentId == treeTaskGraphOps[0].id
        taskGraphOps[2].displayName == "Calculate task graph (:buildC)"
        taskGraphOps[2].details.buildPath == ":buildC"
        taskGraphOps[2].parentId == treeTaskGraphOps[0].id

        def graphNotifyOps = operations.all(NotifyTaskGraphWhenReadyBuildOperationType)
        graphNotifyOps.size() == 3
        graphNotifyOps[0].displayName == "Notify task graph whenReady listeners (:buildB)"
        graphNotifyOps[0].details.buildPath == ":buildB"
        graphNotifyOps[0].parentId == treeTaskGraphOps[0].id
        graphNotifyOps[1].displayName == "Notify task graph whenReady listeners (:buildC)"
        graphNotifyOps[1].details.buildPath == ":buildC"
        graphNotifyOps[1].parentId == treeTaskGraphOps[0].id
        graphNotifyOps[2].displayName == 'Notify task graph whenReady listeners'
        graphNotifyOps[2].details.buildPath == ':'
        graphNotifyOps[2].parentId == treeTaskGraphOps[0].id
    }

    // Also covered by tests in configuration cache project
    @Requires(IntegTestPreconditions.NotConfigCached)
    def "generates build lifecycle operations for multiple included builds used as buildscript dependencies"() {
        given:
        def buildC = multiProjectBuild("buildC", ["someLib"]) {
            buildFile << """
                allprojects {
                    apply plugin: 'java'
                }
            """
        }
        includedBuilds << buildC
        buildA.buildFile.prepend("""
            buildscript {
                dependencies {
                    classpath 'org.test:buildB:1.0'
                    classpath 'org.test:buildC:1.0'
                }
            }
        """)
        dependency buildB, "org.test:buildC:1.0"

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:jar", ":buildC:jar"

        and:
        def root = operations.root(RunBuildBuildOperationType)

        def applyRootProjectBuildScript = operations.first(Pattern.compile("Apply build file 'build.gradle' to root project 'buildA'"))

        def treeTaskGraphOps = operations.all(CalculateTreeTaskGraphBuildOperationType)
        treeTaskGraphOps.size() == 2
        treeTaskGraphOps[0].displayName == "Calculate build tree task graph"
        treeTaskGraphOps[0].parentId == applyRootProjectBuildScript.id
        treeTaskGraphOps[1].displayName == "Calculate build tree task graph"
        treeTaskGraphOps[1].parentId == root.id

        def taskGraphOps = operations.all(CalculateTaskGraphBuildOperationType)
        taskGraphOps.size() == 3
        taskGraphOps[0].displayName == "Calculate task graph (:buildB)"
        taskGraphOps[0].details.buildPath == ":buildB"
        taskGraphOps[0].parentId == treeTaskGraphOps[0].id
        taskGraphOps[1].displayName == "Calculate task graph (:buildC)"
        taskGraphOps[1].details.buildPath == ":buildC"
        taskGraphOps[1].parentId == treeTaskGraphOps[0].id
        taskGraphOps[2].displayName == "Calculate task graph"
        taskGraphOps[2].details.buildPath == ":"
        taskGraphOps[2].parentId == treeTaskGraphOps[1].id

        def graphNotifyOps = operations.all(NotifyTaskGraphWhenReadyBuildOperationType)
        graphNotifyOps.size() == 3
        graphNotifyOps[0].displayName == "Notify task graph whenReady listeners (:buildB)"
        graphNotifyOps[0].details.buildPath == ":buildB"
        graphNotifyOps[0].parentId == treeTaskGraphOps[0].id
        graphNotifyOps[1].displayName == "Notify task graph whenReady listeners (:buildC)"
        graphNotifyOps[1].details.buildPath == ":buildC"
        graphNotifyOps[1].parentId == treeTaskGraphOps[0].id
        graphNotifyOps[2].displayName == 'Notify task graph whenReady listeners'
        graphNotifyOps[2].details.buildPath == ':'
        graphNotifyOps[2].parentId == treeTaskGraphOps[1].id
    }

    // Also covered by tests in configuration cache project
    @Requires(IntegTestPreconditions.NotConfigCached)
    def "generates build lifecycle operations for included build used as buildscript and production dependency"() {
        given:
        buildA.buildFile.prepend("""
            buildscript {
                dependencies {
                    classpath 'org.test:b1:1.0'
                }
            }
        """)
        dependency "org.test:b2:1.0"

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:b1:jar", ":buildB:b2:jar"

        and:
        def root = operations.root(RunBuildBuildOperationType)

        def loadOps = operations.all(LoadBuildBuildOperationType)
        loadOps.size() == 2
        loadOps[0].displayName == "Load build"
        loadOps[0].details.buildPath == ":"
        loadOps[0].parentId == root.id
        loadOps[1].displayName == "Load build (:buildB)"
        loadOps[1].details.buildPath == ":buildB"
        loadOps[1].parentId == loadOps[0].id

        def buildIdentifiedEvents = operations.progress(BuildIdentifiedProgressDetails)
        buildIdentifiedEvents.size() == 2
        buildIdentifiedEvents[0].details.buildPath == ':'
        buildIdentifiedEvents[1].details.buildPath == ':buildB'

        def configureOps = operations.all(ConfigureBuildBuildOperationType)
        configureOps.size() == 2
        configureOps[0].displayName == "Configure build"
        configureOps[0].details.buildPath == ":"
        configureOps[0].parentId == root.id
        configureOps[1].displayName == "Configure build (:buildB)"
        configureOps[1].details.buildPath == ":buildB"
        configureOps[1].parentId == configureOps[0].id

        def applyRootProjectBuildScript = operations.first(Pattern.compile("Apply build file 'build.gradle' to root project 'buildA'"))

        def treeTaskGraphOps = operations.all(CalculateTreeTaskGraphBuildOperationType)
        treeTaskGraphOps.size() == 2
        treeTaskGraphOps[0].displayName == "Calculate build tree task graph"
        treeTaskGraphOps[0].parentId == applyRootProjectBuildScript.id
        treeTaskGraphOps[1].displayName == "Calculate build tree task graph"
        treeTaskGraphOps[1].parentId == root.id

        // The task graph for buildB is calculated multiple times, once for the buildscript dependency and again for the production dependency
        def taskGraphOps = operations.all(CalculateTaskGraphBuildOperationType)
        taskGraphOps.size() == 3
        taskGraphOps[0].displayName == "Calculate task graph (:buildB)"
        taskGraphOps[0].details.buildPath == ":buildB"
        taskGraphOps[0].parentId == treeTaskGraphOps[0].id
        taskGraphOps[1].displayName == "Calculate task graph"
        taskGraphOps[1].details.buildPath == ":"
        taskGraphOps[1].parentId == treeTaskGraphOps[1].id
        taskGraphOps[2].displayName == "Calculate task graph (:buildB)"
        taskGraphOps[2].details.buildPath == ":buildB"
        taskGraphOps[2].parentId == treeTaskGraphOps[1].id

        def runMainTasks = operations.only(RunRequestedWorkBuildOperationType)
        runMainTasks.parentId == root.id

        // Tasks are run for buildB multiple times, once for buildscript dependency and again for production dependency
        def runTasksOps = operations.all(Pattern.compile("Run tasks.*"))
        runTasksOps.size() == 3
        runTasksOps[0].displayName == "Run tasks (:buildB)"
        runTasksOps[0].parentId == applyRootProjectBuildScript.id
        // Build operations are run in parallel, so can appear in either order
        [runTasksOps[1].displayName, runTasksOps[2].displayName].sort() == ["Run tasks", "Run tasks (:buildB)"]
        runTasksOps[1].parentId == runMainTasks.id
        runTasksOps[2].parentId == runMainTasks.id

        // Task graph ready event sent only once
        def graphNotifyOps = operations.all(NotifyTaskGraphWhenReadyBuildOperationType)
        graphNotifyOps.size() == 2
        graphNotifyOps[0].displayName == 'Notify task graph whenReady listeners (:buildB)'
        graphNotifyOps[0].details.buildPath == ':buildB'
        graphNotifyOps[0].parentId == treeTaskGraphOps[0].id
        graphNotifyOps[1].displayName == "Notify task graph whenReady listeners"
        graphNotifyOps[1].details.buildPath == ":"
        graphNotifyOps[1].parentId == treeTaskGraphOps[1].id
    }

    def "generates finish build tree lifecycle operation for included builds without build finished operations"() {
        given:
        def buildC = multiProjectBuild("buildC", ["someLib"]) {
            buildFile << """
                allprojects {
                    apply plugin: 'java'
                }
            """
        }
        includedBuilds << buildC
        buildA.buildFile.prepend("""
            buildscript {
                dependencies {
                    classpath 'org.test:buildB:1.0'
                    classpath 'org.test:buildC:1.0'
                }
            }
        """)
        dependency buildB, "org.test:buildC:1.0"

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:jar", ":buildC:jar"

        and:
        operations.only(FinishRootBuildTreeBuildOperationType)
    }

    def "generates finish build tree lifecycle operation for included builds with #description"() {
        if (GradleContextualExecuter.configCache) {
            Assume.assumeFalse(description == "buildFinished")
        }
        given:
        def buildC = multiProjectBuild("buildC", ["someLib"]) {
            buildFile << """
                allprojects {
                    apply plugin: 'java'
                }
            """ << registration("buildC")
        }
        includedBuilds << buildC
        buildA.buildFile.prepend("""
            buildscript {
                dependencies {
                    classpath 'org.test:buildB:1.0'
                    classpath 'org.test:buildC:1.0'
                }
            }
        """)
        buildA.buildFile << registration("buildA")
        dependency buildB, "org.test:buildC:1.0"
        buildB.buildFile << registration("buildB")

        when:
        execute(buildA, ":jar")

        then:
        executed ":buildB:jar", ":buildC:jar"

        and:
        def buildFinished = operations.only(FinishRootBuildTreeBuildOperationType)
        buildFinished.progress.size() == 3
        buildFinished.progress.details.spans.text.flatten() ==~ ["buildA", "buildB", "buildC"].collect { "$message $it${getPlatformLineSeparator()}".toString() }

        where:
        description     | registration                                                          | message
        "buildFinished" | CompositeBuildOperationsIntegrationTest.&buildFinishedRegistrationFor | "buildFinished from"
        "flow actions"  | CompositeBuildOperationsIntegrationTest.&flowActionRegistrationFor    | "flowAction from"
    }

    def "build tree finished operation happens even when configuration fails"() {
        buildA.buildFile.text = """
            buildscript {
                dependencies {
                    classpath 'org.test:buildB:1.0'
                }
            }
        """ + buildA.buildFile.text
        buildB.file("src/main/java/Broken.java").text = "class Does not compile {}"
        when:
        fails(buildA, ":jar")

        then:
        executed ":buildB:compileJava"

        operations.none(RunRequestedWorkBuildOperationType)
        operations.only(FinishRootBuildTreeBuildOperationType)
    }

    def "generates finish build tree lifecycle operation for empty project"() {
        given:
        includedBuilds.clear()
        buildA.buildFile.text = ""

        when:
        execute(buildA, "help")

        then:
        executed ":help"

        and:
        operations.only(FinishRootBuildTreeBuildOperationType)
    }

    static String getFlowActionClass() {
        """
            abstract class LogBuild implements FlowAction {
                interface Parameters extends FlowParameters {
                    @Input
                    Property getBuildName()
                }

                @Override
                void execute(Parameters parameters) {
                    println "flowAction from \${parameters.buildName.get()}"
                }
            }
        """
    }

    static String buildFinishedRegistrationFor(String buildName) {
        """
            gradle.buildFinished {
                println "buildFinished from $buildName"
            }
        """
    }

    static String flowActionRegistrationFor(String buildName) {
        flowActionClass + """
            def flowScope = gradle.services.get(FlowScope)
            def flowProviders = gradle.services.get(FlowProviders)
            flowScope.always(LogBuild) {
                parameters.buildName = flowProviders.buildWorkResult.map { result -> "$buildName" }
            }
        """
    }

    def assertChildrenNotIn(BuildOperationRecord origin, BuildOperationRecord op, List allOps) {
        for (BuildOperationRecord child : op.children) {
            assert !allOps.contains(child): "Task operation $origin has child $child which is also a task operation"
            assertChildrenNotIn(origin, child, allOps)
        }
    }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy