diff --git a/gradle-palantir-java-format/src/test/java/com/palantir/javaformat/gradle/GradlewExecutor.java b/gradle-palantir-java-format/src/test/java/com/palantir/javaformat/gradle/GradlewExecutor.java new file mode 100644 index 000000000..a1139461c --- /dev/null +++ b/gradle-palantir-java-format/src/test/java/com/palantir/javaformat/gradle/GradlewExecutor.java @@ -0,0 +1,102 @@ +/* + * (c) Copyright 2025 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.javaformat.gradle; + +import com.palantir.javaformat.gradle.spotless.PalantirJavaFormatStep; +import com.palantir.javaformat.java.Formatter; +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import nebula.test.IntegrationTestKitSpec; +import nebula.test.functional.internal.classpath.ClasspathAddingInitScriptBuilder; +import org.gradle.testkit.runner.internal.PluginUnderTestMetadataReading; + +/** + * {@link IntegrationTestKitSpec} currently loads more than what it needs into the classpath. + * This means if we run a test with {@link IntegrationTestKitSpec}'s runner, the {@link Formatter} is on the build's classpath by virtue of being in the test's classpath. + * If the test applies the {@link PalantirJavaFormatPlugin}, it complains that the {@link Formatter} is erroneously loadable. + * To be clear, this complaint is entirely a result of the {@link IntegrationTestKitSpec} loading too many things onto classpath since it doesn't know what the exact plugin classpath is. + * As a workaround, this runner uses the classpath produced by Gradle Test Kit in {@code plugin-under-test-metadata.properties}. + * This classpath only contains the dependencies required by the plugin, as well as the plugin itself. + * This means that even if we put the formatter on the {@code testClassPath}, it won't leak through to the Gradle build under test and subsequently no error from {@link PalantirJavaFormatStep}. + */ +public class GradlewExecutor { + private File projectDir; + + public GradlewExecutor(File projectDir) { + this.projectDir = projectDir; + } + + private static List getBuildPluginClasspathInjector() { + return PluginUnderTestMetadataReading.readImplementationClasspath(); + } + + public GradlewExecutionResult runGradlewTasks(String... tasks) { + try { + ProcessBuilder processBuilder = getProcessBuilder(tasks); + Process process = processBuilder.start(); + String output = readAllInput(process.getInputStream()); + process.waitFor(1, TimeUnit.MINUTES); + return new GradlewExecutionResult(process.exitValue(), output); + } catch (InterruptedException | IOException e) { + return new GradlewExecutionResult(-1, "", e); + } + } + + private static String readAllInput(InputStream inputStream) { + try { + Stream lines = + new BufferedReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)).lines(); + return lines.collect(Collectors.joining("\n")); + } catch (Exception e) { + throw new RuntimeException("GradlewExecutor failed to readAllInput", e); + } + } + + private ProcessBuilder getProcessBuilder(String... tasks) { + File initScript = new File(projectDir, "init.gradle"); + ClasspathAddingInitScriptBuilder.build(initScript, getBuildPluginClasspathInjector()); + + List arguments = Stream.concat( + Stream.of( + "./gradlew", + "--init-script", + initScript.toPath().toString()), + Arrays.stream(tasks)) + .toList(); + + return new ProcessBuilder().command(arguments).directory(projectDir).redirectErrorStream(true); + } + + public record GradlewExecutionResult(boolean success, String standardOutput, Optional failure) { + public GradlewExecutionResult(int exitValue, String output, Throwable failure) { + this(exitValue == 0, output, Optional.of(failure)); + } + + public GradlewExecutionResult(int exitValue, String output) { + this(exitValue == 0, output, Optional.empty()); + } + } +} diff --git a/gradle-palantir-java-format/src/test/groovy/com/palantir/javaformat/gradle/SupportsSpotless622.groovy b/gradle-palantir-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsSpotless622.java similarity index 60% rename from gradle-palantir-java-format/src/test/groovy/com/palantir/javaformat/gradle/SupportsSpotless622.groovy rename to gradle-palantir-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsSpotless622.java index e8b0121cd..edea53077 100644 --- a/gradle-palantir-java-format/src/test/groovy/com/palantir/javaformat/gradle/SupportsSpotless622.groovy +++ b/gradle-palantir-java-format/src/test/java/com/palantir/javaformat/gradle/SupportsSpotless622.java @@ -13,9 +13,16 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package com.palantir.javaformat.gradle +package com.palantir.javaformat.gradle; -import nebula.test.IntegrationTestKitSpec +import static org.assertj.core.api.Assertions.assertThat; + +import com.palantir.gradle.testing.junit.DisabledConfigurationCache; +import com.palantir.gradle.testing.junit.GradlePluginTests; +import com.palantir.gradle.testing.project.RootProject; +import java.io.File; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; /** * When we were getting gradle-baseline to support the configuration cache, spotless had some poorly written tasks @@ -28,20 +35,22 @@ * * This test forces creation of the spotless steps, which will reveal any eager resolution of configurations. */ -class SupportsSpotless622 extends IntegrationTestKitSpec { - private static final CLASSPATH_FILE = new File("build/impl.classpath").absolutePath +@GradlePluginTests +@DisabledConfigurationCache +class SupportsSpotless622 { + private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath(); - private GradlewExecutor executor + private GradlewExecutor executor; - def setup() { - definePluginOutsideOfPluginBlock = true - keepFiles = true - executor = new GradlewExecutor(projectDir) + @BeforeEach + void setup(RootProject project) { + executor = new GradlewExecutor(project.path().toFile()); } - def "PalantirJavaFormatPlugin works with spotless 6.22.0"() { - // language=Gradle - buildFile << ''' + @Test + @SuppressWarnings("GradleTestPluginsBlock") // Uses buildscript with apply plugin syntax intentionally + void palantir_java_format_plugin_works_with_spotless_6_22_0(RootProject project) throws Exception { + project.buildGradle().overwrite(""" buildscript { repositories { mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } } @@ -52,36 +61,35 @@ def setup() { classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.22.0' } } - + apply plugin: 'java' apply plugin: 'com.palantir.java-format' apply plugin: 'com.palantir.consistent-versions' apply plugin: 'com.diffplug.spotless' - - version = '0.1.0' - '''.stripIndent(true) + version = '0.1.0' + """); - file("versions.props") - file("versions.lock") + project.file("versions.props").createEmpty(); + project.file("versions.lock").createEmpty(); - runTasks('wrapper') + // Run wrapper task to generate gradlew + GradlewExecutor.GradlewExecutionResult wrapperResult = executor.runGradlewTasks("wrapper"); + assertThat(wrapperResult.success()).isTrue(); - buildFile << """ + project.buildGradle().append(""" dependencies { - palantirJavaFormat files(file("${CLASSPATH_FILE}").text.split(':')) + palantirJavaFormat files(file("%s").text.split(':')) } - // This forces the realization of the spotlessJava task, creating the spotless steps. - // If any configurations are eagerly resolved in the spotless steps, - // consistent-versions should catch it and throw here. + // This forces the realization of the spotlessJava task, creating the spotless steps. + // If any configurations are eagerly resolved in the spotless steps, + // consistent-versions should catch it and throw here. project.getTasks().getByName("spotlessJava") - """.stripIndent(true) + """, CLASSPATH_FILE); - when: - def result = executor.runGradlewTasks('classes', '--info') + GradlewExecutor.GradlewExecutionResult result = executor.runGradlewTasks("classes", "--info"); - then: - assert result.success + assertThat(result.success()).isTrue(); } } diff --git a/test-migration-notes/SupportsSpotless622.html b/test-migration-notes/SupportsSpotless622.html new file mode 100644 index 000000000..853c75759 --- /dev/null +++ b/test-migration-notes/SupportsSpotless622.html @@ -0,0 +1,1842 @@ + + + + + Diff to HTML by rtfpessoa + + + + + + + + + + + + + +

Diff to HTML by rtfpessoa

+ +
+
+
+
+ + gradle-palantir-java-format/src/test/{groovy/com/palantir/javaformat/gradle/SupportsSpotless622.groovy → java/com/palantir/javaformat/gradle/SupportsSpotless622.java} + RENAMED + +
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
@@ -13,9 +13,16 @@
+
+ 13 + +
+   + * See the License for the specific language governing permissions and +
+
+ 14 + +
+   + * limitations under the License. +
+
+ 15 + +
+   + */ +
+
+ 16 + +
+ - + package com.palantir.javaformat.gradle +
+
+ 17 + +
+   +
+
+
+ 18 + +
+ - + import nebula.test.IntegrationTestKitSpec +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 19 + +
+   +
+
+
+ 20 + +
+   + /** +
+
+ 21 + +
+   + * When we were getting gradle-baseline to support the configuration cache, spotless had some poorly written tasks +
+
+
@@ -28,24 +35,26 @@ import nebula.test.IntegrationTestKitSpec
+
+ 28 + +
+   + * +
+
+ 29 + +
+   + * This test forces creation of the spotless steps, which will reveal any eager resolution of configurations. +
+
+ 30 + +
+   + */ +
+
+ 31 + +
+ - + class SupportsSpotless622 extends IntegrationTestKitSpec { +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 32 + +
+   + // ***DELINEATOR FOR REVIEW: CLASSPATH_FILE +
+
+ 33 + +
+ - + private static final CLASSPATH_FILE = new File("build/impl.classpath").absolutePath +
+
+ 34 + +
+   +
+
+
+ 35 + +
+   + // ***DELINEATOR FOR REVIEW: executor +
+
+ 36 + +
+ - + private GradlewExecutor executor +
+
+ 37 + +
+   +
+
+
+ 38 + +
+   + // ***DELINEATOR FOR REVIEW: setup +
+
+ 39 + +
+ - + def setup() { +
+
+ 40 + +
+ - + definePluginOutsideOfPluginBlock = true +
+
+ 41 + +
+ - + keepFiles = true +
+
+ 42 + +
+ - + executor = new GradlewExecutor(projectDir) +
+
+ 43 + +
+   + } +
+
+ 44 + +
+   +
+
+
+ 45 + +
+   + // ***DELINEATOR FOR REVIEW: palantir_java_format_plugin_works_with_spotless_6_22_0 +
+
+ 46 + +
+ - + def "PalantirJavaFormatPlugin works with spotless 6.22.0"() { +
+
+ 47 + +
+ - + // language=Gradle +
+
+ 48 + +
+ - + buildFile << ''' +
+
+ + +
+   +
+
+
+ 49 + +
+   + buildscript { +
+
+ 50 + +
+   + repositories { +
+
+ 51 + +
+   + mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } } +
+
+
@@ -56,38 +65,37 @@ class SupportsSpotless622 extends IntegrationTestKitSpec {
+
+ 56 + +
+   + classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.22.0' +
+
+ 57 + +
+   + } +
+
+ 58 + +
+   + } +
+
+ 59 + +
+ - + +
+
+ 60 + +
+   + apply plugin: 'java' +
+
+ 61 + +
+   + apply plugin: 'com.palantir.java-format' +
+
+ 62 + +
+   + apply plugin: 'com.palantir.consistent-versions' +
+
+ 63 + +
+   + apply plugin: 'com.diffplug.spotless' +
+
+ 64 + +
+ - + +
+
+ 65 + +
+ - + version = '0.1.0' +
+
+ 66 + +
+ - + '''.stripIndent(true) +
+
+ 67 + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 68 + +
+   +
+
+
+ 69 + +
+ - + file("versions.props") +
+
+ 70 + +
+ - + file("versions.lock") +
+
+ 71 + +
+   +
+
+
+ 72 + +
+ - + runTasks('wrapper') +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 73 + +
+   +
+
+
+ 74 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 75 + +
+ - + buildFile << """ +
+
+ 76 + +
+   + dependencies { +
+
+ 77 + +
+ - + palantirJavaFormat files(file("${CLASSPATH_FILE}").text.split(':')) +
+
+ 78 + +
+   + } +
+
+ 79 + +
+   +
+
+
+ 80 + +
+ - + // This forces the realization of the spotlessJava task, creating the spotless steps. +
+
+ 81 + +
+ - + // If any configurations are eagerly resolved in the spotless steps, +
+
+ 82 + +
+ - + // consistent-versions should catch it and throw here. +
+
+ 83 + +
+   + project.getTasks().getByName("spotlessJava") +
+
+ 84 + +
+ - + """.stripIndent(true) +
+
+ 85 + +
+   +
+
+
+ 86 + +
+ - + when: +
+
+ 87 + +
+ - + def result = executor.runGradlewTasks('classes', '--info') +
+
+ 88 + +
+   +
+
+
+ 89 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 90 + +
+ - + then: +
+
+ 91 + +
+ - + assert result.success +
+
+ 92 + +
+   + } +
+
+ 93 + +
+   + } +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
 
+
+ 13 + +
+   + * See the License for the specific language governing permissions and +
+
+ 14 + +
+   + * limitations under the License. +
+
+ 15 + +
+   + */ +
+
+ 16 + +
+ + + package com.palantir.javaformat.gradle; +
+
+ 17 + +
+   +
+
+
+ 18 + +
+ + + import static org.assertj.core.api.Assertions.assertThat; +
+
+ 19 + +
+ + +
+
+
+ 20 + +
+ + + import com.palantir.gradle.testing.junit.DisabledConfigurationCache; +
+
+ 21 + +
+ + + import com.palantir.gradle.testing.junit.GradlePluginTests; +
+
+ 22 + +
+ + + import com.palantir.gradle.testing.project.RootProject; +
+
+ 23 + +
+ + + import java.io.File; +
+
+ 24 + +
+ + + import org.junit.jupiter.api.BeforeEach; +
+
+ 25 + +
+ + + import org.junit.jupiter.api.Test; +
+
+ 26 + +
+   +
+
+
+ 27 + +
+   + /** +
+
+ 28 + +
+   + * When we were getting gradle-baseline to support the configuration cache, spotless had some poorly written tasks +
+
+
 
+
+ 35 + +
+   + * +
+
+ 36 + +
+   + * This test forces creation of the spotless steps, which will reveal any eager resolution of configurations. +
+
+ 37 + +
+   + */ +
+
+ 38 + +
+ + + @GradlePluginTests +
+
+ 39 + +
+ + + @DisabledConfigurationCache +
+
+ 40 + +
+ + + class SupportsSpotless622 { +
+
+ 41 + +
+   + // ***DELINEATOR FOR REVIEW: CLASSPATH_FILE +
+
+ 42 + +
+ + + private static final String CLASSPATH_FILE = new File("build/impl.classpath").getAbsolutePath(); +
+
+ 43 + +
+   +
+
+
+ 44 + +
+   + // ***DELINEATOR FOR REVIEW: executor +
+
+ 45 + +
+ + + private GradlewExecutor executor; +
+
+ 46 + +
+   +
+
+
+ 47 + +
+   + // ***DELINEATOR FOR REVIEW: setup +
+
+ 48 + +
+ + + @BeforeEach +
+
+ 49 + +
+ + + void setup(RootProject project) { +
+
+ 50 + +
+ + + executor = new GradlewExecutor(project.path().toFile()); +
+
+ + +
+   +
+
+
+ 51 + +
+   + } +
+
+ 52 + +
+   +
+
+
+ 53 + +
+   + // ***DELINEATOR FOR REVIEW: palantir_java_format_plugin_works_with_spotless_6_22_0 +
+
+ 54 + +
+ + + @Test +
+
+ 55 + +
+ + + @SuppressWarnings("GradleTestPluginsBlock") // Uses buildscript with apply plugin syntax intentionally +
+
+ 56 + +
+ + + void palantir_java_format_plugin_works_with_spotless_6_22_0(RootProject project) throws Exception { +
+
+ 57 + +
+ + + project.buildGradle().overwrite(""" +
+
+ 58 + +
+   + buildscript { +
+
+ 59 + +
+   + repositories { +
+
+ 60 + +
+   + mavenCentral() { metadataSources { mavenPom(); ignoreGradleMetadataRedirection() } } +
+
+
 
+
+ 65 + +
+   + classpath 'com.diffplug.spotless:spotless-plugin-gradle:6.22.0' +
+
+ 66 + +
+   + } +
+
+ 67 + +
+   + } +
+
+ 68 + +
+ + +
+
+
+ 69 + +
+   + apply plugin: 'java' +
+
+ 70 + +
+   + apply plugin: 'com.palantir.java-format' +
+
+ 71 + +
+   + apply plugin: 'com.palantir.consistent-versions' +
+
+ 72 + +
+   + apply plugin: 'com.diffplug.spotless' +
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ + +
+   +
+
+
+ 73 + +
+   +
+
+
+ 74 + +
+ + + version = '0.1.0' +
+
+ 75 + +
+ + + """); +
+
+ 76 + +
+   +
+
+
+ 77 + +
+ + + project.file("versions.props").createEmpty(); +
+
+ 78 + +
+ + + project.file("versions.lock").createEmpty(); +
+
+ 79 + +
+   +
+
+
+ 80 + +
+ + + // Run wrapper task to generate gradlew +
+
+ 81 + +
+ + + GradlewExecutor.GradlewExecutionResult wrapperResult = executor.runGradlewTasks("wrapper"); +
+
+ 82 + +
+ + + assertThat(wrapperResult.success()).isTrue(); +
+
+ 83 + +
+   +
+
+
+ 84 + +
+   + // ***DELINEATOR FOR REVIEW: when +
+
+ 85 + +
+ + + project.buildGradle().append(""" +
+
+ 86 + +
+   + dependencies { +
+
+ 87 + +
+ + + palantirJavaFormat files(file("%s").text.split(':')) +
+
+ 88 + +
+   + } +
+
+ 89 + +
+   +
+
+
+ 90 + +
+ + + // This forces the realization of the spotlessJava task, creating the spotless steps. +
+
+ 91 + +
+ + + // If any configurations are eagerly resolved in the spotless steps, +
+
+ 92 + +
+ + + // consistent-versions should catch it and throw here. +
+
+ 93 + +
+   + project.getTasks().getByName("spotlessJava") +
+
+ 94 + +
+ + + """, CLASSPATH_FILE); +
+
+ 95 + +
+   +
+
+
+ 96 + +
+ + + GradlewExecutor.GradlewExecutionResult result = executor.runGradlewTasks("classes", "--info"); +
+
+ + +
+   +
+
+
+ 97 + +
+   +
+
+
+ 98 + +
+   + // ***DELINEATOR FOR REVIEW: then +
+
+ 99 + +
+ + + assertThat(result.success()).isTrue(); +
+
+ + +
+   +
+
+
+ 100 + +
+   + } +
+
+ 101 + +
+   + } +
+
+
+
+
+
+
+
+ +