<commit_before>/*
 * Copyright 2022 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.composite.internal

import org.gradle.StartParameter
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.component.BuildIdentifier
import org.gradle.api.internal.DocumentationRegistry
import org.gradle.api.internal.GradleInternal
import org.gradle.api.internal.SettingsInternal
import org.gradle.api.internal.TaskInternal
import org.gradle.api.internal.artifacts.DefaultBuildIdentifier
import org.gradle.api.internal.file.FileCollectionFactory
import org.gradle.api.internal.file.TestFiles
import org.gradle.api.internal.plugins.PluginManagerInternal
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.internal.project.ProjectState
import org.gradle.api.internal.project.taskfactory.TaskIdentity
import org.gradle.api.internal.tasks.NodeExecutionContext
import org.gradle.api.internal.tasks.TaskDestroyablesInternal
import org.gradle.api.internal.tasks.TaskLocalStateInternal
import org.gradle.api.internal.tasks.properties.PropertyWalker
import org.gradle.api.tasks.TaskDependency
import org.gradle.execution.plan.BuildWorkPlan
import org.gradle.execution.plan.DefaultExecutionPlan
import org.gradle.execution.plan.DefaultPlanExecutor
import org.gradle.execution.plan.ExecutionNodeAccessHierarchies
import org.gradle.execution.plan.ExecutionPlan
import org.gradle.execution.plan.Node
import org.gradle.execution.plan.NodeValidator
import org.gradle.execution.plan.OrdinalGroupFactory
import org.gradle.execution.plan.PlanExecutor
import org.gradle.execution.plan.SelfExecutingNode
import org.gradle.execution.plan.TaskDependencyResolver
import org.gradle.execution.plan.TaskNodeFactory
import org.gradle.initialization.DefaultBuildCancellationToken
import org.gradle.internal.build.BuildLifecycleController
import org.gradle.internal.build.BuildState
import org.gradle.internal.build.BuildToolingModelController
import org.gradle.internal.build.BuildWorkGraphController
import org.gradle.internal.build.DefaultBuildWorkGraphController
import org.gradle.internal.build.ExecutionResult
import org.gradle.internal.buildtree.BuildTreeWorkGraph
import org.gradle.internal.concurrent.CompositeStoppable
import org.gradle.internal.concurrent.DefaultExecutorFactory
import org.gradle.internal.concurrent.DefaultParallelismConfiguration
import org.gradle.internal.concurrent.ExecutorFactory
import org.gradle.internal.operations.TestBuildOperationExecutor
import org.gradle.internal.resources.DefaultResourceLockCoordinationService
import org.gradle.internal.resources.ResourceLock
import org.gradle.internal.service.DefaultServiceRegistry
import org.gradle.internal.snapshot.CaseSensitivity
import org.gradle.internal.work.DefaultWorkerLeaseService
import org.gradle.internal.work.WorkerLeaseService
import org.gradle.util.Path
import org.gradle.util.TestUtil
import org.gradle.util.internal.RedirectStdOutAndErr
import org.jetbrains.annotations.Nullable
import org.junit.Rule
import spock.lang.Shared
import spock.lang.Timeout

import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.function.Function

@Timeout(60)
class DefaultIncludedBuildTaskGraphParallelTest extends AbstractIncludedBuildTaskGraphTest {
    static final int MONITOR_POLL_TIME = 100
    static final int SLOW_NODE_EXECUTION_TIME = MONITOR_POLL_TIME * 4

    @Rule
    RedirectStdOutAndErr stdout = new RedirectStdOutAndErr()

    // Use a reasonable number of workers to catch issues with contention
    @Shared
    def manyWorkers = 10
    def cancellationToken = new DefaultBuildCancellationToken()

    def "does nothing when nothing scheduled"() {
        when:
        def result = scheduleAndRun(new Services(manyWorkers)) {
            // Nothing
        }

        then:
        result.failures.empty
    }

    def "runs scheduled work"() {
        def services = new Services(workers)
        def build = build(services, DefaultBuildIdentifier.ROOT)
        def node = new TestNode()

        when:
        def result = scheduleAndRun(services) { builder ->
            builder.withWorkGraph(build.state) { graphBuilder ->
                def task = task(build, node)
                graphBuilder.addEntryTasks([task])
            }
        }

        then:
        node.executed
        result.failures.empty

        where:
        workers << [1, manyWorkers]
    }

    def "runs scheduled work across multiple builds"() {
        def services = new Services(workers)
        def childBuild = build(services, new DefaultBuildIdentifier("child"))
        def build = build(services, DefaultBuildIdentifier.ROOT)
        def childNode = new TestNode("child build node")
        def node = new DelegateNode("main build node", [childNode])

        when:
        def result = scheduleAndRun(services) { builder ->
            builder.withWorkGraph(build.state) { graphBuilder ->
                def task = task(build, node)
                graphBuilder.addEntryTasks([task])
            }
            builder.withWorkGraph(childBuild.state) { graphBuilder ->
                def task = task(childBuild, childNode)
                graphBuilder.addEntryTasks([task])
            }
        }

        then:
        childNode.executed
        node.executed
        result.failures.empty

        where:
        workers << [1, manyWorkers]
    }

    def "fails when no further nodes can be selected"() {
        def services = new Services(manyWorkers)
        def build = build(services, DefaultBuildIdentifier.ROOT)
        def node = new DependenciesStuckNode()

        when:
        def result = scheduleAndRun(services) { builder ->
            builder.withWorkGraph(build.state) { graphBuilder ->
                def task = task(build, node)
                graphBuilder.addEntryTasks([task])
            }
        }

        then:
        node.stuck
        result.failures.size() == 1
        result.failures.first() instanceof IllegalStateException
        result.failures.first().message == "Unable to make progress running work. There are items queued for execution but none of them can be started"

        stdout.stdOut.contains("Unable to make progress running work. The following items are queued for execution but none of them can be started:")
        stdout.stdOut.contains("- test node (state=SHOULD_RUN")<commit_msg>Rework test to improve diagnostics.<commit_after>/*
 * Copyright 2022 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.composite.internal

import org.gradle.StartParameter
import org.gradle.api.Action
import org.gradle.api.DefaultTask
import org.gradle.api.artifacts.component.BuildIdentifier
import org.gradle.api.internal.DocumentationRegistry
import org.gradle.api.internal.GradleInternal
import org.gradle.api.internal.SettingsInternal
import org.gradle.api.internal.TaskInternal
import org.gradle.api.internal.artifacts.DefaultBuildIdentifier
import org.gradle.api.internal.file.FileCollectionFactory
import org.gradle.api.internal.file.TestFiles
import org.gradle.api.internal.plugins.PluginManagerInternal
import org.gradle.api.internal.project.ProjectInternal
import org.gradle.api.internal.project.ProjectState
import org.gradle.api.internal.project.taskfactory.TaskIdentity
import org.gradle.api.internal.tasks.NodeExecutionContext
import org.gradle.api.internal.tasks.TaskDestroyablesInternal
import org.gradle.api.internal.tasks.TaskLocalStateInternal
import org.gradle.api.internal.tasks.properties.PropertyWalker
import org.gradle.api.tasks.TaskDependency
import org.gradle.execution.plan.BuildWorkPlan
import org.gradle.execution.plan.DefaultExecutionPlan
import org.gradle.execution.plan.DefaultPlanExecutor
import org.gradle.execution.plan.ExecutionNodeAccessHierarchies
import org.gradle.execution.plan.ExecutionPlan
import org.gradle.execution.plan.Node
import org.gradle.execution.plan.NodeValidator
import org.gradle.execution.plan.OrdinalGroupFactory
import org.gradle.execution.plan.PlanExecutor
import org.gradle.execution.plan.SelfExecutingNode
import org.gradle.execution.plan.TaskDependencyResolver
import org.gradle.execution.plan.TaskNodeFactory
import org.gradle.initialization.DefaultBuildCancellationToken
import org.gradle.internal.build.BuildLifecycleController
import org.gradle.internal.build.BuildState
import org.gradle.internal.build.BuildToolingModelController
import org.gradle.internal.build.BuildWorkGraphController
import org.gradle.internal.build.DefaultBuildWorkGraphController
import org.gradle.internal.build.ExecutionResult
import org.gradle.internal.buildtree.BuildTreeWorkGraph
import org.gradle.internal.concurrent.CompositeStoppable
import org.gradle.internal.concurrent.DefaultExecutorFactory
import org.gradle.internal.concurrent.DefaultParallelismConfiguration
import org.gradle.internal.concurrent.ExecutorFactory
import org.gradle.internal.operations.TestBuildOperationExecutor
import org.gradle.internal.resources.DefaultResourceLockCoordinationService
import org.gradle.internal.resources.ResourceLock
import org.gradle.internal.service.DefaultServiceRegistry
import org.gradle.internal.snapshot.CaseSensitivity
import org.gradle.internal.work.DefaultWorkerLeaseService
import org.gradle.internal.work.WorkerLeaseService
import org.gradle.util.Path
import org.gradle.util.TestUtil
import org.gradle.util.internal.RedirectStdOutAndErr
import org.jetbrains.annotations.Nullable
import org.junit.Rule
import spock.lang.Shared
import spock.lang.Timeout

import java.util.concurrent.TimeUnit
import java.util.function.Consumer
import java.util.function.Function

@Timeout(60)
class DefaultIncludedBuildTaskGraphParallelTest extends AbstractIncludedBuildTaskGraphTest {
    static final int MONITOR_POLL_TIME = 100
    static final int SLOW_NODE_EXECUTION_TIME = MONITOR_POLL_TIME * 4

    @Rule
    RedirectStdOutAndErr stdout = new RedirectStdOutAndErr()

    // Use a reasonable number of workers to catch issues with contention
    @Shared
    def manyWorkers = 10
    def cancellationToken = new DefaultBuildCancellationToken()

    def "does nothing when nothing scheduled"() {
        when:
        def result = scheduleAndRun(new Services(manyWorkers)) {
            // Nothing
        }

        then:
        result.failures.empty
    }

    def "runs scheduled work"() {
        def services = new Services(workers)
        def build = build(services, DefaultBuildIdentifier.ROOT)
        def node = new TestNode()

        when:
        def result = scheduleAndRun(services) { builder ->
            builder.withWorkGraph(build.state) { graphBuilder ->
                def task = task(build, node)
                graphBuilder.addEntryTasks([task])
            }
        }

        then:
        result.failures.empty
        node.executed

        where:
        workers << [1, manyWorkers]
    }

    def "runs scheduled work across multiple builds"() {
        def services = new Services(workers)
        def childBuild = build(services, new DefaultBuildIdentifier("child"))
        def build = build(services, DefaultBuildIdentifier.ROOT)
        def childNode = new TestNode("child build node")
        def node = new DelegateNode("main build node", [childNode])

        when:
        def result = scheduleAndRun(services) { builder ->
            builder.withWorkGraph(build.state) { graphBuilder ->
                def task = task(build, node)
                graphBuilder.addEntryTasks([task])
            }
            builder.withWorkGraph(childBuild.state) { graphBuilder ->
                def task = task(childBuild, childNode)
                graphBuilder.addEntryTasks([task])
            }
        }

        then:
        result.failures.empty
        childNode.executed
        node.executed

        where:
        workers << [1, manyWorkers]
    }

    def "fails when no further nodes can be selected"() {
        def services = new Services(manyWorkers)
        def build = build(services, DefaultBuildIdentifier.ROOT)
        def node = new DependenciesStuckNode()

        when:
        def result = scheduleAndRun(services) { builder ->
            builder.withWorkGraph(build.state) { graphBuilder ->
                def task = task(build, node)
                graphBuilder.addEntryTasks([task])
            }
        }

        then:
        node.stuck
        result.failures.size() == 1
        result.failures.first() instanceof IllegalStateException
        result.failures.first().message == "Unable to make progress running work. There are items queued for execution but none of them can be started"

        stdout.stdOut.contains("Unable to make progress running work. The following items are queued for execution but none of them can be started:")
        stdout.stdOut.contains("- test node (state=SHOULD_RUN")
{'fraction_non_alphanumeric': 0.07839621288193946, 'fraction_numerical': 0.0027255773920527903, 'mean_word_length': 3.619946984758118, 'pattern_counts': {'":': 0, '<': 11, '<?xml version=': 0, '>': 17, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '146823', 'n_tokens_mistral': 3734, 'n_tokens_neox': 3517, 'n_words': 889}