From a1e671b7190570e6ef9508d75f0aa9077ceb892c Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Mon, 23 Jan 2023 22:06:05 +0000 Subject: [PATCH] Fix and test jar processor caching. --- .../accesswidener/AccessWidenerEntry.java | 2 + .../AccessWidenerJarProcessor.java | 15 ++- .../LocalAccessWidenerEntry.java | 22 ++++- .../accesswidener/ModAccessWidenerEntry.java | 5 + .../MinecraftJarProcessorManager.java | 34 ++++++- .../java/net/fabricmc/loom/util/Checksum.java | 2 +- .../fabricmc/loom/util/fmj/FabricModJson.java | 21 ++++- .../loom/util/fmj/ModEnvironment.java | 27 +++++- .../test/unit/ArtifactMetadataTest.groovy | 32 +------ .../test/unit/fmj/FabricModJsonV0Test.groovy | 9 ++ .../test/unit/fmj/FabricModJsonV1Test.groovy | 9 ++ .../test/unit/fmj/FabricModJsonV2Test.groovy | 9 ++ .../AccessWidenerJarProcessorTest.groovy | 92 +++++++++++++++++++ .../MinecraftJarProcessorManagerTest.groovy | 70 ++++++++++++++ .../ModAccessWidenerEntryTest.groovy | 50 ++++++++++ .../loom/test/util/GradleTestUtil.groovy | 24 +++++ .../loom/test/util/ZipTestUtils.groovy | 61 ++++++++++++ .../TestMinecraftJarProcessor.groovy | 57 ++++++++++++ ...ccessWidenerJarProcessorTest.accesswidener | 3 + 19 files changed, 501 insertions(+), 43 deletions(-) create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/processor/AccessWidenerJarProcessorTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/processor/MinecraftJarProcessorManagerTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/unit/processor/ModAccessWidenerEntryTest.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy create mode 100644 src/test/groovy/net/fabricmc/loom/test/util/processor/TestMinecraftJarProcessor.groovy create mode 100644 src/test/resources/accesswidener/AccessWidenerJarProcessorTest.accesswidener diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerEntry.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerEntry.java index 7dd2d2d1..802b7c85 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerEntry.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerEntry.java @@ -42,5 +42,7 @@ public interface AccessWidenerEntry { @Nullable String mappingId(); + String getSortKey(); + void read(AccessWidenerVisitor visitor, LazyCloseable remapper) throws IOException; } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java index b56cb9d5..212ef932 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/AccessWidenerJarProcessor.java @@ -24,10 +24,13 @@ package net.fabricmc.loom.configuration.accesswidener; +import java.io.FileNotFoundException; import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Collections; +import java.util.Comparator; import java.util.List; import javax.inject.Inject; @@ -62,8 +65,14 @@ public class AccessWidenerJarProcessor implements MinecraftJarProcessor accessWideners = new ArrayList<>(); if (localAccessWidenerProperty.isPresent()) { + Path path = localAccessWidenerProperty.get().getAsFile().toPath(); + + if (Files.notExists(path)) { + throw new UncheckedIOException(new FileNotFoundException("Could not find access widener file at {%s}".formatted(path))); + } + // Add the access widener specified in the extension - accessWideners.add(new LocalAccessWidenerEntry(localAccessWidenerProperty.get().getAsFile().toPath())); + accessWideners.add(LocalAccessWidenerEntry.create(path)); } /* Uncomment to read all access wideners from local mods. @@ -84,7 +93,7 @@ public class AccessWidenerJarProcessor implements MinecraftJarProcessor remapper) throws IOException { var reader = new AccessWidenerReader(visitor); @@ -52,4 +62,14 @@ public record LocalAccessWidenerEntry(Path path) implements AccessWidenerEntry { public @Nullable String mappingId() { return null; } + + @Override + public String getSortKey() { + return "local"; + } + + @Override + public int hashCode() { + return hash.hashCode(); + } } diff --git a/src/main/java/net/fabricmc/loom/configuration/accesswidener/ModAccessWidenerEntry.java b/src/main/java/net/fabricmc/loom/configuration/accesswidener/ModAccessWidenerEntry.java index 14e84872..4731422c 100644 --- a/src/main/java/net/fabricmc/loom/configuration/accesswidener/ModAccessWidenerEntry.java +++ b/src/main/java/net/fabricmc/loom/configuration/accesswidener/ModAccessWidenerEntry.java @@ -61,6 +61,11 @@ public record ModAccessWidenerEntry(FabricModJson mod, String path, ModEnvironme return transitiveOnly ? mod.getId() : null; } + @Override + public String getSortKey() { + return mod.getId() + ":" + path; + } + @Override public void read(AccessWidenerVisitor visitor, LazyCloseable remapper) throws IOException { if (transitiveOnly) { diff --git a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java index 291cfa46..e0d114df 100644 --- a/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java +++ b/src/main/java/net/fabricmc/loom/configuration/processors/MinecraftJarProcessorManager.java @@ -34,10 +34,13 @@ import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.Objects; +import java.util.StringJoiner; import java.util.stream.Collectors; import org.gradle.api.Project; import org.jetbrains.annotations.Nullable; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import net.fabricmc.loom.LoomGradleExtension; import net.fabricmc.loom.api.processor.MappingProcessorContext; @@ -49,6 +52,7 @@ import net.fabricmc.mappingio.tree.MemoryMappingTree; public final class MinecraftJarProcessorManager { private static final String CACHE_VALUE_FILE_PATH = "META-INF/Loom-Jar-Processor-Cache"; + private static final Logger LOGGER = LoggerFactory.getLogger(MinecraftJarProcessorManager.class); private final List> jarProcessors; @@ -73,14 +77,17 @@ public final class MinecraftJarProcessorManager { List> entries = new ArrayList<>(); for (MinecraftJarProcessor processor : processors) { + LOGGER.debug("Building processor spec for {}", processor.getName()); MinecraftJarProcessor.Spec spec = processor.buildSpec(context); if (spec != null) { + LOGGER.debug("Adding processor entry for {}", processor.getName()); entries.add(new ProcessorEntry<>(processor, spec)); } } if (entries.isEmpty()) { + LOGGER.debug("No processor entries"); return null; } @@ -94,10 +101,23 @@ public final class MinecraftJarProcessorManager { .collect(Collectors.joining("::")); } + private String getDebugString() { + final StringJoiner sj = new StringJoiner("\n"); + + for (ProcessorEntry jarProcessor : jarProcessors) { + sj.add(jarProcessor.name() + ":"); + sj.add("\tHash: " + jarProcessor.hashCode()); + sj.add("\tStr: " + jarProcessor.toString()); + } + + return sj.toString(); + } + public boolean requiresProcessingJar(Path jar) { Objects.requireNonNull(jar); if (Files.notExists(jar)) { + LOGGER.debug("{} does not exist, generating", jar); return true; } @@ -110,11 +130,23 @@ public final class MinecraftJarProcessorManager { } if (existingCache == null) { + LOGGER.info("{} does not contain a processor cache value, regenerating", jar); return true; } final String existingCacheValue = new String(existingCache, StandardCharsets.UTF_8); - return !existingCacheValue.equals(getCacheValue()); + final String expectedCacheValue = getCacheValue(); + final boolean matches = existingCacheValue.equals(expectedCacheValue); + + if (!matches) { + LOGGER.info("{} has an invalid cache, got {} expected {}", jar, existingCacheValue, expectedCacheValue); + + if (LOGGER.isInfoEnabled()) { + LOGGER.info("Expected state: {}", getDebugString()); + } + } + + return !matches; } public void processJar(Path jar, ProcessorContext context) throws IOException { diff --git a/src/main/java/net/fabricmc/loom/util/Checksum.java b/src/main/java/net/fabricmc/loom/util/Checksum.java index bfeff16f..5337b867 100644 --- a/src/main/java/net/fabricmc/loom/util/Checksum.java +++ b/src/main/java/net/fabricmc/loom/util/Checksum.java @@ -62,7 +62,7 @@ public class Checksum { HashCode hash = Files.asByteSource(file).hash(Hashing.sha256()); return hash.asBytes(); } catch (IOException e) { - throw new RuntimeException("Failed to get file hash"); + throw new UncheckedIOException("Failed to get file hash", e); } } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java index 592ae3f8..8356bfa7 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/FabricModJson.java @@ -33,8 +33,9 @@ import java.util.Objects; import com.google.gson.JsonElement; import com.google.gson.JsonObject; import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.VisibleForTesting; -public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2 { +public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJsonV1, FabricModJsonV2, FabricModJson.Mockable { protected final JsonObject jsonObject; private final FabricModJsonSource source; @@ -59,4 +60,22 @@ public abstract sealed class FabricModJson permits FabricModJsonV0, FabricModJso public final FabricModJsonSource getSource() { return source; } + + @Override + public final String toString() { + return getClass().getName() + "[id=%s, version=%s, classTweakers=%s]".formatted(getId(), getVersion(), getClassTweakers()); + } + + @Override + public final int hashCode() { + return Objects.hash(getId(), getVersion()); + } + + @VisibleForTesting + public abstract non-sealed class Mockable extends FabricModJson { + private Mockable() { + super(null, null); + throw new AssertionError(); + } + } } diff --git a/src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java b/src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java index b96687ad..aaa53169 100644 --- a/src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java +++ b/src/main/java/net/fabricmc/loom/util/fmj/ModEnvironment.java @@ -24,17 +24,21 @@ package net.fabricmc.loom.util.fmj; -public enum ModEnvironment { - UNIVERSAL(true, true), - CLIENT(true, false), - SERVER(false, true); +import java.util.Objects; + +public final class ModEnvironment { + public static final ModEnvironment UNIVERSAL = new ModEnvironment(true, true, "universal"); + public static final ModEnvironment CLIENT = new ModEnvironment(true, false, "client"); + public static final ModEnvironment SERVER = new ModEnvironment(false, true, "server"); private final boolean client; private final boolean server; + private final String name; - ModEnvironment(boolean client, boolean server) { + private ModEnvironment(boolean client, boolean server, String name) { this.client = client; this.server = server; + this.name = name; } public boolean isClient() { @@ -44,4 +48,17 @@ public enum ModEnvironment { public boolean isServer() { return server; } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + ModEnvironment that = (ModEnvironment) o; + return name.equals(that.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy index cc499ecb..3cb65956 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/ArtifactMetadataTest.groovy @@ -26,16 +26,12 @@ package net.fabricmc.loom.test.unit import net.fabricmc.loom.configuration.mods.ArtifactMetadata import net.fabricmc.loom.configuration.mods.ArtifactRef -import net.fabricmc.loom.util.FileSystemUtil import spock.lang.Specification -import java.nio.charset.StandardCharsets -import java.nio.file.Files import java.nio.file.Path -import java.util.jar.Attributes -import java.util.jar.Manifest import static net.fabricmc.loom.configuration.mods.ArtifactMetadata.RemapRequirements.* +import static net.fabricmc.loom.test.util.ZipTestUtils.* class ArtifactMetadataTest extends Specification { def "is fabric mod"() { @@ -111,30 +107,4 @@ class ArtifactMetadataTest extends Specification { private static ArtifactRef createArtifact(Path zip) { return new ArtifactRef.FileArtifactRef(zip, "net.fabric", "loom-test", "1.0") } - - private static Path createZip(Map entries) { - def file = Files.createTempFile("loom-test", ".zip") - Files.delete(file) - - FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip -> - entries.forEach { path, value -> - def fsPath = zip.getPath(path) - def fsPathParent = fsPath.getParent() - if (fsPathParent != null) Files.createDirectories(fsPathParent) - Files.writeString(fsPath, value, StandardCharsets.UTF_8) - } - } - - return file - } - - private String manifest(String key, String value) { - def manifest = new Manifest() - manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") - manifest.getMainAttributes().putValue(key, value) - - def out = new ByteArrayOutputStream() - manifest.write(out) - return out.toString(StandardCharsets.UTF_8) - } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy index c8585ab3..6e7ec14a 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV0Test.groovy @@ -101,4 +101,13 @@ class FabricModJsonV0Test extends Specification { then: fmj.getClassTweakers() == [:] } + + def "hash code"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.hashCode() == 930565976 + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy index b11da5bc..500405b9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV1Test.groovy @@ -107,4 +107,13 @@ class FabricModJsonV1Test extends Specification { then: fmj.getClassTweakers() == ["modid.accesswidener": ModEnvironment.UNIVERSAL] } + + def "hash code"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.hashCode() == 930565977 + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy index dba71c69..0e530dc9 100644 --- a/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/unit/fmj/FabricModJsonV2Test.groovy @@ -125,4 +125,13 @@ class FabricModJsonV2Test extends Specification { "universal.ct": ModEnvironment.UNIVERSAL ] } + + def "hash code"() { + given: + def mockSource = Mock(FabricModJsonSource) + when: + def fmj = FabricModJsonFactory.create(JSON_OBJECT, mockSource) + then: + fmj.hashCode() == 930565978 + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/processor/AccessWidenerJarProcessorTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/processor/AccessWidenerJarProcessorTest.groovy new file mode 100644 index 00000000..ae982486 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/processor/AccessWidenerJarProcessorTest.groovy @@ -0,0 +1,92 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2023 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit.processor + +import net.fabricmc.loom.api.processor.SpecContext +import net.fabricmc.loom.configuration.accesswidener.AccessWidenerJarProcessor +import net.fabricmc.loom.test.util.GradleTestUtil +import net.fabricmc.loom.util.fmj.FabricModJson +import net.fabricmc.loom.util.fmj.ModEnvironment +import spock.lang.Specification + +class AccessWidenerJarProcessorTest extends Specification { + def "Local AW"() { + given: + def specContext = Mock(SpecContext) + def file = new File("src/test/resources/accesswidener/AccessWidenerJarProcessorTest.accesswidener") + def localAccessWidenerProperty = GradleTestUtil.mockRegularFileProperty(file) + + def processor = new AccessWidenerJarProcessor("AccessWidener", true, localAccessWidenerProperty) + specContext.modDependencies() >> [] + + when: + def spec = processor.buildSpec(specContext) + + then: + spec != null + spec.hashCode() == 1205905061 + } + + def "Dep AW"() { + given: + def specContext = Mock(SpecContext) + + def mod1 = Mock(FabricModJson.Mockable) + mod1.getClassTweakers() >> ["test.accesswidener": ModEnvironment.UNIVERSAL] + mod1.getId() >> "modid1" + + def mod2 = Mock(FabricModJson.Mockable) + mod2.getClassTweakers() >> ["test2.accesswidener": ModEnvironment.UNIVERSAL] + mod2.getId() >> "modid2" + + specContext.modDependencies() >> [ + mod1, + mod2 + ].shuffled() + + def processor = new AccessWidenerJarProcessor("AccessWidener", true, GradleTestUtil.mockRegularFileProperty(null)) + + when: + def spec = processor.buildSpec(specContext) + + then: + spec != null + spec.hashCode() == 1534839952 + } + + def "No AWs"() { + given: + def specContext = Mock(SpecContext) + specContext.modDependencies() >> [] + + def processor = new AccessWidenerJarProcessor("AccessWidener", true, GradleTestUtil.mockRegularFileProperty(null)) + + when: + def spec = processor.buildSpec(specContext) + + then: + spec == null + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/processor/MinecraftJarProcessorManagerTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/processor/MinecraftJarProcessorManagerTest.groovy new file mode 100644 index 00000000..3f812e38 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/processor/MinecraftJarProcessorManagerTest.groovy @@ -0,0 +1,70 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2023 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit.processor + +import net.fabricmc.loom.api.processor.ProcessorContext +import net.fabricmc.loom.api.processor.SpecContext +import net.fabricmc.loom.configuration.processors.MinecraftJarProcessorManager +import net.fabricmc.loom.test.util.processor.TestMinecraftJarProcessor +import spock.lang.Specification + +import static net.fabricmc.loom.test.util.ZipTestUtils.createZip + +class MinecraftJarProcessorManagerTest extends Specification { + def "Does not require re-processing"() { + given: + def specContext = Mock(SpecContext) + def processorContext = Mock(ProcessorContext) + + def processor1 = new TestMinecraftJarProcessor(input: "Test1") + def processor2 = new TestMinecraftJarProcessor(input: "Test2") + def manager = MinecraftJarProcessorManager.create([processor1, processor2], specContext) + + when: + def jar = createZip(["fabric.mod.json": "{}"]) + manager.processJar(jar, processorContext) + + then: + !manager.requiresProcessingJar(jar) + } + + def "Requires re-processing"() { + given: + def specContext = Mock(SpecContext) + def processorContext = Mock(ProcessorContext) + + def processor1 = new TestMinecraftJarProcessor(input: "Test1") + def processor2 = new TestMinecraftJarProcessor(input: "Test2") + def manager1 = MinecraftJarProcessorManager.create([processor1], specContext) + def manager2 = MinecraftJarProcessorManager.create([processor1, processor2], specContext) + + when: + def jar = createZip(["fabric.mod.json": "{}"]) + manager1.processJar(jar, processorContext) + + then: + manager2.requiresProcessingJar(jar) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/unit/processor/ModAccessWidenerEntryTest.groovy b/src/test/groovy/net/fabricmc/loom/test/unit/processor/ModAccessWidenerEntryTest.groovy new file mode 100644 index 00000000..e36785bf --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/unit/processor/ModAccessWidenerEntryTest.groovy @@ -0,0 +1,50 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2023 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.unit.processor + +import net.fabricmc.loom.configuration.accesswidener.ModAccessWidenerEntry +import net.fabricmc.loom.util.fmj.FabricModJson +import net.fabricmc.loom.util.fmj.ModEnvironment +import spock.lang.Specification + +class ModAccessWidenerEntryTest extends Specification { + def "read local mod"() { + given: + def mod = Mock(FabricModJson.Mockable) + mod.getClassTweakers() >> ["test.accesswidener": ModEnvironment.UNIVERSAL] + mod.hashCode() >> 0 + + when: + def entries = ModAccessWidenerEntry.readAll(mod, true) + then: + entries.size() == 1 + def entry = entries[0] + + entry.path() == "test.accesswidener" + entry.environment() == ModEnvironment.UNIVERSAL + entry.transitiveOnly() + entry.hashCode() == -1218981396 + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy index 3e416a12..29ee5f7d 100644 --- a/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy +++ b/src/test/groovy/net/fabricmc/loom/test/util/GradleTestUtil.groovy @@ -26,6 +26,8 @@ package net.fabricmc.loom.test.util import net.fabricmc.loom.LoomGradleExtension import org.gradle.api.Project +import org.gradle.api.file.RegularFile +import org.gradle.api.file.RegularFileProperty import org.gradle.api.file.SourceDirectorySet import org.gradle.api.internal.tasks.DefaultSourceSet import org.gradle.api.model.ObjectFactory @@ -33,6 +35,7 @@ import org.gradle.api.plugins.ExtensionContainer import org.gradle.api.provider.Property import org.gradle.api.tasks.SourceSet import org.gradle.api.tasks.util.PatternFilterable +import org.jetbrains.annotations.Nullable import static org.mockito.ArgumentMatchers.any import static org.mockito.Mockito.mock @@ -90,4 +93,25 @@ class GradleTestUtil { def mock = mock(PatternFilterable.class) return mock } + + static RegularFile mockRegularFile(File file) { + def mock = mock(RegularFile.class) + when(mock.getAsFile()).thenReturn(file) + return mock + } + + static RegularFileProperty mockRegularFileProperty(@Nullable File file) { + if (file == null) { + def mock = mock(RegularFileProperty.class) + when(mock.isPresent()).thenReturn(false) + return mock + } + + def regularFile = mockRegularFile(file.getAbsoluteFile()) + + def mock = mock(RegularFileProperty.class) + when(mock.get()).thenReturn(regularFile) + when(mock.isPresent()).thenReturn(true) + return mock + } } diff --git a/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy b/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy new file mode 100644 index 00000000..757bd5a2 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/util/ZipTestUtils.groovy @@ -0,0 +1,61 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2023 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.util + +import net.fabricmc.loom.util.FileSystemUtil + +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.nio.file.Path +import java.util.jar.Attributes +import java.util.jar.Manifest + +class ZipTestUtils { + static Path createZip(Map entries) { + def file = Files.createTempFile("loom-test", ".zip") + Files.delete(file) + + FileSystemUtil.getJarFileSystem(file, true).withCloseable { zip -> + entries.forEach { path, value -> + def fsPath = zip.getPath(path) + def fsPathParent = fsPath.getParent() + if (fsPathParent != null) Files.createDirectories(fsPathParent) + Files.writeString(fsPath, value, StandardCharsets.UTF_8) + } + } + + return file + } + + static String manifest(String key, String value) { + def manifest = new Manifest() + manifest.getMainAttributes().put(Attributes.Name.MANIFEST_VERSION, "1.0") + manifest.getMainAttributes().putValue(key, value) + + def out = new ByteArrayOutputStream() + manifest.write(out) + return out.toString(StandardCharsets.UTF_8) + } +} diff --git a/src/test/groovy/net/fabricmc/loom/test/util/processor/TestMinecraftJarProcessor.groovy b/src/test/groovy/net/fabricmc/loom/test/util/processor/TestMinecraftJarProcessor.groovy new file mode 100644 index 00000000..24190e52 --- /dev/null +++ b/src/test/groovy/net/fabricmc/loom/test/util/processor/TestMinecraftJarProcessor.groovy @@ -0,0 +1,57 @@ +/* + * This file is part of fabric-loom, licensed under the MIT License (MIT). + * + * Copyright (c) 2023 FabricMC + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +package net.fabricmc.loom.test.util.processor + +import groovy.transform.Immutable +import net.fabricmc.loom.api.processor.MinecraftJarProcessor +import net.fabricmc.loom.api.processor.ProcessorContext +import net.fabricmc.loom.api.processor.SpecContext + +import java.nio.file.Path + +@Immutable +class TestMinecraftJarProcessor implements MinecraftJarProcessor { + String input + + final String name = "TestProcessor" + + @Override + Spec buildSpec(SpecContext context) { + if (input == null) { + return null + } + + return new Spec(input) + } + + @Immutable + class Spec implements MinecraftJarProcessor.Spec { + String input + } + + @Override + void processJar(Path jar, Spec spec, ProcessorContext context) throws IOException { + } +} diff --git a/src/test/resources/accesswidener/AccessWidenerJarProcessorTest.accesswidener b/src/test/resources/accesswidener/AccessWidenerJarProcessorTest.accesswidener new file mode 100644 index 00000000..d1d479de --- /dev/null +++ b/src/test/resources/accesswidener/AccessWidenerJarProcessorTest.accesswidener @@ -0,0 +1,3 @@ +accessWidener v2 named + +transitive-accessible method net/minecraft/recipe/BrewingRecipeRegistry registerPotionType (Lnet/minecraft/item/Item;)V \ No newline at end of file