diff --git a/at-gradle/build.gradle b/at-gradle/build.gradle index 643b60d..b314cf6 100644 --- a/at-gradle/build.gradle +++ b/at-gradle/build.gradle @@ -81,6 +81,18 @@ gradlePlugin { } } +testing { + suites { + functionalTest(JvmTestSuite) { + useJUnitJupiter() + dependencies { + implementation project() + implementation gradleTestKit() + } + } + } +} + publishing { repositories { maven gradleutils.getPublishingForgeMaven(rootProject.file('../repo')) @@ -106,3 +118,23 @@ publishing { } } } + +// We are not a module but unifying the sourcesets fixes pluginUnderTestMetadata producing the correct paths +sourceSets.each { sourceSet -> + sourceSet.output.resourcesDir = sourceSet.java.destinationDirectory = layout.projectDirectory.dir("bin/$sourceSet.name") +} + +// We need to put the plugin metadata file on the resource path, because eclipse doesn't use gradle's build folder +sourceSets.functionalTest.resources.srcDirs += [ 'src/functionalTest/generated/' ] +tasks.named('pluginUnderTestMetadata') { + outputDirectory = file('src/functionalTest/generated/') +} +// We need to write the plugin info so that eclipse can find it without invoking gradle +sourceSets.main.resources.srcDirs += [ 'src/main/generated/' ] +tasks.named('pluginDescriptors') { + outputDirectory = file('src/main/generated/META-INF/gradle-plugins/') +} + +eclipse { + synchronizationTasks pluginUnderTestMetadata, pluginDescriptors +} diff --git a/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/FunctionalTests.java b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/FunctionalTests.java new file mode 100644 index 0000000..c7083c1 --- /dev/null +++ b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/FunctionalTests.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.accesstransformers.gradle.tests; + +import java.io.IOException; + +import org.junit.jupiter.api.Disabled; +import org.junit.jupiter.api.Test; + +public class FunctionalTests extends FunctionalTestsBase { + private static final String BUILD = ":build"; + + public FunctionalTests() { + super("9.0.0"); + } + + @Test + public void makeFieldAccessible() throws IOException { + makeFieldAccessible(new GroovyProject(projectDir)); + } + + @Test + public void makeFieldAccessibleKotlin() throws IOException { + makeFieldAccessible(new KotlinProject(projectDir)); + } + + private void makeFieldAccessible(GradleProject project) throws IOException { + project.transformDependency("org.apache.maven:maven-artifact:3.9.11", "transformer.cfg"); + project.writeFile( + "transformer.cfg", + "public org.apache.maven.artifact.versioning.DefaultArtifactVersion majorVersion" + ); + project.writeFile("src/java/Test.java", """ + import org.apache.maven.artifact.versioning.DefaultArtifactVersion; + + public class Test { + public static void test() { + new DefaultArtifactVersion("0").majorVersion; + } + } + """); + + var results = build(BUILD); + assertTaskSuccess(results, BUILD); + } + + @Test + public void makeMethodAccessible() throws IOException { + makeMethodAccessible(new GroovyProject(projectDir)); + } + + @Test + public void makeMethodAccessibleKotlin() throws IOException { + makeMethodAccessible(new KotlinProject(projectDir)); + } + + private void makeMethodAccessible(GradleProject project) throws IOException { + project.transformDependency("org.ow2.asm:asm:9.7", "transformer.cfg"); + writeMethodTest(project); + + var results = build(BUILD); + assertTaskSuccess(results, BUILD); + } + + @Test + @Disabled // I don't know what the point of the constraint system was so I don't have a good test + public void makeTransitiveAccessible() throws IOException { + makeTransitiveAccessible(new GroovyProject(projectDir)); + } + + @Test + @Disabled // I don't know what the point of the constraint system was so I don't have a good test + public void makeTransitiveAccessibleKotlin() throws IOException { + makeTransitiveAccessible(new KotlinProject(projectDir)); + } + + private void makeTransitiveAccessible(GradleProject project) throws IOException { + project.transformConstraint("org.ow2.asm:asm-tree:9.7", "org.ow2.asm:asm", "transformer.cfg"); + writeMethodTest(project); + + var results = build(BUILD); + assertTaskSuccess(results, BUILD); + } + + private void writeMethodTest(GradleProject project) throws IOException { + project.writeFile( + "transformer.cfg", + "public org.objectweb.asm.Attribute (Ljava/lang/String;)V" + ); + project.writeFile("src/java/Test.java", """ + import org.objectweb.asm.Attribute; + + public class Test { + public static void test() { + new Attribute("123"); + } + } + """); + } +} diff --git a/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/FunctionalTestsBase.java b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/FunctionalTestsBase.java new file mode 100644 index 0000000..99e8d9d --- /dev/null +++ b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/FunctionalTestsBase.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.accesstransformers.gradle.tests; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import org.gradle.testkit.runner.BuildResult; +import org.gradle.testkit.runner.GradleRunner; +import org.gradle.testkit.runner.TaskOutcome; +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.RegisterExtension; +import org.junit.jupiter.api.io.CleanupMode; +import org.junit.jupiter.api.io.TempDir; + +import static org.junit.jupiter.api.Assertions.*; + +public abstract class FunctionalTestsBase { + @TempDir(cleanup = CleanupMode.ON_SUCCESS) + protected Path projectDir; + + @RegisterExtension + AfterTestExecutionCallback afterTestExecutionCallback = this::after; + private void after(ExtensionContext context) throws Exception { + if (context.getExecutionException().isPresent()) + System.out.println(context.getDisplayName() + " Failed: " + projectDir); + } + + protected final String gradleVersion; + + protected FunctionalTestsBase(String gradleVersion) { + this.gradleVersion = gradleVersion; + } + + protected void writeFile(Path file, String content) throws IOException { + Files.createDirectories(file.getParent()); + Files.writeString(file, content, StandardCharsets.UTF_8); + } + + protected GradleRunner runner(String... args) { + return GradleRunner.create() + .withGradleVersion(gradleVersion) + .withProjectDir(projectDir.toFile()) + .withArguments(args) + .withPluginClasspath(); + } + + protected BuildResult build(String... args) { + return runner(args).build(); + } + + protected static void assertTaskSuccess(BuildResult result, String task) { + assertTaskOutcome(result, task, TaskOutcome.SUCCESS); + } + + protected static void assertTaskFailed(BuildResult result, String task) { + assertTaskOutcome(result, task, TaskOutcome.FAILED); + } + + protected static void assertTaskOutcome(BuildResult result, String task, TaskOutcome expected) { + var info = result.task(task); + assertNotNull(info, "Could not find task `" + task + "` in build results"); + assertEquals(expected, info.getOutcome()); + } + + protected static byte[] readJarEntry(Path path, String name) throws IOException { + try (var fs = FileSystems.newFileSystem(path)) { + var target = fs.getPath(name); + assertTrue(Files.exists(target), "Archive " + path + " does not contain " + name); + return Files.readAllBytes(target); + } + } + + protected static void assertFileMissing(Path path, String name) throws IOException { + try (var fs = FileSystems.newFileSystem(path)) { + var target = fs.getPath(name); + assertFalse(Files.exists(target), "Archive " + path + " contains `" + name + "` when it should not"); + } + } +} diff --git a/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/GradleProject.java b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/GradleProject.java new file mode 100644 index 0000000..bc00b53 --- /dev/null +++ b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/GradleProject.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.accesstransformers.gradle.tests; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; + +public abstract class GradleProject { + protected final Path projectDir; + protected final String buildFile; + protected final String settingsFile; + + protected GradleProject(Path projectDir, String buildFile, String settingsFile) { + this.projectDir = projectDir; + this.buildFile = buildFile; + this.settingsFile = settingsFile; + } + + protected static final String BASIC_SETTINGS = """ + plugins { + id("org.gradle.toolchains.foojay-resolver-convention") version "1.0.0" + } + + rootProject.name = "test" + """; + + protected void settingsFile() throws IOException { + settingsFile(BASIC_SETTINGS); + } + + protected void settingsFile(String content) throws IOException { + writeFile(settingsFile, content); + } + + protected static final String BASIC_BUILD = """ + plugins { + id("java") + id("net.minecraftforge.accesstransformers") + } + + repositories { + mavenCentral(); + } + """; + + protected void buildFile() throws IOException { + buildFile(BASIC_BUILD); + } + + protected void buildFile(int javaVersion) throws IOException { + buildFile(BASIC_BUILD + javaVersion(javaVersion)); + } + + protected String javaVersion(int javaVersion) { + return "java.toolchain.languageVersion = JavaLanguageVersion.of(" + javaVersion + ")\n"; + } + + protected void buildFile(String content) throws IOException { + writeFile(buildFile, content); + } + + public void writeFile(String name, String content) throws IOException { + writeFile(projectDir.resolve(name), content); + } + + protected void writeFile(Path file, String content) throws IOException { + Files.createDirectories(file.getParent()); + Files.writeString(file, content, StandardCharsets.UTF_8); + } + + // Simple dependency transformation + // The test will write the config file, a java class which access the transformed field/method, + // Then attempt to run build. + protected abstract void transformDependency(String library, String config) throws IOException; + + // Transform a transitive dependency using Gradle's constraints function. + // This allows you to transform the a dependency if it exists, but not have a hard reference + // to it in your metadata + // - I have no idea why you would do that - Lex + // + // The test will write the config file, a java class which access the transformed field/method, + // Then attempt to run ':build'. + protected abstract void transformConstraint(String library, String constraint, String config) throws IOException; +} diff --git a/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/GroovyProject.java b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/GroovyProject.java new file mode 100644 index 0000000..2b56d44 --- /dev/null +++ b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/GroovyProject.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.accesstransformers.gradle.tests; + +import java.io.IOException; +import java.nio.file.Path; + +public class GroovyProject extends GradleProject { + protected GroovyProject(Path projectDir) { + super(projectDir, "build.gradle", "settings.gradle"); + } + + @Override + protected void transformDependency(String library, String config) throws IOException { + settingsFile(); + buildFile( + BASIC_BUILD + + """ + dependencies { + implementation('{library}') { + accessTransformers.configure(it) { + config = project.file('{config}') + } + } + } + """ + .replace("{library}", library) + .replace("{config}", config) + ); + } + + @Override + protected void transformConstraint(String library, String constraint, String config) throws IOException { + settingsFile(); + buildFile( + BASIC_BUILD + + """ + dependencies { + implementation('{library}') + constraints { + implementation('{constraint}') { + accessTransformers.configure(it) { + config = project.file('{config}') + } + } + } + } + """ + .replace("{library}", library) + .replace("{constraint}", constraint) + .replace("{config}", config) + ); + } +} diff --git a/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/KotlinProject.java b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/KotlinProject.java new file mode 100644 index 0000000..94a5299 --- /dev/null +++ b/at-gradle/src/functionalTest/java/net/minecraftforge/accesstransformers/gradle/tests/KotlinProject.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.accesstransformers.gradle.tests; + +import java.io.IOException; +import java.nio.file.Path; + +public class KotlinProject extends GradleProject { + protected KotlinProject(Path projectDir) { + super(projectDir, "build.gradle.kts", "settings.gradle.kts"); + } + + @Override + protected void transformDependency(String library, String config) throws IOException { + settingsFile(); + buildFile( + BASIC_BUILD + + """ + dependencies { + implementation("{library}") { + accessTransformers.configure(this) { + config = project.file("{config}") + } + } + } + """ + .replace("{library}", library) + .replace("{config}", config) + ); + } + + @Override + protected void transformConstraint(String library, String constraint, String config) throws IOException { + settingsFile(); + buildFile( + BASIC_BUILD + + """ + dependencies { + constraints { + implementation("{constraint}") { + accessTransformers.configure(this) { + config = project.file("{config}") + } + } + } + implementation("{library}") + } + """ + .replace("{library}", library) + .replace("{constraint}", constraint) + .replace("{config}", config) + ); + } +} diff --git a/at-gradle/src/main/java/net/minecraftforge/accesstransformers/gradle/AccessTransformersExtension.java b/at-gradle/src/main/java/net/minecraftforge/accesstransformers/gradle/AccessTransformersExtension.java index e47fb69..b5b1f0d 100644 --- a/at-gradle/src/main/java/net/minecraftforge/accesstransformers/gradle/AccessTransformersExtension.java +++ b/at-gradle/src/main/java/net/minecraftforge/accesstransformers/gradle/AccessTransformersExtension.java @@ -5,7 +5,6 @@ package net.minecraftforge.accesstransformers.gradle; import org.gradle.api.Action; -import org.jetbrains.annotations.ApiStatus; /// The extension interface for the AccessTransformers Gradle plugin. ///