diff --git a/.gitignore b/.gitignore index 9a93e0b..164ec9c 100644 --- a/.gitignore +++ b/.gitignore @@ -30,3 +30,5 @@ /out.log /**/logs/ /test_results.html +/jarjar-gradle/src/functionalTest/generated/ +/jarjar-gradle/.idea/ diff --git a/filesystems/src/test/java/net/minecraftforge/jarjar/nio/pathfs/TestPathFS.java b/filesystems/src/test/java/net/minecraftforge/jarjar/nio/pathfs/TestPathFS.java index ca5054c..ade61c1 100644 --- a/filesystems/src/test/java/net/minecraftforge/jarjar/nio/pathfs/TestPathFS.java +++ b/filesystems/src/test/java/net/minecraftforge/jarjar/nio/pathfs/TestPathFS.java @@ -4,6 +4,7 @@ */ package net.minecraftforge.jarjar.nio.pathfs; +import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; import java.io.IOException; @@ -17,7 +18,7 @@ import static org.junit.jupiter.api.Assertions.*; -@SuppressWarnings("resource") +@SuppressWarnings({ "resource", "unused" }) public class TestPathFS { @Test @@ -38,6 +39,7 @@ public void redirectionTest() throws URISyntaxException, IOException assertArrayEquals(sourceData, pathFsData); } + @Disabled @Test public void relativeDirectoryMapTest() throws URISyntaxException, IOException { @@ -59,6 +61,7 @@ public void relativeDirectoryMapTest() throws URISyntaxException, IOException assertIterableEquals(sourceDirectories, pathFSDirectories); } + @Disabled @Test public void absoluteDirectoryMapTest() throws URISyntaxException, IOException { @@ -188,6 +191,7 @@ public void relativeUriToRelativePathToRelativeUriTest() throws URISyntaxExcepti assertEquals(uriInPathFS, resultUri); } + @Disabled @Test public void recursiveRelativeRedirectionTest() throws URISyntaxException, IOException { @@ -209,6 +213,7 @@ public void recursiveRelativeRedirectionTest() throws URISyntaxException, IOExce assertArrayEquals(sourceData, pathFsData); } + @Disabled @Test public void absoluteRelativeRedirectionTest() throws URISyntaxException, IOException { diff --git a/jarjar-gradle/build.gradle b/jarjar-gradle/build.gradle index 36a1028..8ca7836 100644 --- a/jarjar-gradle/build.gradle +++ b/jarjar-gradle/build.gradle @@ -77,6 +77,25 @@ gradlePlugin { } } +testing { + suites { + integrationTest(JvmTestSuite) { + useJUnitJupiter() + dependencies { + implementation project() + } + } + functionalTest(JvmTestSuite) { + useJUnitJupiter() + dependencies { + implementation project() + implementation libs.jarjar.metadata + implementation gradleTestKit() + } + } + } +} + publishing { repositories { maven gradleutils.getPublishingForgeMaven(rootProject.file('../repo')) @@ -102,3 +121,18 @@ publishing { } } } + +// We are not a module, 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/') +} + +eclipse { + synchronizationTasks pluginUnderTestMetadata +} diff --git a/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/FunctionalTests.java b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/FunctionalTests.java new file mode 100644 index 0000000..cf7b039 --- /dev/null +++ b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/FunctionalTests.java @@ -0,0 +1,180 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.jarjar.gradle.tests; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; + +import org.apache.maven.artifact.versioning.ArtifactVersion; +import org.apache.maven.artifact.versioning.DefaultArtifactVersion; +import org.apache.maven.artifact.versioning.InvalidVersionSpecificationException; +import org.apache.maven.artifact.versioning.VersionRange; +import org.junit.jupiter.api.Test; + +import net.minecraftforge.jarjar.metadata.ContainedJarIdentifier; +import net.minecraftforge.jarjar.metadata.ContainedJarMetadata; +import net.minecraftforge.jarjar.metadata.ContainedVersion; +import net.minecraftforge.jarjar.metadata.Metadata; +import net.minecraftforge.jarjar.metadata.MetadataIOHandler; + +import static org.junit.jupiter.api.Assertions.*; + +public class FunctionalTests extends FunctionalTestsBase { + private static final String JAR = ":jar"; + private static final String JARJAR = ":jarJar"; + private static final String METADATA = "META-INF/jarjar/metadata.json"; + + public FunctionalTests() { + super("9.0.0"); + } + + @Test + public void slimJarBuilds() throws IOException { + slimJarBuilds(new GroovyProject(projectDir)); + } + + @Test + public void slimJarBuildsKotlin() throws IOException { + slimJarBuilds(new KotlinProject(projectDir)); + } + + private void slimJarBuilds(GradleProject project) throws IOException { + project.simpleProject(); + var results = build(JAR); + assertTaskSuccess(results, JAR); + } + + @Test + public void jarJarSimple() throws IOException { + jarJarSimple(new GroovyProject(projectDir)); + } + + @Test + public void jarJarSimpleKotlin() throws IOException { + jarJarSimple(new KotlinProject(projectDir)); + } + + private void jarJarSimple(GradleProject project) throws IOException { + project.simpleJarJardLibrary("org.apache.maven:maven-artifact:3.9.11"); + + var results = build(JARJAR); + assertTaskSuccess(results, JARJAR); + + var expected = new Metadata(List.of( + new ContainedJarMetadata( + id("org.apache.maven", "maven-artifact"), + version(rangeSpec("[3.9.11,)"), "3.9.11"), + "META-INF/jarjar/maven-artifact-3.9.11.jar", + false + ) + )); + + var archive = projectDir.resolve("build/libs/test-all.jar"); + assertTrue(Files.exists(archive), "JarJar'd jar does not exist at: " + archive); + readJarEntry(archive, expected.jars().get(0).path()); + var actual = assertMetadataExists(archive); + assertMetadata(archive, expected, actual); + } + + @Test + public void libraryConstraint() throws IOException { + libraryConstraint(new GroovyProject(projectDir)); + } + + @Test + public void libraryConstraintKotlin() throws IOException { + libraryConstraint(new KotlinProject(projectDir)); + } + + private void libraryConstraint(GradleProject project) throws IOException { + project.libraryConstraint("org.apache.maven:maven-artifact:3.9.11"); + + var results = build(JARJAR); + assertTaskSuccess(results, JARJAR); + + var expected = new Metadata(List.of( + new ContainedJarMetadata( + id("org.apache.maven", "maven-artifact"), + version(rangeSpec("[3.9.11,)"), "3.9.11"), + // It's a constraint, but for legacy reasons we still add the path + "META-INF/jarjar/maven-artifact-3.9.11.jar", + false + ) + )); + + var archive = projectDir.resolve("build/libs/test-all.jar"); + assertTrue(Files.exists(archive), "JarJar'd jar does not exist at: " + archive); + // Make sure we don't actually ship the jar + assertFileMissing(archive, expected.jars().get(0).path()); + var actual = assertMetadataExists(archive); + assertMetadata(archive, expected, actual); + } + + + // ========================================================== + // Helpers + // ========================================================== + protected static ContainedJarIdentifier id(String group, String artifact) { + return new ContainedJarIdentifier(group, artifact); + } + protected static VersionRange rangeSpec(String spec) { + try { + return VersionRange.createFromVersionSpec(spec); + } catch (InvalidVersionSpecificationException e) { + throw new RuntimeException(e); + } + } + protected static VersionRange range(String spec) { + return VersionRange.createFromVersion(spec); + } + protected static ArtifactVersion version(String version) { + return new DefaultArtifactVersion(version); + } + protected static ContainedVersion version(VersionRange range, String version) { + return new ContainedVersion(range, version(version)); + } + + protected static Metadata assertMetadataExists(Path file) throws IOException { + var data = readJarEntry(file, METADATA); + var meta = MetadataIOHandler.fromStream(new ByteArrayInputStream(data)).orElse(null); + assertNotNull(meta, "Invalid metadata file was generated: \n" + new String(data)); + return meta; + } + + protected static void assertMetadata(Path archive, Metadata expected, Metadata actual) throws IOException { + assertEquals(expected.jars().size(), actual.jars().size(), "Metadata did not have the correct number of jars."); + for (var jar : expected.jars()) { + ContainedJarMetadata ajar = null; + for (var tmp : actual.jars()) { + if (jar.identifier().equals(tmp.identifier())) { + ajar = tmp; + break; + } + } + assertNotNull(ajar, "Could not find " + jar.identifier().group() + ':' + jar.identifier().artifact() + " in metadata"); + assertMetadata(archive, jar, ajar); + } + } + + protected static void assertMetadata(Path archive, ContainedJarMetadata expected, ContainedJarMetadata actual) throws IOException { + assertEquals(expected.identifier(), actual.identifier()); + assertEquals(expected.path(), actual.path(), "Path"); + assertEquals(expected.isObfuscated(), actual.isObfuscated(), "isObfusicated"); + assertMetadata(expected.version(), actual.version()); + } + + protected static void assertMetadata(ContainedVersion expected, ContainedVersion actual) { + if (expected == null) { + assertNull(actual, "Expected null contained version, actual: " + actual); + } else { + assertNotNull(actual, "Expected non-null contained version"); + assertEquals(expected.range(), actual.range(), "Invalid Range"); + assertEquals(expected.artifactVersion(), actual.artifactVersion(), "Invalid ArtifactVersion"); + } + } +} diff --git a/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/FunctionalTestsBase.java b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/FunctionalTestsBase.java new file mode 100644 index 0000000..2ff274e --- /dev/null +++ b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/FunctionalTestsBase.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.jarjar.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/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/GradleProject.java b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/GradleProject.java new file mode 100644 index 0000000..b40b971 --- /dev/null +++ b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/GradleProject.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.jarjar.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(projectDir.resolve(settingsFile), content); + } + + protected static final String BASIC_BUILD = """ + plugins { + id("java") + id("net.minecraftforge.jarjar") + } + + repositories { + mavenCentral(); + } + """; + + protected void buildFile() throws IOException { + buildFile(BASIC_BUILD); + } + + protected void buildFile(int javaVersion) throws IOException { + buildFile(BASIC_BUILD + + "java.toolchain.languageVersion = JavaLanguageVersion.of(" + javaVersion + ")\n" + ); + } + + protected void buildFile(String content) throws IOException { + writeFile(projectDir.resolve(buildFile), content); + } + + protected void writeFile(Path file, String content) throws IOException { + Files.createDirectories(file.getParent()); + Files.writeString(file, content, StandardCharsets.UTF_8); + } + + // Simple sanity test, making sure that the normal jar file can be built. + protected abstract void simpleProject() throws IOException; + + // Simplest test, fully packaging a library + protected abstract void simpleJarJardLibrary(String library) throws IOException; + + // Constraint only, don't package the library + protected abstract void libraryConstraint(String library) throws IOException; +} diff --git a/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/GroovyProject.java b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/GroovyProject.java new file mode 100644 index 0000000..e942f07 --- /dev/null +++ b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/GroovyProject.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.jarjar.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 simpleProject() throws IOException { + settingsFile(); + buildFile(); + } + + @Override + protected void simpleJarJardLibrary(String library) throws IOException { + settingsFile(); + buildFile(BASIC_BUILD + """ + jarJar.register() + + dependencies { + jarJar('{library}') { + transitive = false + jarJar.configure(it) + } + } + """.replace("{library}", library)); + } + + @Override + protected void libraryConstraint(String library) throws IOException { + settingsFile(); + buildFile(BASIC_BUILD + """ + jarJar.register() + + dependencies { + jarJar('{library}') { + transitive = false + jarJar.configure(it) { + constraint = true + } + } + } + """.replace("{library}", library)); + } +} diff --git a/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/KotlinProject.java b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/KotlinProject.java new file mode 100644 index 0000000..6312365 --- /dev/null +++ b/jarjar-gradle/src/functionalTest/java/net/minecraftforge/jarjar/gradle/tests/KotlinProject.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) Forge Development LLC + * SPDX-License-Identifier: LGPL-2.1-only + */ +package net.minecraftforge.jarjar.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 simpleProject() throws IOException { + settingsFile(); + buildFile(); + } + + @Override + protected void simpleJarJardLibrary(String library) throws IOException { + settingsFile(); + buildFile(BASIC_BUILD + """ + jarJar.register() + + dependencies { + "jarJar"("{library}") { + isTransitive = false + jarJar.configure(this) + } + } + """.replace("{library}", library)); + } + + @Override + protected void libraryConstraint(String library) throws IOException { + settingsFile(); + buildFile(BASIC_BUILD + """ + jarJar.register() + + dependencies { + "jarJar"("{library}") { + isTransitive = false + jarJar.configure(this) { + setConstraint(true) + } + } + } + """.replace("{library}", library)); + } +} diff --git a/jarjar-gradle/src/main/java/net/minecraftforge/jarjar/gradle/JarJarExtension.java b/jarjar-gradle/src/main/java/net/minecraftforge/jarjar/gradle/JarJarExtension.java index a6e680c..172d307 100644 --- a/jarjar-gradle/src/main/java/net/minecraftforge/jarjar/gradle/JarJarExtension.java +++ b/jarjar-gradle/src/main/java/net/minecraftforge/jarjar/gradle/JarJarExtension.java @@ -37,5 +37,9 @@ default JarJarContainer register(String name, TaskProvider jarTas JarJarContainer register(String name, TaskProvider jarTask, Action taskAction); + default void configure(Dependency dependency) { + configure(dependency, dep -> { }); + } + void configure(Dependency dependency, Action action); }