diff --git a/src/test/groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy b/src/test/groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy deleted file mode 100644 index 0dbd7985e..000000000 --- a/src/test/groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy +++ /dev/null @@ -1,424 +0,0 @@ -/* - * (c) Copyright 2026 Palantir Technologies Inc. All rights reserved. - * - * 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 com.palantir.gradle.versions - -import org.gradle.testkit.runner.BuildResult -import org.gradle.testkit.runner.TaskOutcome -import spock.lang.Unroll - -import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS -import static com.palantir.gradle.versions.PomUtils.makePlatformPom - -/** - * This tests the interaction of this plugin with Gradle's configuration-on-demand feature: - * https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand - */ -@Unroll -class ConfigurationOnDemandSpec extends IntegrationSpec { - - static def PLUGIN_NAME = "com.palantir.consistent-versions" - private File mavenRepo - - /* - * Project structure (arrows indicate dependencies): - * - * upstream unrelated - * ^ ^ - * | | - * downstream1 downstream2 - */ - void setup() { - mavenRepo = generateMavenRepo( - 'com.example:dependency-of-upstream:1.2.3', - 'com.example:dependency-of-upstream:100.1.1', - 'com.example:dependency-of-downstream1:1.2.3', - 'com.example:dependency-of-downstream2:1.2.3', - 'com.example:dependency-of-unrelated:1.2.3', - 'com.example:dep-with-version-bumped-by-unrelated:1.0.0', - 'com.example:dep-with-version-bumped-by-unrelated:1.1.0', - 'com.example:transitive-test-dep:1.0.0', - 'com.example:transitive-test-dep:1.1.0', - 'com.example:transitive-test-dep:1.2.0', - ) - - makePlatformPom(mavenRepo, "org", "platform", "1.0") - - buildFile.text = """ - plugins { - id '${PLUGIN_NAME}' - } - allprojects { - repositories { - maven { url "file:///${mavenRepo.getAbsolutePath()}" } - } - } - subprojects { - tasks.register('writeClasspath') { - doLast { - println(configurations.runtimeClasspath.getFiles()) - } - } - } - - // Get rid of deprecation warnings for Gradle 7+ - versionRecommendations { - excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime' - } - """.stripIndent(true) - - file('versions.props').text = """ - com.example:dependency-of-upstream = 1.2.3 - com.example:dependency-of-downstream1 = 1.2.3 - com.example:dependency-of-downstream2 = 1.2.3 - com.example:dependency-of-unrelated = 1.2.3 - # 1.0.0 is a minimum, we expect this to be locked to 1.1.0 - com.example:dep-with-version-bumped-by-unrelated = 1.0.0 - """.stripIndent(true) - - addSubproject("upstream", """ - plugins { - id 'java' - } - println 'configuring upstream' - dependencies { - implementation 'com.example:dependency-of-upstream' - } - """.stripIndent(true)) - - addSubproject("downstream1", """ - plugins { - id 'java' - } - println 'configuring downstream1' - dependencies { - implementation project(':upstream') - implementation 'com.example:dependency-of-downstream1' - } - """.stripIndent(true)) - - addSubproject("downstream2", """ - plugins { - id 'java' - } - println 'configuring downstream2' - dependencies { - implementation project(':upstream') - implementation 'com.example:dependency-of-downstream2' - implementation 'com.example:dep-with-version-bumped-by-unrelated' - } - """.stripIndent(true)) - - addSubproject("unrelated", """ - plugins { - id 'java' - } - println 'configuring unrelated' - dependencies { - implementation 'com.example:dependency-of-unrelated' - implementation 'com.example:dep-with-version-bumped-by-unrelated:1.1.0' - } - """.stripIndent(true)) - - file("gradle.properties").text = """ - org.gradle.configureondemand=true - """.stripIndent(true) - } - - def '#gradleVersionNumber: can write locks'() { - setup: - gradleVersion = gradleVersionNumber - - when: - BuildResult result = runTasks('--write-locks') - - then: - result.output.contains('configuring upstream') - result.output.contains('configuring downstream1') - result.output.contains('configuring downstream2') - result.output.contains('configuring unrelated') - - new File(projectDir, "versions.lock").exists() - file("versions.lock").text.contains("com.example:dependency-of-upstream:1.2.3") - file("versions.lock").text.contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0") - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: can write locks when a task in one project is specified'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks(':downstream1:build', '--write-locks') - - then: - new File(projectDir, "versions.lock").exists() - file("versions.lock").text.contains("com.example:dependency-of-unrelated:1.2.3") - file("versions.lock").text.contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0") - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: applying the plugin does not force all projects to be configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - // Both absolute and relative formats work, as long as Gradle is run from the root project directory - BuildResult result1 = runTasks(':downstream1:build') - BuildResult result2 = runTasks('downstream1:build') - - then: - result1.output.contains('configuring upstream') - result1.output.contains('configuring downstream1') - !result1.output.contains('configuring downstream2') - !result1.output.contains('configuring unrelated') - - result2.output.contains('configuring upstream') - result2.output.contains('configuring downstream1') - !result2.output.contains('configuring downstream2') - !result2.output.contains('configuring unrelated') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: resolving a classpath does not force all projects to be configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - BuildResult result = runTasks(':downstream1:writeClasspath') - - then: - result.output.contains('configuring upstream') - result.output.contains('configuring downstream1') - !result.output.contains('configuring downstream2') - !result.output.contains('configuring unrelated') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: after lockfile is written, versions constraints due to non-configured projects are still respected'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - BuildResult result = runTasks(':downstream2:writeClasspath') - - then: - // Version used is 1.1.0 due to the unrelated project - result.output.contains('dep-with-version-bumped-by-unrelated-1.1.0.jar') - !result.output.contains('configured unrelated') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: transitive dependencies cause upstream projects to be configured sufficiently early'() { - setup: - gradleVersion = gradleVersionNumber - addSubproject("a", """ - plugins { id 'java' } - dependencies { - implementation 'com.example:transitive-test-dep:1.0.0' - } - """.stripIndent(true)) - addSubproject("b", """ - plugins { id 'java' } - dependencies { - implementation project(':a') - } - """.stripIndent(true)) - addSubproject("c", """ - plugins { id 'java' } - dependencies { - implementation project(':b') - } - tasks.register('writeClasspathOfA') { - doLast { - println project(':a').configurations.runtimeClasspath.files - } - } - """.stripIndent(true)) - addSubproject("u", """ - plugins { id 'java' } - dependencies { - implementation 'com.example:transitive-test-dep:1.1.0' - } - """.stripIndent(true)) - - when: - runTasks('--write-locks') - BuildResult result = runTasks(':c:writeClasspathOfA') - - then: - // Version used should be 1.1.0, indicating that the version.lock constraint was applied - result.output.contains('transitive-test-dep-1.1.0.jar') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: task dependencies cause upstream projects to be configured sufficiently early'() { - setup: - gradleVersion = gradleVersionNumber - addSubproject("a", """ - plugins { id 'java' } - dependencies { - implementation 'com.example:transitive-test-dep:1.0.0' - } - """.stripIndent(true)) - addSubproject("b", """ - tasks.register('foo') { - dependsOn ':a:writeClasspath' - } - """.stripIndent(true)) - addSubproject("c", """ - tasks.register('bar') { - dependsOn ':b:foo' - } - """.stripIndent(true)) - addSubproject("u", """ - plugins { id 'java' } - dependencies { - implementation 'com.example:transitive-test-dep:1.1.0' - } - """.stripIndent(true)) - - when: - runTasks('--write-locks') - BuildResult result = runTasks(':c:bar') - - then: - // Version used should be 1.1.0, indicating that the version.lock constraint was applied - result.output.contains('transitive-test-dep-1.1.0.jar') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: verification tasks pass when all projects are configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - // Note: Not specifying the project causes all projects to be configured regardless of CoD - BuildResult result = runTasks('checkUnusedConstraints', 'verifyLocks') - - then: - result.output.contains('configuring upstream') - result.task(':checkUnusedConstraints').outcome == TaskOutcome.SUCCESS - result.task(':verifyLocks').outcome == TaskOutcome.SUCCESS - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: checkUnusedConstraints succeeds when not all projects are configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - BuildResult result = runTasks(':checkUnusedConstraints') - - then: - result.task(':checkUnusedConstraints').outcome == TaskOutcome.SUCCESS - result.output.contains('configuring upstream') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: verifyLocks fails and warns when not all projects are configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - BuildResult result = runTasksAndFail(':verifyLocks') - - then: - !result.output.contains('configuring upstream') - result.task(':verifyLocks').outcome == TaskOutcome.FAILED - result.output.contains("All projects must have been configured for this task to work correctly, but due to " + - "Gradle configuration-on-demand, not all projects were configured.") - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - // As failing tasks can't be considered UP-TO-DATE, we only need to check the case where the task passing - // is followed by the task running with incomplete configuration. - def '#gradleVersionNumber: verification tasks are not UP-TO-DATE when the set of configured projects differs'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - runTasks('build') - - then: - runTasksAndFail(':verifyLocks') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: the why task works when all projects are configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - BuildResult result = runTasks('why', '--hash=0805f935') - - then: - result.task(':why').outcome == TaskOutcome.SUCCESS - - where: - gradleVersionNumber << GRADLE_VERSIONS - } - - def '#gradleVersionNumber: the why task somehow forces all projects to be configured'() { - setup: - gradleVersion = gradleVersionNumber - - when: - runTasks('--write-locks') - BuildResult result = runTasks(':why', '--hash=0805f935') - - then: - result.output.contains('configuring upstream') - result.output.contains('configuring downstream1') - result.output.contains('configuring downstream2') - result.output.contains('configuring unrelated') - result.task(':why').outcome == TaskOutcome.SUCCESS - result.output.contains('com.example:dependency-of-unrelated:1.2.3\n\tprojects -> 1.2.3') - - where: - gradleVersionNumber << GRADLE_VERSIONS - } -} diff --git a/src/test/java/com/palantir/gradle/versions/ConfigurationOnDemandTest.java b/src/test/java/com/palantir/gradle/versions/ConfigurationOnDemandTest.java new file mode 100644 index 000000000..2d0446543 --- /dev/null +++ b/src/test/java/com/palantir/gradle/versions/ConfigurationOnDemandTest.java @@ -0,0 +1,366 @@ +/* + * (c) Copyright 2021 Palantir Technologies Inc. All rights reserved. + * + * 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 com.palantir.gradle.versions; + +import static com.palantir.gradle.testing.assertion.GradlePluginTestAssertions.assertThat; +import static org.assertj.core.api.Assertions.assertThat; + +import com.palantir.gradle.testing.execution.GradleInvoker; +import com.palantir.gradle.testing.execution.InvocationResult; +import com.palantir.gradle.testing.junit.DisabledConfigurationCache; +import com.palantir.gradle.testing.junit.GradlePluginTests; +import com.palantir.gradle.testing.maven.MavenArtifact; +import com.palantir.gradle.testing.maven.MavenRepo; +import com.palantir.gradle.testing.project.RootProject; +import com.palantir.gradle.testing.project.SubProject; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +/** + * This tests the interaction of this plugin with Gradle's configuration-on-demand feature: + * https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand + */ +@GradlePluginTests +@DisabledConfigurationCache("configuration cache cannot be reused due to --write-locks") +class ConfigurationOnDemandTest { + + private static final String PLUGIN_NAME = "com.palantir.consistent-versions"; + + /* + * Project structure (arrows indicate dependencies): + * + * upstream unrelated + * ^ ^ + * | | + * downstream1 downstream2 + */ + @BeforeEach + void setup( + MavenRepo repo, + RootProject rootProject, + SubProject upstream, + SubProject downstream1, + SubProject downstream2, + SubProject unrelated) { + repo.publish( + MavenArtifact.of("com.example:dependency-of-upstream:1.2.3"), + MavenArtifact.of("com.example:dependency-of-upstream:100.1.1"), + MavenArtifact.of("com.example:dependency-of-downstream1:1.2.3"), + MavenArtifact.of("com.example:dependency-of-downstream2:1.2.3"), + MavenArtifact.of("com.example:dependency-of-unrelated:1.2.3"), + MavenArtifact.of("com.example:dep-with-version-bumped-by-unrelated:1.0.0"), + MavenArtifact.of("com.example:dep-with-version-bumped-by-unrelated:1.1.0"), + MavenArtifact.of("com.example:transitive-test-dep:1.0.0"), + MavenArtifact.of("com.example:transitive-test-dep:1.1.0"), + MavenArtifact.of("com.example:transitive-test-dep:1.2.0")); + + PomUtils.makePlatformPom(rootProject, repo, "org", "platform", "1.0"); + + rootProject.buildGradle().plugins().add(PLUGIN_NAME); + rootProject.buildGradle().append(""" + allprojects { + repositories { + maven { url uri("%s") } + } + } + subprojects { + tasks.register('writeClasspath') { + doLast { + println(configurations.runtimeClasspath.getFiles()) + } + } + } + + // Get rid of deprecation warnings for Gradle 7+ + versionRecommendations { + excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime' + } + """, repo.path()); + + rootProject.file("versions.props").overwrite(""" + com.example:dependency-of-upstream = 1.2.3 + com.example:dependency-of-downstream1 = 1.2.3 + com.example:dependency-of-downstream2 = 1.2.3 + com.example:dependency-of-unrelated = 1.2.3 + # 1.0.0 is a minimum, we expect this to be locked to 1.1.0 + com.example:dep-with-version-bumped-by-unrelated = 1.0.0 + """); + + upstream.buildGradle().plugins().add("java"); + upstream.buildGradle().append(""" + println 'configuring upstream' + dependencies { + implementation 'com.example:dependency-of-upstream' + } + """); + + downstream1.buildGradle().plugins().add("java"); + downstream1.buildGradle().append(""" + println 'configuring downstream1' + dependencies { + implementation project(':upstream') + implementation 'com.example:dependency-of-downstream1' + } + """); + + downstream2.buildGradle().plugins().add("java"); + downstream2.buildGradle().append(""" + println 'configuring downstream2' + dependencies { + implementation project(':upstream') + implementation 'com.example:dependency-of-downstream2' + implementation 'com.example:dep-with-version-bumped-by-unrelated' + } + """); + + unrelated.buildGradle().plugins().add("java"); + unrelated.buildGradle().append(""" + println 'configuring unrelated' + dependencies { + implementation 'com.example:dependency-of-unrelated' + implementation 'com.example:dep-with-version-bumped-by-unrelated:1.1.0' + } + """); + + rootProject.gradlePropertiesFile().setProperty("org.gradle.configureondemand", "true"); + } + + @Test + void can_write_locks(GradleInvoker gradle, RootProject rootProject) { + InvocationResult result = gradle.withArgs("--write-locks").buildsSuccessfully(); + + assertThat(result) + .output() + .contains("configuring upstream") + .contains("configuring downstream1") + .contains("configuring downstream2") + .contains("configuring unrelated"); + + rootProject.file("versions.lock").assertThat().exists(); + assertThat(rootProject.file("versions.lock").text()) + .contains("com.example:dependency-of-upstream:1.2.3") + .contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0"); + } + + @Test + void can_write_locks_when_a_task_in_one_project_is_specified(GradleInvoker gradle, RootProject rootProject) { + gradle.withArgs(":downstream1:build", "--write-locks").buildsSuccessfully(); + + rootProject.file("versions.lock").assertThat().exists(); + assertThat(rootProject.file("versions.lock").text()) + .contains("com.example:dependency-of-unrelated:1.2.3") + .contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0"); + } + + @Test + void applying_the_plugin_does_not_force_all_projects_to_be_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + // Both absolute and relative formats work, as long as Gradle is run from the root project directory + InvocationResult result1 = gradle.withArgs(":downstream1:build").buildsSuccessfully(); + InvocationResult result2 = gradle.withArgs("downstream1:build").buildsSuccessfully(); + + assertThat(result1) + .output() + .contains("configuring upstream") + .contains("configuring downstream1") + .doesNotContain("configuring downstream2") + .doesNotContain("configuring unrelated"); + + assertThat(result2) + .output() + .contains("configuring upstream") + .contains("configuring downstream1") + .doesNotContain("configuring downstream2") + .doesNotContain("configuring unrelated"); + } + + @Test + void resolving_a_classpath_does_not_force_all_projects_to_be_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":downstream1:writeClasspath").buildsSuccessfully(); + + assertThat(result) + .output() + .contains("configuring upstream") + .contains("configuring downstream1") + .doesNotContain("configuring downstream2") + .doesNotContain("configuring unrelated"); + } + + @Test + void after_lockfile_is_written_versions_constraints_due_to_non_configured_projects_are_still_respected( + GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":downstream2:writeClasspath").buildsSuccessfully(); + + assertThat(result) + .output() + .as("Version used is 1.1.0 due to the unrelated project") + .contains("dep-with-version-bumped-by-unrelated-1.1.0.jar") + .doesNotContain("configured unrelated"); + } + + @Test + void transitive_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early( + GradleInvoker gradle, SubProject projectA, SubProject projectB, SubProject projectC, SubProject projectU) { + projectA.buildGradle().plugins().add("java"); + projectA.buildGradle().append(""" + dependencies { + implementation 'com.example:transitive-test-dep:1.0.0' + } + """); + + projectB.buildGradle().plugins().add("java"); + projectB.buildGradle().append(""" + dependencies { + implementation project(':projectA') + } + """); + + projectC.buildGradle().plugins().add("java"); + projectC.buildGradle().append(""" + dependencies { + implementation project(':projectB') + } + tasks.register('writeClasspathOfA') { + doLast { + println project(':projectA').configurations.runtimeClasspath.files + } + } + """); + + projectU.buildGradle().plugins().add("java"); + projectU.buildGradle().append(""" + dependencies { + implementation 'com.example:transitive-test-dep:1.1.0' + } + """); + + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":projectC:writeClasspathOfA").buildsSuccessfully(); + + assertThat(result) + .output() + .as("Version used should be 1.1.0, indicating that the version.lock constraint was applied") + .contains("transitive-test-dep-1.1.0.jar"); + } + + @Test + void task_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early( + GradleInvoker gradle, SubProject projectA, SubProject projectB, SubProject projectC, SubProject projectU) { + projectA.buildGradle().plugins().add("java"); + projectA.buildGradle().append(""" + dependencies { + implementation 'com.example:transitive-test-dep:1.0.0' + } + """); + + projectB.buildGradle().append(""" + tasks.register('foo') { + dependsOn ':projectA:writeClasspath' + } + """); + + projectC.buildGradle().append(""" + tasks.register('bar') { + dependsOn ':projectB:foo' + } + """); + + projectU.buildGradle().plugins().add("java"); + projectU.buildGradle().append(""" + dependencies { + implementation 'com.example:transitive-test-dep:1.1.0' + } + """); + + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":projectC:bar").buildsSuccessfully(); + + assertThat(result) + .output() + .as("Version used should be 1.1.0, indicating that the version.lock constraint was applied") + .contains("transitive-test-dep-1.1.0.jar"); + } + + @Test + void verification_tasks_pass_when_all_projects_are_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + // Note: Not specifying the project causes all projects to be configured regardless of CoD + InvocationResult result = + gradle.withArgs("checkUnusedConstraints", "verifyLocks").buildsSuccessfully(); + + assertThat(result).output().contains("configuring upstream"); + assertThat(result).task(":checkUnusedConstraints").succeeded(); + assertThat(result).task(":verifyLocks").succeeded(); + } + + @Test + void checkUnusedConstraints_succeeds_when_not_all_projects_are_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":checkUnusedConstraints").buildsSuccessfully(); + + assertThat(result).task(":checkUnusedConstraints").succeeded(); + assertThat(result).output().contains("configuring upstream"); + } + + @Test + void verifyLocks_fails_and_warns_when_not_all_projects_are_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":verifyLocks").buildsWithFailure(); + + assertThat(result) + .output() + .doesNotContain("configuring upstream") + .contains("All projects must have been configured for this task to work correctly, but due to " + + "Gradle configuration-on-demand, not all projects were configured."); + assertThat(result).task(":verifyLocks").failed(); + } + + // As failing tasks can't be considered UP-TO-DATE, we only need to check the case where the task passing + // is followed by the task running with incomplete configuration. + @Test + void verification_tasks_are_not_up_to_date_when_the_set_of_configured_projects_differs(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + gradle.withArgs("build").buildsSuccessfully(); + + gradle.withArgs(":verifyLocks").buildsWithFailure(); + } + + @Test + void the_why_task_works_when_all_projects_are_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs("why", "--hash=0805f935").buildsSuccessfully(); + + assertThat(result).task(":why").succeeded(); + } + + @Test + void the_why_task_somehow_forces_all_projects_to_be_configured(GradleInvoker gradle) { + gradle.withArgs("--write-locks").buildsSuccessfully(); + InvocationResult result = gradle.withArgs(":why", "--hash=0805f935").buildsSuccessfully(); + + assertThat(result) + .output() + .contains("configuring upstream") + .contains("configuring downstream1") + .contains("configuring downstream2") + .contains("configuring unrelated") + .contains("com.example:dependency-of-unrelated:1.2.3\n\tprojects -> 1.2.3"); + assertThat(result).task(":why").succeeded(); + } +} diff --git a/test-migration-notes/ConfigurationOnDemandSpec.html b/test-migration-notes/ConfigurationOnDemandSpec.html new file mode 100644 index 000000000..b3bd4bbd4 --- /dev/null +++ b/test-migration-notes/ConfigurationOnDemandSpec.html @@ -0,0 +1,9972 @@ + + + + + Diff to HTML by rtfpessoa + + + + + + + + + + + + + +

Diff to HTML by rtfpessoa

+ +
+
+
+ Files changed (1) + hide + show +
+
    +
  1. + + src/test/{groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy → java/com/palantir/gradle/versions/ConfigurationOnDemandTest.java} + + +237 + -269 + + +
  2. +
+
+
+
+ + src/test/{groovy/com/palantir/gradle/versions/ConfigurationOnDemandSpec.groovy → java/com/palantir/gradle/versions/ConfigurationOnDemandTest.java} + RENAMED + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
@@ -14,26 +14,33 @@
+
+ 14 + +
+   + * limitations under the License. +
+
+ 15 + +
+   + */ +
+
+ 16 + +
+   +
+
+
+ 17 + +
+ - + package com.palantir.gradle.versions +
+
+ 18 + +
+ - +
+
+
+ 19 + +
+ - + import org.gradle.testkit.runner.BuildResult +
+
+ 20 + +
+ - + import org.gradle.testkit.runner.TaskOutcome +
+
+ 21 + +
+ - + import spock.lang.Unroll +
+
+ 22 + +
+ - +
+
+
+ 23 + +
+ - + import static com.palantir.gradle.versions.GradleTestVersions.GRADLE_VERSIONS +
+
+ 24 + +
+ - + import static com.palantir.gradle.versions.PomUtils.makePlatformPom +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 25 + +
+   +
+
+
+ 26 + +
+   + /** +
+
+ 27 + +
+   + * This tests the interaction of this plugin with Gradle's configuration-on-demand feature: +
+
+ 28 + +
+   + * https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand +
+
+ 29 + +
+   + */ +
+
+ 30 + +
+ - + @Unroll +
+
+ 31 + +
+ - + class ConfigurationOnDemandSpec extends IntegrationSpec { +
+
+ + +
+   +
+
+
+ 32 + +
+   +
+
+
+ 33 + +
+   + // ***DELINEATOR FOR REVIEW: PLUGIN_NAME +
+
+ 34 + +
+ - + static def PLUGIN_NAME = "com.palantir.consistent-versions" +
+
+ 35 + +
+ - + // ***DELINEATOR FOR REVIEW: mavenRepo +
+
+ 36 + +
+ - + private File mavenRepo +
+
+ 37 + +
+   +
+
+
+ 38 + +
+   + /* +
+
+ 39 + +
+   + * Project structure (arrows indicate dependencies): +
+
+
@@ -44,29 +51,35 @@ class ConfigurationOnDemandSpec extends IntegrationSpec {
+
+ 44 + +
+   + * downstream1 downstream2 +
+
+ 45 + +
+   + */ +
+
+ 46 + +
+   + // ***DELINEATOR FOR REVIEW: setup +
+
+ 47 + +
+ - + void setup() { +
+
+ 48 + +
+ - + mavenRepo = generateMavenRepo( +
+
+ 49 + +
+ - + 'com.example:dependency-of-upstream:1.2.3', +
+
+ 50 + +
+ - + 'com.example:dependency-of-upstream:100.1.1', +
+
+ 51 + +
+ - + 'com.example:dependency-of-downstream1:1.2.3', +
+
+ 52 + +
+ - + 'com.example:dependency-of-downstream2:1.2.3', +
+
+ 53 + +
+ - + 'com.example:dependency-of-unrelated:1.2.3', +
+
+ 54 + +
+ - + 'com.example:dep-with-version-bumped-by-unrelated:1.0.0', +
+
+ 55 + +
+ - + 'com.example:dep-with-version-bumped-by-unrelated:1.1.0', +
+
+ 56 + +
+ - + 'com.example:transitive-test-dep:1.0.0', +
+
+ 57 + +
+ - + 'com.example:transitive-test-dep:1.1.0', +
+
+ 58 + +
+ - + 'com.example:transitive-test-dep:1.2.0', +
+
+ 59 + +
+ - + ) +
+
+ 60 + +
+ - +
+
+
+ 61 + +
+ - + makePlatformPom(mavenRepo, "org", "platform", "1.0") +
+
+ 62 + +
+ - +
+
+
+ 63 + +
+ - + buildFile.text = """ +
+
+ 64 + +
+ - + plugins { +
+
+ 65 + +
+ - + id '${PLUGIN_NAME}' +
+
+ 66 + +
+ - + } +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 67 + +
+   + allprojects { +
+
+ 68 + +
+   + repositories { +
+
+ 69 + +
+ - + maven { url "file:///${mavenRepo.getAbsolutePath()}" } +
+
+ 70 + +
+   + } +
+
+ 71 + +
+   + } +
+
+ 72 + +
+   + subprojects { +
+
+
@@ -76,201 +89,199 @@ class ConfigurationOnDemandSpec extends IntegrationSpec {
+
+ 76 + +
+   + } +
+
+ 77 + +
+   + } +
+
+ 78 + +
+   + } +
+
+ 79 + +
+ - + +
+
+ 80 + +
+   + // Get rid of deprecation warnings for Gradle 7+ +
+
+ 81 + +
+   + versionRecommendations { +
+
+ 82 + +
+   + excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime' +
+
+ 83 + +
+   + } +
+
+ 84 + +
+ - + """.stripIndent(true) +
+
+ 85 + +
+   +
+
+
+ 86 + +
+ - + file('versions.props').text = """ +
+
+ 87 + +
+   + com.example:dependency-of-upstream = 1.2.3 +
+
+ 88 + +
+   + com.example:dependency-of-downstream1 = 1.2.3 +
+
+ 89 + +
+   + com.example:dependency-of-downstream2 = 1.2.3 +
+
+ 90 + +
+   + com.example:dependency-of-unrelated = 1.2.3 +
+
+ 91 + +
+   + # 1.0.0 is a minimum, we expect this to be locked to 1.1.0 +
+
+ 92 + +
+   + com.example:dep-with-version-bumped-by-unrelated = 1.0.0 +
+
+ 93 + +
+ - + """.stripIndent(true) +
+
+ 94 + +
+   +
+
+
+ 95 + +
+ - + addSubproject("upstream", """ +
+
+ 96 + +
+ - + plugins { +
+
+ 97 + +
+ - + id 'java' +
+
+ 98 + +
+ - + } +
+
+ 99 + +
+   + println 'configuring upstream' +
+
+ 100 + +
+   + dependencies { +
+
+ 101 + +
+   + implementation 'com.example:dependency-of-upstream' +
+
+ 102 + +
+   + } +
+
+ 103 + +
+ - + """.stripIndent(true)) +
+
+ 104 + +
+   +
+
+
+ 105 + +
+ - + addSubproject("downstream1", """ +
+
+ 106 + +
+ - + plugins { +
+
+ 107 + +
+ - + id 'java' +
+
+ 108 + +
+ - + } +
+
+ 109 + +
+   + println 'configuring downstream1' +
+
+ 110 + +
+   + dependencies { +
+
+ 111 + +
+   + implementation project(':upstream') +
+
+ 112 + +
+   + implementation 'com.example:dependency-of-downstream1' +
+
+ 113 + +
+   + } +
+
+ 114 + +
+ - + """.stripIndent(true)) +
+
+ 115 + +
+   +
+
+
+ 116 + +
+ - + addSubproject("downstream2", """ +
+
+ 117 + +
+ - + plugins { +
+
+ 118 + +
+ - + id 'java' +
+
+ 119 + +
+ - + } +
+
+ 120 + +
+   + println 'configuring downstream2' +
+
+ 121 + +
+   + dependencies { +
+
+ 122 + +
+   + implementation project(':upstream') +
+
+ 123 + +
+   + implementation 'com.example:dependency-of-downstream2' +
+
+ 124 + +
+   + implementation 'com.example:dep-with-version-bumped-by-unrelated' +
+
+ 125 + +
+   + } +
+
+ 126 + +
+ - + """.stripIndent(true)) +
+
+ 127 + +
+   +
+
+
+ 128 + +
+ - + addSubproject("unrelated", """ +
+
+ 129 + +
+ - + plugins { +
+
+ 130 + +
+ - + id 'java' +
+
+ 131 + +
+ - + } +
+
+ 132 + +
+   + println 'configuring unrelated' +
+
+ 133 + +
+   + dependencies { +
+
+ 134 + +
+   + implementation 'com.example:dependency-of-unrelated' +
+
+ 135 + +
+   + implementation 'com.example:dep-with-version-bumped-by-unrelated:1.1.0' +
+
+ 136 + +
+   + } +
+
+ 137 + +
+ - + """.stripIndent(true)) +
+
+ 138 + +
+   +
+
+
+ 139 + +
+ - + file("gradle.properties").text = """ +
+
+ 140 + +
+ - + org.gradle.configureondemand=true +
+
+ 141 + +
+ - + """.stripIndent(true) +
+
+ 142 + +
+   + } +
+
+ 143 + +
+   +
+
+
+ 144 + +
+ - + // ***DELINEATOR FOR REVIEW: can_write_locks +
+
+ 145 + +
+ - + def '#gradleVersionNumber: can write locks'() { +
+
+ 146 + +
+ - + setup: +
+
+ 147 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 148 + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 149 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 150 + +
+ - + when: +
+
+ 151 + +
+ - + BuildResult result = runTasks('--write-locks') +
+
+ 152 + +
+   +
+
+
+ 153 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 154 + +
+ - + then: +
+
+ 155 + +
+ - + result.output.contains('configuring upstream') +
+
+ 156 + +
+ - + result.output.contains('configuring downstream1') +
+
+ 157 + +
+ - + result.output.contains('configuring downstream2') +
+
+ 158 + +
+ - + result.output.contains('configuring unrelated') +
+
+ 159 + +
+ - +
+
+
+ 160 + +
+ - + new File(projectDir, "versions.lock").exists() +
+
+ 161 + +
+ - + file("versions.lock").text.contains("com.example:dependency-of-upstream:1.2.3") +
+
+ 162 + +
+ - + file("versions.lock").text.contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0") +
+
+ 163 + +
+ - +
+
+
+ 164 + +
+ - + where: +
+
+ 165 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 166 + +
+   + } +
+
+ 167 + +
+   +
+
+
+ 168 + +
+   + // ***DELINEATOR FOR REVIEW: can_write_locks_when_a_task_in_one_project_is_specified +
+
+ 169 + +
+ - + def '#gradleVersionNumber: can write locks when a task in one project is specified'() { +
+
+ 170 + +
+ - + setup: +
+
+ 171 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 172 + +
+ - +
+
+
+ 173 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 174 + +
+ - + when: +
+
+ 175 + +
+ - + runTasks(':downstream1:build', '--write-locks') +
+
+ 176 + +
+   +
+
+
+ 177 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 178 + +
+ - + then: +
+
+ 179 + +
+ - + new File(projectDir, "versions.lock").exists() +
+
+ 180 + +
+ - + file("versions.lock").text.contains("com.example:dependency-of-unrelated:1.2.3") +
+
+ 181 + +
+ - + file("versions.lock").text.contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0") +
+
+ 182 + +
+ - +
+
+
+ 183 + +
+ - + where: +
+
+ 184 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 185 + +
+   + } +
+
+ 186 + +
+   +
+
+
+ 187 + +
+   + // ***DELINEATOR FOR REVIEW: applying_the_plugin_does_not_force_all_projects_to_be_configured +
+
+ 188 + +
+ - + def '#gradleVersionNumber: applying the plugin does not force all projects to be configured'() { +
+
+ 189 + +
+ - + setup: +
+
+ 190 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 191 + +
+ - +
+
+
+ 192 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 193 + +
+ - + when: +
+
+ 194 + +
+ - + runTasks('--write-locks') +
+
+ 195 + +
+   + // Both absolute and relative formats work, as long as Gradle is run from the root project directory +
+
+ 196 + +
+ - + BuildResult result1 = runTasks(':downstream1:build') +
+
+ 197 + +
+ - + BuildResult result2 = runTasks('downstream1:build') +
+
+ 198 + +
+   +
+
+
+ 199 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 200 + +
+ - + then: +
+
+ 201 + +
+ - + result1.output.contains('configuring upstream') +
+
+ 202 + +
+ - + result1.output.contains('configuring downstream1') +
+
+ 203 + +
+ - + !result1.output.contains('configuring downstream2') +
+
+ 204 + +
+ - + !result1.output.contains('configuring unrelated') +
+
+ 205 + +
+ - +
+
+
+ 206 + +
+ - + result2.output.contains('configuring upstream') +
+
+ 207 + +
+ - + result2.output.contains('configuring downstream1') +
+
+ 208 + +
+ - + !result2.output.contains('configuring downstream2') +
+
+ 209 + +
+ - + !result2.output.contains('configuring unrelated') +
+
+ 210 + +
+ - +
+
+
+ 211 + +
+ - + where: +
+
+ 212 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 213 + +
+   + } +
+
+ 214 + +
+   +
+
+
+ 215 + +
+   + // ***DELINEATOR FOR REVIEW: resolving_a_classpath_does_not_force_all_projects_to_be_configured +
+
+ 216 + +
+ - + def '#gradleVersionNumber: resolving a classpath does not force all projects to be configured'() { +
+
+ 217 + +
+ - + setup: +
+
+ 218 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 219 + +
+ - +
+
+
+ 220 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 221 + +
+ - + when: +
+
+ 222 + +
+ - + runTasks('--write-locks') +
+
+ 223 + +
+ - + BuildResult result = runTasks(':downstream1:writeClasspath') +
+
+ 224 + +
+   +
+
+
+ 225 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 226 + +
+ - + then: +
+
+ 227 + +
+ - + result.output.contains('configuring upstream') +
+
+ 228 + +
+ - + result.output.contains('configuring downstream1') +
+
+ 229 + +
+ - + !result.output.contains('configuring downstream2') +
+
+ 230 + +
+ - + !result.output.contains('configuring unrelated') +
+
+ 231 + +
+ - +
+
+
+ 232 + +
+ - + where: +
+
+ 233 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 234 + +
+   + } +
+
+ 235 + +
+   +
+
+
+ 236 + +
+ - + // ***DELINEATOR FOR REVIEW: after_lockfile_is_written_versions_constraints_due_to_non_configured_projects_are_still_respected +
+
+ 237 + +
+ - + def '#gradleVersionNumber: after lockfile is written, versions constraints due to non-configured projects are still respected'() { +
+
+ 238 + +
+ - + setup: +
+
+ 239 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 240 + +
+ - +
+
+
+ 241 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 242 + +
+ - + when: +
+
+ 243 + +
+ - + runTasks('--write-locks') +
+
+ 244 + +
+ - + BuildResult result = runTasks(':downstream2:writeClasspath') +
+
+ 245 + +
+   +
+
+
+ 246 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 247 + +
+ - + then: +
+
+ 248 + +
+   + // Version used is 1.1.0 due to the unrelated project +
+
+ 249 + +
+ - + result.output.contains('dep-with-version-bumped-by-unrelated-1.1.0.jar') +
+
+ 250 + +
+ - + !result.output.contains('configured unrelated') +
+
+ 251 + +
+ - +
+
+
+ 252 + +
+ - + where: +
+
+ 253 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 254 + +
+   + } +
+
+ 255 + +
+   +
+
+
+ 256 + +
+   + // ***DELINEATOR FOR REVIEW: transitive_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early +
+
+ 257 + +
+ - + def '#gradleVersionNumber: transitive dependencies cause upstream projects to be configured sufficiently early'() { +
+
+ 258 + +
+ - + setup: +
+
+ 259 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 260 + +
+ - + addSubproject("a", """ +
+
+ 261 + +
+ - + plugins { id 'java' } +
+
+ 262 + +
+   + dependencies { +
+
+ 263 + +
+   + implementation 'com.example:transitive-test-dep:1.0.0' +
+
+ 264 + +
+   + } +
+
+ 265 + +
+ - + """.stripIndent(true)) +
+
+ 266 + +
+ - + addSubproject("b", """ +
+
+ 267 + +
+ - + plugins { id 'java' } +
+
+ + +
+   +
+
+
+ 268 + +
+   + dependencies { +
+
+ 269 + +
+   + implementation project(':a') +
+
+ 270 + +
+   + } +
+
+ 271 + +
+ - + """.stripIndent(true)) +
+
+ 272 + +
+ - + addSubproject("c", """ +
+
+ 273 + +
+ - + plugins { id 'java' } +
+
+ + +
+   +
+
+
+ 274 + +
+   + dependencies { +
+
+ 275 + +
+   + implementation project(':b') +
+
+ 276 + +
+   + } +
+
+
@@ -279,188 +290,145 @@ class ConfigurationOnDemandSpec extends IntegrationSpec {
+
+ 279 + +
+   + println project(':a').configurations.runtimeClasspath.files +
+
+ 280 + +
+   + } +
+
+ 281 + +
+   + } +
+
+ 282 + +
+ - + """.stripIndent(true)) +
+
+ 283 + +
+ - + addSubproject("u", """ +
+
+ 284 + +
+ - + plugins { id 'java' } +
+
+ + +
+   +
+
+
+ 285 + +
+   + dependencies { +
+
+ 286 + +
+   + implementation 'com.example:transitive-test-dep:1.1.0' +
+
+ 287 + +
+   + } +
+
+ 288 + +
+ - + """.stripIndent(true)) +
+
+ 289 + +
+   +
+
+
+ 290 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 291 + +
+ - + when: +
+
+ 292 + +
+ - + runTasks('--write-locks') +
+
+ 293 + +
+ - + BuildResult result = runTasks(':c:writeClasspathOfA') +
+
+ 294 + +
+   +
+
+
+ 295 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 296 + +
+ - + then: +
+
+ 297 + +
+   + // Version used should be 1.1.0, indicating that the version.lock constraint was applied +
+
+ 298 + +
+ - + result.output.contains('transitive-test-dep-1.1.0.jar') +
+
+ 299 + +
+ - +
+
+
+ 300 + +
+ - + where: +
+
+ 301 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 302 + +
+   + } +
+
+ 303 + +
+   +
+
+
+ 304 + +
+   + // ***DELINEATOR FOR REVIEW: task_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early +
+
+ 305 + +
+ - + def '#gradleVersionNumber: task dependencies cause upstream projects to be configured sufficiently early'() { +
+
+ 306 + +
+ - + setup: +
+
+ 307 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 308 + +
+ - + addSubproject("a", """ +
+
+ 309 + +
+ - + plugins { id 'java' } +
+
+ 310 + +
+   + dependencies { +
+
+ 311 + +
+   + implementation 'com.example:transitive-test-dep:1.0.0' +
+
+ 312 + +
+   + } +
+
+ 313 + +
+ - + """.stripIndent(true)) +
+
+ 314 + +
+ - + addSubproject("b", """ +
+
+ + +
+   +
+
+
+ 315 + +
+   + tasks.register('foo') { +
+
+ 316 + +
+   + dependsOn ':a:writeClasspath' +
+
+ 317 + +
+   + } +
+
+ 318 + +
+ - + """.stripIndent(true)) +
+
+ 319 + +
+ - + addSubproject("c", """ +
+
+ + +
+   +
+
+
+ 320 + +
+   + tasks.register('bar') { +
+
+ 321 + +
+   + dependsOn ':b:foo' +
+
+ 322 + +
+   + } +
+
+ 323 + +
+ - + """.stripIndent(true)) +
+
+ 324 + +
+ - + addSubproject("u", """ +
+
+ 325 + +
+ - + plugins { id 'java' } +
+
+ + +
+   +
+
+
+ 326 + +
+   + dependencies { +
+
+ 327 + +
+   + implementation 'com.example:transitive-test-dep:1.1.0' +
+
+ 328 + +
+   + } +
+
+ 329 + +
+ - + """.stripIndent(true)) +
+
+ 330 + +
+   +
+
+
+ 331 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 332 + +
+ - + when: +
+
+ 333 + +
+ - + runTasks('--write-locks') +
+
+ 334 + +
+ - + BuildResult result = runTasks(':c:bar') +
+
+ 335 + +
+   +
+
+
+ 336 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 337 + +
+ - + then: +
+
+ 338 + +
+   + // Version used should be 1.1.0, indicating that the version.lock constraint was applied +
+
+ 339 + +
+ - + result.output.contains('transitive-test-dep-1.1.0.jar') +
+
+ 340 + +
+ - +
+
+
+ 341 + +
+ - + where: +
+
+ 342 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 343 + +
+   + } +
+
+ 344 + +
+   +
+
+
+ 345 + +
+   + // ***DELINEATOR FOR REVIEW: verification_tasks_pass_when_all_projects_are_configured +
+
+ 346 + +
+ - + def '#gradleVersionNumber: verification tasks pass when all projects are configured'() { +
+
+ 347 + +
+ - + setup: +
+
+ 348 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 349 + +
+ - +
+
+
+ 350 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 351 + +
+ - + when: +
+
+ 352 + +
+ - + runTasks('--write-locks') +
+
+ 353 + +
+   + // Note: Not specifying the project causes all projects to be configured regardless of CoD +
+
+ 354 + +
+ - + BuildResult result = runTasks('checkUnusedConstraints', 'verifyLocks') +
+
+ + +
+   +
+
+
+ 355 + +
+   +
+
+
+ 356 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 357 + +
+ - + then: +
+
+ 358 + +
+ - + result.output.contains('configuring upstream') +
+
+ 359 + +
+ - + result.task(':checkUnusedConstraints').outcome == TaskOutcome.SUCCESS +
+
+ 360 + +
+ - + result.task(':verifyLocks').outcome == TaskOutcome.SUCCESS +
+
+ 361 + +
+ - +
+
+
+ 362 + +
+ - + where: +
+
+ 363 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 364 + +
+   + } +
+
+ 365 + +
+   +
+
+
+ 366 + +
+   + // ***DELINEATOR FOR REVIEW: checkUnusedConstraints_succeeds_when_not_all_projects_are_configured +
+
+ 367 + +
+ - + def '#gradleVersionNumber: checkUnusedConstraints succeeds when not all projects are configured'() { +
+
+ 368 + +
+ - + setup: +
+
+ 369 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 370 + +
+ - +
+
+
+ 371 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 372 + +
+ - + when: +
+
+ 373 + +
+ - + runTasks('--write-locks') +
+
+ 374 + +
+ - + BuildResult result = runTasks(':checkUnusedConstraints') +
+
+ 375 + +
+   +
+
+
+ 376 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 377 + +
+ - + then: +
+
+ 378 + +
+ - + result.task(':checkUnusedConstraints').outcome == TaskOutcome.SUCCESS +
+
+ 379 + +
+ - + result.output.contains('configuring upstream') +
+
+ 380 + +
+ - +
+
+
+ 381 + +
+ - + where: +
+
+ 382 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 383 + +
+   + } +
+
+ 384 + +
+   +
+
+
+ 385 + +
+   + // ***DELINEATOR FOR REVIEW: verifyLocks_fails_and_warns_when_not_all_projects_are_configured +
+
+ 386 + +
+ - + def '#gradleVersionNumber: verifyLocks fails and warns when not all projects are configured'() { +
+
+ 387 + +
+ - + setup: +
+
+ 388 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 389 + +
+ - +
+
+
+ 390 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 391 + +
+ - + when: +
+
+ 392 + +
+ - + runTasks('--write-locks') +
+
+ 393 + +
+ - + BuildResult result = runTasksAndFail(':verifyLocks') +
+
+ 394 + +
+   +
+
+
+ 395 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 396 + +
+ - + then: +
+
+ 397 + +
+ - + !result.output.contains('configuring upstream') +
+
+ 398 + +
+ - + result.task(':verifyLocks').outcome == TaskOutcome.FAILED +
+
+ 399 + +
+ - + result.output.contains("All projects must have been configured for this task to work correctly, but due to " + +
+
+ 400 + +
+ - + "Gradle configuration-on-demand, not all projects were configured.") +
+
+ 401 + +
+ - +
+
+
+ 402 + +
+ - + where: +
+
+ 403 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 404 + +
+   + } +
+
+ 405 + +
+   +
+
+
+ 406 + +
+   + // As failing tasks can't be considered UP-TO-DATE, we only need to check the case where the task passing +
+
+ 407 + +
+   + // is followed by the task running with incomplete configuration. +
+
+ 408 + +
+   + // ***DELINEATOR FOR REVIEW: verification_tasks_are_not_UP_TO_DATE_when_the_set_of_configured_projects_differs +
+
+ 409 + +
+ - + def '#gradleVersionNumber: verification tasks are not UP-TO-DATE when the set of configured projects differs'() { +
+
+ 410 + +
+ - + setup: +
+
+ 411 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 412 + +
+ - +
+
+
+ 413 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 414 + +
+ - + when: +
+
+ 415 + +
+ - + runTasks('--write-locks') +
+
+ 416 + +
+ - + runTasks('build') +
+
+ 417 + +
+   +
+
+
+ 418 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 419 + +
+ - + then: +
+
+ 420 + +
+ - + runTasksAndFail(':verifyLocks') +
+
+ 421 + +
+ - +
+
+
+ 422 + +
+ - + where: +
+
+ 423 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 424 + +
+   + } +
+
+ 425 + +
+   +
+
+
+ 426 + +
+   + // ***DELINEATOR FOR REVIEW: the_why_task_works_when_all_projects_are_configured +
+
+ 427 + +
+ - + def '#gradleVersionNumber: the why task works when all projects are configured'() { +
+
+ 428 + +
+ - + setup: +
+
+ 429 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 430 + +
+ - +
+
+
+ 431 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 432 + +
+ - + when: +
+
+ 433 + +
+ - + runTasks('--write-locks') +
+
+ 434 + +
+ - + BuildResult result = runTasks('why', '--hash=0805f935') +
+
+ 435 + +
+   +
+
+
+ 436 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 437 + +
+ - + then: +
+
+ 438 + +
+ - + result.task(':why').outcome == TaskOutcome.SUCCESS +
+
+ 439 + +
+ - +
+
+
+ 440 + +
+ - + where: +
+
+ 441 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 442 + +
+   + } +
+
+ 443 + +
+   +
+
+
+ 444 + +
+   + // ***DELINEATOR FOR REVIEW: the_why_task_somehow_forces_all_projects_to_be_configured +
+
+ 445 + +
+ - + def '#gradleVersionNumber: the why task somehow forces all projects to be configured'() { +
+
+ 446 + +
+ - + setup: +
+
+ 447 + +
+ - + gradleVersion = gradleVersionNumber +
+
+ 448 + +
+ - +
+
+
+ 449 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 450 + +
+ - + when: +
+
+ 451 + +
+ - + runTasks('--write-locks') +
+
+ 452 + +
+ - + BuildResult result = runTasks(':why', '--hash=0805f935') +
+
+ 453 + +
+   +
+
+
+ 454 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 455 + +
+ - + then: +
+
+ 456 + +
+ - + result.output.contains('configuring upstream') +
+
+ 457 + +
+ - + result.output.contains('configuring downstream1') +
+
+ 458 + +
+ - + result.output.contains('configuring downstream2') +
+
+ 459 + +
+ - + result.output.contains('configuring unrelated') +
+
+ 460 + +
+ - + result.task(':why').outcome == TaskOutcome.SUCCESS +
+
+ 461 + +
+ - + result.output.contains('com.example:dependency-of-unrelated:1.2.3\n\tprojects -> 1.2.3') +
+
+ 462 + +
+ - +
+
+
+ 463 + +
+ - + where: +
+
+ 464 + +
+ - + gradleVersionNumber << GRADLE_VERSIONS +
+
+ 465 + +
+   + } +
+
+ 466 + +
+   + } +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
 
+
+ 14 + +
+   + * limitations under the License. +
+
+ 15 + +
+   + */ +
+
+ 16 + +
+   +
+
+
+ 17 + +
+ + + package com.palantir.gradle.versions; +
+
+ 18 + +
+ + +
+
+
+ 19 + +
+ + + import static com.palantir.gradle.testing.assertion.GradlePluginTestAssertions.assertThat; +
+
+ 20 + +
+ + + import static org.assertj.core.api.Assertions.assertThat; +
+
+ 21 + +
+ + +
+
+
+ 22 + +
+ + + import com.palantir.gradle.testing.execution.GradleInvoker; +
+
+ 23 + +
+ + + import com.palantir.gradle.testing.execution.InvocationResult; +
+
+ 24 + +
+ + + import com.palantir.gradle.testing.junit.DisabledConfigurationCache; +
+
+ 25 + +
+ + + import com.palantir.gradle.testing.junit.GradlePluginTests; +
+
+ 26 + +
+ + + import com.palantir.gradle.testing.maven.MavenArtifact; +
+
+ 27 + +
+ + + import com.palantir.gradle.testing.maven.MavenRepo; +
+
+ 28 + +
+ + + import com.palantir.gradle.testing.project.RootProject; +
+
+ 29 + +
+ + + import com.palantir.gradle.testing.project.SubProject; +
+
+ 30 + +
+ + + import java.io.UncheckedIOException; +
+
+ 31 + +
+ + + import org.junit.jupiter.api.BeforeEach; +
+
+ 32 + +
+ + + import org.junit.jupiter.api.Test; +
+
+ 33 + +
+   +
+
+
+ 34 + +
+   + /** +
+
+ 35 + +
+   + * This tests the interaction of this plugin with Gradle's configuration-on-demand feature: +
+
+ 36 + +
+   + * https://docs.gradle.org/current/userguide/multi_project_configuration_and_execution.html#sec:configuration_on_demand +
+
+ 37 + +
+   + */ +
+
+ 38 + +
+ + + @GradlePluginTests +
+
+ 39 + +
+ + + @DisabledConfigurationCache +
+
+ 40 + +
+ + + class ConfigurationOnDemandTest { +
+
+ 41 + +
+   +
+
+
+ 42 + +
+   + // ***DELINEATOR FOR REVIEW: PLUGIN_NAME +
+
+ 43 + +
+ + + private static final String PLUGIN_NAME = "com.palantir.consistent-versions"; +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 44 + +
+   +
+
+
+ 45 + +
+   + /* +
+
+ 46 + +
+   + * Project structure (arrows indicate dependencies): +
+
+
 
+
+ 51 + +
+   + * downstream1 downstream2 +
+
+ 52 + +
+   + */ +
+
+ 53 + +
+   + // ***DELINEATOR FOR REVIEW: setup +
+
+ 54 + +
+ + + @BeforeEach +
+
+ 55 + +
+ + + void setup( +
+
+ 56 + +
+ + + MavenRepo repo, +
+
+ 57 + +
+ + + RootProject rootProject, +
+
+ 58 + +
+ + + SubProject upstream, +
+
+ 59 + +
+ + + SubProject downstream1, +
+
+ 60 + +
+ + + SubProject downstream2, +
+
+ 61 + +
+ + + SubProject unrelated) { +
+
+ 62 + +
+ + + // ***DELINEATOR FOR REVIEW: mavenRepo +
+
+ 63 + +
+ + + repo.publish( +
+
+ 64 + +
+ + + MavenArtifact.of("com.example:dependency-of-upstream:1.2.3"), +
+
+ 65 + +
+ + + MavenArtifact.of("com.example:dependency-of-upstream:100.1.1"), +
+
+ 66 + +
+ + + MavenArtifact.of("com.example:dependency-of-downstream1:1.2.3"), +
+
+ 67 + +
+ + + MavenArtifact.of("com.example:dependency-of-downstream2:1.2.3"), +
+
+ 68 + +
+ + + MavenArtifact.of("com.example:dependency-of-unrelated:1.2.3"), +
+
+ 69 + +
+ + + MavenArtifact.of("com.example:dep-with-version-bumped-by-unrelated:1.0.0"), +
+
+ 70 + +
+ + + MavenArtifact.of("com.example:dep-with-version-bumped-by-unrelated:1.1.0"), +
+
+ 71 + +
+ + + MavenArtifact.of("com.example:transitive-test-dep:1.0.0"), +
+
+ 72 + +
+ + + MavenArtifact.of("com.example:transitive-test-dep:1.1.0"), +
+
+ 73 + +
+ + + MavenArtifact.of("com.example:transitive-test-dep:1.2.0")); +
+
+ 74 + +
+ + +
+
+
+ 75 + +
+ + + makePlatformPom(repo, "org", "platform", "1.0"); +
+
+ 76 + +
+ + +
+
+
+ 77 + +
+ + + rootProject.buildGradle().plugins().add(PLUGIN_NAME); +
+
+ 78 + +
+ + + rootProject.buildGradle().withMavenRepo(repo); +
+
+ 79 + +
+ + + rootProject.buildGradle().append(""" +
+
+ 80 + +
+   + allprojects { +
+
+ 81 + +
+   + repositories { +
+
+ 82 + +
+ + + maven { url "%s" } +
+
+ 83 + +
+   + } +
+
+ 84 + +
+   + } +
+
+ 85 + +
+   + subprojects { +
+
+
 
+
+ 89 + +
+   + } +
+
+ 90 + +
+   + } +
+
+ 91 + +
+   + } +
+
+ 92 + +
+ + +
+
+
+ 93 + +
+   + // Get rid of deprecation warnings for Gradle 7+ +
+
+ 94 + +
+   + versionRecommendations { +
+
+ 95 + +
+   + excludeConfigurations 'compile', 'runtime', 'testCompile', 'testRuntime' +
+
+ 96 + +
+   + } +
+
+ 97 + +
+ + + """, repo.path().toUri()); +
+
+ 98 + +
+   +
+
+
+ 99 + +
+ + + rootProject.file("versions.props").overwrite(""" +
+
+ 100 + +
+   + com.example:dependency-of-upstream = 1.2.3 +
+
+ 101 + +
+   + com.example:dependency-of-downstream1 = 1.2.3 +
+
+ 102 + +
+   + com.example:dependency-of-downstream2 = 1.2.3 +
+
+ 103 + +
+   + com.example:dependency-of-unrelated = 1.2.3 +
+
+ 104 + +
+   + # 1.0.0 is a minimum, we expect this to be locked to 1.1.0 +
+
+ 105 + +
+   + com.example:dep-with-version-bumped-by-unrelated = 1.0.0 +
+
+ 106 + +
+ + + """); +
+
+ 107 + +
+   +
+
+
+ 108 + +
+ + + upstream.buildGradle().plugins().add("java"); +
+
+ 109 + +
+ + + upstream.buildGradle().append(""" +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 110 + +
+   + println 'configuring upstream' +
+
+ 111 + +
+   + dependencies { +
+
+ 112 + +
+   + implementation 'com.example:dependency-of-upstream' +
+
+ 113 + +
+   + } +
+
+ 114 + +
+ + + """); +
+
+ 115 + +
+   +
+
+
+ 116 + +
+ + + downstream1.buildGradle().plugins().add("java"); +
+
+ 117 + +
+ + + downstream1.buildGradle().append(""" +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 118 + +
+   + println 'configuring downstream1' +
+
+ 119 + +
+   + dependencies { +
+
+ 120 + +
+   + implementation project(':upstream') +
+
+ 121 + +
+   + implementation 'com.example:dependency-of-downstream1' +
+
+ 122 + +
+   + } +
+
+ 123 + +
+ + + """); +
+
+ 124 + +
+   +
+
+
+ 125 + +
+ + + downstream2.buildGradle().plugins().add("java"); +
+
+ 126 + +
+ + + downstream2.buildGradle().append(""" +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 127 + +
+   + println 'configuring downstream2' +
+
+ 128 + +
+   + dependencies { +
+
+ 129 + +
+   + implementation project(':upstream') +
+
+ 130 + +
+   + implementation 'com.example:dependency-of-downstream2' +
+
+ 131 + +
+   + implementation 'com.example:dep-with-version-bumped-by-unrelated' +
+
+ 132 + +
+   + } +
+
+ 133 + +
+ + + """); +
+
+ 134 + +
+   +
+
+
+ 135 + +
+ + + unrelated.buildGradle().plugins().add("java"); +
+
+ 136 + +
+ + + unrelated.buildGradle().append(""" +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 137 + +
+   + println 'configuring unrelated' +
+
+ 138 + +
+   + dependencies { +
+
+ 139 + +
+   + implementation 'com.example:dependency-of-unrelated' +
+
+ 140 + +
+   + implementation 'com.example:dep-with-version-bumped-by-unrelated:1.1.0' +
+
+ 141 + +
+   + } +
+
+ 142 + +
+ + + """); +
+
+ 143 + +
+   +
+
+
+ 144 + +
+ + + rootProject.gradlePropertiesFile().appendProperty("org.gradle.configureondemand", "true"); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 145 + +
+   + } +
+
+ 146 + +
+   +
+
+
+ 147 + +
+ + + // Helper method to create a platform POM similar to PomUtils.makePlatformPom +
+
+ 148 + +
+ + + private void makePlatformPom(MavenRepo repo, String group, String name, String version) { +
+
+ 149 + +
+ + + java.nio.file.Path pomPath = +
+
+ 150 + +
+ + + repo.path().resolve(group).resolve(name).resolve(version).resolve("platform-1.0.pom"); +
+
+ 151 + +
+ + +
+
+
+ 152 + +
+ + + try { +
+
+ 153 + +
+ + + java.nio.file.Files.createDirectories(pomPath.getParent()); +
+
+ 154 + +
+ + + java.nio.file.Files.writeString(pomPath, """ +
+
+ 155 + +
+ + + <?xml version="1.0" encoding="UTF-8"?> +
+
+ 156 + +
+ + + <project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0" +
+
+ 157 + +
+ + + xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> +
+
+ 158 + +
+ + + <modelVersion>4.0.0</modelVersion> +
+
+ 159 + +
+ + + <packaging>pom</packaging> +
+
+ 160 + +
+ + + <groupId>%s</groupId> +
+
+ 161 + +
+ + + <artifactId>%s</artifactId> +
+
+ 162 + +
+ + + <version>%s</version> +
+
+ 163 + +
+ + + <dependencyManagement> +
+
+ 164 + +
+ + + <dependencies> +
+
+ 165 + +
+ + + </dependencies> +
+
+ 166 + +
+ + + </dependencyManagement> +
+
+ 167 + +
+ + + </project> +
+
+ 168 + +
+ + + """.formatted(group, name, version)); +
+
+ 169 + +
+ + + } catch (java.io.IOException e) { +
+
+ 170 + +
+ + + throw new UncheckedIOException(e); +
+
+ 171 + +
+ + + } +
+
+ 172 + +
+ + + } +
+
+ 173 + +
+   +
+
+
+ 174 + +
+ + + // ***DELINEATOR FOR REVIEW: can_write_locks +
+
+ 175 + +
+ + + @Test +
+
+ 176 + +
+ + + void can_write_locks(GradleInvoker gradle, RootProject rootProject) { +
+
+ 177 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 178 + +
+ + + InvocationResult result = gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 179 + +
+   +
+
+
+ 180 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 181 + +
+ + + assertThat(result) +
+
+ 182 + +
+ + + .output() +
+
+ 183 + +
+ + + .contains("configuring upstream") +
+
+ 184 + +
+ + + .contains("configuring downstream1") +
+
+ 185 + +
+ + + .contains("configuring downstream2") +
+
+ 186 + +
+ + + .contains("configuring unrelated"); +
+
+ 187 + +
+ + +
+
+
+ 188 + +
+ + + rootProject.file("versions.lock").assertThat().exists(); +
+
+ 189 + +
+ + + assertThat(rootProject.file("versions.lock").text()) +
+
+ 190 + +
+ + + .contains("com.example:dependency-of-upstream:1.2.3") +
+
+ 191 + +
+ + + .contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0"); +
+
+ + +
+   +
+
+
+ 192 + +
+   + } +
+
+ 193 + +
+   +
+
+
+ 194 + +
+   + // ***DELINEATOR FOR REVIEW: can_write_locks_when_a_task_in_one_project_is_specified +
+
+ 195 + +
+ + + @Test +
+
+ 196 + +
+ + + void can_write_locks_when_a_task_in_one_project_is_specified(GradleInvoker gradle, RootProject rootProject) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 197 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 198 + +
+ + + gradle.withArgs(":downstream1:build", "--write-locks").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 199 + +
+   +
+
+
+ 200 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 201 + +
+ + + rootProject.file("versions.lock").assertThat().exists(); +
+
+ 202 + +
+ + + assertThat(rootProject.file("versions.lock").text()) +
+
+ 203 + +
+ + + .contains("com.example:dependency-of-unrelated:1.2.3") +
+
+ 204 + +
+ + + .contains("com.example:dep-with-version-bumped-by-unrelated:1.1.0"); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 205 + +
+   + } +
+
+ 206 + +
+   +
+
+
+ 207 + +
+   + // ***DELINEATOR FOR REVIEW: applying_the_plugin_does_not_force_all_projects_to_be_configured +
+
+ 208 + +
+ + + @Test +
+
+ 209 + +
+ + + void applying_the_plugin_does_not_force_all_projects_to_be_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 210 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 211 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 212 + +
+   + // Both absolute and relative formats work, as long as Gradle is run from the root project directory +
+
+ 213 + +
+ + + InvocationResult result1 = gradle.withArgs(":downstream1:build").buildsSuccessfully(); +
+
+ 214 + +
+ + + InvocationResult result2 = gradle.withArgs("downstream1:build").buildsSuccessfully(); +
+
+ 215 + +
+   +
+
+
+ 216 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 217 + +
+ + + assertThat(result1) +
+
+ 218 + +
+ + + .output() +
+
+ 219 + +
+ + + .contains("configuring upstream") +
+
+ 220 + +
+ + + .contains("configuring downstream1") +
+
+ 221 + +
+ + + .doesNotContain("configuring downstream2") +
+
+ 222 + +
+ + + .doesNotContain("configuring unrelated"); +
+
+ 223 + +
+ + +
+
+
+ 224 + +
+ + + assertThat(result2) +
+
+ 225 + +
+ + + .output() +
+
+ 226 + +
+ + + .contains("configuring upstream") +
+
+ 227 + +
+ + + .contains("configuring downstream1") +
+
+ 228 + +
+ + + .doesNotContain("configuring downstream2") +
+
+ 229 + +
+ + + .doesNotContain("configuring unrelated"); +
+
+ 230 + +
+   + } +
+
+ 231 + +
+   +
+
+
+ 232 + +
+   + // ***DELINEATOR FOR REVIEW: resolving_a_classpath_does_not_force_all_projects_to_be_configured +
+
+ 233 + +
+ + + @Test +
+
+ 234 + +
+ + + void resolving_a_classpath_does_not_force_all_projects_to_be_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 235 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 236 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 237 + +
+ + + InvocationResult result = gradle.withArgs(":downstream1:writeClasspath").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 238 + +
+   +
+
+
+ 239 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 240 + +
+ + + assertThat(result) +
+
+ 241 + +
+ + + .output() +
+
+ 242 + +
+ + + .contains("configuring upstream") +
+
+ 243 + +
+ + + .contains("configuring downstream1") +
+
+ 244 + +
+ + + .doesNotContain("configuring downstream2") +
+
+ 245 + +
+ + + .doesNotContain("configuring unrelated"); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 246 + +
+   + } +
+
+ 247 + +
+   +
+
+
+ 248 + +
+ + + // ***DELINEATOR FOR REVIEW: +
+
+ 249 + +
+ + + // after_lockfile_is_written_versions_constraints_due_to_non_configured_projects_are_still_respected +
+
+ 250 + +
+ + + @Test +
+
+ 251 + +
+ + + void after_lockfile_is_written_versions_constraints_due_to_non_configured_projects_are_still_respected( +
+
+ 252 + +
+ + + GradleInvoker gradle) { +
+
+ 253 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 254 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 255 + +
+ + + InvocationResult result = gradle.withArgs(":downstream2:writeClasspath").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 256 + +
+   +
+
+
+ 257 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ + +
+   +
+
+
+ 258 + +
+   + // Version used is 1.1.0 due to the unrelated project +
+
+ 259 + +
+ + + assertThat(result) +
+
+ 260 + +
+ + + .output() +
+
+ 261 + +
+ + + .contains("dep-with-version-bumped-by-unrelated-1.1.0.jar") +
+
+ 262 + +
+ + + .doesNotContain("configured unrelated"); +
+
+ + +
+   +
+
+
+ 263 + +
+   + } +
+
+ 264 + +
+   +
+
+
+ 265 + +
+   + // ***DELINEATOR FOR REVIEW: transitive_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early +
+
+ 266 + +
+ + + @Test +
+
+ 267 + +
+ + + void transitive_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early( +
+
+ 268 + +
+ + + GradleInvoker gradle, SubProject a, SubProject b, SubProject c, SubProject u) { +
+
+ 269 + +
+ + + a.buildGradle().plugins().add("java"); +
+
+ 270 + +
+ + + a.buildGradle().append(""" +
+
+ 271 + +
+   + dependencies { +
+
+ 272 + +
+   + implementation 'com.example:transitive-test-dep:1.0.0' +
+
+ 273 + +
+   + } +
+
+ 274 + +
+ + + """); +
+
+ 275 + +
+ + +
+
+
+ 276 + +
+ + + b.buildGradle().plugins().add("java"); +
+
+ 277 + +
+ + + b.buildGradle().append(""" +
+
+ 278 + +
+   + dependencies { +
+
+ 279 + +
+   + implementation project(':a') +
+
+ 280 + +
+   + } +
+
+ 281 + +
+ + + """); +
+
+ 282 + +
+ + +
+
+
+ 283 + +
+ + + c.buildGradle().plugins().add("java"); +
+
+ 284 + +
+ + + c.buildGradle().append(""" +
+
+ 285 + +
+   + dependencies { +
+
+ 286 + +
+   + implementation project(':b') +
+
+ 287 + +
+   + } +
+
+
 
+
+ 290 + +
+   + println project(':a').configurations.runtimeClasspath.files +
+
+ 291 + +
+   + } +
+
+ 292 + +
+   + } +
+
+ 293 + +
+ + + """); +
+
+ 294 + +
+ + +
+
+
+ 295 + +
+ + + u.buildGradle().plugins().add("java"); +
+
+ 296 + +
+ + + u.buildGradle().append(""" +
+
+ 297 + +
+   + dependencies { +
+
+ 298 + +
+   + implementation 'com.example:transitive-test-dep:1.1.0' +
+
+ 299 + +
+   + } +
+
+ 300 + +
+ + + """); +
+
+ 301 + +
+   +
+
+
+ 302 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 303 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 304 + +
+ + + InvocationResult result = gradle.withArgs(":c:writeClasspathOfA").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 305 + +
+   +
+
+
+ 306 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ + +
+   +
+
+
+ 307 + +
+   + // Version used should be 1.1.0, indicating that the version.lock constraint was applied +
+
+ 308 + +
+ + + assertThat(result).output().contains("transitive-test-dep-1.1.0.jar"); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 309 + +
+   + } +
+
+ 310 + +
+   +
+
+
+ 311 + +
+   + // ***DELINEATOR FOR REVIEW: task_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early +
+
+ 312 + +
+ + + @Test +
+
+ 313 + +
+ + + void task_dependencies_cause_upstream_projects_to_be_configured_sufficiently_early( +
+
+ 314 + +
+ + + GradleInvoker gradle, SubProject a, SubProject b, SubProject c, SubProject u) { +
+
+ 315 + +
+ + + a.buildGradle().plugins().add("java"); +
+
+ 316 + +
+ + + a.buildGradle().append(""" +
+
+ 317 + +
+   + dependencies { +
+
+ 318 + +
+   + implementation 'com.example:transitive-test-dep:1.0.0' +
+
+ 319 + +
+   + } +
+
+ 320 + +
+ + + """); +
+
+ 321 + +
+ + +
+
+
+ 322 + +
+ + + b.buildGradle().append(""" +
+
+ 323 + +
+   + tasks.register('foo') { +
+
+ 324 + +
+   + dependsOn ':a:writeClasspath' +
+
+ 325 + +
+   + } +
+
+ 326 + +
+ + + """); +
+
+ 327 + +
+ + +
+
+
+ 328 + +
+ + + c.buildGradle().append(""" +
+
+ 329 + +
+   + tasks.register('bar') { +
+
+ 330 + +
+   + dependsOn ':b:foo' +
+
+ 331 + +
+   + } +
+
+ 332 + +
+ + + """); +
+
+ 333 + +
+ + +
+
+
+ 334 + +
+ + + u.buildGradle().plugins().add("java"); +
+
+ 335 + +
+ + + u.buildGradle().append(""" +
+
+ 336 + +
+   + dependencies { +
+
+ 337 + +
+   + implementation 'com.example:transitive-test-dep:1.1.0' +
+
+ 338 + +
+   + } +
+
+ 339 + +
+ + + """); +
+
+ 340 + +
+   +
+
+
+ 341 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 342 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 343 + +
+ + + InvocationResult result = gradle.withArgs(":c:bar").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 344 + +
+   +
+
+
+ 345 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ + +
+   +
+
+
+ 346 + +
+   + // Version used should be 1.1.0, indicating that the version.lock constraint was applied +
+
+ 347 + +
+ + + assertThat(result).output().contains("transitive-test-dep-1.1.0.jar"); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 348 + +
+   + } +
+
+ 349 + +
+   +
+
+
+ 350 + +
+   + // ***DELINEATOR FOR REVIEW: verification_tasks_pass_when_all_projects_are_configured +
+
+ 351 + +
+ + + @Test +
+
+ 352 + +
+ + + void verification_tasks_pass_when_all_projects_are_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 353 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 354 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 355 + +
+   + // Note: Not specifying the project causes all projects to be configured regardless of CoD +
+
+ 356 + +
+ + + InvocationResult result = +
+
+ 357 + +
+ + + gradle.withArgs("checkUnusedConstraints", "verifyLocks").buildsSuccessfully(); +
+
+ 358 + +
+   +
+
+
+ 359 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 360 + +
+ + + assertThat(result).output().contains("configuring upstream"); +
+
+ 361 + +
+ + + assertThat(result).task(":checkUnusedConstraints").succeeded(); +
+
+ 362 + +
+ + + assertThat(result).task(":verifyLocks").succeeded(); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 363 + +
+   + } +
+
+ 364 + +
+   +
+
+
+ 365 + +
+   + // ***DELINEATOR FOR REVIEW: checkUnusedConstraints_succeeds_when_not_all_projects_are_configured +
+
+ 366 + +
+ + + @Test +
+
+ 367 + +
+ + + void checkUnusedConstraints_succeeds_when_not_all_projects_are_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 368 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 369 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 370 + +
+ + + InvocationResult result = gradle.withArgs(":checkUnusedConstraints").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 371 + +
+   +
+
+
+ 372 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 373 + +
+ + + assertThat(result).task(":checkUnusedConstraints").succeeded(); +
+
+ 374 + +
+ + + assertThat(result).output().contains("configuring upstream"); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 375 + +
+   + } +
+
+ 376 + +
+   +
+
+
+ 377 + +
+   + // ***DELINEATOR FOR REVIEW: verifyLocks_fails_and_warns_when_not_all_projects_are_configured +
+
+ 378 + +
+ + + @Test +
+
+ 379 + +
+ + + void verifyLocks_fails_and_warns_when_not_all_projects_are_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 380 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 381 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 382 + +
+ + + InvocationResult result = gradle.withArgs(":verifyLocks").buildsWithFailure(); +
+
+ + +
+   +
+
+
+ 383 + +
+   +
+
+
+ 384 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 385 + +
+ + + assertThat(result) +
+
+ 386 + +
+ + + .output() +
+
+ 387 + +
+ + + .doesNotContain("configuring upstream") +
+
+ 388 + +
+ + + .contains("All projects must have been configured for this task to work correctly, but due to " +
+
+ 389 + +
+ + + + "Gradle configuration-on-demand, not all projects were configured."); +
+
+ 390 + +
+ + + assertThat(result).task(":verifyLocks").failed(); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 391 + +
+   + } +
+
+ 392 + +
+   +
+
+
+ 393 + +
+   + // As failing tasks can't be considered UP-TO-DATE, we only need to check the case where the task passing +
+
+ 394 + +
+   + // is followed by the task running with incomplete configuration. +
+
+ 395 + +
+   + // ***DELINEATOR FOR REVIEW: verification_tasks_are_not_UP_TO_DATE_when_the_set_of_configured_projects_differs +
+
+ 396 + +
+ + + @Test +
+
+ 397 + +
+ + + void verification_tasks_are_not_UP_TO_DATE_when_the_set_of_configured_projects_differs(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 398 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 399 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 400 + +
+ + + gradle.withArgs("build").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 401 + +
+   +
+
+
+ 402 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 403 + +
+ + + gradle.withArgs(":verifyLocks").buildsWithFailure(); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 404 + +
+   + } +
+
+ 405 + +
+   +
+
+
+ 406 + +
+   + // ***DELINEATOR FOR REVIEW: the_why_task_works_when_all_projects_are_configured +
+
+ 407 + +
+ + + @Test +
+
+ 408 + +
+ + + void the_why_task_works_when_all_projects_are_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 409 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 410 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 411 + +
+ + + InvocationResult result = gradle.withArgs("why", "--hash=0805f935").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 412 + +
+   +
+
+
+ 413 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 414 + +
+ + + assertThat(result).task(":why").succeeded(); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 415 + +
+   + } +
+
+ 416 + +
+   +
+
+
+ 417 + +
+   + // ***DELINEATOR FOR REVIEW: the_why_task_somehow_forces_all_projects_to_be_configured +
+
+ 418 + +
+ + + @Test +
+
+ 419 + +
+ + + void the_why_task_somehow_forces_all_projects_to_be_configured(GradleInvoker gradle) { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 420 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 421 + +
+ + + gradle.withArgs("--write-locks").buildsSuccessfully(); +
+
+ 422 + +
+ + + InvocationResult result = gradle.withArgs(":why", "--hash=0805f935").buildsSuccessfully(); +
+
+ + +
+   +
+
+
+ 423 + +
+   +
+
+
+ 424 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 425 + +
+ + + assertThat(result) +
+
+ 426 + +
+ + + .output() +
+
+ 427 + +
+ + + .contains("configuring upstream") +
+
+ 428 + +
+ + + .contains("configuring downstream1") +
+
+ 429 + +
+ + + .contains("configuring downstream2") +
+
+ 430 + +
+ + + .contains("configuring unrelated") +
+
+ 431 + +
+ + + .contains("com.example:dependency-of-unrelated:1.2.3\n\tprojects -> 1.2.3"); +
+
+ 432 + +
+ + + assertThat(result).task(":why").succeeded(); +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 433 + +
+   + } +
+
+ 434 + +
+   + } +
+
+
+
+
+
+
+
+ +